github-router 0.3.313 → 0.3.314

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/aic-ledger-Cx_Ubon3.js +238 -0
  2. package/dist/aic-ledger-Cx_Ubon3.js.map +1 -0
  3. package/dist/{attribution-settings-Dnt04Iyn.js → attribution-settings-Btc5EZTj.js} +4 -4
  4. package/dist/{attribution-settings-Dnt04Iyn.js.map → attribution-settings-Btc5EZTj.js.map} +1 -1
  5. package/dist/browser-ext/manifest.json +1 -1
  6. package/dist/{claude-D_Me80Yi.js → claude-D0vuAqTV.js} +122 -8
  7. package/dist/claude-D0vuAqTV.js.map +1 -0
  8. package/dist/{codex-CE7zZm-M.js → codex-BNm9D0iJ.js} +4 -4
  9. package/dist/{codex-CE7zZm-M.js.map → codex-BNm9D0iJ.js.map} +1 -1
  10. package/dist/engine-DsVZuCrD.js +2 -0
  11. package/dist/{gate-discovery-_flEdhYo.js → gate-discovery-CO2njGBB.js} +2 -2
  12. package/dist/{gate-discovery-_flEdhYo.js.map → gate-discovery-CO2njGBB.js.map} +1 -1
  13. package/dist/hooks.mjs +142 -3
  14. package/dist/hooks.sha256 +1 -1
  15. package/dist/internal-aic-status-C_swX3_7.js +95 -0
  16. package/dist/internal-aic-status-C_swX3_7.js.map +1 -0
  17. package/dist/{internal-stop-hook-BJ3o61-L.js → internal-stop-hook-D56b6iVj.js} +2 -2
  18. package/dist/{internal-stop-hook-BJ3o61-L.js.map → internal-stop-hook-D56b6iVj.js.map} +1 -1
  19. package/dist/main.js +8 -7
  20. package/dist/main.js.map +1 -1
  21. package/dist/{peer-mcp-personas-DklYru_1.js → peer-mcp-personas-0_XU6nm9.js} +31 -4
  22. package/dist/{peer-mcp-personas-DklYru_1.js.map → peer-mcp-personas-0_XU6nm9.js.map} +1 -1
  23. package/dist/{provision-BYdIsKcp.js → provision-DuCnZJj_.js} +6 -2
  24. package/dist/provision-DuCnZJj_.js.map +1 -0
  25. package/dist/{serve-DI4QgSSB.js → serve-DXFVR6A0.js} +5 -5
  26. package/dist/{serve-DI4QgSSB.js.map → serve-DXFVR6A0.js.map} +1 -1
  27. package/dist/{server-setup-s2Os8RfG.js → server-setup-CP8nGop3.js} +105 -6
  28. package/dist/server-setup-CP8nGop3.js.map +1 -0
  29. package/dist/{start-DV5iDhbP.js → start-BHEjESCB.js} +3 -3
  30. package/dist/{start-DV5iDhbP.js.map → start-BHEjESCB.js.map} +1 -1
  31. package/package.json +1 -1
  32. package/dist/claude-D_Me80Yi.js.map +0 -1
  33. package/dist/engine-DyzGsCUb.js +0 -2
  34. package/dist/provision-BYdIsKcp.js.map +0 -1
  35. package/dist/server-setup-s2Os8RfG.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"internal-stop-hook-BJ3o61-L.js","names":[],"sources":["../src/internal-stop-hook.ts"],"sourcesContent":["/**\n * The internal `internal-stop-hook` subcommand: the executable a spawned Claude\n * Code session's Stop hook invokes (registered into the mirrored settings.json by\n * the launcher when `GH_ROUTER_ENABLE_STOP_GATE` is set). It reads Claude Code's\n * hook payload from stdin, runs the SEALED structural gate over the session's\n * working-tree diff, and maps the result to the hook exit contract: exit 2 (with\n * the reason on stderr) blocks the stop so the model fixes the failure; exit 0\n * allows it. The `stop_hook_active` loop guard (in `decideStopHook`) guarantees\n * it can never wedge the session.\n *\n * All decision logic lives in `decideStopHook` (pure, unit-tested); this wrapper\n * only does stdin read + the live `git diff` capture + the exit. The live firing\n * is verified by the gated E2E (it needs a real spawned session).\n */\n\nimport { defineCommand } from \"citty\"\n\nimport { spawn } from \"node:child_process\"\nimport { randomBytes } from \"node:crypto\"\nimport { mkdirSync, readFileSync, unlinkSync, writeFileSync } from \"node:fs\"\nimport { tmpdir } from \"node:os\"\nimport path from \"node:path\"\n\nimport { parseBoolEnv, runCommandCapture } from \"./lib/exec\"\nimport { PACKAGE_ROOT_FLAG, explicitPackageRoot } from \"./lib/package-root\"\nimport { liveExec } from \"./lib/orchestration\"\nimport { readDiscoveredGate } from \"./lib/orchestration/gate-discovery\"\nimport { checksForDescriptor, descriptorHash, parseGateDescriptor, type GateDescriptor } from \"./lib/orchestration/harness-parse\"\nimport { hookMcpRuntimeFromEnv } from \"./lib/orchestration/hook-mcp-client\"\nimport {\n decideStopHook,\n fileBlockBudget,\n launchBaselineKey,\n stopGateId,\n stopReviewEnabled,\n type StopReviewContext,\n} from \"./lib/orchestration/stop-gate-hook\"\nimport {\n fileBaselineStore,\n fileLastPromptStore,\n fileReviewDebounce,\n repoRoot,\n stopGateEnabledForRepo,\n stopReviewStateDir,\n} from \"./lib/orchestration/stop-gate-policy\"\nimport { parseIntEnv } from \"./lib/exec\"\n\n/**\n * Read the hook payload from stdin SYNCHRONOUSLY (`readFileSync(0)`). An async\n * stdin read leaves an in-flight libuv FS request that, on Windows, races the\n * process teardown and trips a `uv_async_send` assertion; a synchronous read has\n * no such handle. Hooks always receive piped/redirected stdin, so this never\n * blocks (guarded against an interactive TTY, and any error -> \"\").\n */\nfunction readStdin(): string {\n try {\n if (process.stdin.isTTY) return \"\"\n return readFileSync(0, \"utf8\")\n } catch {\n return \"\"\n }\n}\n\n/** Max diff bytes scanned for gate-weakening: a hard cap so a huge generated diff\n * (e.g. a lockfile) can never OOM or stall the hook. */\nconst MAX_DIFF_BYTES = 2 * 1024 * 1024\n\n/** Build a tiny synthetic diff for untracked paths so the weakening scan sees\n * their contents too. No index mutation (`git add -N`) is needed. */\nasync function captureUntrackedDiff(cwd: string, paths: string[]): Promise<string> {\n const chunks: string[] = []\n let bytes = 0\n for (const untrackedPath of paths) {\n const result = await runCommandCapture(\n [\"git\", \"diff\", \"--no-index\", \"--\", \"/dev/null\", untrackedPath],\n { cwd, timeoutMs: 5_000, maxStdoutBytes: MAX_DIFF_BYTES },\n )\n // `git diff --no-index` returns 1 when differences exist; anything else is an\n // actual failure. A truncated untracked diff is unknown, never \"no diff\".\n if (result.code !== 1 || result.truncated) throw new Error(`git diff for untracked file failed with exit ${result.code}`)\n bytes += Buffer.byteLength(result.stdout, \"utf8\")\n if (bytes > MAX_DIFF_BYTES) throw new Error(\"git working-tree capture exceeded its size limit\")\n chunks.push(result.stdout)\n }\n return chunks.join(\"\")\n}\n\n/** Capture every working-tree change WITHOUT mutating the user's index (no\n * `git add -N`). `git diff HEAD` covers staged + unstaged tracked changes;\n * `git ls-files --others` catches untracked files and each is diffed against an\n * empty file so gate-weakening in new tests is visible. Git failures/truncation\n * reject, so the decision layer runs the full checks instead of mistaking an\n * unknown tree for a no-diff turn. */\nexport async function captureStopGateDiff(cwd: string): Promise<string> {\n const [diffResult, untrackedResult] = await Promise.all([\n runCommandCapture([\"git\", \"diff\", \"HEAD\"], { cwd, timeoutMs: 5_000, maxStdoutBytes: MAX_DIFF_BYTES }),\n runCommandCapture([\"git\", \"ls-files\", \"--others\", \"--exclude-standard\", \"-z\"], {\n cwd,\n timeoutMs: 5_000,\n maxStdoutBytes: MAX_DIFF_BYTES,\n }),\n ])\n if (diffResult.code !== 0) throw new Error(`git diff failed with exit ${diffResult.code}`)\n if (untrackedResult.code !== 0) throw new Error(`git ls-files failed with exit ${untrackedResult.code}`)\n if (diffResult.truncated || untrackedResult.truncated) throw new Error(\"git working-tree capture exceeded its size limit\")\n\n const untrackedPaths = untrackedResult.stdout.split(\"\\0\").filter(Boolean)\n if (untrackedPaths.length === 0) return diffResult.stdout\n const untrackedDiff = await captureUntrackedDiff(cwd, untrackedPaths)\n if (Buffer.byteLength(diffResult.stdout, \"utf8\") + Buffer.byteLength(untrackedDiff, \"utf8\") > MAX_DIFF_BYTES) {\n throw new Error(\"git working-tree capture exceeded its size limit\")\n }\n return diffResult.stdout + untrackedDiff\n}\n\n/** Flush a message to stderr before exiting (process.exit can drop an unflushed\n * write; the model reads this stderr on a block). */\nasync function writeStderr(msg: string): Promise<void> {\n await new Promise<void>((resolve) => {\n process.stderr.write(msg, () => resolve())\n })\n}\n\n/**\n * Fire-and-forget spawn of the detached background reviewer. The payload (which\n * includes the up-to-2-MiB diff) is written to a temp file SYNCHRONOUSLY before\n * the spawn — a pipe to the child's stdin would race the parent's `process.exit`\n * and could deliver a truncated diff. The child reads the file (path passed via\n * `GH_ROUTER_STOP_REVIEW_PAYLOAD`), unlinks it, and inherits the proxy URL/nonce\n * env. Everything is swallowed: the advisory layer never affects the stop.\n */\nfunction spawnStopReview(ctx: StopReviewContext, extras: { prompt: string; transcriptPath: string }): void {\n let payloadPath: string | undefined\n try {\n const dir = stopReviewStateDir()\n mkdirSync(dir, { recursive: true })\n payloadPath = path.join(dir, `payload-${process.pid}-${randomBytes(4).toString(\"hex\")}.json`)\n writeFileSync(\n payloadPath,\n JSON.stringify({\n session_id: ctx.sessionId,\n cwd: ctx.cwd,\n diff: ctx.diff,\n prompt: extras.prompt,\n transcript_path: extras.transcriptPath,\n }),\n { mode: 0o600 },\n )\n // Invoke the same binary's `internal-stop-review` subcommand. Preserve the\n // package root when this hook itself runs from the relocated launcher.\n const scriptArgs = process.argv[1] && process.argv[1] !== process.execPath ? [process.argv[1]] : []\n const root = explicitPackageRoot()\n const packageRootArgs = root ? [PACKAGE_ROOT_FLAG, root] : []\n const child = spawn(process.execPath, [...scriptArgs, ...packageRootArgs, \"internal-stop-review\"], {\n detached: true,\n windowsHide: true,\n stdio: \"ignore\",\n env: { ...process.env, GH_ROUTER_STOP_REVIEW_PAYLOAD: payloadPath },\n })\n // A spawn failure (EAGAIN / EACCES / fork limit) is delivered ASYNCHRONOUSLY\n // via the child's 'error' event, AFTER this synchronous try block exits. With\n // no listener Node escalates it to an uncaughtException — which main.ts turns\n // into process.exit(1), corrupting the Stop hook's exit code. Swallow it here\n // (and drop the now-orphaned payload the child never read) so the advisory\n // spawn truly never affects the stop.\n const orphan = payloadPath\n child.on(\"error\", () => {\n if (orphan) {\n try {\n unlinkSync(orphan)\n } catch {\n /* best-effort */\n }\n }\n })\n child.unref()\n } catch {\n // Advisory spawn is best-effort; never disrupt the stop. If we wrote the\n // payload file but the spawn failed, drop it so it doesn't orphan (the\n // child — which would normally unlink it — never started).\n if (payloadPath) {\n try {\n unlinkSync(payloadPath)\n } catch {\n /* best-effort */\n }\n }\n }\n}\n\n/** The checks for a resolved descriptor: sealed descriptors resolve their sealed\n * command set; parsed/discovered carry their own. */\ntype ResolveChecks = NonNullable<Parameters<typeof decideStopHook>[0][\"resolveChecks\"]>\n\n/**\n * Build the dynamic `resolveChecks` for the live Stop hook, per the env the\n * launcher set:\n * - GH_ROUTER_STOP_GATE_PARSED → re-derive the deterministic parser at the\n * stop-time tree (stateless, no cache);\n * - GH_ROUTER_STOP_GATE_DISCOVERED → read the cached evidence-pinned record.\n * Both pin the checks to the descriptor's `workdir` (the repo root) and compute\n * the launch-stable baseline key so the launch-captured (pre-mutation) baseline\n * is read. Any miss → null → the hook fails OPEN.\n */\nfunction buildResolveChecks(mode: \"parsed\" | \"discovered\"): ResolveChecks {\n const includeTests = parseBoolEnv(process.env.GH_ROUTER_STOP_GATE_RUN_TESTS) === true\n return async (cwd: string) => {\n const root = await repoRoot(cwd).catch(() => cwd)\n let descriptor: GateDescriptor | null = null\n if (mode === \"parsed\") {\n descriptor = await parseGateDescriptor(root, { includeTests }).catch(() => null)\n } else {\n const rec = await readDiscoveredGate(root).catch(() => null)\n if (rec) {\n descriptor = {\n kind: \"discovered\",\n checks: rec.checks,\n ecosystem: rec.ecosystem,\n workdir: root,\n evidence: rec.evidence,\n }\n }\n }\n if (!descriptor) return null\n const checks = checksForDescriptor(descriptor)\n if (checks.length === 0) return null\n const descriptorKey = descriptorHash(descriptor)\n const workdir = descriptor.workdir || root\n const token = process.env.GH_ROUTER_STOP_GATE_BASELINE_TOKEN || undefined\n return { checks, workdir, descriptorKey, baselineKey: launchBaselineKey(workdir, descriptorKey, token) }\n }\n}\n\n/** Which dynamic gate mode the launcher armed (sealed → undefined). */\nfunction dynamicMode(): \"parsed\" | \"discovered\" | undefined {\n if (parseBoolEnv(process.env.GH_ROUTER_STOP_GATE_PARSED) === true) return \"parsed\"\n if (parseBoolEnv(process.env.GH_ROUTER_STOP_GATE_DISCOVERED) === true) return \"discovered\"\n return undefined\n}\n\nexport const internalStopHook = defineCommand({\n meta: {\n name: \"internal-stop-hook\",\n description:\n \"Internal: the structural-gate Stop hook. Reads the Claude Code hook payload on stdin, \"\n + \"runs the sealed gate, exits 2 (blocks the stop) on a red gate or gate-weakening diff.\",\n },\n async run() {\n const stdin = readStdin()\n // The advisory review (hook V2) is wired only when it's enabled AND the\n // launcher injected the proxy URL/nonce. It is side-effect-only: the\n // deterministic gate below is unchanged and remains the only blocker.\n const reviewEnabled = stopReviewEnabled() && hookMcpRuntimeFromEnv() !== undefined\n let transcriptPath = \"\"\n let userPrompt = \"\"\n if (reviewEnabled) {\n // Parse the payload once for the transcript pointer + the session id used\n // to look up the user's last prompt (the Stop payload carries no prompt;\n // the UserPromptSubmit hook stashed it). Best-effort — a parse miss just\n // means the reviewer judges against the diff alone.\n try {\n const p: unknown = JSON.parse(stdin)\n if (p && typeof p === \"object\") {\n const obj = p as { transcript_path?: unknown; session_id?: unknown }\n transcriptPath = typeof obj.transcript_path === \"string\" ? obj.transcript_path : \"\"\n const sid = typeof obj.session_id === \"string\" ? obj.session_id : \"\"\n if (sid) {\n userPrompt = (await fileLastPromptStore(stopReviewStateDir()).read(sid).catch(() => null)) ?? \"\"\n }\n }\n } catch {\n /* tolerate a non-JSON stdin */\n }\n }\n\n let decision: { exitCode: 0 | 2; stderr?: string }\n try {\n const timeoutEnv = parseIntEnv(process.env.GH_ROUTER_STOP_GATE_TIMEOUT_MS)\n const mode = dynamicMode()\n decision = await decideStopHook({\n stdin,\n gateId: stopGateId(),\n exec: liveExec,\n captureDiff: captureStopGateDiff,\n fallbackCwd: process.cwd(),\n budget: fileBlockBudget(path.join(tmpdir(), \"gh-router-stopgate\")),\n baseline: fileBaselineStore(path.join(tmpdir(), \"gh-router-stopgate-baseline\")),\n isEnabledForRepo: (cwd) => stopGateEnabledForRepo(cwd),\n resolveChecks: mode ? buildResolveChecks(mode) : undefined,\n // parseIntEnv already rejects non-positive / non-integer / truncating\n // values, so undefined here means \"use the built-in default\".\n timeoutMs: timeoutEnv,\n reviewDebounce: reviewEnabled ? fileReviewDebounce(stopReviewStateDir()) : undefined,\n spawnReview: reviewEnabled\n ? (ctx) => spawnStopReview(ctx, { prompt: userPrompt, transcriptPath })\n : undefined,\n })\n } catch {\n // Fail OPEN on ANY unexpected error: a Stop hook must never wedge the\n // session, so an internal crash allows the stop (exit 0) rather than\n // surfacing a non-blocking error or a hang.\n process.exitCode = 0\n return\n }\n // Write any stderr the decision carries — both the exit-2 block reason AND\n // the LOUD exit-0 stand-down (max-block budget reached). A clean green stop\n // carries no stderr, so this stays silent on the common path.\n if (decision.stderr) {\n await writeStderr(`${decision.stderr}\\n`)\n }\n // Natural exit: set the code and return. A hard process.exit() races libuv's\n // stdio teardown on Windows (uv_async_send assertion) on the fast-return\n // paths. The detached review child is unref'd and the gate's children are\n // reaped, so no handle keeps the loop alive — the process exits with this\n // code. The exit-2 block contract is preserved (stderr is flushed above).\n process.exitCode = decision.exitCode\n },\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"internal-stop-hook-D56b6iVj.js","names":[],"sources":["../src/internal-stop-hook.ts"],"sourcesContent":["/**\n * The internal `internal-stop-hook` subcommand: the executable a spawned Claude\n * Code session's Stop hook invokes (registered into the mirrored settings.json by\n * the launcher when `GH_ROUTER_ENABLE_STOP_GATE` is set). It reads Claude Code's\n * hook payload from stdin, runs the SEALED structural gate over the session's\n * working-tree diff, and maps the result to the hook exit contract: exit 2 (with\n * the reason on stderr) blocks the stop so the model fixes the failure; exit 0\n * allows it. The `stop_hook_active` loop guard (in `decideStopHook`) guarantees\n * it can never wedge the session.\n *\n * All decision logic lives in `decideStopHook` (pure, unit-tested); this wrapper\n * only does stdin read + the live `git diff` capture + the exit. The live firing\n * is verified by the gated E2E (it needs a real spawned session).\n */\n\nimport { defineCommand } from \"citty\"\n\nimport { spawn } from \"node:child_process\"\nimport { randomBytes } from \"node:crypto\"\nimport { mkdirSync, readFileSync, unlinkSync, writeFileSync } from \"node:fs\"\nimport { tmpdir } from \"node:os\"\nimport path from \"node:path\"\n\nimport { parseBoolEnv, runCommandCapture } from \"./lib/exec\"\nimport { PACKAGE_ROOT_FLAG, explicitPackageRoot } from \"./lib/package-root\"\nimport { liveExec } from \"./lib/orchestration\"\nimport { readDiscoveredGate } from \"./lib/orchestration/gate-discovery\"\nimport { checksForDescriptor, descriptorHash, parseGateDescriptor, type GateDescriptor } from \"./lib/orchestration/harness-parse\"\nimport { hookMcpRuntimeFromEnv } from \"./lib/orchestration/hook-mcp-client\"\nimport {\n decideStopHook,\n fileBlockBudget,\n launchBaselineKey,\n stopGateId,\n stopReviewEnabled,\n type StopReviewContext,\n} from \"./lib/orchestration/stop-gate-hook\"\nimport {\n fileBaselineStore,\n fileLastPromptStore,\n fileReviewDebounce,\n repoRoot,\n stopGateEnabledForRepo,\n stopReviewStateDir,\n} from \"./lib/orchestration/stop-gate-policy\"\nimport { parseIntEnv } from \"./lib/exec\"\n\n/**\n * Read the hook payload from stdin SYNCHRONOUSLY (`readFileSync(0)`). An async\n * stdin read leaves an in-flight libuv FS request that, on Windows, races the\n * process teardown and trips a `uv_async_send` assertion; a synchronous read has\n * no such handle. Hooks always receive piped/redirected stdin, so this never\n * blocks (guarded against an interactive TTY, and any error -> \"\").\n */\nfunction readStdin(): string {\n try {\n if (process.stdin.isTTY) return \"\"\n return readFileSync(0, \"utf8\")\n } catch {\n return \"\"\n }\n}\n\n/** Max diff bytes scanned for gate-weakening: a hard cap so a huge generated diff\n * (e.g. a lockfile) can never OOM or stall the hook. */\nconst MAX_DIFF_BYTES = 2 * 1024 * 1024\n\n/** Build a tiny synthetic diff for untracked paths so the weakening scan sees\n * their contents too. No index mutation (`git add -N`) is needed. */\nasync function captureUntrackedDiff(cwd: string, paths: string[]): Promise<string> {\n const chunks: string[] = []\n let bytes = 0\n for (const untrackedPath of paths) {\n const result = await runCommandCapture(\n [\"git\", \"diff\", \"--no-index\", \"--\", \"/dev/null\", untrackedPath],\n { cwd, timeoutMs: 5_000, maxStdoutBytes: MAX_DIFF_BYTES },\n )\n // `git diff --no-index` returns 1 when differences exist; anything else is an\n // actual failure. A truncated untracked diff is unknown, never \"no diff\".\n if (result.code !== 1 || result.truncated) throw new Error(`git diff for untracked file failed with exit ${result.code}`)\n bytes += Buffer.byteLength(result.stdout, \"utf8\")\n if (bytes > MAX_DIFF_BYTES) throw new Error(\"git working-tree capture exceeded its size limit\")\n chunks.push(result.stdout)\n }\n return chunks.join(\"\")\n}\n\n/** Capture every working-tree change WITHOUT mutating the user's index (no\n * `git add -N`). `git diff HEAD` covers staged + unstaged tracked changes;\n * `git ls-files --others` catches untracked files and each is diffed against an\n * empty file so gate-weakening in new tests is visible. Git failures/truncation\n * reject, so the decision layer runs the full checks instead of mistaking an\n * unknown tree for a no-diff turn. */\nexport async function captureStopGateDiff(cwd: string): Promise<string> {\n const [diffResult, untrackedResult] = await Promise.all([\n runCommandCapture([\"git\", \"diff\", \"HEAD\"], { cwd, timeoutMs: 5_000, maxStdoutBytes: MAX_DIFF_BYTES }),\n runCommandCapture([\"git\", \"ls-files\", \"--others\", \"--exclude-standard\", \"-z\"], {\n cwd,\n timeoutMs: 5_000,\n maxStdoutBytes: MAX_DIFF_BYTES,\n }),\n ])\n if (diffResult.code !== 0) throw new Error(`git diff failed with exit ${diffResult.code}`)\n if (untrackedResult.code !== 0) throw new Error(`git ls-files failed with exit ${untrackedResult.code}`)\n if (diffResult.truncated || untrackedResult.truncated) throw new Error(\"git working-tree capture exceeded its size limit\")\n\n const untrackedPaths = untrackedResult.stdout.split(\"\\0\").filter(Boolean)\n if (untrackedPaths.length === 0) return diffResult.stdout\n const untrackedDiff = await captureUntrackedDiff(cwd, untrackedPaths)\n if (Buffer.byteLength(diffResult.stdout, \"utf8\") + Buffer.byteLength(untrackedDiff, \"utf8\") > MAX_DIFF_BYTES) {\n throw new Error(\"git working-tree capture exceeded its size limit\")\n }\n return diffResult.stdout + untrackedDiff\n}\n\n/** Flush a message to stderr before exiting (process.exit can drop an unflushed\n * write; the model reads this stderr on a block). */\nasync function writeStderr(msg: string): Promise<void> {\n await new Promise<void>((resolve) => {\n process.stderr.write(msg, () => resolve())\n })\n}\n\n/**\n * Fire-and-forget spawn of the detached background reviewer. The payload (which\n * includes the up-to-2-MiB diff) is written to a temp file SYNCHRONOUSLY before\n * the spawn — a pipe to the child's stdin would race the parent's `process.exit`\n * and could deliver a truncated diff. The child reads the file (path passed via\n * `GH_ROUTER_STOP_REVIEW_PAYLOAD`), unlinks it, and inherits the proxy URL/nonce\n * env. Everything is swallowed: the advisory layer never affects the stop.\n */\nfunction spawnStopReview(ctx: StopReviewContext, extras: { prompt: string; transcriptPath: string }): void {\n let payloadPath: string | undefined\n try {\n const dir = stopReviewStateDir()\n mkdirSync(dir, { recursive: true })\n payloadPath = path.join(dir, `payload-${process.pid}-${randomBytes(4).toString(\"hex\")}.json`)\n writeFileSync(\n payloadPath,\n JSON.stringify({\n session_id: ctx.sessionId,\n cwd: ctx.cwd,\n diff: ctx.diff,\n prompt: extras.prompt,\n transcript_path: extras.transcriptPath,\n }),\n { mode: 0o600 },\n )\n // Invoke the same binary's `internal-stop-review` subcommand. Preserve the\n // package root when this hook itself runs from the relocated launcher.\n const scriptArgs = process.argv[1] && process.argv[1] !== process.execPath ? [process.argv[1]] : []\n const root = explicitPackageRoot()\n const packageRootArgs = root ? [PACKAGE_ROOT_FLAG, root] : []\n const child = spawn(process.execPath, [...scriptArgs, ...packageRootArgs, \"internal-stop-review\"], {\n detached: true,\n windowsHide: true,\n stdio: \"ignore\",\n env: { ...process.env, GH_ROUTER_STOP_REVIEW_PAYLOAD: payloadPath },\n })\n // A spawn failure (EAGAIN / EACCES / fork limit) is delivered ASYNCHRONOUSLY\n // via the child's 'error' event, AFTER this synchronous try block exits. With\n // no listener Node escalates it to an uncaughtException — which main.ts turns\n // into process.exit(1), corrupting the Stop hook's exit code. Swallow it here\n // (and drop the now-orphaned payload the child never read) so the advisory\n // spawn truly never affects the stop.\n const orphan = payloadPath\n child.on(\"error\", () => {\n if (orphan) {\n try {\n unlinkSync(orphan)\n } catch {\n /* best-effort */\n }\n }\n })\n child.unref()\n } catch {\n // Advisory spawn is best-effort; never disrupt the stop. If we wrote the\n // payload file but the spawn failed, drop it so it doesn't orphan (the\n // child — which would normally unlink it — never started).\n if (payloadPath) {\n try {\n unlinkSync(payloadPath)\n } catch {\n /* best-effort */\n }\n }\n }\n}\n\n/** The checks for a resolved descriptor: sealed descriptors resolve their sealed\n * command set; parsed/discovered carry their own. */\ntype ResolveChecks = NonNullable<Parameters<typeof decideStopHook>[0][\"resolveChecks\"]>\n\n/**\n * Build the dynamic `resolveChecks` for the live Stop hook, per the env the\n * launcher set:\n * - GH_ROUTER_STOP_GATE_PARSED → re-derive the deterministic parser at the\n * stop-time tree (stateless, no cache);\n * - GH_ROUTER_STOP_GATE_DISCOVERED → read the cached evidence-pinned record.\n * Both pin the checks to the descriptor's `workdir` (the repo root) and compute\n * the launch-stable baseline key so the launch-captured (pre-mutation) baseline\n * is read. Any miss → null → the hook fails OPEN.\n */\nfunction buildResolveChecks(mode: \"parsed\" | \"discovered\"): ResolveChecks {\n const includeTests = parseBoolEnv(process.env.GH_ROUTER_STOP_GATE_RUN_TESTS) === true\n return async (cwd: string) => {\n const root = await repoRoot(cwd).catch(() => cwd)\n let descriptor: GateDescriptor | null = null\n if (mode === \"parsed\") {\n descriptor = await parseGateDescriptor(root, { includeTests }).catch(() => null)\n } else {\n const rec = await readDiscoveredGate(root).catch(() => null)\n if (rec) {\n descriptor = {\n kind: \"discovered\",\n checks: rec.checks,\n ecosystem: rec.ecosystem,\n workdir: root,\n evidence: rec.evidence,\n }\n }\n }\n if (!descriptor) return null\n const checks = checksForDescriptor(descriptor)\n if (checks.length === 0) return null\n const descriptorKey = descriptorHash(descriptor)\n const workdir = descriptor.workdir || root\n const token = process.env.GH_ROUTER_STOP_GATE_BASELINE_TOKEN || undefined\n return { checks, workdir, descriptorKey, baselineKey: launchBaselineKey(workdir, descriptorKey, token) }\n }\n}\n\n/** Which dynamic gate mode the launcher armed (sealed → undefined). */\nfunction dynamicMode(): \"parsed\" | \"discovered\" | undefined {\n if (parseBoolEnv(process.env.GH_ROUTER_STOP_GATE_PARSED) === true) return \"parsed\"\n if (parseBoolEnv(process.env.GH_ROUTER_STOP_GATE_DISCOVERED) === true) return \"discovered\"\n return undefined\n}\n\nexport const internalStopHook = defineCommand({\n meta: {\n name: \"internal-stop-hook\",\n description:\n \"Internal: the structural-gate Stop hook. Reads the Claude Code hook payload on stdin, \"\n + \"runs the sealed gate, exits 2 (blocks the stop) on a red gate or gate-weakening diff.\",\n },\n async run() {\n const stdin = readStdin()\n // The advisory review (hook V2) is wired only when it's enabled AND the\n // launcher injected the proxy URL/nonce. It is side-effect-only: the\n // deterministic gate below is unchanged and remains the only blocker.\n const reviewEnabled = stopReviewEnabled() && hookMcpRuntimeFromEnv() !== undefined\n let transcriptPath = \"\"\n let userPrompt = \"\"\n if (reviewEnabled) {\n // Parse the payload once for the transcript pointer + the session id used\n // to look up the user's last prompt (the Stop payload carries no prompt;\n // the UserPromptSubmit hook stashed it). Best-effort — a parse miss just\n // means the reviewer judges against the diff alone.\n try {\n const p: unknown = JSON.parse(stdin)\n if (p && typeof p === \"object\") {\n const obj = p as { transcript_path?: unknown; session_id?: unknown }\n transcriptPath = typeof obj.transcript_path === \"string\" ? obj.transcript_path : \"\"\n const sid = typeof obj.session_id === \"string\" ? obj.session_id : \"\"\n if (sid) {\n userPrompt = (await fileLastPromptStore(stopReviewStateDir()).read(sid).catch(() => null)) ?? \"\"\n }\n }\n } catch {\n /* tolerate a non-JSON stdin */\n }\n }\n\n let decision: { exitCode: 0 | 2; stderr?: string }\n try {\n const timeoutEnv = parseIntEnv(process.env.GH_ROUTER_STOP_GATE_TIMEOUT_MS)\n const mode = dynamicMode()\n decision = await decideStopHook({\n stdin,\n gateId: stopGateId(),\n exec: liveExec,\n captureDiff: captureStopGateDiff,\n fallbackCwd: process.cwd(),\n budget: fileBlockBudget(path.join(tmpdir(), \"gh-router-stopgate\")),\n baseline: fileBaselineStore(path.join(tmpdir(), \"gh-router-stopgate-baseline\")),\n isEnabledForRepo: (cwd) => stopGateEnabledForRepo(cwd),\n resolveChecks: mode ? buildResolveChecks(mode) : undefined,\n // parseIntEnv already rejects non-positive / non-integer / truncating\n // values, so undefined here means \"use the built-in default\".\n timeoutMs: timeoutEnv,\n reviewDebounce: reviewEnabled ? fileReviewDebounce(stopReviewStateDir()) : undefined,\n spawnReview: reviewEnabled\n ? (ctx) => spawnStopReview(ctx, { prompt: userPrompt, transcriptPath })\n : undefined,\n })\n } catch {\n // Fail OPEN on ANY unexpected error: a Stop hook must never wedge the\n // session, so an internal crash allows the stop (exit 0) rather than\n // surfacing a non-blocking error or a hang.\n process.exitCode = 0\n return\n }\n // Write any stderr the decision carries — both the exit-2 block reason AND\n // the LOUD exit-0 stand-down (max-block budget reached). A clean green stop\n // carries no stderr, so this stays silent on the common path.\n if (decision.stderr) {\n await writeStderr(`${decision.stderr}\\n`)\n }\n // Natural exit: set the code and return. A hard process.exit() races libuv's\n // stdio teardown on Windows (uv_async_send assertion) on the fast-return\n // paths. The detached review child is unref'd and the gate's children are\n // reaped, so no handle keeps the loop alive — the process exits with this\n // code. The exit-2 block contract is preserved (stderr is flushed above).\n process.exitCode = decision.exitCode\n },\n})\n"],"mappings":"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"}
package/dist/main.js CHANGED
@@ -13,7 +13,7 @@ process.on("uncaughtException", (error) => {
13
13
  const version = getPackageVersion();
14
14
  const argv = process.argv.slice(2);
15
15
  const isVersionFlag = argv.includes("--version");
16
- const isInternalHook = argv[0] === "internal-stop-hook" || argv[0] === "internal-prompt-submit" || argv[0] === "internal-stop-review" || argv[0] === "internal-plan-review" || argv[0] === "internal-session-bind" || argv[0] === "internal-workspace-header" || argv[0] === "internal-artifact-open" || argv[0] === "internal-first-mate-guard" || argv[0] === "internal-worker-guard" || argv[0] === "internal-fast-dispatch-guard";
16
+ const isInternalHook = argv[0] === "internal-stop-hook" || argv[0] === "internal-prompt-submit" || argv[0] === "internal-stop-review" || argv[0] === "internal-plan-review" || argv[0] === "internal-session-bind" || argv[0] === "internal-workspace-header" || argv[0] === "internal-artifact-open" || argv[0] === "internal-first-mate-guard" || argv[0] === "internal-worker-guard" || argv[0] === "internal-fast-dispatch-guard" || argv[0] === "internal-aic-status";
17
17
  if (!isVersionFlag && !isInternalHook) consola.info(`github-router v${version}`);
18
18
  /**
19
19
  * Subcommands are LAZY THUNKS, not static imports.
@@ -47,14 +47,14 @@ const main = defineCommand({
47
47
  },
48
48
  subCommands: {
49
49
  auth: () => import("./auth-O_UG1ABO.js").then((m) => m.auth),
50
- start: () => import("./start-DV5iDhbP.js").then((m) => m.start),
51
- claude: () => import("./claude-D_Me80Yi.js").then((m) => m.claude),
52
- codex: () => import("./codex-CE7zZm-M.js").then((m) => m.codex),
53
- serve: () => import("./serve-DI4QgSSB.js").then((m) => m.serve),
50
+ start: () => import("./start-BHEjESCB.js").then((m) => m.start),
51
+ claude: () => import("./claude-D0vuAqTV.js").then((m) => m.claude),
52
+ codex: () => import("./codex-BNm9D0iJ.js").then((m) => m.codex),
53
+ serve: () => import("./serve-DXFVR6A0.js").then((m) => m.serve),
54
54
  models: () => import("./models-COuMOfM6.js").then((m) => m.models),
55
55
  "check-usage": () => import("./check-usage-dicJOWML.js").then((m) => m.checkUsage),
56
56
  debug: () => import("./debug-2PgImRnr.js").then((m) => m.debug),
57
- "internal-stop-hook": () => import("./internal-stop-hook-BJ3o61-L.js").then((m) => m.internalStopHook),
57
+ "internal-stop-hook": () => import("./internal-stop-hook-D56b6iVj.js").then((m) => m.internalStopHook),
58
58
  "internal-prompt-submit": () => import("./internal-prompt-submit-DG94f8E-.js").then((m) => m.internalPromptSubmit),
59
59
  "internal-stop-review": () => import("./internal-stop-review-DlKzZgFm.js").then((m) => m.internalStopReview),
60
60
  "internal-plan-review": () => import("./internal-plan-review-DLduDz41.js").then((m) => m.internalPlanReview),
@@ -64,7 +64,8 @@ const main = defineCommand({
64
64
  "internal-first-mate-guard": () => import("./internal-first-mate-guard-Nf1H0mCK.js").then((m) => m.internalFirstMateGuard),
65
65
  "internal-worker-guard": () => import("./internal-worker-guard-D7sfByyo.js").then((m) => m.internalWorkerGuard),
66
66
  "internal-fast-dispatch-guard": () => import("./internal-fast-dispatch-guard-D8YNlnuQ.js").then((m) => m.internalFastDispatchGuard),
67
- "internal-max-dispatch-guard": () => import("./internal-max-dispatch-guard-_Pp4LltM.js").then((m) => m.internalMaxDispatchGuard)
67
+ "internal-max-dispatch-guard": () => import("./internal-max-dispatch-guard-_Pp4LltM.js").then((m) => m.internalMaxDispatchGuard),
68
+ "internal-aic-status": () => import("./internal-aic-status-C_swX3_7.js").then((m) => m.internalAicStatus)
68
69
  }
69
70
  });
70
71
  /**
package/dist/main.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"main.js","names":[],"sources":["../src/main.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { defineCommand, runMain, showUsage } from \"citty\"\nimport consola from \"consola\"\n\nimport { getPackageVersion } from \"./lib/version\"\n\nprocess.on(\"unhandledRejection\", (error) => {\n consola.error(\"Unhandled rejection:\", error)\n})\n\nprocess.on(\"uncaughtException\", (error) => {\n consola.error(\"Uncaught exception:\", error)\n process.exit(1)\n})\n\nconst version = getPackageVersion()\n\n// Always surface the running version on stderr so crash reports\n// identify which build is in use. Suppressed only for `--version`\n// (citty's built-in handler prints just the bare version number, and\n// a banner above it is redundant). `-v` is NOT special-cased: citty\n// doesn't treat it as `--version`, so users typing `-v` still get\n// the banner — which is the closest thing to \"show me the version\".\nconst argv = process.argv.slice(2)\nconst isVersionFlag = argv.includes(\"--version\")\n// Suppress the banner for the internal hooks: their stdout/stderr is consumed by\n// Claude Code (the Stop hook's stderr is the block message; the prompt-submit\n// hook's stdout is injected context), so it must stay clean. `internal-stop-review`\n// and `internal-plan-review` are detached/background with no consumer, but stay\n// quiet for the same reason.\nconst isInternalHook =\n argv[0] === \"internal-stop-hook\"\n || argv[0] === \"internal-prompt-submit\"\n || argv[0] === \"internal-stop-review\"\n || argv[0] === \"internal-plan-review\"\n || argv[0] === \"internal-session-bind\"\n || argv[0] === \"internal-workspace-header\"\n || argv[0] === \"internal-artifact-open\"\n || argv[0] === \"internal-first-mate-guard\"\n || argv[0] === \"internal-worker-guard\"\n || argv[0] === \"internal-fast-dispatch-guard\"\nif (!isVersionFlag && !isInternalHook) {\n consola.info(`github-router v${version}`)\n}\n\n/**\n * Subcommands are LAZY THUNKS, not static imports.\n *\n * citty resolves only the named entry (`_findSubCommand` → `resolveValue`), so\n * running one subcommand no longer evaluates the other 17 module graphs. That\n * matters because several of these are per-turn Claude Code hooks — the\n * `UserPromptSubmit`, `Stop` and `PreToolUse` handlers registered by\n * `src/claude.ts` each spawn a FRESH `node dist/main.js <sub>` process, so any\n * import-time cost lands in the user's interactive loop on every prompt.\n * `internal-worker-guard`, for instance, needs three modules but was loading\n * the entire claude/serve/MCP graph plus the Pi vendor runtime.\n *\n * Safe because the import-time side effects in this codebase\n * (`assertMcpToolSurfaceConsistent` in `server.ts`, the tree-sitter grammar\n * preload, the worktree/session exit-handler registrations, the first-mate\n * allowlist assertion) are reachable ONLY from claude/codex/serve/start, and\n * all four reach all of them independently — no subcommand relies on another's\n * module being loaded. Verified by an AST walk of the runtime import graph.\n *\n * `--help` is the one path that still resolves everything: citty's\n * `renderUsage` reads each subcommand's meta to print descriptions. That is\n * not latency-critical, and `resolveHelpTarget` below resolves only the single\n * subcommand when help is scoped to one.\n */\nconst main = defineCommand({\n meta: {\n name: \"github-router\",\n version,\n description:\n \"A reverse proxy that exposes GitHub Copilot as OpenAI and Anthropic compatible API endpoints.\",\n },\n subCommands: {\n auth: () => import(\"./auth\").then((m) => m.auth),\n start: () => import(\"./start\").then((m) => m.start),\n claude: () => import(\"./claude\").then((m) => m.claude),\n codex: () => import(\"./codex\").then((m) => m.codex),\n serve: () => import(\"./serve\").then((m) => m.serve),\n models: () => import(\"./models\").then((m) => m.models),\n \"check-usage\": () => import(\"./check-usage\").then((m) => m.checkUsage),\n debug: () => import(\"./debug\").then((m) => m.debug),\n \"internal-stop-hook\": () =>\n import(\"./internal-stop-hook\").then((m) => m.internalStopHook),\n \"internal-prompt-submit\": () =>\n import(\"./internal-prompt-submit\").then((m) => m.internalPromptSubmit),\n \"internal-stop-review\": () =>\n import(\"./internal-stop-review\").then((m) => m.internalStopReview),\n \"internal-plan-review\": () =>\n import(\"./internal-plan-review\").then((m) => m.internalPlanReview),\n \"internal-session-bind\": () =>\n import(\"./internal-session-bind\").then((m) => m.internalSessionBind),\n \"internal-workspace-header\": () =>\n import(\"./internal-workspace-header\").then(\n (m) => m.internalWorkspaceHeader,\n ),\n \"internal-artifact-open\": () =>\n import(\"./internal-artifact-open\").then((m) => m.internalArtifactOpen),\n \"internal-first-mate-guard\": () =>\n import(\"./internal-first-mate-guard\").then(\n (m) => m.internalFirstMateGuard,\n ),\n \"internal-worker-guard\": () =>\n import(\"./internal-worker-guard\").then((m) => m.internalWorkerGuard),\n \"internal-fast-dispatch-guard\": () =>\n import(\"./internal-fast-dispatch-guard\").then((m) => m.internalFastDispatchGuard),\n \"internal-max-dispatch-guard\": () =>\n import(\"./internal-max-dispatch-guard\").then((m) => m.internalMaxDispatchGuard),\n },\n})\n\n/**\n * Print usage ourselves and let the process DRAIN, instead of letting citty\n * hard-exit while stdout is still queued.\n *\n * citty's `runMain` calls `process.exit(0)` immediately after `showUsage`\n * resolves (citty 0.2.2 dist/index.mjs:389; 0.1.6 does the same, so this is\n * not an upgrade regression). On Windows a PIPE-backed stdout is async, so the\n * usage text is still in a libuv handle when the hard exit tears the loop down\n * and node aborts:\n * `Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\\win\\async.c`\n * — after printing the help the user asked for.\n *\n * `--version` is unaffected: citty's version branch has no `process.exit()`\n * and drains naturally. That asymmetry is why this survived so long — the\n * cheapest smoke test passes while every `--help` path aborts. Node 22 on\n * Windows tolerated it; node 24 does not, and release.yml publishes with\n * node 24.\n *\n * Forcing `_handle.setBlocking(true)` first was tried and does NOT fix it —\n * the abort still reproduces through a pipe. The only reliable fix is to not\n * hard-exit at all, which is the same resolution the internal hooks already\n * use (`process.exitCode = 0`, never `process.exit()`), guarded by the tests\n * in this repo's internal-hooks-crash suite.\n *\n * Only the help path is intercepted; everything else still goes through citty\n * so argument parsing, subcommand dispatch and error handling stay citty's.\n */\nconst HELP_FLAGS = new Set([\"--help\", \"-h\"])\n\nasync function resolveHelpTarget(): Promise<{\n cmd: Parameters<typeof showUsage>[0]\n parent?: Parameters<typeof showUsage>[1]\n}> {\n // First non-flag token is the subcommand, matching citty's own resolution\n // for our single-level command tree.\n const name = argv.find((arg) => !arg.startsWith(\"-\"))\n const table = main.subCommands as Record<string, unknown> | undefined\n const entry = name ? table?.[name] : undefined\n if (!entry) return { cmd: main }\n // citty allows a subcommand to be a value, a promise, or a thunk.\n const resolved = typeof entry === \"function\" ? await entry() : await entry\n return {\n cmd: resolved as Parameters<typeof showUsage>[0],\n parent: main as Parameters<typeof showUsage>[1],\n }\n}\n\nif (argv.some((arg) => HELP_FLAGS.has(arg))) {\n const { cmd, parent } = await resolveHelpTarget()\n await showUsage(cmd, parent)\n // Signal success WITHOUT tearing the loop down — node drains stdout and\n // exits 0 on its own.\n process.exitCode = 0\n} else {\n await runMain(main)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"main.js","names":[],"sources":["../src/main.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { defineCommand, runMain, showUsage } from \"citty\"\nimport consola from \"consola\"\n\nimport { getPackageVersion } from \"./lib/version\"\n\nprocess.on(\"unhandledRejection\", (error) => {\n consola.error(\"Unhandled rejection:\", error)\n})\n\nprocess.on(\"uncaughtException\", (error) => {\n consola.error(\"Uncaught exception:\", error)\n process.exit(1)\n})\n\nconst version = getPackageVersion()\n\n// Always surface the running version on stderr so crash reports\n// identify which build is in use. Suppressed only for `--version`\n// (citty's built-in handler prints just the bare version number, and\n// a banner above it is redundant). `-v` is NOT special-cased: citty\n// doesn't treat it as `--version`, so users typing `-v` still get\n// the banner — which is the closest thing to \"show me the version\".\nconst argv = process.argv.slice(2)\nconst isVersionFlag = argv.includes(\"--version\")\n// Suppress the banner for the internal hooks: their stdout/stderr is consumed by\n// Claude Code (the Stop hook's stderr is the block message; the prompt-submit\n// hook's stdout is injected context), so it must stay clean. `internal-stop-review`\n// and `internal-plan-review` are detached/background with no consumer, but stay\n// quiet for the same reason.\nconst isInternalHook =\n argv[0] === \"internal-stop-hook\"\n || argv[0] === \"internal-prompt-submit\"\n || argv[0] === \"internal-stop-review\"\n || argv[0] === \"internal-plan-review\"\n || argv[0] === \"internal-session-bind\"\n || argv[0] === \"internal-workspace-header\"\n || argv[0] === \"internal-artifact-open\"\n || argv[0] === \"internal-first-mate-guard\"\n || argv[0] === \"internal-worker-guard\"\n || argv[0] === \"internal-fast-dispatch-guard\"\n || argv[0] === \"internal-aic-status\"\nif (!isVersionFlag && !isInternalHook) {\n consola.info(`github-router v${version}`)\n}\n\n/**\n * Subcommands are LAZY THUNKS, not static imports.\n *\n * citty resolves only the named entry (`_findSubCommand` → `resolveValue`), so\n * running one subcommand no longer evaluates the other 17 module graphs. That\n * matters because several of these are per-turn Claude Code hooks — the\n * `UserPromptSubmit`, `Stop` and `PreToolUse` handlers registered by\n * `src/claude.ts` each spawn a FRESH `node dist/main.js <sub>` process, so any\n * import-time cost lands in the user's interactive loop on every prompt.\n * `internal-worker-guard`, for instance, needs three modules but was loading\n * the entire claude/serve/MCP graph plus the Pi vendor runtime.\n *\n * Safe because the import-time side effects in this codebase\n * (`assertMcpToolSurfaceConsistent` in `server.ts`, the tree-sitter grammar\n * preload, the worktree/session exit-handler registrations, the first-mate\n * allowlist assertion) are reachable ONLY from claude/codex/serve/start, and\n * all four reach all of them independently — no subcommand relies on another's\n * module being loaded. Verified by an AST walk of the runtime import graph.\n *\n * `--help` is the one path that still resolves everything: citty's\n * `renderUsage` reads each subcommand's meta to print descriptions. That is\n * not latency-critical, and `resolveHelpTarget` below resolves only the single\n * subcommand when help is scoped to one.\n */\nconst main = defineCommand({\n meta: {\n name: \"github-router\",\n version,\n description:\n \"A reverse proxy that exposes GitHub Copilot as OpenAI and Anthropic compatible API endpoints.\",\n },\n subCommands: {\n auth: () => import(\"./auth\").then((m) => m.auth),\n start: () => import(\"./start\").then((m) => m.start),\n claude: () => import(\"./claude\").then((m) => m.claude),\n codex: () => import(\"./codex\").then((m) => m.codex),\n serve: () => import(\"./serve\").then((m) => m.serve),\n models: () => import(\"./models\").then((m) => m.models),\n \"check-usage\": () => import(\"./check-usage\").then((m) => m.checkUsage),\n debug: () => import(\"./debug\").then((m) => m.debug),\n \"internal-stop-hook\": () =>\n import(\"./internal-stop-hook\").then((m) => m.internalStopHook),\n \"internal-prompt-submit\": () =>\n import(\"./internal-prompt-submit\").then((m) => m.internalPromptSubmit),\n \"internal-stop-review\": () =>\n import(\"./internal-stop-review\").then((m) => m.internalStopReview),\n \"internal-plan-review\": () =>\n import(\"./internal-plan-review\").then((m) => m.internalPlanReview),\n \"internal-session-bind\": () =>\n import(\"./internal-session-bind\").then((m) => m.internalSessionBind),\n \"internal-workspace-header\": () =>\n import(\"./internal-workspace-header\").then(\n (m) => m.internalWorkspaceHeader,\n ),\n \"internal-artifact-open\": () =>\n import(\"./internal-artifact-open\").then((m) => m.internalArtifactOpen),\n \"internal-first-mate-guard\": () =>\n import(\"./internal-first-mate-guard\").then(\n (m) => m.internalFirstMateGuard,\n ),\n \"internal-worker-guard\": () =>\n import(\"./internal-worker-guard\").then((m) => m.internalWorkerGuard),\n \"internal-fast-dispatch-guard\": () =>\n import(\"./internal-fast-dispatch-guard\").then((m) => m.internalFastDispatchGuard),\n \"internal-max-dispatch-guard\": () =>\n import(\"./internal-max-dispatch-guard\").then((m) => m.internalMaxDispatchGuard),\n \"internal-aic-status\": () =>\n import(\"./internal-aic-status\").then((m) => m.internalAicStatus),\n },\n})\n\n/**\n * Print usage ourselves and let the process DRAIN, instead of letting citty\n * hard-exit while stdout is still queued.\n *\n * citty's `runMain` calls `process.exit(0)` immediately after `showUsage`\n * resolves (citty 0.2.2 dist/index.mjs:389; 0.1.6 does the same, so this is\n * not an upgrade regression). On Windows a PIPE-backed stdout is async, so the\n * usage text is still in a libuv handle when the hard exit tears the loop down\n * and node aborts:\n * `Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\\win\\async.c`\n * — after printing the help the user asked for.\n *\n * `--version` is unaffected: citty's version branch has no `process.exit()`\n * and drains naturally. That asymmetry is why this survived so long — the\n * cheapest smoke test passes while every `--help` path aborts. Node 22 on\n * Windows tolerated it; node 24 does not, and release.yml publishes with\n * node 24.\n *\n * Forcing `_handle.setBlocking(true)` first was tried and does NOT fix it —\n * the abort still reproduces through a pipe. The only reliable fix is to not\n * hard-exit at all, which is the same resolution the internal hooks already\n * use (`process.exitCode = 0`, never `process.exit()`), guarded by the tests\n * in this repo's internal-hooks-crash suite.\n *\n * Only the help path is intercepted; everything else still goes through citty\n * so argument parsing, subcommand dispatch and error handling stay citty's.\n */\nconst HELP_FLAGS = new Set([\"--help\", \"-h\"])\n\nasync function resolveHelpTarget(): Promise<{\n cmd: Parameters<typeof showUsage>[0]\n parent?: Parameters<typeof showUsage>[1]\n}> {\n // First non-flag token is the subcommand, matching citty's own resolution\n // for our single-level command tree.\n const name = argv.find((arg) => !arg.startsWith(\"-\"))\n const table = main.subCommands as Record<string, unknown> | undefined\n const entry = name ? table?.[name] : undefined\n if (!entry) return { cmd: main }\n // citty allows a subcommand to be a value, a promise, or a thunk.\n const resolved = typeof entry === \"function\" ? await entry() : await entry\n return {\n cmd: resolved as Parameters<typeof showUsage>[0],\n parent: main as Parameters<typeof showUsage>[1],\n }\n}\n\nif (argv.some((arg) => HELP_FLAGS.has(arg))) {\n const { cmd, parent } = await resolveHelpTarget()\n await showUsage(cmd, parent)\n // Signal success WITHOUT tearing the loop down — node drains stdout and\n // exits 0 on its own.\n process.exitCode = 0\n} else {\n await runMain(main)\n}\n"],"mappings":"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"}
@@ -10,6 +10,7 @@ import { i as registerExitHandlers$1, n as getInstanceUuid, r as recordWorkerRep
10
10
  import { t as MCP_WORKSPACE_HEADER } from "./mcp-workspace-header-B60WSR1Y.js";
11
11
  import { n as ArtifactError, r as applyInsecureTls, t as ArtifactClient } from "./client-CQMfroGV.js";
12
12
  import { n as isPidAlive$1, r as registerColbertExitHandlers, s as trackChild, t as getColbertInstanceUuid } from "./lifecycle-CtRA3Y7p.js";
13
+ import { r as extractAndRecordAic, s as recordAic, u as extractCopilotUsage } from "./aic-ledger-Cx_Ubon3.js";
13
14
  import { f as resolveSealedGate, m as runGateChecks, p as sealedGateIds } from "./stop-gate-hook-BEU8qQrC.js";
14
15
  import { t as liveExec } from "./orchestration-DjLA-lxB.js";
15
16
  import { createRequire } from "node:module";
@@ -29606,6 +29607,9 @@ function relayAnthropicStream(body, opts) {
29606
29607
  }
29607
29608
  if (result.value) {
29608
29609
  bytesRelayed += result.value.byteLength;
29610
+ if (opts.onBytes) try {
29611
+ opts.onBytes(result.value);
29612
+ } catch {}
29609
29613
  try {
29610
29614
  controller.enqueue(result.value);
29611
29615
  } catch (enqueueError) {
@@ -30609,6 +30613,7 @@ async function runAdvisor(conversation, advisorModel, advisorEffort, signal, adv
30609
30613
  }
30610
30614
  const text = out.join("");
30611
30615
  if (!text) throw new Error(`Advisor model ${resolvedAdvisorModel} returned empty assistant output`);
30616
+ extractAndRecordAic(resolvedAdvisorModel, response);
30612
30617
  return text;
30613
30618
  }
30614
30619
  if (transport === "chat") {
@@ -30625,11 +30630,13 @@ async function runAdvisor(conversation, advisorModel, advisorEffort, signal, adv
30625
30630
  stream: false,
30626
30631
  ...ladder ? { reasoning_effort: advisorEffort } : {}
30627
30632
  };
30628
- const text = (await withTransientRetry(() => createChatCompletions(chatPayload, void 0, signal), {
30633
+ const response = await withTransientRetry(() => createChatCompletions(chatPayload, void 0, signal), {
30629
30634
  signal,
30630
30635
  label: resolvedAdvisorModel
30631
- })).choices?.[0]?.message?.content;
30636
+ });
30637
+ const text = response.choices?.[0]?.message?.content;
30632
30638
  if (typeof text !== "string" || text.length === 0) throw new Error(`Advisor model ${resolvedAdvisorModel} returned empty response`);
30639
+ extractAndRecordAic(resolvedAdvisorModel, response);
30633
30640
  return text;
30634
30641
  }
30635
30642
  const advisorEntry = state.models?.data?.find((m) => m.id === resolvedAdvisorModel);
@@ -30655,6 +30662,7 @@ async function runAdvisor(conversation, advisorModel, advisorEffort, signal, adv
30655
30662
  })).json();
30656
30663
  const text = (Array.isArray(json.content) ? json.content : []).filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n\n");
30657
30664
  if (!text) throw new Error(`Advisor model ${resolvedAdvisorModel} returned empty response`);
30665
+ extractAndRecordAic(resolvedAdvisorModel, json);
30658
30666
  return text;
30659
30667
  }
30660
30668
  /**
@@ -30775,6 +30783,9 @@ function buildAdvisorStream(opts) {
30775
30783
  let nextSyntheticIndex = 0;
30776
30784
  let turnsRun;
30777
30785
  let pendingMessageDelta = null;
30786
+ let pendingCopilotNano = 0;
30787
+ const pendingCopilotDetails = [];
30788
+ const leadModelForAic = typeof opts.baseBody.model === "string" && opts.baseBody.model.length > 0 ? opts.baseBody.model : void 0;
30778
30789
  const safeEnqueue = (bytes) => {
30779
30790
  try {
30780
30791
  controller.enqueue(bytes);
@@ -30801,6 +30812,12 @@ function buildAdvisorStream(opts) {
30801
30812
  if (delta.stop_sequence !== void 0) pendingMessageDelta.stopSequence = delta.stop_sequence;
30802
30813
  }
30803
30814
  for (const [key, value] of Object.entries(usage)) if (typeof value === "number" && Number.isFinite(value)) pendingMessageDelta.usage[key] = (pendingMessageDelta.usage[key] ?? 0) + value;
30815
+ const turnAic = extractCopilotUsage(payload.copilot_usage);
30816
+ if (turnAic) {
30817
+ pendingCopilotNano += turnAic.totalNanoAiu;
30818
+ pendingCopilotDetails.push(...turnAic.tokenDetails);
30819
+ recordAic(leadModelForAic, turnAic);
30820
+ }
30804
30821
  };
30805
30822
  const emitTerminal = (forcedStopReason) => {
30806
30823
  if (pendingMessageDelta || forcedStopReason) {
@@ -30815,7 +30832,17 @@ function buildAdvisorStream(opts) {
30815
30832
  stop_reason: forcedStopReason ?? terminal.stopReason,
30816
30833
  stop_sequence: terminal.stopSequence
30817
30834
  },
30818
- usage: terminal.usage
30835
+ usage: terminal.usage,
30836
+ ...pendingCopilotNano > 0 ? { copilot_usage: {
30837
+ total_nano_aiu: pendingCopilotNano,
30838
+ token_details: pendingCopilotDetails.map((d) => ({
30839
+ batch_size: d.batchSize,
30840
+ cost_per_batch: d.costPerBatch,
30841
+ ...d.model ? { model: d.model } : {},
30842
+ token_count: d.tokenCount,
30843
+ ...d.tokenType ? { token_type: d.tokenType } : {}
30844
+ }))
30845
+ } } : {}
30819
30846
  })) return false;
30820
30847
  }
30821
30848
  return safeEnqueueEvent("message_stop", { type: "message_stop" });
@@ -37594,4 +37621,4 @@ function enumerateInjectedMcpToolNames(groupKeys, opts = {}) {
37594
37621
  //#endregion
37595
37622
  export { UNKNOWN_EFFORT_ANCHOR as $, colbertDegradedWarning as $t, TOOLBELT_TOOLS$1 as A, CHEAP_PROFILE_MODELS as An, scribeModel as At, resolveAdvisorEffort as B, registerLaunch as Bt, runWorkerAgent as C, upstreamMaxConnections as Cn, generalPurposeFastModel as Ct, toolbeltEnabled as D, withOneMSuffix as Dn, reviewerFastModel as Dt, buildToolbeltAwareness as E, oneMContextDisabled as En, resolveGeminiReviewModel as Et, ADVISOR_TOOL_INSTRUCTIONS as F, withInstallLock as Fn, createMessages as Ft, repairRejectedThinkingHistory as G, createResponses as Gt, formatThinkingRepairDecline as H, assembleResponsesPayload as Ht, FAST_ADVISOR_TOOL_INSTRUCTIONS as I, getTextTokenCount as It, isControllerClosedError as J, readResponseBodyCapped as Jt, buildAnthropicErrorEvent as K, createChatCompletions as Kt, buildAdvisorStream as L, getTokenCount as Lt, satisfiesMinVersion as M, CHEAP_PROFILE_NATIVE_EFFORTS as Mn, workerToolsEnabled as Mt, searchWeb as N, CHEAP_PROFILE_NATIVE_MODELS as Nn, shimDefaultsToXhigh as Nt, toolbeltSkipSet as O, withOneMSuffixForLead as On, reviewerModel as Ot, ADVISOR_INTERNAL_TOOL_NAME as P, CHEAP_PROFILE_SUBAGENT_CONTEXT_TOKENS as Pn, countTokens as Pt, EFFORT_ORDER as Q, hasSupportedBrowserInstalled as Qt, injectAdvisorTool as R, getTokenizerFromModel as Rt, resolveWorkerRunOpts as S, upstreamAllowH2 as Sn, geminiAvailable as St, availableToolCommands as T, catalogAdvertises1M as Tn, nativeSubagentModel as Tt, rememberThinkingHistoryRepair as U, warnOnTokenPriceDrift as Ut, resolveAdvisorModel as V, unregisterLaunch as Vt, repairKnownThinkingHistory as W, resolveMcpToolTimeoutMs as Wt, readIteratorWithTimeout as X, normalizeOpenAIUsage as Xt, logStreamError as Y, parseJsonOrDiagnose as Yt, relayAnthropicStream as Z, provisionBrowserAssets as Zt, REVIEW_DEFAULT_MODEL as _, UPSTREAM_INACTIVITY_TIMEOUT_MS as _n, fastOracleModel as _t, buildAgentPrompt as a, CONDENSED_OPERATING_SEQUENCE as an, artifactToolsEnabled as at, resolveDefaultModel as b, pickClaudeDefault as bn, fastScoutModel as bt, enumerateInjectedMcpToolNames as c, collapsePathKeys as cn, browserCompoundToolsEnabled as ct, BROWSE_DEFAULT_MODEL as d, BUDGET_SMALL_FAST_SLUG as dn, cheapOracleModel as dt, provisionAndIndexColbert as en, bucketEffort as et, DEFAULT_MODEL_CHAIN as f, DEFAULT_CLAUDE_MODEL_FALLBACKS as fn, cheapReviewerModel as ft, PLAN_DEFAULT_MODEL as g, UPSTREAM_FETCH_TIMEOUT_MS as gn, fastImplementerModel as gt, IMPLEMENT_DEFAULT_MODEL as h, DEFAULT_PORT as hn, fastGeneralPurposeModel as ht, assertMcpToolSurfaceConsistent as i, provisionTreeSitterAssets as in, agentToolsEnabled as it, assetFor as j, CHEAP_PROFILE_NATIVE_AGENT_NAMES as jn, standInToolEnabled as jt, vscodeRipgrepPath as k, CHEAP_PROFILE_ADVISOR_CLIENT_MODEL as kn, scoutModel as kt, maxPersonasFor as l, toolbeltPathOverride as ln, browserToolsEnabled as lt, EXPLORE_DEFAULT_THINKING as m, DEFAULT_CODEX_MODEL_FALLBACKS as mn, fastAstraModel as mt, MCP_GROUPS as n, extractZipMember as nn, handleMcpDelete as nt, buildPeerAwarenessSnippet as o, DEFINITION_OF_GREATNESS as on, brainstormModel as ot, EXPLORE_DEFAULT_MODEL as p, DEFAULT_CODEX_MODEL as pn, fastAdvisorModel as pt, buildOpenAIErrorEvent as q, MAX_RESPONSE_BODY_BYTES as qt, agentNamesForToolAllowlist as r, warmTreeSitterPool as rn, handleMcpPost as rt, buildPeerAwarenessSummary as s, shouldUseInsecureTls as sn, browseAgentEnabled as st, GROUP_META as t, extractTarGzMember as tn, clampEffort as tt, personasFor as u, BUDGET_SMALL_FAST_CATALOG_ID as un, cheapAdvisorModel as ut, TEST_DEFAULT_MODEL as v, generateRandomPort as vn, fastPlanModel as vt, buildEnv as w, classifyMessagesRoute as wn, implementerFastModel as wt, resolveModeDefaults as x, resolveLeadSlugArg as xn, fleetToolsEnabled as xt, appendPlanReminder as y, isBudgetClaudeLead as yn, fastReviewerModel as yt, isAdvisorRequested as z, findLaunchBySecret as zt };
37596
37623
 
37597
- //# sourceMappingURL=peer-mcp-personas-DklYru_1.js.map
37624
+ //# sourceMappingURL=peer-mcp-personas-0_XU6nm9.js.map