ktg-plugin-marketplace/plugins/voyage/hooks/scripts/post-compact-flush.mjs
Kjell Tore Guttormsen 7a90d348ad feat(voyage)!: marketplace handoff — rename plugins/ultraplan-local to plugins/voyage [skip-docs]
Session 5 of voyage-rebrand (V6). Operator-authorized cross-plugin scope.

- git mv plugins/ultraplan-local plugins/voyage (rename detected, history preserved)
- .claude-plugin/marketplace.json: voyage entry replaces ultraplan-local
- CLAUDE.md: voyage row in plugin list, voyage in design-system consumer list
- README.md: bulk rename ultra*-local commands -> trek* commands; ultraplan-local refs -> voyage; type discriminators (type: trekbrief/trekreview); session-title pattern (voyage:<command>:<slug>); v4.0.0 release-note paragraph
- plugins/voyage/.claude-plugin/plugin.json: homepage/repository URLs point to monorepo voyage path
- plugins/voyage/verify.sh: drop URL whitelist exception (no longer needed)

Closes voyage-rebrand. bash plugins/voyage/verify.sh PASS 7/7. npm test 361/361.
2026-05-05 15:37:52 +02:00

74 lines
2.6 KiB
JavaScript
Executable file

#!/usr/bin/env node
// Hook: post-compact-flush.mjs
// Event: PostCompact (Claude Code v2.1.105+)
// Purpose: Re-inject .session-state.local.json after compaction so
// /trekcontinue and `/trekexecute --resume` see fresh
// session-state and the model has Handover 7 context immediately
// after a context-compaction event.
//
// Read-only — never writes. Always exits 0; never blocks compaction.
//
// Behavior:
// 1. Auto-discover the most-recently-modified
// <cwd>/.claude/projects/*/.session-state.local.json
// 2. Validate it via lib/validators/session-state-validator.mjs
// 3. Emit additionalContext containing project + next_session_label +
// status so the next assistant turn has resume context loaded.
//
// Notes:
// - Uses only node:fs sync APIs that have existed since Node 12 (no
// glob dependency — that requires Node 22).
// - Silent no-op if no state file is discoverable, or if the file is
// malformed. Compaction must not be blocked under any circumstance.
import { readdirSync, statSync } from 'node:fs';
import { join } from 'node:path';
import { validateSessionState } from '../../lib/validators/session-state-validator.mjs';
function findActiveStateFile() {
// Auto-discover: most recently modified .session-state.local.json
// under <cwd>/.claude/projects/*/. Returns absolute path or null.
const projectsDir = '.claude/projects';
let entries;
try { entries = readdirSync(projectsDir, { withFileTypes: true }); }
catch { return null; } // .claude/projects/ absent → silent no-op
let best = null;
let bestMtime = 0;
for (const ent of entries) {
if (!ent.isDirectory()) continue;
const candidate = join(projectsDir, ent.name, '.session-state.local.json');
let st;
try { st = statSync(candidate); }
catch { continue; } // file missing in this project — skip
if (st.mtimeMs > bestMtime) {
bestMtime = st.mtimeMs;
best = candidate;
}
}
return best;
}
function main() {
const stateFile = findActiveStateFile();
if (!stateFile) {
process.stdout.write(JSON.stringify({})); // silent no-op
return;
}
const result = validateSessionState(stateFile);
if (!result.valid || !result.parsed) {
process.stdout.write(JSON.stringify({})); // silent fail
return;
}
const p = result.parsed;
const summary = `[Session resumed after compact]
project: ${p.project}
next_session: ${p.next_session_label}
status: ${p.status}`;
process.stdout.write(JSON.stringify({
additionalContext: summary.slice(0, 10000),
}));
}
try { main(); }
catch { process.stdout.write(JSON.stringify({})); } // never block compaction
process.exit(0);