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
|
||||
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 <from> <to> 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<string> {
|
||||
// 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}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user