feat(KB-289): complete Step 1 — Remove AI title generation from store
This commit is contained in:
@@ -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 "";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user