diff --git a/migrations/014_session_context.sql b/migrations/014_session_context.sql new file mode 100644 index 0000000..6a1f614 --- /dev/null +++ b/migrations/014_session_context.sql @@ -0,0 +1,28 @@ +-- Migration 014: Session Context for Auto-linking +-- Tracks current working task per session for automatic task relationship creation + +CREATE TABLE IF NOT EXISTS session_context ( + session_id TEXT PRIMARY KEY, + current_task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_session_context_task ON session_context(current_task_id); + +-- Auto-update timestamp on changes +CREATE OR REPLACE FUNCTION update_session_context_timestamp() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER session_context_updated + BEFORE UPDATE ON session_context + FOR EACH ROW + EXECUTE FUNCTION update_session_context_timestamp(); + +-- Record migration +INSERT INTO schema_migrations (version) VALUES ('014_session_context') +ON CONFLICT (version) DO NOTHING; diff --git a/src/tools/crud.ts b/src/tools/crud.ts index 5a424ef..3b24a20 100644 --- a/src/tools/crud.ts +++ b/src/tools/crud.ts @@ -5,6 +5,7 @@ import { getEmbedding, formatEmbedding } from '../embeddings.js'; import type { Task, ChecklistItem, TaskLink } from '../types.js'; import { getRecentDelegations } from './delegations.js'; import { getTaskCommits } from './commits.js'; +import { taskLink } from './relations.js'; import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; @@ -18,8 +19,8 @@ function getSessionId(): string { return process.env.CLAUDE_SESSION_ID; } - // Try to read from cache file (usage-stats format) - const cacheFile = path.join(os.homedir(), '.claude', 'cache', '.current_session_id'); + // Try to read from cache file (session-memory format) + const cacheFile = path.join(os.homedir(), '.cache', 'session-memory', 'current_session'); try { const sessionId = fs.readFileSync(cacheFile, 'utf-8').trim(); if (sessionId) return sessionId; @@ -138,7 +139,29 @@ 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)' : ''}${duplicateWarning}`; + // Check for session context and auto-link to current working task + let autoLinkMessage = ''; + const session_id = getSessionId(); + try { + const sessionContext = await queryOne<{ current_task_id: string }>( + `SELECT current_task_id 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)`; + } + } catch { + // Silently fail if session context unavailable + } + + return `Created: ${taskId}\n Title: ${title}\n Type: ${type}\n Priority: ${priority}\n Project: ${projectKey}${embedding ? '\n (embedded for semantic search)' : ''}${duplicateWarning}${autoLinkMessage}`; } /** @@ -404,5 +427,30 @@ export async function taskUpdate(args: TaskUpdateArgs): Promise { await recordActivity(id, 'updated'); } + // Manage session context based on status changes + if (status) { + const session_id = getSessionId(); + try { + if (status === 'in_progress') { + // Set as current working task + await execute( + `INSERT INTO session_context (session_id, current_task_id) + VALUES ($1, $2) + ON CONFLICT (session_id) DO UPDATE SET current_task_id = $2, updated_at = NOW()`, + [session_id, id] + ); + } else if (status === 'completed') { + // Clear if this is the current working task + await execute( + `DELETE FROM session_context + WHERE session_id = $1 AND current_task_id = $2`, + [session_id, id] + ); + } + } catch { + // Silently fail if session context unavailable + } + } + return `Updated: ${id}`; }