/** * Context Hook Handler (SessionStart) * * Injects memory context at the start of a Claude Code session. * Provides previous session history and relevant observations. * * @module @agentkits/memory/hooks/context */ import { NormalizedHookInput, HookResult, EventHandler, } from './types.js'; import { MemoryHookService } from './service.js'; /** * Context Hook - SessionStart Event * * Called when a new Claude Code session starts. * Retrieves and injects previous context to help Claude * understand the project history. */ export class ContextHook implements EventHandler { private service: MemoryHookService; private ownsService: boolean; constructor(service: MemoryHookService, ownsService = false) { this.service = service; this.ownsService = ownsService; } /** * Shutdown the hook (closes database if owned) */ async shutdown(): Promise { if (this.ownsService) { await this.service.shutdown(); } } /** * Execute the context hook */ async execute(input: NormalizedHookInput): Promise { try { // Initialize service await this.service.initialize(); // Catch-up: spawn workers if stale pending tasks exist from previous sessions try { if (this.service.hasPendingEmbeddings()) { this.service.ensureWorkerRunning(input.cwd, 'embed-session', 'embed-worker.lock'); } if (this.service.hasPendingEnrichments()) { this.service.ensureWorkerRunning(input.cwd, 'enrich-session', 'enrich-worker.lock'); } if (this.service.hasPendingCompressions()) { this.service.ensureWorkerRunning(input.cwd, 'compress-session', 'compress-worker.lock'); } } catch { /* non-critical — don't block context injection */ } // Get context for this project const context = await this.service.getContext(input.project); const hasHistory = context.markdown && !context.markdown.includes('No previous session context'); // Display status to user via stderr (merged from user-message hook) const obsCount = context.recentObservations.length; const sessionCount = context.sessionSummaries.length || context.previousSessions.length; const promptCount = context.userPrompts.length; if (obsCount > 0 || sessionCount > 0 || promptCount > 0) { const stats: string[] = []; if (sessionCount > 0) stats.push(`${sessionCount} session${sessionCount > 1 ? 's' : ''}`); if (obsCount > 0) stats.push(`${obsCount} observation${obsCount > 1 ? 's' : ''}`); if (promptCount > 0) stats.push(`${promptCount} prompt${promptCount > 1 ? 's' : ''}`); console.error(`\n AgentKits Memory: ${stats.join(', ')}\n`); } else { console.error('\n AgentKits Memory: Fresh — use memory_save to start\n'); } if (hasHistory) { // Inject full context with history return { continue: true, suppressOutput: false, additionalContext: context.markdown, }; } // Empty state: still inject tool guidance so Claude knows memory tools exist return { continue: true, suppressOutput: false, additionalContext: this.buildEmptyStateGuidance(input.project), }; } catch (error) { // Log error but don't block session console.error('[AgentKits Memory] Context hook error:', error); return { continue: true, suppressOutput: true, error: error instanceof Error ? error.message : 'Unknown error', }; } } /** * Build guidance for empty state (no previous sessions/memories). * Teaches Claude about available memory tools and proper usage order. */ private buildEmptyStateGuidance(project: string): string { return `# Memory Context - ${project} > **Memory tools available** — Use MCP tools to search and manage project memory: > \`memory_save\`, \`memory_recall\`, \`memory_list\`, \`memory_search\`, \`memory_timeline\`, \`memory_details\`, \`memory_update\`, \`memory_delete\`, \`memory_status\` ## Getting Started No previous session context found. This is a fresh memory. **To build memory**, use \`memory_save(content, category, tags, importance)\` to store: - **decisions** — architectural choices, tech stack picks, trade-offs - **patterns** — coding conventions, project patterns, recurring approaches - **errors** — bug fixes, error solutions, debugging insights - **context** — project background, team conventions, environment setup **Important:** Do NOT call \`memory_search\`, \`memory_timeline\`, or \`memory_details\` until memories exist. Use \`memory_status()\` to check if memories are available before searching. **After saving**, use the 3-layer search workflow: 1. \`memory_search(query)\` → Get index with IDs (~50 tokens/result) 2. \`memory_timeline(anchor="ID")\` → Get context around interesting results 3. \`memory_details(ids=["ID1","ID2"])\` → Fetch full content ONLY for filtered IDs `; } } /** * Create context hook handler */ export function createContextHook(cwd: string): ContextHook { const service = new MemoryHookService(cwd); return new ContextHook(service, true); // owns service } export default ContextHook;