Add investigation type and duplicate detection (CF-166 Phase 1)
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 <from> <to> 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 <noreply@anthropic.com>
This commit is contained in:
13
migrations/013_investigation_type.sql
Normal file
13
migrations/013_investigation_type.sql
Normal file
@@ -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;
|
||||||
@@ -88,14 +88,38 @@ export async function taskAdd(args: TaskAddArgs): Promise<string> {
|
|||||||
// Get project key
|
// Get project key
|
||||||
const projectKey = await getProjectKey(project);
|
const projectKey = await getProjectKey(project);
|
||||||
|
|
||||||
// Get next task ID
|
// Generate embedding for duplicate detection
|
||||||
const taskId = await getNextTaskId(projectKey);
|
|
||||||
|
|
||||||
// Generate embedding
|
|
||||||
const embedText = description ? `${title}. ${description}` : title;
|
const embedText = description ? `${title}. ${description}` : title;
|
||||||
const embedding = await getEmbedding(embedText);
|
const embedding = await getEmbedding(embedText);
|
||||||
const embeddingValue = embedding ? formatEmbedding(embedding) : null;
|
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 <from> <to> relates_to';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get next task ID
|
||||||
|
const taskId = await getNextTaskId(projectKey);
|
||||||
|
|
||||||
// Insert task
|
// Insert task
|
||||||
if (embeddingValue) {
|
if (embeddingValue) {
|
||||||
await execute(
|
await execute(
|
||||||
@@ -114,7 +138,7 @@ export async function taskAdd(args: TaskAddArgs): Promise<string> {
|
|||||||
// Record activity for session tracking
|
// Record activity for session tracking
|
||||||
await recordActivity(taskId, 'created', undefined, 'open');
|
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}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export const toolDefinitions = [
|
|||||||
properties: {
|
properties: {
|
||||||
title: { type: 'string', description: 'Task title (required)' },
|
title: { type: 'string', description: 'Task title (required)' },
|
||||||
project: { type: 'string', description: 'Project key (e.g., ST, VPN). Auto-detected from CWD if not provided.' },
|
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)' },
|
priority: { type: 'string', enum: ['P0', 'P1', 'P2', 'P3'], description: 'Priority level (default: P2)' },
|
||||||
description: { type: 'string', description: 'Optional description' },
|
description: { type: 'string', description: 'Optional description' },
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user