#!/usr/bin/env node /** * Session Finalizer Hook - Stop hook * Finalizes session, creates documentation, and saves state */ const fs = require('fs-extra'); const path = require('path'); // Add lib directory to require path const libPath = path.join(__dirname, '..', 'lib'); const { SessionStateManager } = require(path.join(libPath, 'session-state')); const { ShadowLearner } = require(path.join(libPath, 'shadow-learner')); const { ContextMonitor } = require(path.join(libPath, 'context-monitor')); async function createRecoveryInfo(sessionSummary, contextMonitor) { /** * Create recovery information if needed */ try { const contextUsage = contextMonitor.getContextUsageRatio(); // If context was high when session ended, create recovery guide if (contextUsage > 0.8) { let recoveryContent = `# Session Recovery Information ## Context Status - **Context Usage**: ${(contextUsage * 100).toFixed(1)}% when session ended - **Reason**: Session ended with high context usage ## What This Means Your Claude session ended while using a significant amount of context. This could mean: 1. You were working on a complex task 2. Context limits were approaching 3. Session was interrupted ## Recovery Steps ### 1. Check Your Progress Review these recently modified files: `; for (const filePath of sessionSummary.modifiedFiles || sessionSummary.modified_files || []) { recoveryContent += `- ${filePath}\n`; } recoveryContent += ` ### 2. Review Last Actions Recent commands executed: `; const recentCommands = (sessionSummary.commandsExecuted || sessionSummary.commands_executed || []).slice(-5); for (const cmdInfo of recentCommands) { recoveryContent += `- \`${cmdInfo.command}\`\n`; } recoveryContent += ` ### 3. Continue Your Work 1. Check \`ACTIVE_TODOS.md\` for pending tasks 2. Review \`LAST_SESSION.md\` for complete session history 3. Use \`git status\` to see current file changes 4. Consider committing your progress: \`git add -A && git commit -m "Work in progress"\` ### 4. Available Backups `; for (const backup of sessionSummary.backupHistory || sessionSummary.backup_history || []) { const status = backup.success ? '✅' : '❌'; recoveryContent += `- ${status} ${backup.backup_id || backup.backupId} - ${backup.reason}\n`; } recoveryContent += ` ## Quick Recovery Commands \`\`\`bash # Check current status git status # View recent changes git diff # List available backups ls .claude_hooks/backups/ # View active todos cat ACTIVE_TODOS.md # View last session summary cat LAST_SESSION.md \`\`\` *This recovery guide was created because your session ended with ${(contextUsage * 100).toFixed(1)}% context usage.* `; await fs.writeFile('RECOVERY_GUIDE.md', recoveryContent); } } catch (error) { // Don't let recovery guide creation break session finalization } } async function logSessionCompletion(sessionSummary) { /** * Log session completion for analysis */ try { const logDir = path.join('.claude_hooks', 'logs'); await fs.ensureDir(logDir); const completionLog = { timestamp: new Date().toISOString(), type: 'session_completion', session_id: sessionSummary.sessionId || sessionSummary.session_id || 'unknown', duration_minutes: (sessionSummary.sessionStats || sessionSummary.session_stats || {}).duration_minutes || 0, total_tools: (sessionSummary.sessionStats || sessionSummary.session_stats || {}).total_tool_calls || 0, files_modified: (sessionSummary.modifiedFiles || sessionSummary.modified_files || []).length, commands_executed: (sessionSummary.sessionStats || sessionSummary.session_stats || {}).total_commands || 0, backups_created: (sessionSummary.backupHistory || sessionSummary.backup_history || []).length }; const logFile = path.join(logDir, 'session_completions.jsonl'); await fs.appendFile(logFile, JSON.stringify(completionLog) + '\n'); } catch (error) { // Don't let logging errors break finalization } } async function main() { try { let inputData = {}; // Handle stdin input if (!process.stdin.isTTY) { try { let input = ''; process.stdin.setEncoding('utf8'); for await (const chunk of process.stdin) { input += chunk; } if (input.trim()) { inputData = JSON.parse(input); } } catch (error) { // If input parsing fails, use empty object inputData = {}; } } // Initialize components const sessionManager = new SessionStateManager(); const shadowLearner = new ShadowLearner(); const contextMonitor = new ContextMonitor(); // Create session documentation await sessionManager.createContinuationDocs(); // Save all learned patterns await shadowLearner.saveDatabase(); // Get session summary for logging const sessionSummary = await sessionManager.getSessionSummary(); // Create recovery guide if session was interrupted await createRecoveryInfo(sessionSummary, contextMonitor); // Clean up session await sessionManager.cleanupSession(); // Log session completion await logSessionCompletion(sessionSummary); const modifiedFiles = sessionSummary.modifiedFiles || sessionSummary.modified_files || []; const totalTools = (sessionSummary.sessionStats || sessionSummary.session_stats || {}).total_tool_calls || 0; // Always allow - this is a cleanup hook const response = { allow: true, message: `Session finalized. Modified ${modifiedFiles.length} files, used ${totalTools} tools.` }; console.log(JSON.stringify(response)); process.exit(0); } catch (error) { // Session finalization should never block const response = { allow: true, message: `Session finalization error: ${error.message}` }; console.log(JSON.stringify(response)); process.exit(0); } } // Handle unhandled promise rejections process.on('unhandledRejection', (error) => { const response = { allow: true, message: `Session finalization error: ${error.message}` }; console.log(JSON.stringify(response)); process.exit(0); }); main();