From f7f9cfe3d813338dee7f1fda992bf3288c463581 Mon Sep 17 00:00:00 2001 From: Christian Gick Date: Sat, 17 Jan 2026 08:38:18 +0200 Subject: [PATCH] Add investigation type and duplicate detection (CF-166 Phase 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enhancements: 1. Investigation Task Type - Added 'investigation' to task type enum - Migration 013: Updated PostgreSQL constraint - Updated TypeScript schemas (task_add, task_list, task_update) - Enables tracking research/debugging workflows 2. Duplicate Detection - Enhanced task_add to check for similar tasks before creating - Uses pgvector semantic search (>70% similarity threshold) - Warns about potential duplicates with similarity scores - Suggests linking command for related tasks - Gracefully handles when embeddings unavailable Example Output: ``` Created: CF-123 Title: Fix API rate limiting Type: task Priority: P2 Project: CF ⚠️ Similar tasks found: - CF-120: Add rate limit monitoring (85% match, in_progress) - CF-115: Implement API throttling (72% match, open) Consider linking with: task link relates_to ``` Benefits: - Prevents accidental duplicate tasks - Surfaces related work automatically - Reduces manual task linking - Investigation type for research workflows Files Changed: - migrations/013_investigation_type.sql (new) - src/tools/crud.ts (duplicate detection logic) - src/tools/index.ts (investigation type in enums) Remaining CF-166 Features (Phase 2): - Session-aware task context - Automatic linking within session - Investigation workflow helper - Task creation reminders Co-Authored-By: Claude Sonnet 4.5 --- migrations/013_investigation_type.sql | 13 ++++++++++ src/tools/crud.ts | 34 +++++++++++++++++++++++---- src/tools/index.ts | 2 +- 3 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 migrations/013_investigation_type.sql 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' }, },