diff --git a/migrations/013_investigation_type.sql b/migrations/013_investigation_type.sql new file mode 100644 index 0000000..2111841 --- /dev/null +++ b/migrations/013_investigation_type.sql @@ -0,0 +1,13 @@ +-- Migration 013: Add investigation task type +-- Adds 'investigation' to task type enum for tracking research/debugging sessions + +-- Drop existing constraint +ALTER TABLE tasks DROP CONSTRAINT IF EXISTS tasks_type_check; + +-- Add new constraint with investigation type +ALTER TABLE tasks ADD CONSTRAINT tasks_type_check + CHECK (type IN ('task', 'bug', 'feature', 'debt', 'investigation')); + +-- Record migration +INSERT INTO schema_migrations (version) VALUES ('013_investigation_type') +ON CONFLICT (version) DO NOTHING; diff --git a/src/tools/crud.ts b/src/tools/crud.ts index 6347b82..5a424ef 100644 --- a/src/tools/crud.ts +++ b/src/tools/crud.ts @@ -88,14 +88,38 @@ export async function taskAdd(args: TaskAddArgs): Promise { // Get project key const projectKey = await getProjectKey(project); - // Get next task ID - const taskId = await getNextTaskId(projectKey); - - // Generate embedding + // Generate embedding for duplicate detection const embedText = description ? `${title}. ${description}` : title; const embedding = await getEmbedding(embedText); const embeddingValue = embedding ? formatEmbedding(embedding) : null; + // Check for similar/duplicate tasks (only if embedding succeeded) + let duplicateWarning = ''; + if (embeddingValue) { + const similarTasks = await query<{ id: string; title: string; status: string; similarity: number }>( + `SELECT id, title, status, 1 - (embedding <=> $1) as similarity + FROM tasks + WHERE project = $2 AND embedding IS NOT NULL AND status != 'completed' + ORDER BY embedding <=> $1 + LIMIT 3`, + [embeddingValue, projectKey] + ); + + // Warn if highly similar tasks exist (>70% similarity) + const highSimilarity = similarTasks.filter(t => t.similarity > 0.70); + if (highSimilarity.length > 0) { + duplicateWarning = '\n\n⚠️ Similar tasks found:\n'; + for (const t of highSimilarity) { + const pct = Math.round(t.similarity * 100); + duplicateWarning += ` - ${t.id}: ${t.title} (${pct}% match, ${t.status})\n`; + } + duplicateWarning += '\nConsider linking with: task link relates_to'; + } + } + + // Get next task ID + const taskId = await getNextTaskId(projectKey); + // Insert task if (embeddingValue) { await execute( @@ -114,7 +138,7 @@ export async function taskAdd(args: TaskAddArgs): Promise { // Record activity for session tracking await recordActivity(taskId, 'created', undefined, 'open'); - return `Created: ${taskId}\n Title: ${title}\n Type: ${type}\n Priority: ${priority}\n Project: ${projectKey}${embedding ? '\n (embedded for semantic search)' : ''}`; + return `Created: ${taskId}\n Title: ${title}\n Type: ${type}\n Priority: ${priority}\n Project: ${projectKey}${embedding ? '\n (embedded for semantic search)' : ''}${duplicateWarning}`; } /** diff --git a/src/tools/index.ts b/src/tools/index.ts index 191adaf..a2ddf75 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -10,7 +10,7 @@ export const toolDefinitions = [ properties: { title: { type: 'string', description: 'Task title (required)' }, project: { type: 'string', description: 'Project key (e.g., ST, VPN). Auto-detected from CWD if not provided.' }, - type: { type: 'string', enum: ['task', 'bug', 'feature', 'debt'], description: 'Task type (default: task)' }, + type: { type: 'string', enum: ['task', 'bug', 'feature', 'debt', 'investigation'], description: 'Task type (default: task)' }, priority: { type: 'string', enum: ['P0', 'P1', 'P2', 'P3'], description: 'Priority level (default: P2)' }, description: { type: 'string', description: 'Optional description' }, },