feat(KB-289): complete Step 1 — Remove AI title generation from store

This commit is contained in:
gsxdsm
2026-03-31 09:25:35 -07:00
parent 202ec6b687
commit c6b5757b1d

View File

@@ -382,14 +382,11 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
if (input.dependencies?.includes(id)) {
throw new Error(`Task ${id} cannot depend on itself`);
}
// Generate title from description if not provided
const generatedTitle = await generateTitleFromDescription(input.description, this.rootDir);
const title = input.title?.trim() || generatedTitle;
const now = new Date().toISOString();
const task: Task = {
id,
title: title || undefined,
title: input.title?.trim() || undefined,
description: input.description,
column: input.column || "triage",
dependencies: input.dependencies || [],
@@ -2237,130 +2234,3 @@ ${notificationsSection}`;
}
}
}
/**
* Generate a concise title from a description by extracting the first N words.
* Uses first 8-10 words, capped at ~50 characters.
* Handles edge cases: very short descriptions, descriptions with only special chars.
*
* @param description - The task description
* @returns A generated title string, or empty string if no valid words found
*/
/** System prompt for AI title generation */
const TITLE_GENERATION_PROMPT = `You are a title generation assistant for a task management system.
Your job is to create a concise, descriptive title from a task description.
## Guidelines
- Maximum 60 characters
- 3-8 words preferred
- Summarize the key intent/action of the task
- Use clear, professional language
- Remove filler words (the, a, an) where possible
- Output ONLY the title text, no quotes, no markdown, no explanations
- If the input is already a good short title (3 words or less), return it as-is`;
// Dynamic import for @kb/engine to avoid resolution issues in test environment
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let createKbAgent: any;
// Initialize the import (this runs in actual code, mocked in tests)
async function initEngine() {
if (!createKbAgent) {
try {
// Use dynamic import with variable to prevent static analysis
const engineModule = "@kb/engine";
const engine = await import(/* @vite-ignore */ engineModule);
createKbAgent = engine.createKbAgent;
} catch {
// Allow failure in test environments - agent functionality will be stubbed
createKbAgent = undefined;
}
}
}
// Initialize on module load (will be awaited in actual usage)
const engineReady = initEngine();
/**
* Generate a title from description using AI summarization.
* Returns empty string if AI fails or description is empty.
*/
async function generateTitleFromDescription(description: string, rootDir: string): Promise<string> {
if (!description?.trim()) {
return "";
}
// For very short descriptions (3 words or less), use as-is without AI call
const trimmed = description.trim();
const wordCount = trimmed.split(/\s+/).length;
if (wordCount <= 3 && trimmed.length <= 60) {
return trimmed;
}
// Ensure engine is loaded before using createKbAgent
await engineReady;
if (!createKbAgent) {
// AI engine not available - return empty string (no fallback to truncation)
return "";
}
try {
const agentResult = await createKbAgent({
cwd: rootDir,
systemPrompt: TITLE_GENERATION_PROMPT,
tools: "readonly",
});
if (!agentResult?.session) {
return "";
}
// Send the description to the agent
await agentResult.session.prompt(`Generate a title for this task:\n\n${description}`);
// Get the response text from the agent's state
interface AgentMessage {
role: string;
content?: string | Array<{ type: string; text: string }>;
}
const lastMessage = (agentResult.session.state.messages as AgentMessage[])
.filter((m: AgentMessage) => m.role === "assistant")
.pop();
let title = "";
if (lastMessage?.content) {
// Handle both string and array content types
if (typeof lastMessage.content === "string") {
title = lastMessage.content.trim();
} else if (Array.isArray(lastMessage.content)) {
// Extract text from content blocks
title = lastMessage.content
.filter((c: { type: string; text: string }): c is { type: "text"; text: string } => c.type === "text")
.map((c: { type: string; text: string }) => c.text)
.join("")
.trim();
}
}
// Clean the title: remove surrounding quotes, normalize whitespace
title = title
.replace(/^["']|["']$/g, "") // Remove surrounding quotes
.replace(/\s+/g, " ") // Normalize whitespace
.trim();
// Dispose the agent session
try {
agentResult.session.dispose?.();
} catch {
// Ignore disposal errors
}
// Return empty string if AI returned nothing (no fallback)
return title || "";
} catch {
// AI failed - return empty string (no fallback to truncation)
return "";
}
}