Files
oh-my-claudecode/scripts/project-memory-session.mjs
Yeachan-Heo a6a0ff61eb Restore project memory on real session starts
The installed SessionStart hook runs in its own Node process, so the
existing project-memory registration path never reached the startup
additionalContext consumed by Claude Code. Load and, when needed,
refresh persisted project memory directly from the plugin runtime
inside the session-start script, then append the formatted summary
without relying on process-local collector state. Keep the legacy
helper aligned with plugin-root-aware imports.

Constraint: The installed SessionStart runtime is `scripts/session-start.mjs`, not the in-process bridge path
Constraint: Session-start hooks must remain failure-tolerant and continue on missing dist/runtime artifacts
Rejected: Refactor all session-start assembly into shared bridge/script runtime | broader change than needed for the bug
Rejected: Preserve collector-only registration across processes | process isolation makes that design ineffective here
Confidence: high
Scope-risk: narrow
Directive: Do not rely on in-memory contextCollector state for subprocess hook injection paths without a persisted or returned handoff
Tested: node --check scripts/session-start.mjs scripts/project-memory-session.mjs
Tested: Manual A/B subprocess reproduction against pre-fix session-start script vs patched script with persisted project-memory.json
Tested: Added focused regression test in src/__tests__/session-start-script-context.test.ts
Not-tested: Full Vitest run in this worktree (local shared node_modules does not expose a runnable repo-local vitest binary)
Related: issue #1779
2026-03-20 03:50:54 +00:00

81 lines
2.8 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* SessionStart Hook: Project Memory Detection
* Auto-detects project environment and injects context
*/
import { dirname, join } from 'path';
import { fileURLToPath, pathToFileURL } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
function getRuntimeBaseDir() {
return process.env.CLAUDE_PLUGIN_ROOT || join(__dirname, '..');
}
// Import timeout-protected stdin reader (prevents hangs on Linux/Windows, see issue #240, #524)
let readStdin;
try {
const mod = await import(pathToFileURL(join(__dirname, 'lib', 'stdin.mjs')).href);
readStdin = mod.readStdin;
} catch {
// Fallback: inline timeout-protected readStdin if lib module is missing
readStdin = (timeoutMs = 5000) => new Promise((resolve) => {
const chunks = [];
let settled = false;
const timeout = setTimeout(() => {
if (!settled) { settled = true; process.stdin.removeAllListeners(); process.stdin.destroy(); resolve(Buffer.concat(chunks).toString('utf-8')); }
}, timeoutMs);
process.stdin.on('data', (chunk) => { chunks.push(chunk); });
process.stdin.on('end', () => { if (!settled) { settled = true; clearTimeout(timeout); resolve(Buffer.concat(chunks).toString('utf-8')); } });
process.stdin.on('error', () => { if (!settled) { settled = true; clearTimeout(timeout); resolve(''); } });
if (process.stdin.readableEnded) { if (!settled) { settled = true; clearTimeout(timeout); resolve(Buffer.concat(chunks).toString('utf-8')); } }
});
}
// Dynamic import of project memory module (prevents crash if dist is missing, see issue #362)
let registerProjectMemoryContext;
try {
const mod = await import(pathToFileURL(join(getRuntimeBaseDir(), 'dist', 'hooks', 'project-memory', 'index.js')).href);
registerProjectMemoryContext = mod.registerProjectMemoryContext;
} catch {
// dist not built or missing - skip project memory detection silently
registerProjectMemoryContext = null;
}
/**
* Main hook execution
*/
async function main() {
try {
const input = await readStdin();
let data = {};
try { data = JSON.parse(input); } catch {}
// Extract directory and session ID
const directory = data.cwd || data.directory || process.cwd();
const sessionId = data.session_id || data.sessionId || '';
// Register project memory context (skip if module unavailable)
if (registerProjectMemoryContext) {
await registerProjectMemoryContext(sessionId, directory);
}
// Return success (context registered via contextCollector, not returned here)
console.log(JSON.stringify({
continue: true,
suppressOutput: true
}));
} catch (error) {
// Always continue on error
console.log(JSON.stringify({
continue: true,
suppressOutput: true
}));
}
}
main();