#!/usr/bin/env node // coord - SessionStart hook: inject this repo's pending coordination inbox // (directed messages + unseen broadcasts) as additionalContext. // // Thin Node wrapper (marketplace convention: hooks are .mjs) around the bash // engine scripts/coord-inbox.sh, which owns the mailbox semantics and is // covered by scripts/coord-selftest.sh. Zero dependencies. Always exits 0 - // a broken mailbox must never block a session. import { execFileSync } from 'node:child_process'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; function emit(context) { const out = { continue: true }; if (context) { out.hookSpecificOutput = { hookEventName: 'SessionStart', additionalContext: context }; } process.stdout.write(JSON.stringify(out) + '\n'); } try { const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || join(dirname(fileURLToPath(import.meta.url)), '..', '..'); // No --repo, and no identity resolution here. This used to resolve the repo // itself and fall back to process.cwd(), which made it a fourth independent // copy of the identity rule - and the only one that runs in production, so // the engine's guards were bypassed exactly where they mattered. The engine // runs in this same cwd and derives the identity from git alone; passing // --repo would also mark the read as an explicit override and suppress the // mailbox-collision check. Boundary rule: no mailbox logic lives in the hook. const inbox = execFileSync('bash', [join(pluginRoot, 'scripts', 'coord-inbox.sh')], { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8' }); emit(inbox.trim() ? '== Repo coordination (unread messages) ==\n' + inbox : ''); } catch { emit(''); }