diff --git a/src/hooks/context.ts b/src/hooks/context.ts index bacffe25..5561067e 100644 --- a/src/hooks/context.ts +++ b/src/hooks/context.ts @@ -15,54 +15,21 @@ export interface SessionStartInput { * Shows user what happened in recent sessions */ export function contextHook(input?: SessionStartInput): void { + if (!input) { + throw new Error('contextHook requires input'); + } + + const project = input.cwd ? path.basename(input.cwd) : path.basename(path.dirname(input.transcript_path)); + const db = new HooksDatabase(); + try { - // Log hook invocation - console.error('[claude-mem context] Hook fired with input:', JSON.stringify({ - session_id: input?.session_id, - transcript_path: input?.transcript_path, - hook_event_name: input?.hook_event_name, - source: input?.source, - has_input: !!input - })); - - // Handle standalone execution (no input provided) - if (!input) { - console.error('[claude-mem context] No input provided - exiting (standalone mode)'); - console.log('No input provided - this script is designed to run as a Claude Code SessionStart hook'); - process.exit(0); - } - - // Extract project from cwd (same as new-hook to ensure consistency) - // If cwd is not available, fall back to extracting from transcript_path - const project = input.cwd ? path.basename(input.cwd) : path.basename(path.dirname(input.transcript_path)); - console.error('[claude-mem context] Extracted project name:', project, 'from', input.cwd ? 'cwd' : 'transcript_path'); - - // Get recent summaries - console.error('[claude-mem context] Querying database for recent summaries...'); - const db = new HooksDatabase(); const summaries = db.getRecentSummaries(project, 5); - db.close(); - console.error('[claude-mem context] Database query complete - found', summaries.length, 'summaries'); - - // Log preview of each summary found - if (summaries.length > 0) { - console.error('[claude-mem context] Summary previews:'); - summaries.forEach((summary, idx) => { - const preview = summary.request?.substring(0, 100) || summary.completed?.substring(0, 100) || '(no content)'; - console.error(` [${idx + 1}]`, preview + (preview.length >= 100 ? '...' : '')); - }); - } - - // If no summaries, provide helpful message if (summaries.length === 0) { - console.error('[claude-mem context] No summaries found - outputting empty context message'); console.log('# Recent Session Context\n\nNo previous sessions found for this project yet.'); - process.exit(0); + return; } - // Format output for Claude - console.error('[claude-mem context] Building markdown context from summaries...'); const output: string[] = []; output.push('# Recent Session Context'); output.push(''); @@ -90,7 +57,6 @@ export function contextHook(input?: SessionStartInput): void { output.push(`**Next Steps:** ${summary.next_steps}`); } - // Show files that were read during the session if (summary.files_read) { try { const files = JSON.parse(summary.files_read); @@ -98,14 +64,12 @@ export function contextHook(input?: SessionStartInput): void { output.push(`**Files Read:** ${files.join(', ')}`); } } catch { - // Backwards compatibility: if not valid JSON, show as text if (summary.files_read.trim()) { output.push(`**Files Read:** ${summary.files_read}`); } } } - // Show files that were edited/written during the session if (summary.files_edited) { try { const files = JSON.parse(summary.files_edited); @@ -113,7 +77,6 @@ export function contextHook(input?: SessionStartInput): void { output.push(`**Files Edited:** ${files.join(', ')}`); } } catch { - // Backwards compatibility: if not valid JSON, show as text if (summary.files_edited.trim()) { output.push(`**Files Edited:** ${summary.files_edited}`); } @@ -124,25 +87,8 @@ export function contextHook(input?: SessionStartInput): void { output.push(''); } - // Log details about the markdown output - const markdownOutput = output.join('\n'); - console.error('[claude-mem context] Markdown built successfully'); - console.error('[claude-mem context] Output length:', markdownOutput.length, 'characters,', output.length, 'lines'); - console.error('[claude-mem context] Output preview (first 200 chars):', markdownOutput.substring(0, 200) + '...'); - console.error('[claude-mem context] Outputting context to stdout for Claude Code injection'); - - // Output to stdout for Claude Code to inject - console.log(markdownOutput); - - console.error('[claude-mem context] Context hook completed successfully'); - process.exit(0); - - } catch (error: any) { - // On error, exit silently - don't block Claude Code - console.error('[claude-mem context] ERROR occurred during context hook execution'); - console.error('[claude-mem context] Error message:', error.message); - console.error('[claude-mem context] Error stack:', error.stack); - console.error('[claude-mem context] Exiting gracefully to avoid blocking Claude Code'); - process.exit(0); + console.log(output.join('\n')); + } finally { + db.close(); } } diff --git a/src/hooks/new.ts b/src/hooks/new.ts index 21557045..fa7a70c4 100644 --- a/src/hooks/new.ts +++ b/src/hooks/new.ts @@ -14,47 +14,30 @@ export interface UserPromptSubmitInput { * Initializes SDK memory session in background */ export function newHook(input?: UserPromptSubmitInput): void { + if (!input) { + throw new Error('newHook requires input'); + } + + const { session_id, cwd, prompt } = input; + const project = path.basename(cwd); + const db = new HooksDatabase(); + try { - // Handle standalone execution (no input provided) - if (!input) { - console.log('No input provided - this script is designed to run as a Claude Code UserPromptSubmit hook'); - console.log('\nExpected input format:'); - console.log(JSON.stringify({ - session_id: "string", - cwd: "string", - prompt: "string" - }, null, 2)); - process.exit(0); - } - - const { session_id, cwd, prompt } = input; - - // Extract project from cwd - const project = path.basename(cwd); - - // Check if session already exists - const db = new HooksDatabase(); const existing = db.findActiveSDKSession(session_id); if (existing) { - // Session already initialized, just continue - db.close(); console.log('{"continue": true, "suppressOutput": true}'); - process.exit(0); + return; } - // Create SDK session record const sessionId = db.createSDKSession(session_id, project, prompt); - db.close(); - // Start SDK worker in background as detached process const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT; if (!pluginRoot) { - throw new Error('CLAUDE_PLUGIN_ROOT not set - claude-mem must be installed as a Claude Code plugin'); + throw new Error('CLAUDE_PLUGIN_ROOT not set'); } - // Use bundled worker const workerPath = path.join(pluginRoot, 'scripts', 'hooks', 'worker.js'); const child = spawn('bun', [workerPath, sessionId.toString()], { detached: true, @@ -63,14 +46,8 @@ export function newHook(input?: UserPromptSubmitInput): void { child.unref(); - // Output hook response console.log('{"continue": true, "suppressOutput": true}'); - process.exit(0); - - } catch (error: any) { - // On error, don't block Claude Code - console.error(`[claude-mem new error: ${error.message}]`); - console.log('{"continue": true, "suppressOutput": true}'); - process.exit(0); + } finally { + db.close(); } } diff --git a/src/hooks/save.ts b/src/hooks/save.ts index 35ebf547..44c8d125 100644 --- a/src/hooks/save.ts +++ b/src/hooks/save.ts @@ -22,72 +22,40 @@ const SKIP_TOOLS = new Set([ * Sends tool observations to worker via Unix socket */ export function saveHook(input?: PostToolUseInput): void { - try { - // Handle standalone execution (no input provided) - if (!input) { - console.log('No input provided - this script is designed to run as a Claude Code PostToolUse hook'); - console.log('\nExpected input format:'); - console.log(JSON.stringify({ - session_id: "string", - cwd: "string", - tool_name: "string", - tool_input: {}, - tool_output: {} - }, null, 2)); - process.exit(0); - } - - const { session_id, tool_name, tool_input, tool_output } = input; - - // Skip certain tools - if (SKIP_TOOLS.has(tool_name)) { - console.log('{"continue": true, "suppressOutput": true}'); - process.exit(0); - } - - // Find active SDK session - const db = new HooksDatabase(); - const session = db.findActiveSDKSession(session_id); - db.close(); - - if (!session) { - // No active session yet - this can happen if UserPromptSubmit hasn't run - // Just exit silently - console.log('{"continue": true, "suppressOutput": true}'); - process.exit(0); - } - - // Get socket path - const socketPath = getWorkerSocketPath(session.id); - - // Send observation via Unix socket - const message = { - type: 'observation', - tool_name, - tool_input: JSON.stringify(tool_input), - tool_output: JSON.stringify(tool_output) - }; - - const client = net.connect(socketPath, () => { - client.write(JSON.stringify(message) + '\n'); - client.end(); - }); - - client.on('error', (err) => { - // Socket not available - worker may have crashed or not started - console.error(`[claude-mem save] Socket error: ${err.message}`); - // Continue anyway, don't block Claude - }); - - client.on('close', () => { - console.log('{"continue": true, "suppressOutput": true}'); - process.exit(0); - }); - - } catch (error: any) { - // On error, don't block Claude Code - console.error(`[claude-mem save error: ${error.message}]`); - console.log('{"continue": true, "suppressOutput": true}'); - process.exit(0); + if (!input) { + throw new Error('saveHook requires input'); } + + const { session_id, tool_name, tool_input, tool_output } = input; + + if (SKIP_TOOLS.has(tool_name)) { + console.log('{"continue": true, "suppressOutput": true}'); + return; + } + + const db = new HooksDatabase(); + const session = db.findActiveSDKSession(session_id); + db.close(); + + if (!session) { + console.log('{"continue": true, "suppressOutput": true}'); + return; + } + + const socketPath = getWorkerSocketPath(session.id); + const message = { + type: 'observation', + tool_name, + tool_input: JSON.stringify(tool_input), + tool_output: JSON.stringify(tool_output) + }; + + const client = net.connect(socketPath, () => { + client.write(JSON.stringify(message) + '\n'); + client.end(); + }); + + client.on('close', () => { + console.log('{"continue": true, "suppressOutput": true}'); + }); } diff --git a/src/hooks/summary.ts b/src/hooks/summary.ts index 486d6e1c..298b8728 100644 --- a/src/hooks/summary.ts +++ b/src/hooks/summary.ts @@ -13,87 +13,31 @@ export interface StopInput { * Sends FINALIZE message to worker via Unix socket */ export function summaryHook(input?: StopInput): void { - try { - // Log hook entry point - console.error('[claude-mem summary] Hook fired', { - input: input ? { session_id: input.session_id, cwd: input.cwd } : null - }); - - // Handle standalone execution (no input provided) - if (!input) { - console.log('No input provided - this script is designed to run as a Claude Code Stop hook'); - console.log('\nExpected input format:'); - console.log(JSON.stringify({ - session_id: "string", - cwd: "string" - }, null, 2)); - process.exit(0); - } - - const { session_id } = input; - console.error('[claude-mem summary] Searching for active SDK session', { session_id }); - - // Find active SDK session - const db = new HooksDatabase(); - const session = db.findActiveSDKSession(session_id); - db.close(); - - if (!session) { - // No active session - nothing to finalize - console.error('[claude-mem summary] No active SDK session found', { session_id }); - console.log('{"continue": true, "suppressOutput": true}'); - process.exit(0); - } - - console.error('[claude-mem summary] Active SDK session found', { - session_id: session.id, - collection_name: session.collection_name, - worker_pid: session.worker_pid - }); - - // Get socket path - const socketPath = getWorkerSocketPath(session.id); - - // Send FINALIZE message via Unix socket - const message = { - type: 'finalize' - }; - - console.error('[claude-mem summary] Attempting to send FINALIZE message to worker socket', { - socketPath, - message - }); - - const client = net.connect(socketPath, () => { - console.error('[claude-mem summary] Socket connection established, sending message'); - client.write(JSON.stringify(message) + '\n'); - client.end(); - }); - - client.on('error', (err) => { - // Socket not available - worker may have already finished or crashed - console.error('[claude-mem summary] Socket error occurred', { - error: err.message, - code: (err as any).code, - socketPath - }); - // Continue anyway, don't block Claude - }); - - client.on('close', () => { - console.error('[claude-mem summary] Socket connection closed successfully'); - console.log('{"continue": true, "suppressOutput": true}'); - process.exit(0); - }); - - } catch (error: any) { - // On error, don't block Claude Code - console.error('[claude-mem summary] Unexpected error in hook', { - error: error.message, - stack: error.stack, - name: error.name - }); - console.log('{"continue": true, "suppressOutput": true}'); - process.exit(0); + if (!input) { + throw new Error('summaryHook requires input'); } + + const { session_id } = input; + const db = new HooksDatabase(); + const session = db.findActiveSDKSession(session_id); + db.close(); + + if (!session) { + console.log('{"continue": true, "suppressOutput": true}'); + return; + } + + const socketPath = getWorkerSocketPath(session.id); + const message = { + type: 'finalize' + }; + + const client = net.connect(socketPath, () => { + client.write(JSON.stringify(message) + '\n'); + client.end(); + }); + + client.on('close', () => { + console.log('{"continue": true, "suppressOutput": true}'); + }); }