feat(CF-450): Check completed tasks on task creation to avoid circular work

Enhanced task_add to search both open AND completed tasks when checking for
duplicates. Previously only checked non-completed tasks.

**Changes:**
- Removed `status != 'completed'` filter from similarity search
- Increased search limit from 3 to 5 tasks
- Separated display: open tasks vs previously completed
- Show 150-char context snippet from completed task descriptions
- Suggest using "task show <id>" to see full solution

**Benefits:**
- Prevents recreating already-solved tasks
- Surfaces solutions from previous attempts
- Reduces circular work on recurring issues
- Maintains context from closed tasks

**Example output:**
```
⚠️  Similar tasks found:

**Open/In Progress:**
  - CF-442: Next.js cache issue (92% match, open)

**Previously Completed:**
  - CF-378: Pre-test validation (85% match)
    Context: "Created comprehensive pre-test quality validation workflow..."

  💡 Use "task show <id>" to see full solution before recreating work
```

Fixes: CF-450

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Christian Gick
2026-01-25 13:26:23 +02:00
parent d735c580dc
commit 49284d2911

View File

@@ -95,14 +95,15 @@ export async function taskAdd(args: TaskAddArgs): Promise<string> {
const embeddingValue = embedding ? formatEmbedding(embedding) : null; const embeddingValue = embedding ? formatEmbedding(embedding) : null;
// Check for similar/duplicate tasks (only if embedding succeeded) // Check for similar/duplicate tasks (only if embedding succeeded)
// CF-450: Check both open AND completed tasks to avoid circular work
let duplicateWarning = ''; let duplicateWarning = '';
if (embeddingValue) { if (embeddingValue) {
const similarTasks = await query<{ id: string; title: string; status: string; similarity: number }>( const similarTasks = await query<{ id: string; title: string; status: string; description: string; similarity: number }>(
`SELECT id, title, status, 1 - (embedding <=> $1) as similarity `SELECT id, title, status, description, 1 - (embedding <=> $1) as similarity
FROM tasks FROM tasks
WHERE project = $2 AND embedding IS NOT NULL AND status != 'completed' WHERE project = $2 AND embedding IS NOT NULL
ORDER BY embedding <=> $1 ORDER BY embedding <=> $1
LIMIT 3`, LIMIT 5`,
[embeddingValue, projectKey] [embeddingValue, projectKey]
); );
@@ -110,11 +111,35 @@ export async function taskAdd(args: TaskAddArgs): Promise<string> {
const highSimilarity = similarTasks.filter(t => t.similarity > 0.70); const highSimilarity = similarTasks.filter(t => t.similarity > 0.70);
if (highSimilarity.length > 0) { if (highSimilarity.length > 0) {
duplicateWarning = '\n\n⚠ Similar tasks found:\n'; duplicateWarning = '\n\n⚠ Similar tasks found:\n';
for (const t of highSimilarity) {
const openTasks = highSimilarity.filter(t => t.status !== 'completed');
const completedTasks = highSimilarity.filter(t => t.status === 'completed');
if (openTasks.length > 0) {
duplicateWarning += '\n**Open/In Progress:**\n';
for (const t of openTasks) {
const pct = Math.round(t.similarity * 100); const pct = Math.round(t.similarity * 100);
duplicateWarning += ` - ${t.id}: ${t.title} (${pct}% match, ${t.status})\n`; duplicateWarning += ` - ${t.id}: ${t.title} (${pct}% match, ${t.status})\n`;
} }
duplicateWarning += '\nConsider linking with: task link <from> <to> relates_to'; }
if (completedTasks.length > 0) {
duplicateWarning += '\n**Previously Completed:**\n';
for (const t of completedTasks) {
const pct = Math.round(t.similarity * 100);
duplicateWarning += ` - ${t.id}: ${t.title} (${pct}% match)\n`;
// Show snippet of solution/outcome from description
if (t.description) {
const snippet = t.description.substring(0, 150).replace(/\n/g, ' ').replace(/"/g, '\\"');
const ellipsis = t.description.length > 150 ? '...' : '';
duplicateWarning += ` Context: "${snippet}${ellipsis}"\n`;
}
}
duplicateWarning += '\n 💡 Use "task show <id>" to see full solution before recreating work\n';
}
duplicateWarning += '\nConsider linking with: task link <new-id> <related-id> relates_to';
} }
} }