feat(CF-166): Add investigation workflow with auto-linking

Implements comprehensive investigation task management:

**Schema Changes (Migration 020):**
- Add 'investigation' task type to tasks table
- Add investigation_parent_id and auto_link_enabled columns to session_context
- Add auto_linked and linked_at columns to task_links for tracking
- Create indexes for efficient auto-linking queries

**Auto-Linking Logic:**
1. Investigation Parent Linking: Tasks auto-link to active investigation
2. Current Task Linking: Tasks link to current working task
3. Time-Based Linking: Tasks within 1 hour auto-link (fallback)

**New Tool:**
- task_investigate: Start investigation workflow, sets session context

**Documentation:**
- Add comprehensive investigation workflow guide to README
- Include examples and session context explanation

All checklist items completed:
✓ Investigation task type in schema
✓ Session context table enhancements
✓ Auto-linking implementation
✓ task_investigate command
✓ Documentation updates

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Christian Gick
2026-01-19 19:15:25 +02:00
parent caf9356aad
commit 7be5878d35
6 changed files with 246 additions and 17 deletions

View File

@@ -142,25 +142,75 @@ export async function taskAdd(args: TaskAddArgs): Promise<string> {
// Record activity for session tracking
await recordActivity(taskId, 'created', undefined, 'open');
// Check for session context and auto-link to current working task
// Enhanced auto-linking logic (CF-166)
let autoLinkMessage = '';
try {
const sessionContext = await queryOne<{ current_task_id: string }>(
`SELECT current_task_id FROM session_context WHERE session_id = $1`,
const sessionContext = await queryOne<{
current_task_id: string | null;
investigation_parent_id: string | null;
auto_link_enabled: boolean;
}>(
`SELECT current_task_id, investigation_parent_id, auto_link_enabled
FROM session_context WHERE session_id = $1`,
[session_id]
);
if (sessionContext?.current_task_id) {
// Auto-link to current working task
await taskLink({
from_id: taskId,
to_id: sessionContext.current_task_id,
link_type: 'relates_to'
});
autoLinkMessage = `\n\n🔗 Auto-linked to: ${sessionContext.current_task_id} (current working task)`;
if (sessionContext?.auto_link_enabled !== false) {
const linkedTasks: string[] = [];
// 1. Auto-link to investigation parent if this is created during an investigation
if (sessionContext?.investigation_parent_id) {
await execute(
`INSERT INTO task_links (from_task_id, to_task_id, link_type, auto_linked)
VALUES ($1, $2, 'relates_to', true)
ON CONFLICT DO NOTHING`,
[taskId, sessionContext.investigation_parent_id]
);
linkedTasks.push(`${sessionContext.investigation_parent_id} (investigation)`);
}
// 2. Auto-link to current working task if different from investigation parent
if (sessionContext?.current_task_id &&
sessionContext.current_task_id !== sessionContext?.investigation_parent_id) {
await execute(
`INSERT INTO task_links (from_task_id, to_task_id, link_type, auto_linked)
VALUES ($1, $2, 'relates_to', true)
ON CONFLICT DO NOTHING`,
[taskId, sessionContext.current_task_id]
);
linkedTasks.push(`${sessionContext.current_task_id} (current task)`);
}
// 3. Time-based auto-linking: find tasks created within 1 hour in same session
if (!sessionContext?.investigation_parent_id && !sessionContext?.current_task_id) {
const recentTasks = await query<{ id: string; title: string }>(
`SELECT id, title FROM tasks
WHERE session_id = $1 AND id != $2
AND created_at > NOW() - INTERVAL '1 hour'
AND status != 'completed'
ORDER BY created_at DESC
LIMIT 3`,
[session_id, taskId]
);
for (const task of recentTasks) {
await execute(
`INSERT INTO task_links (from_task_id, to_task_id, link_type, auto_linked)
VALUES ($1, $2, 'relates_to', true)
ON CONFLICT DO NOTHING`,
[taskId, task.id]
);
linkedTasks.push(`${task.id} (recent)`);
}
}
if (linkedTasks.length > 0) {
autoLinkMessage = `\n\n🔗 Auto-linked to: ${linkedTasks.join(', ')}`;
}
}
} catch {
// Silently fail if session context unavailable
} catch (error) {
// Log but don't fail if auto-linking fails
console.error('Auto-linking failed:', error);
}
return `Created: ${taskId}\n Title: ${title}\n Type: ${type}\n Priority: ${priority}\n Project: ${projectKey}${embedding ? '\n (embedded for semantic search)' : ''}${duplicateWarning}${autoLinkMessage}`;
@@ -460,3 +510,44 @@ export async function taskUpdate(args: TaskUpdateArgs): Promise<string> {
return `Updated: ${id}`;
}
/**
* Start an investigation workflow (CF-166)
* Creates an investigation task and sets it as the session context parent
* All subsequent tasks will auto-link to this investigation
*/
export async function taskInvestigate(args: TaskAddArgs): Promise<string> {
const { title, project, priority = 'P1', description = '' } = args;
// Create investigation task
const taskResult = await taskAdd({
title,
project,
type: 'investigation',
priority,
description: description || 'Investigation task to coordinate related subtasks',
});
// Extract task ID from result (format: "Created: XX-123\n...")
const taskIdMatch = taskResult.match(/Created: ([\w-]+)/);
if (!taskIdMatch) {
return taskResult; // Return original message if format unexpected
}
const taskId = taskIdMatch[1];
// Set as investigation parent in session context
const session_id = getSessionId();
try {
await execute(
`INSERT INTO session_context (session_id, current_task_id, investigation_parent_id)
VALUES ($1, $2, $2)
ON CONFLICT (session_id) DO UPDATE
SET investigation_parent_id = $2, current_task_id = $2, updated_at = NOW()`,
[session_id, taskId]
);
} catch (error) {
console.error('Failed to set investigation context:', error);
}
return taskResult + '\n\n🔍 Investigation started! All new tasks will auto-link to this investigation.';
}

View File

@@ -25,7 +25,7 @@ export const toolDefinitions = [
properties: {
project: { type: 'string', description: 'Filter by project key' },
status: { type: 'string', enum: ['open', 'in_progress', 'testing', 'blocked', 'completed'], description: 'Filter by status' },
type: { type: 'string', enum: ['task', 'bug', 'feature', 'debt'], description: 'Filter by type' },
type: { type: 'string', enum: ['task', 'bug', 'feature', 'debt', 'investigation'], description: 'Filter by type' },
priority: { type: 'string', enum: ['P0', 'P1', 'P2', 'P3'], description: 'Filter by priority' },
limit: { type: 'number', description: 'Max results (default: 20)' },
},
@@ -62,12 +62,26 @@ export const toolDefinitions = [
id: { type: 'string', description: 'Task ID to update' },
status: { type: 'string', enum: ['open', 'in_progress', 'testing', 'blocked', 'completed'], description: 'New status' },
priority: { type: 'string', enum: ['P0', 'P1', 'P2', 'P3'], description: 'New priority' },
type: { type: 'string', enum: ['task', 'bug', 'feature', 'debt'], description: 'New type' },
type: { type: 'string', enum: ['task', 'bug', 'feature', 'debt', 'investigation'], description: 'New type' },
title: { type: 'string', description: 'New title' },
},
required: ['id'],
},
},
{
name: 'task_investigate',
description: 'Start an investigation workflow: creates an investigation task and auto-links all subsequent tasks to it. Use when beginning multi-step problem analysis.',
inputSchema: {
type: 'object',
properties: {
title: { type: 'string', description: 'Investigation title (required)' },
project: { type: 'string', description: 'Project key (e.g., ST, VPN). Auto-detected from CWD if not provided.' },
priority: { type: 'string', enum: ['P0', 'P1', 'P2', 'P3'], description: 'Priority level (default: P1)' },
description: { type: 'string', description: 'Optional description of investigation scope' },
},
required: ['title'],
},
},
// Semantic Search Tools
{