#!/usr/bin/env node // session-start-load-handoff.mjs — graceful-handoff v2.0 // SessionStart hook: on `source: resume` or `source: compact`, find // NEXT-SESSION-PROMPT.local.md (or NEXT-SESSION-*.local.md) in cwd and up // to 3 levels above, inject contents into the new session's context, then // archive the file to prevent stale-load in subsequent sessions. import { readFileSync, existsSync, readdirSync, renameSync } from 'node:fs'; import { dirname, join } from 'node:path'; function readStdin() { try { return readFileSync(0, 'utf-8'); } catch { return ''; } } // Find NEXT-SESSION-*.local.md in dir; returns path or null function findHandoffIn(dir) { try { const entries = readdirSync(dir); // Prefer NEXT-SESSION-PROMPT.local.md, then any NEXT-SESSION-*.local.md const exact = entries.find(e => e === 'NEXT-SESSION-PROMPT.local.md'); if (exact) return join(dir, exact); const match = entries.find(e => /^NEXT-SESSION-.*\.local\.md$/.test(e) && !/\.archived\./.test(e)); if (match) return join(dir, match); } catch { /* ignore */ } return null; } // Walk up from start, max 3 levels, looking for handoff function findHandoffUpwards(start) { let cur = start; for (let i = 0; i < 4; i++) { // 0,1,2,3 — start + 3 ancestors const found = findHandoffIn(cur); if (found) return found; const parent = dirname(cur); if (parent === cur) break; cur = parent; } return null; } function main() { const raw = readStdin(); if (!raw.trim()) process.exit(0); let payload; try { payload = JSON.parse(raw); } catch { process.exit(0); } const source = payload?.source; if (source !== 'resume' && source !== 'compact') { process.exit(0); } const cwd = payload?.cwd || process.cwd(); const handoffPath = findHandoffUpwards(cwd); if (!handoffPath) { process.exit(0); } let content; try { content = readFileSync(handoffPath, 'utf-8'); } catch { process.exit(0); } // Inject via additionalContext for clean structured output const output = { hookSpecificOutput: { hookEventName: 'SessionStart', additionalContext: `\n${content}\n`, }, }; process.stdout.write(JSON.stringify(output)); // Archive to prevent stale-load in subsequent sessions try { const archived = handoffPath.replace(/\.local\.md$/, '.archived.local.md'); if (!existsSync(archived)) { renameSync(handoffPath, archived); } } catch { /* archival is best-effort, never block injection */ } process.exit(0); } main();