feat(KB-621): add AI title summarization feature

- Add core AI summarization service with model selection hierarchy
- Modify task store to support title generation callback pattern
- Add POST /api/ai/summarize-title endpoint with rate limiting
- Wire up summarization in dashboard task creation flow
- Add AI Summarization settings UI section in dashboard
- Add comprehensive tests for summarization service and API
- Document auto-summarization settings in AGENTS.md
This commit is contained in:
gsxdsm
2026-03-31 18:59:02 -07:00
parent 48dfdb3fa8
commit 9490792afd
12 changed files with 1240 additions and 15 deletions

View File

@@ -582,7 +582,13 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
return join(this.tasksDir, id);
}
async createTask(input: TaskCreateInput): Promise<Task> {
async createTask(
input: TaskCreateInput,
options?: {
onSummarize?: (description: string) => Promise<string | null>;
settings?: { autoSummarizeTitles?: boolean };
}
): Promise<Task> {
if (!input.description?.trim()) {
throw new Error("Description is required and cannot be empty");
}
@@ -592,10 +598,31 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
if (input.dependencies?.includes(id)) {
throw new Error(`Task ${id} cannot depend on itself`);
}
// Determine if we should try to summarize the title
let title = input.title?.trim() || undefined;
const shouldSummarize =
!title && // Only if no title provided
input.description.length > 140 && // Only if description is long enough
(input.summarize === true || // Explicit request
options?.settings?.autoSummarizeTitles === true); // Auto-enabled
if (shouldSummarize && options?.onSummarize) {
try {
const generatedTitle = await options.onSummarize(input.description);
if (generatedTitle) {
title = generatedTitle;
}
} catch (err) {
// Log warning but don't block task creation
console.warn(`[TaskStore] Title summarization failed for task ${id}:`, err instanceof Error ? err.message : err);
}
}
const now = new Date().toISOString();
const task: Task = {
id,
title: input.title?.trim() || undefined,
title,
description: input.description,
column: input.column || "triage",
dependencies: input.dependencies || [],