#!/usr/bin/env node /** * SessionStart Hook - Load previous context on new session * * Cross-platform (Windows, macOS, Linux) * * Runs when a new Claude session starts. Loads the most recent session * summary into Claude's context via stdout, and reports available * sessions and learned skills. */ const { getSessionsDir, getSessionSearchDirs, getLearnedSkillsDir, findFiles, ensureDir, readFile, stripAnsi, log } = require('../lib/utils'); const { getPackageManager, getSelectionPrompt } = require('../lib/package-manager'); const { listAliases } = require('../lib/session-aliases'); const { detectProjectType } = require('../lib/project-detect'); const path = require('path'); function dedupeRecentSessions(searchDirs) { const recentSessionsByName = new Map(); for (const [dirIndex, dir] of searchDirs.entries()) { const matches = findFiles(dir, '*-session.tmp', { maxAge: 7 }); for (const match of matches) { const basename = path.basename(match.path); const current = { ...match, basename, dirIndex, }; const existing = recentSessionsByName.get(basename); if ( !existing || current.mtime > existing.mtime || (current.mtime === existing.mtime && current.dirIndex < existing.dirIndex) ) { recentSessionsByName.set(basename, current); } } } return Array.from(recentSessionsByName.values()) .sort((left, right) => right.mtime - left.mtime || left.dirIndex - right.dirIndex); } async function main() { const sessionsDir = getSessionsDir(); const learnedDir = getLearnedSkillsDir(); const additionalContextParts = []; // Ensure directories exist ensureDir(sessionsDir); ensureDir(learnedDir); // Check for recent session files (last 7 days) const recentSessions = dedupeRecentSessions(getSessionSearchDirs()); if (recentSessions.length > 0) { const latest = recentSessions[0]; log(`[SessionStart] Found ${recentSessions.length} recent session(s)`); log(`[SessionStart] Latest: ${latest.path}`); // Read and inject the latest session content into Claude's context const content = stripAnsi(readFile(latest.path)); if (content && !content.includes('[Session context goes here]')) { // Only inject if the session has actual content (not the blank template) additionalContextParts.push(`Previous session summary:\n${content}`); } } // Check for learned skills const learnedSkills = findFiles(learnedDir, '*.md'); if (learnedSkills.length > 0) { log(`[SessionStart] ${learnedSkills.length} learned skill(s) available in ${learnedDir}`); } // Check for available session aliases const aliases = listAliases({ limit: 5 }); if (aliases.length > 0) { const aliasNames = aliases.map(a => a.name).join(', '); log(`[SessionStart] ${aliases.length} session alias(es) available: ${aliasNames}`); log(`[SessionStart] Use /sessions load to continue a previous session`); } // Detect and report package manager const pm = getPackageManager(); log(`[SessionStart] Package manager: ${pm.name} (${pm.source})`); // If no explicit package manager config was found, show selection prompt if (pm.source === 'default') { log('[SessionStart] No package manager preference found.'); log(getSelectionPrompt()); } // Detect project type and frameworks (#293) const projectInfo = detectProjectType(); if (projectInfo.languages.length > 0 || projectInfo.frameworks.length > 0) { const parts = []; if (projectInfo.languages.length > 0) { parts.push(`languages: ${projectInfo.languages.join(', ')}`); } if (projectInfo.frameworks.length > 0) { parts.push(`frameworks: ${projectInfo.frameworks.join(', ')}`); } log(`[SessionStart] Project detected — ${parts.join('; ')}`); additionalContextParts.push(`Project type: ${JSON.stringify(projectInfo)}`); } else { log('[SessionStart] No specific project type detected'); } await writeSessionStartPayload(additionalContextParts.join('\n\n')); } function writeSessionStartPayload(additionalContext) { return new Promise((resolve, reject) => { let settled = false; const payload = JSON.stringify({ hookSpecificOutput: { hookEventName: 'SessionStart', additionalContext } }); const handleError = (err) => { if (settled) return; settled = true; if (err) { log(`[SessionStart] stdout write error: ${err.message}`); } reject(err || new Error('stdout stream error')); }; process.stdout.once('error', handleError); process.stdout.write(payload, (err) => { process.stdout.removeListener('error', handleError); if (settled) return; settled = true; if (err) { log(`[SessionStart] stdout write error: ${err.message}`); reject(err); return; } resolve(); }); }); } main().catch(err => { console.error('[SessionStart] Error:', err.message); process.exitCode = 0; // Don't block on errors });