github-router 0.3.276 → 0.3.285
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.
- package/dist/{attribution-settings-VOPvSn6v.js → attribution-settings-B8M2fvhz.js} +44 -45
- package/dist/attribution-settings-B8M2fvhz.js.map +1 -0
- package/dist/{auth-CYoRwhC9.js → auth-VUL2Zxvw.js} +3 -3
- package/dist/{auth-CYoRwhC9.js.map → auth-VUL2Zxvw.js.map} +1 -1
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/{check-usage-DDUyg8ZB.js → check-usage-CcLdFPGr.js} +4 -4
- package/dist/{check-usage-DDUyg8ZB.js.map → check-usage-CcLdFPGr.js.map} +1 -1
- package/dist/{claude-OTHh7iKz.js → claude-e-JIQGhR.js} +55 -38
- package/dist/claude-e-JIQGhR.js.map +1 -0
- package/dist/{codex-tCxK30Su.js → codex-DSnVZ9oK.js} +5 -5
- package/dist/{codex-tCxK30Su.js.map → codex-DSnVZ9oK.js.map} +1 -1
- package/dist/{debug-CJgsxoVw.js → debug-CtzYWxpJ.js} +2 -2
- package/dist/{debug-CJgsxoVw.js.map → debug-CtzYWxpJ.js.map} +1 -1
- package/dist/engine-DMveEa9x.js +2 -0
- package/dist/{gate-discovery-nf7PGgnS.js → gate-discovery-BCFwLm0q.js} +5 -5
- package/dist/{gate-discovery-nf7PGgnS.js.map → gate-discovery-BCFwLm0q.js.map} +1 -1
- package/dist/{get-copilot-usage-D1OAH0EU.js → get-copilot-usage-B-EDAqQb.js} +2 -2
- package/dist/{get-copilot-usage-D1OAH0EU.js.map → get-copilot-usage-B-EDAqQb.js.map} +1 -1
- package/dist/hooks.mjs +37870 -0
- package/dist/hooks.sha256 +1 -0
- package/dist/{internal-artifact-open-BCz9lnq0.js → internal-artifact-open-Dj5Nlq0L.js} +2 -2
- package/dist/{internal-artifact-open-BCz9lnq0.js.map → internal-artifact-open-Dj5Nlq0L.js.map} +1 -1
- package/dist/{internal-first-mate-guard-7v2CtMA6.js → internal-first-mate-guard-B7p4NttK.js} +1 -1
- package/dist/{internal-first-mate-guard-La0tIjTU.js → internal-first-mate-guard-DOgFVki5.js} +5 -4
- package/dist/{internal-first-mate-guard-La0tIjTU.js.map → internal-first-mate-guard-DOgFVki5.js.map} +1 -1
- package/dist/{internal-plan-review-jRMpHu68.js → internal-plan-review-DRFHET_B.js} +11 -4
- package/dist/internal-plan-review-DRFHET_B.js.map +1 -0
- package/dist/{internal-prompt-submit-CqWRFCuv.js → internal-prompt-submit-f1LR2P2G.js} +4 -4
- package/dist/{internal-prompt-submit-CqWRFCuv.js.map → internal-prompt-submit-f1LR2P2G.js.map} +1 -1
- package/dist/{internal-session-bind-BiHWDxuC.js → internal-session-bind-DhZhxJ3T.js} +2 -2
- package/dist/{internal-session-bind-BiHWDxuC.js.map → internal-session-bind-DhZhxJ3T.js.map} +1 -1
- package/dist/{internal-stop-hook-Ds9LwmZA.js → internal-stop-hook-Cf-7w3RH.js} +68 -20
- package/dist/internal-stop-hook-Cf-7w3RH.js.map +1 -0
- package/dist/{internal-stop-review-BOFj7R3q.js → internal-stop-review-BBsLcbPG.js} +2 -2
- package/dist/{internal-stop-review-BOFj7R3q.js.map → internal-stop-review-BBsLcbPG.js.map} +1 -1
- package/dist/{internal-worker-guard-Lu8VHj5k.js → internal-worker-guard-CKgYFiFO.js} +2 -2
- package/dist/{internal-worker-guard-Lu8VHj5k.js.map → internal-worker-guard-CKgYFiFO.js.map} +1 -1
- package/dist/{internal-workspace-header-BRMz0Yql.js → internal-workspace-header-OYgHEnFt.js} +2 -2
- package/dist/{internal-workspace-header-BRMz0Yql.js.map → internal-workspace-header-OYgHEnFt.js.map} +1 -1
- package/dist/lib/tree-sitter-pool/lifecycle-C7W8D2lx.js +165 -0
- package/dist/lib/tree-sitter-pool/lifecycle-Dx0SnP7Z.js +157 -0
- package/dist/lib/tree-sitter-pool/worker.js +287 -14
- package/dist/{lifecycle-LA4rAuAL.js → lifecycle-B7CHqKlF.js} +2 -2
- package/dist/{lifecycle-LA4rAuAL.js.map → lifecycle-B7CHqKlF.js.map} +1 -1
- package/dist/lifecycle-CbmHMMSD.js +2 -0
- package/dist/{lifecycle-AGXnd-ZY.js → lifecycle-D-rL81tT.js} +2 -2
- package/dist/{lifecycle-AGXnd-ZY.js.map → lifecycle-D-rL81tT.js.map} +1 -1
- package/dist/lifecycle-DTcZwa7U.js +2 -0
- package/dist/lifecycle-K9oVtGde.mjs +16 -0
- package/dist/main.js +18 -18
- package/dist/{mcp-workspace-header-CGJbNeHb.js → mcp-workspace-header-ucs2SDST.js} +5 -6
- package/dist/mcp-workspace-header-ucs2SDST.js.map +1 -0
- package/dist/{models-4Q45Q2Dw.js → models-C16mBK2M.js} +3 -3
- package/dist/{models-4Q45Q2Dw.js.map → models-C16mBK2M.js.map} +1 -1
- package/dist/{orchestration-CpkG8ScN.js → orchestration-CtM6FYNx.js} +2 -2
- package/dist/{orchestration-CpkG8ScN.js.map → orchestration-CtM6FYNx.js.map} +1 -1
- package/dist/package-root-B-osctCk.js +29 -0
- package/dist/package-root-B-osctCk.js.map +1 -0
- package/dist/paths-CV9K7Xqm.js +2 -0
- package/dist/{paths-j2B7b0DZ.js → paths-wLC0InjX.js} +28 -4
- package/dist/paths-wLC0InjX.js.map +1 -0
- package/dist/{peer-mcp-personas-DRL_xT4h.js → peer-mcp-personas-V6stFvpq.js} +1158 -297
- package/dist/peer-mcp-personas-V6stFvpq.js.map +1 -0
- package/dist/{plan-review-hook-Dk5zTNke.js → plan-review-hook-Lf9ISdF6.js} +5 -6
- package/dist/plan-review-hook-Lf9ISdF6.js.map +1 -0
- package/dist/{prompt-submit-hook-lvTWwaTV.js → prompt-submit-hook-BlijaOn7.js} +6 -11
- package/dist/prompt-submit-hook-BlijaOn7.js.map +1 -0
- package/dist/{provision-HzZ547dW.js → provision-BpL6gZIt.js} +4 -4
- package/dist/{provision-HzZ547dW.js.map → provision-BpL6gZIt.js.map} +1 -1
- package/dist/self-invocation-CKMjcA5F.js +380 -0
- package/dist/self-invocation-CKMjcA5F.js.map +1 -0
- package/dist/{serve-CM3OmF4I.js → serve-CXUf7RtJ.js} +34 -27
- package/dist/serve-CXUf7RtJ.js.map +1 -0
- package/dist/{server-setup-C9r7jYnz.js → server-setup-Bppjt9xO.js} +81 -207
- package/dist/server-setup-Bppjt9xO.js.map +1 -0
- package/dist/{start-KAUCsDao.js → start-C1-jrHfU.js} +3 -3
- package/dist/{start-KAUCsDao.js.map → start-C1-jrHfU.js.map} +1 -1
- package/dist/{stop-gate-hook-CaUrldLh.js → stop-gate-hook-DgJ6sW8N.js} +100 -52
- package/dist/stop-gate-hook-DgJ6sW8N.js.map +1 -0
- package/dist/{stop-gate-policy-lAabj_pP.js → stop-gate-policy-DG5yYWGn.js} +2 -2
- package/dist/{stop-gate-policy-lAabj_pP.js.map → stop-gate-policy-DG5yYWGn.js.map} +1 -1
- package/dist/{token-CxPaBEUS.js → token-CnlB0884.js} +2 -2
- package/dist/token-CnlB0884.js.map +1 -0
- package/dist/{version-_Q1WpsQp.js → version-C8x2hQrZ.js} +14 -2
- package/dist/version-C8x2hQrZ.js.map +1 -0
- package/dist/{worker-dispatch-Bj1uYyG9.js → worker-dispatch-zW8Zi69V.js} +11 -7
- package/dist/worker-dispatch-zW8Zi69V.js.map +1 -0
- package/package.json +2 -2
- package/dist/attribution-settings-VOPvSn6v.js.map +0 -1
- package/dist/claude-OTHh7iKz.js.map +0 -1
- package/dist/engine-BKJKMAzh.js +0 -2
- package/dist/internal-plan-review-jRMpHu68.js.map +0 -1
- package/dist/internal-stop-hook-Ds9LwmZA.js.map +0 -1
- package/dist/lifecycle-DCrKbaIU.js +0 -2
- package/dist/lifecycle-KKSTKwd6.js +0 -2
- package/dist/mcp-workspace-header-CGJbNeHb.js.map +0 -1
- package/dist/paths-DN3Nio42.js +0 -2
- package/dist/paths-j2B7b0DZ.js.map +0 -1
- package/dist/peer-mcp-personas-DRL_xT4h.js.map +0 -1
- package/dist/plan-review-hook-Dk5zTNke.js.map +0 -1
- package/dist/prompt-submit-hook-lvTWwaTV.js.map +0 -1
- package/dist/serve-CM3OmF4I.js.map +0 -1
- package/dist/server-setup-C9r7jYnz.js.map +0 -1
- package/dist/stop-gate-hook-CaUrldLh.js.map +0 -1
- package/dist/token-CxPaBEUS.js.map +0 -1
- package/dist/version-_Q1WpsQp.js.map +0 -1
- package/dist/worker-dispatch-Bj1uYyG9.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"claude-OTHh7iKz.js","names":["nodePath"],"sources":["../src/lib/injected-skills/artifact-review-skill.ts","../src/claude.ts"],"sourcesContent":["export const ARTIFACT_REVIEW_SKILL = {\n name: \"gh-artifact-review\",\n md: `---\nname: gh-artifact-review\ndescription: Reviews plans and artifacts in the ai-or-die panel. Defaults to authoring a self-contained HTML artifact (rich, annotatable, optionally interactive) and opening THAT for the human, then drains feedback with artifact_await, revises, and ends the loop. Use when running inside an ai-or-die tab and there is a plan, comparison, diagram, table, diff, or report the user should see before proceeding.\nuser-invocable: true\n---\n\n# gh-artifact-review: human review in the ai-or-die panel\n\nUse this when you finish a plan or produce something a user should review and you are inside an ai-or-die tab (the \\`mcp__peers__artifact_*\\` tools drive a live panel). The human can click any block or select text to attach a comment, and can click declarative action controls you emit — both come back to you as typed events.\n\n## Default: present HTML, not raw markdown\n\nHTML is the canonical review artifact — it renders richly and is annotatable element-by-element. When the content is anything visual or structured (a comparison, table, diagram, diff, dashboard, or a plan you want to look polished), **author a self-contained \\`.html\\` file** (inline CSS, no external deps, readable typography) and open THAT.\n\n- Plan-mode plans: the panel auto-opens them already rendered to HTML — you do not need to convert them by hand.\n- Do not paste raw markdown into an \\`.html\\`; write real HTML (headings, lists, tables, \\`<pre>\\` for code). Opening a raw \\`.md\\` still renders, but a purpose-built HTML artifact reads better and annotates cleanly.\n\n## When to reach for an artifact\n\nDefault to one for anything easier to grasp visually than as terminal prose: plans, design proposals, comparisons / trade-offs, decisions that need the user's input, diagrams / architecture, tables, code diffs, reports. Skip it only for trivial one-line answers.\n\n## Playbooks (what a good artifact of each type contains)\n\n- **plan**: goal, current state, the proposed approach (high-level decisions, not every line), then risks and open questions at the end.\n- **comparison**: options as columns, trade-offs as rows, current-vs-target where relevant, and an explicit recommendation. Do not make the reader infer the winner.\n- **table**: scannable rows, a sticky header, aligned numeric columns; group/section dense records rather than one flat wall.\n- **diagram**: boxes + arrows. Mermaid when automatic layout matters; positioned SVG/CSS when each node needs prose, code, or controls.\n- **code / diff**: \\`<pre>\\` with before/after or unified-diff styling; keep line context tight; call out the changed lines.\n- **report / dashboard**: lead with the headline number / verdict, then supporting detail; keep one idea per section.\n\n## Design system\n\nArtifacts stay portable (they must render identically opened standalone), so do not depend on a server-injected theme. Pick the look in priority order: (1) a look the user named; (2) the **subject project's** own design system, its Tailwind / theme config, CSS variables / tokens, component library, or existing styled pages, especially when the artifact previews that app's UI; (3) only when both come up empty, clean readable defaults (system font stack, generous spacing, a single accent).\n\n## Loop\n\n1. Open: \\`mcp__peers__artifact_open\\` with the absolute path of the \\`.html\\` (or the file). Pass \\`mode:\"interactive\"\\` when the HTML carries \\`data-aod-*\\` action controls (below). Relay the returned \\`viewUrl\\` and tell the user to review in the panel.\n2. Drain: \\`mcp__peers__artifact_await\\`. It long-holds for the human's next events and returns \\`{events, status, cursor}\\`. **Pass the returned \\`cursor\\` on your next call** so you only receive newer events; if \\`events\\` is empty, call again with that cursor.\n3. Act on each event by \\`kind\\`:\n - \\`comment\\` — a free-text note anchored to the artifact (\\`selector\\`, quoted \\`text\\`, \\`sourceLine\\`); apply the requested change at that exact spot.\n - \\`action\\` — the human clicked a control you emitted (\\`action\\` verb, \\`elementId\\`, optional \\`value\\`; a multi-select submit arrives as one action carrying the selected set). Do what the verb means (approve a step, choose an option, apply a toggle set).\n4. Reply: \\`mcp__peers__artifact_reply\\` with a concise summary of what you changed. Optionally \\`mcp__peers__artifact_update({file})\\` or \\`({html})\\` to replace the artifact content in place, or \\`mcp__peers__artifact_refresh\\` to reload it from disk.\n5. Repeat 2-4 until the user is satisfied; then \\`mcp__peers__artifact_end\\`. Use \\`mcp__peers__artifact_dismiss\\` to hide the panel while keeping the review alive (queued feedback preserved) if the user wants it out of the way without ending.\n\n### Push arrival (you do not have to be polling)\n\nWhen you are idle at the prompt, panel feedback can arrive on its own as a new turn (the tab injects it). Structured actions that answer a pending decision are routed to you directly. Either way, the durable record is the \\`artifact_await\\` drain, so when in doubt call \\`artifact_await\\` (with your last cursor) to reconcile — it replays anything you missed.\n\n### \\`artifact_poll\\` (frozen legacy)\n\n\\`mcp__peers__artifact_poll\\` still resolves for back-compat but returns the OLD payload (human comments only, no structured actions). Prefer \\`artifact_await\\`.\n\n## Interactive controls (data-aod-* authoring)\n\nTo let the human act on the artifact (not just comment), emit declarative controls; the panel wires them and delivers a typed \\`action\\` event. No JS in the artifact.\n\n- **choose-one (fires immediately):** each option is a button that posts the moment it is clicked.\n \\`<button data-aod-action=\"choose\" data-aod-group=\"decision-1\" data-aod-id=\"opt-jwt\" data-aod-value=\"jwt\">JWT</button>\\`\n- **multi-select (toggle then submit):** checkboxes sharing a \\`data-aod-group\\`, plus one submit button with the same group. The submit delivers ONE action carrying \\`{group, selected:[{elementId, value?}]}\\`.\n \\`<input type=\"checkbox\" data-aod-action=\"check\" data-aod-group=\"opts\" data-aod-id=\"a\" data-aod-value=\"a\">\\` … \\`<button data-aod-action=\"submit\" data-aod-group=\"opts\" data-aod-id=\"opts-submit\">Apply</button>\\`\n- **plan steps:** \\`<li class=\"aod-step\" data-aod-id=\"plan-step-3\" data-source-line=\"14\">\\` with per-step \\`data-aod-action=\"approve\"/\"skip\"\\` buttons.\n- **Required attrs:** \\`data-aod-action\\` (verb), \\`data-aod-id\\` (stable, echoed back as \\`elementId\\`; a control missing it is ignored), optional \\`data-aod-value\\`. Keep \\`data-source-line\\` for comment mapping.\n\n## Honest limits\n\n- If a tool errors (e.g. \\`NOT_IN_AIORDIE_TAB\\`, \\`UNREACHABLE\\`, \\`INVALID_REQUEST\\`), report the code/message verbatim; do not claim the panel opened.\n- The panel is a review surface, not an approver: outward/irreversible actions still need explicit user confirmation.\n`,\n} as const\n","import process from \"node:process\"\n\nimport { defineCommand, type ArgsDef } from \"citty\"\nimport consola from \"consola\"\n\nimport {\n checkClaudeVersion,\n updateClaude,\n} from \"./lib/claude-version-check\"\nimport { runSelfUpdate } from \"./lib/self-update\"\nimport {\n injectPeerMcpIntoMirror,\n resolveCodexCliBackend,\n resolveGroupKeysFromMirror,\n workersKeyOf,\n writePeerMcpRuntimeFiles,\n} from \"./lib/codex-mcp-config\"\nimport { enableFileLogging } from \"./lib/file-log-reporter\"\nimport { getCodexVersion, launchChild } from \"./lib/launch\"\nimport { injectAllowRules, planModeAllowRules } from \"./lib/mcp-permissions-settings\"\nimport { listModelsForEndpoint } from \"./lib/model-validation\"\nimport { ensureClaudeConfigMirror, PATHS, removeOwnClaudeConfigMirror, writeArtifactCredsToMirror } from \"./lib/paths\"\nimport {\n buildArtifactOpenHookCommand,\n buildSessionBindHookCommand,\n buildStopHookCommand,\n captureLaunchBaseline,\n injectStopHookIntoSettingsFile,\n stopGateId,\n stopGateDisabled,\n} from \"./lib/orchestration/stop-gate-hook\"\nimport { buildPlanReviewHookCommand, planReviewEnabled } from \"./lib/orchestration/plan-review-hook\"\nimport {\n fileBaselineStore,\n repoFingerprint,\n repoRoot,\n stopGateEnabledForRepo,\n trustRepo,\n} from \"./lib/orchestration/stop-gate-policy\"\nimport {\n checksForDescriptor,\n descriptorHash,\n parseGateDescriptor,\n type GateDescriptor,\n} from \"./lib/orchestration/harness-parse\"\nimport {\n discoverGateCommands,\n readDiscoveredGate,\n sourcesHash,\n writeDiscoveredGate,\n} from \"./lib/orchestration/gate-discovery\"\nimport { liveExec } from \"./lib/orchestration/live-exec\"\nimport { hookMcpRuntimeFromEnv } from \"./lib/orchestration/hook-mcp-client\"\nimport { buildPromptSubmitHookCommand } from \"./lib/orchestration/prompt-submit-hook\"\nimport {\n FIRST_MATE_GUARD_MATCHER,\n buildFirstMateGuardHookCommand,\n} from \"./internal-first-mate-guard\"\nimport { assertShapingInstalled } from \"./lib/first-mate/operator-shaping\"\nimport {\n activeDispatchModes,\n buildWorkerGuardHookCommand,\n guardToolMatcher,\n} from \"./lib/worker-dispatch\"\nimport { ARTIFACT_REVIEW_SKILL, INJECTED_SKILLS, writeInjectedSkill } from \"./lib/injected-skills\"\nimport { shouldUseInsecureTls } from \"./lib/artifact/tools\"\nimport { parseBoolEnv } from \"./lib/exec\"\nimport nodePath from \"node:path\"\nimport { tmpdir } from \"node:os\"\nimport { randomBytes } from \"node:crypto\"\nimport { buildPeerAwarenessSnippet, buildPeerAwarenessSummary, type McpGroup } from \"./lib/peer-mcp-personas\"\nimport { injectAttributionSuppressionIntoSettingsFile } from \"./lib/attribution-settings\"\nimport { appendPeerAwarenessToMirroredClaudeMd, appendToolbeltAwarenessToMirroredClaudeMd, buildOperatingDefaultsDirective, type NativeAgentAvailability, OPERATING_DEFAULTS_DIGEST, prependArtifactPanelDirectiveToMirroredClaudeMd, prependOperatingDefaultsToMirroredClaudeMd, prependStyleDirectiveToMirroredClaudeMd } from \"./lib/claude-md-injection\"\nimport { availableToolCommands, buildToolbeltAwareness, toolbeltEnabled } from \"./lib/toolbelt\"\nimport { provisionToolbelt } from \"./lib/toolbelt/provision\"\nimport { colbertDegradedWarning, provisionAndIndexColbert } from \"./lib/colbert\"\nimport { startKeepAwake, stopKeepAwake } from \"./lib/keep-awake\"\nimport { warmTreeSitterPool } from \"./lib/tree-sitter-pool/pool\"\nimport { provisionBrowserAssets } from \"./lib/browser-mcp/provision\"\nimport {\n DEFAULT_CLAUDE_MODEL_FALLBACKS,\n pickClaudeDefault,\n} from \"./lib/port\"\nimport {\n agentToolsEnabled,\n artifactToolsEnabled,\n browseAgentEnabled,\n browserCompoundToolsEnabled,\n browserToolsEnabled,\n fleetToolsEnabled,\n geminiAvailable,\n nativeSubagentModel,\n brainstormModel,\n reviewerModel,\n scoutModel,\n scribeModel,\n genericModel,\n genericFastModel,\n genericCheapModel,\n standInToolEnabled,\n workerToolsEnabled,\n} from \"./lib/mcp-capabilities\"\nimport {\n getClaudeCodeEnvVars,\n parseSharedArgs,\n setupAndServe,\n sharedServerArgs,\n} from \"./lib/server-setup\"\nimport { state } from \"./lib/state\"\nimport { resolveModel } from \"./lib/utils\"\n\nfunction isFirstMateSkillName(name: string): boolean {\n return (\n name === \"gh-first-mate\" ||\n name === \"gh-first-mate-scaffold\" ||\n name === \"gh-first-mate-operate\" ||\n name === \"gh-first-mate-conduct\"\n )\n}\n\nexport const claudeArgs = {\n ...sharedServerArgs,\n model: {\n alias: \"m\",\n type: \"string\",\n description:\n \"Override the default model for Claude Code. Accepts a full slug (e.g. claude-opus-4-7) or an Opus family shorthand (e.g. 4.7, 4.8, 4.6) which expands to the best variant for that family — adding the [1m] suffix when a 1M-context backend is in the catalog.\",\n },\n \"codex-mcp\": {\n type: \"boolean\" as const,\n default: true,\n description:\n \"Wire peer-model MCP personas (codex-critic, codex-reviewer, gemini-critic) into the spawned Claude Code session\",\n },\n \"codex-cli\": {\n type: \"boolean\" as const,\n default: false,\n description:\n \"Add a `codex mcp-server` stdio backend so codex-implementer can mutate files. Requires codex CLI 0.129+; gracefully falls back to HTTP-only if absent.\",\n },\n \"codex-mcp-only\": {\n type: \"boolean\" as const,\n default: false,\n description:\n \"Pass --strict-mcp-config to claude code so only github-router's MCP servers are loaded (hides user's existing MCP servers)\",\n },\n stealth: {\n type: \"boolean\" as const,\n default: false,\n description:\n \"Opt back into VS Code-only beta header filtering. Loses leverage features (task budgets, token-efficient tools, prompt caching, etc.) but minimizes the wire-fingerprint difference from VS Code Copilot Chat. By default the `claude` subcommand enables extended/leverage betas because the spawned Claude Code already identifies itself via UA and other headers — partial stealth doesn't buy much.\",\n },\n \"trust-gate\": {\n type: \"boolean\" as const,\n default: false,\n description:\n \"Explicitly record consent for the structural Stop-gate in THIS repo (pinned to the repo's root-commit). The gate is ON BY DEFAULT when a harness is detected (consent-by-launching), so this is now mostly redundant; it stays for explicit/scripted use. Disable the gate entirely with GH_ROUTER_DISABLE_STOP_GATE=1.\",\n },\n \"no-stop-gate\": {\n type: \"boolean\" as const,\n default: false,\n description:\n \"Disable the structural Stop-gate for THIS session (same effect as GH_ROUTER_DISABLE_STOP_GATE=1). Intended for driven/automated sessions where a blocking Stop hook would hang the turn-end while a fleet driver waits.\",\n },\n \"auto-update\": {\n type: \"boolean\" as const,\n default: true,\n description:\n \"Check for and install the latest Claude Code on launch via `claude update` (throttled to once per hour via ~/.local/share/github-router/last-update-check). `claude update` respects the real install method (native installer or npm), so it never creates a conflicting second install; builds too old to support it fall back to `npm install -g @anthropic-ai/claude-code@latest`. Set to false (--no-auto-update) to check and warn only. Falls back gracefully if claude/npm/network unavailable.\",\n },\n \"update-check\": {\n type: \"boolean\" as const,\n default: true,\n description:\n \"Check the npm registry for a newer Claude Code version on launch and warn if stale (non-blocking ~500ms cost). Set to false (--no-update-check) to skip the check entirely (useful for offline/CI). Independent from --auto-update: --no-update-check implies no auto-install (nothing to install since we never check).\",\n },\n} satisfies ArgsDef\n\n/**\n * Build the argv to forward to the spawned `claude` child from citty's\n * rawArgs (every token after the `claude` subcommand). citty is non-strict,\n * so an unknown flag such as `--print`/`-p`… `--output-format` is absorbed\n * into the parsed `args` object AND its value swallowed, instead of landing\n * in `args._`; forwarding only `args._` therefore drops headless flags unless\n * the user wrapped them in `--`. Here we walk rawArgs and forward every token\n * that is NOT one of github-router's OWN declared flags (or that flag's\n * consumed value). Everything after a literal `--` is forwarded verbatim,\n * preserving the prior explicit-passthrough behavior.\n *\n * A child flag whose NAME collides with a github-router flag (`-p`/`--port`,\n * `-v`/`--verbose`, `-m`/`--model`, `-a`/`--account-type`, `-r`/`--rate-limit`,\n * `-g`/`--github-token`) is owned by github-router; forward it to the child\n * explicitly after `--` (e.g. `github-router claude -- -p`). Every other Claude\n * flag (`--print`, `--output-format`, `--resume`, `--continue`, …) flows\n * through automatically.\n */\nexport function collectChildPassthroughArgs(\n rawArgs: ReadonlyArray<string>,\n argsDef: ArgsDef,\n): string[] {\n const known = new Set<string>()\n const stringTyped = new Set<string>()\n for (const [name, def] of Object.entries(argsDef)) {\n const rawAlias = \"alias\" in def ? def.alias : undefined\n const aliases =\n rawAlias === undefined\n ? []\n : Array.isArray(rawAlias)\n ? rawAlias\n : [rawAlias]\n for (const n of [name, ...aliases]) {\n known.add(n)\n if (def.type === \"string\") stringTyped.add(n)\n }\n }\n\n const forwarded: string[] = []\n for (let i = 0; i < rawArgs.length; i++) {\n const tok = rawArgs[i]\n // Explicit passthrough separator: forward everything after `--` verbatim\n // (matches the prior `args._`-only behavior, where citty routed post-`--`\n // tokens into `_`).\n if (tok === \"--\") {\n forwarded.push(...rawArgs.slice(i + 1))\n break\n }\n // A bare `-` or a non-dash token is a positional / value → forward.\n if (tok === \"-\" || !tok.startsWith(\"-\")) {\n forwarded.push(tok)\n continue\n }\n const doubleDash = tok.startsWith(\"--\")\n const afterDashes = tok.slice(doubleDash ? 2 : 1)\n const eq = afterDashes.indexOf(\"=\")\n const rawName = eq >= 0 ? afterDashes.slice(0, eq) : afterDashes\n const hasInlineValue = eq >= 0\n const negated = doubleDash && rawName.startsWith(\"no-\")\n const baseName = negated ? rawName.slice(3) : rawName\n\n const isKnown = known.has(rawName) || (negated && known.has(baseName))\n if (!isKnown) {\n // Unknown to github-router → belongs to the child claude (e.g. --print).\n forwarded.push(tok)\n continue\n }\n // Own flag: drop it. Also drop a separately-tokenized value for a\n // string-typed flag written as `--flag value` / `-a value` (no inline\n // `=`, next token present and not itself a flag). Booleans and `--no-…`\n // negations never consume a following token under citty/mri.\n const consumesValue =\n !negated\n && !hasInlineValue\n && stringTyped.has(rawName)\n && i + 1 < rawArgs.length\n && rawArgs[i + 1] !== \"--\"\n && !rawArgs[i + 1].startsWith(\"-\")\n if (consumesValue) i++\n }\n return forwarded\n}\n\nexport const claude = defineCommand({\n meta: {\n name: \"claude\",\n description: \"Start the proxy server and launch Claude Code\",\n },\n args: claudeArgs,\n async run({ args, rawArgs }) {\n if (!process.stdout.isTTY) {\n consola.error(\"The claude subcommand requires a TTY (interactive terminal).\")\n process.exit(1)\n }\n\n const parsed = parseSharedArgs(args as unknown as Record<string, unknown>)\n\n // Phase E P2.2: stealth-vs-leverage policy.\n // The `claude` subcommand defaults to LEVERAGE mode (extended-betas\n // ON) because the spawned Claude Code already identifies itself via\n // UA / editor-version / x-app headers — partial stealth doesn't\n // meaningfully reduce the wire fingerprint, and the cost of stealth\n // is losing features the user explicitly chose to install Claude\n // Code for (--max-budget-usd, token-efficient tools, prompt caching,\n // structured outputs, MCP, etc.).\n //\n // The `--stealth` flag opts back into the VS Code-only filter for\n // users who specifically want minimal wire diff over leverage.\n // The shared `--extended-betas` flag still works (treated as alias).\n //\n // Note: `advisor-tool-` is stripped in BOTH modes regardless of this\n // setting (Phase A: Copilot 400s on it). ADVISOR will be served via\n // Phase I's proxy-side translate path independently.\n if (args.stealth) {\n // Stealth wins if explicitly requested.\n parsed.extendedBetas = false\n consola.info(\n \"Stealth mode: VS Code-only beta filtering. Leverage features disabled.\",\n )\n } else if (!args[\"extended-betas\"]) {\n // No explicit --extended-betas AND no --stealth → default ON.\n parsed.extendedBetas = true\n }\n // If user passed --extended-betas explicitly, parsed already reflects it.\n\n // Phase H P2: Claude Code version check + opt-in auto-update.\n // Default: check + auto-install if newer version available\n // (throttled to once per hour). The user explicitly chose to install\n // a Claude Code wrapper — they want the latest features and bug\n // fixes. Opt-out via --no-auto-update (check only, warn) or\n // --no-update-check (silence entirely). Best-effort: skips silently\n // if npm is offline or claude is not on PATH. The check happens\n // BEFORE setupAndServe so a stale version doesn't get spawned.\n if (args[\"update-check\"] !== false) {\n try {\n const versionCheck = await checkClaudeVersion({\n noCheck: false,\n })\n if (versionCheck.skipped && versionCheck.skipReason === \"no-claude\") {\n // Claude isn't on PATH — let launchChild surface the more\n // contextual \"claude not found\" error in the spawn step.\n consola.debug(\n \"claude --version probe failed; skipping auto-update.\",\n )\n } else if (versionCheck.skipped && versionCheck.skipReason === \"no-npm\") {\n // npm view failed — likely offline. Don't block launch.\n consola.debug(\n \"npm view @anthropic-ai/claude-code failed; skipping auto-update check (likely offline).\",\n )\n } else if (\n versionCheck.needsUpdate\n && versionCheck.installedVersion\n && versionCheck.latestVersion\n ) {\n if (args[\"auto-update\"] !== false) {\n try {\n await updateClaude(versionCheck.latestVersion)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n consola.warn(\n `Auto-update of Claude Code from ${versionCheck.installedVersion} to ${versionCheck.latestVersion} failed (${msg}); continuing with installed version. Run \\`claude update\\` (or \\`npm install -g @anthropic-ai/claude-code@latest\\`) manually to retry.`,\n )\n }\n } else {\n consola.warn(\n `Claude Code v${versionCheck.installedVersion} is installed; v${versionCheck.latestVersion} is available. Run with --auto-update (the default) to install on launch, or \\`claude update\\` manually.`,\n )\n }\n }\n } catch (err) {\n // Whole version-check should never block launch.\n consola.debug(\"Claude version check failed:\", err)\n }\n }\n\n let server: Awaited<ReturnType<typeof setupAndServe>>[\"server\"]\n let serverUrl: string\n try {\n const result = await setupAndServe({\n ...parsed,\n port: parsed.port, // undefined = random port\n silent: true,\n })\n server = result.server\n serverUrl = result.serverUrl\n } catch (error) {\n consola.error(\"Failed to start server:\", error instanceof Error ? error.message : error)\n process.exit(1)\n }\n\n // Queue a best-effort self-update of github-router itself (detached,\n // applies next launch). Fire-and-forget so the bounded npm probe\n // never delays spawning Claude Code.\n void runSelfUpdate({ selfUpdate: args[\"self-update\"] !== false })\n\n // Provision the router-owned CLAUDE_CONFIG_DIR with our synthetic\n // .credentials.json + a snapshot copy of the user's ~/.claude/.\n // The spawned Claude Code (and any teammates it spawns via the\n // agent-teams primitive) reads this dir instead of ~/.claude/,\n // finds our synthetic credential, and authenticates — closing the\n // teammate-spawn allowlist gap that drops ANTHROPIC_AUTH_TOKEN.\n // See ensureClaudeConfigMirror in src/lib/paths.ts.\n //\n // Run BEFORE enableFileLogging so a fatal credentials-write failure\n // surfaces on the user's terminal (we only throw on the credentials\n // write — copy failures of individual user files are debug-logged\n // and skipped).\n try {\n await ensureClaudeConfigMirror()\n } catch (err) {\n consola.error(\n `Failed to provision CLAUDE_CONFIG_DIR mirror: ${\n err instanceof Error ? err.message : String(err)\n }. Spawned Claude Code would not be able to authenticate.`,\n )\n process.exit(1)\n }\n\n enableFileLogging() // redirect errors/warnings to file; suppress terminal output\n\n // Two slugs flow through this code:\n // * `chosenSlug` — the value we set for `ANTHROPIC_MODEL`. Must be an\n // Anthropic-published slug (e.g. `claude-opus-4-8`) so Claude Code's\n // hardcoded `/model` registry matches it and the UI shows the right\n // menu entry. The proxy's resolver translates this back to a Copilot\n // slug at request time, so the actual upstream call still works.\n // * `resolvedSlug` — the Copilot-side slug after `resolveModel`. Used\n // only for cache-presence validation (fallback chain) and the\n // launch banner.\n //\n // For the implicit-default path only, we walk\n // DEFAULT_CLAUDE_MODEL_FALLBACKS when neither the default nor any\n // earlier fallback resolves to a model present in the Copilot cache.\n // Explicit `--model` is respected as-is — including Copilot slugs\n // (which Claude Code's UI won't recognize, but power users may want\n // for explicit pinning).\n // `-m 4.7` / `-m 4.8` shorthand: treat a bare \"N.M\" value as an\n // Opus-family preference and let pickClaudeDefault pick the best\n // variant for that family (adding [1m] when an opus-N.M-1m backend\n // exists in the catalog). Full slugs and any non-matching string\n // continue to pass through unchanged. `usingDefault` stays false\n // for shorthand so the DEFAULT_CLAUDE_MODEL_FALLBACKS walk doesn't\n // override an explicit family request with the next-older Opus.\n const usingDefault = !args.model\n const opusFamilyShorthand = args.model?.match(/^(\\d+\\.\\d+)$/)?.[1]\n const requestedSlug = opusFamilyShorthand\n ? pickClaudeDefault(opusFamilyShorthand)\n : (args.model ?? pickClaudeDefault())\n let chosenSlug = requestedSlug\n let resolvedSlug = resolveModel(chosenSlug)\n\n if (usingDefault && state.models) {\n const inCache = (slug: string) =>\n state.models?.data.some((m) => m.id === resolveModel(slug)) ?? false\n if (!inCache(chosenSlug)) {\n for (const fallback of DEFAULT_CLAUDE_MODEL_FALLBACKS) {\n if (inCache(fallback)) {\n // Re-derive through pickClaudeDefault so 1M detection runs against\n // the FALLBACK's own family. The constant holds bare slugs, so\n // assigning one directly silently drops [1m] accounting on a\n // family that is 1M-capable — the user lands on a working model\n // with a 200k context budget it did not need to accept.\n // `inCache` already proved the family is present, so this cannot\n // trigger pickClaudeDefault's family-absent warning.\n const fallbackSlug = pickClaudeDefault(\n fallback.replace(/^claude-opus-/, \"\"),\n )\n consola.info(\n `Default model \"${chosenSlug}\" not in your Copilot model list; falling back to \"${fallbackSlug}\".`,\n )\n chosenSlug = fallbackSlug\n resolvedSlug = resolveModel(fallbackSlug)\n break\n }\n }\n }\n }\n\n if (resolvedSlug !== chosenSlug) {\n consola.info(`Model \"${chosenSlug}\" resolved to \"${resolvedSlug}\"`)\n }\n const modelEntry = state.models?.data.find((m) => m.id === resolvedSlug)\n if (!modelEntry) {\n const available = listModelsForEndpoint(\"/v1/messages\")\n consola.warn(\n `Model \"${resolvedSlug}\" not found. Available claude models: ${available.join(\", \")}`,\n )\n }\n\n // Banner shows the round-trip so the user sees both names. Claude Code's\n // UI will display the chosenSlug; Copilot upstream sees resolvedSlug.\n const banner =\n chosenSlug === resolvedSlug\n ? chosenSlug\n : `${chosenSlug} → ${resolvedSlug}`\n // Print to stderr directly — consola's terminal reporter is already gone\n process.stderr.write(`Server ready on ${serverUrl}, launching Claude Code (${banner})...\\n`)\n\n const envVars = getClaudeCodeEnvVars(serverUrl, chosenSlug)\n // Forward unrecognized flags (e.g. --print / --output-format / --resume)\n // to the spawned Claude Code child. citty is non-strict, so those land in\n // the parsed `args` object with their values swallowed rather than in\n // `args._`; walking rawArgs and dropping only github-router's OWN flags\n // makes `github-router claude -m <model> --print \"…\"` work headless\n // without requiring the `--` separator. See collectChildPassthroughArgs.\n const extraArgs = collectChildPassthroughArgs(rawArgs, claudeArgs)\n\n // LLM toolbelt: materialize curated CLI tools (rg/fd/jq/sd/sg/yq)\n // into the router bin dir prepended to the agent's PATH (the prepend\n // itself is done in getClaudeCodeEnvVars). Materialization runs in\n // the BACKGROUND so it never delays launch; the awareness one-liner\n // lists EVERY curated tool reachable this launch (system installs +\n // the toolbelt bin), appended to the mirrored CLAUDE.md so the main\n // agent AND descendants know which fast tools to prefer. Best-effort.\n if (toolbeltEnabled()) {\n void provisionToolbelt().catch((err) =>\n consola.debug(\"Toolbelt provisioning failed:\", err),\n )\n const toolbeltLine = buildToolbeltAwareness(availableToolCommands())\n if (toolbeltLine) {\n try {\n await appendToolbeltAwarenessToMirroredClaudeMd(toolbeltLine)\n } catch (err) {\n consola.warn(\n `Toolbelt CLAUDE.md append failed: ${\n err instanceof Error ? err.message : String(err)\n }`,\n )\n }\n }\n }\n\n // Best-effort ColBERT semantic-search provision + background index of\n // the launch cwd (if a git repo). ON by default; never blocks launch,\n // never throws. Opt out with GH_ROUTER_DISABLE_SEMANTIC_SEARCH=1.\n void provisionAndIndexColbert()\n\n // Surface a terminally-failed semantic index to the HUMAN. Without this\n // the only signals are a `notice` the model reads and a log line, so a\n // degraded index can (and did) go unnoticed for weeks. Fire-and-forget:\n // lexical search still works, so this must never delay or fail a launch.\n void colbertDegradedWarning(undefined, { logsToFile: true })\n .then((warning) => {\n if (warning) process.stderr.write(`${warning}\\n`)\n })\n .catch(() => {})\n\n // Best-effort, bounded launch warm-up: ready one tree-sitter worker.\n // Opt out with GH_ROUTER_DISABLE_TS_POOL_WARMUP=1.\n void warmTreeSitterPool()\n\n // Best-effort: keep the machine awake while the proxy/Claude Code\n // session runs (win32 default-on; opt out GH_ROUTER_DISABLE_KEEP_AWAKE=1).\n // Released via stopKeepAwake() in the shutdown chain below AND via the\n // module's own self-registered SIGINT/SIGTERM/exit reaper.\n startKeepAwake()\n\n // Best-effort: materialize the browser extension + bridge into the\n // stable app-dir and stamp the running version, so a one-time \"Load\n // unpacked\" survives npx/bunx upgrades. Gated on --browse; never\n // blocks launch, never throws.\n if (browserToolsEnabled()) {\n void provisionBrowserAssets().catch((err) =>\n consola.debug(\"Browser extension provisioning failed:\", err),\n )\n }\n\n // Peer-MCP wiring. Default-on. When enabled:\n // 1. Decide between HTTP backend (always works, read-only personas)\n // and the `--codex-cli` stdio backend (requires codex 0.129+,\n // adds the implementer persona).\n // 2. Probe the live Copilot catalog for gemini-3.1-pro-preview.\n // 3. Generate a per-launch nonce, write the MCP config tempfile\n // under PATHS.CLAUDE_RUNTIME_DIR with mode 0o600, AND write\n // one .md subagent file per peer agent into ~/.claude/agents/\n // (Phase 2.5 — `--agents` JSON does NOT populate Claude Code's\n // Task subagent_type enum on v2.1.138; .md files in the canonical\n // agents dir do).\n // 4. Inject `--mcp-config <path>` into the spawned Claude Code's\n // argv. Add `--strict-mcp-config` if the user explicitly opts\n // out of their existing MCP servers. The `--agents` JSON path\n // is intentionally NOT passed: the .md registration in the\n // canonical agents dir is the authoritative surface.\n // 5. Plumb `cleanup()` into launchChild's onShutdown so tempfiles\n // are unlinked on signal exit.\n //\n // The per-launch CLAUDE_CONFIG_DIR mirror is ALWAYS cleaned up on\n // shutdown (regardless of codex-mcp), since `ensureClaudeConfigMirror`\n // above always provisioned it. We chain the peer-MCP cleanup\n // (if any) ahead of the mirror removal so files inside the mirror\n // get unlinked first via known paths; the recursive `fs.rm` is\n // belt-and-braces for everything else.\n const baseShutdown = async (): Promise<void> => {\n await stopKeepAwake()\n await removeOwnClaudeConfigMirror()\n }\n let onShutdown: () => Promise<void> = baseShutdown\n // Captured inside the codex-mcp block (when built) so the always-on\n // operating-defaults injection below can ride the peer-awareness snippet\n // along in the SAME --append-system-prompt arg. Undefined when codex-mcp is\n // off (then only the operating-defaults directive is injected).\n let peerAwarenessSnippet: string | undefined\n let peerAwarenessSummary: string | undefined\n // Resolved ONCE per launch and reused by the `.md` generation, the\n // awareness snippet, and the operating-defaults directive, so the three\n // surfaces cannot disagree about which conditionally-emitted natives exist.\n // Four of them are dropped rather than downgraded when their chain misses,\n // and a prompt naming a dropped agent sends the lead at something absent\n // from the Task `subagent_type` enum.\n const nativeAvailability: NativeAgentAvailability = {\n scoutAvailable: scoutModel() != null,\n genericAvailable: genericModel() != null,\n genericFastAvailable: genericFastModel() != null,\n genericCheapAvailable: genericCheapModel() != null,\n }\n const codexMcpEnabled = (args as Record<string, unknown>)[\"codex-mcp\"] !== false\n if (codexMcpEnabled) {\n try {\n const requestedCli =\n ((args as Record<string, unknown>)[\"codex-cli\"] as boolean | undefined) ?? false\n const backend = resolveCodexCliBackend({\n requested: requestedCli,\n codexInfo: requestedCli ? getCodexVersion() : null,\n })\n const geminiModelsAvailable = geminiAvailable()\n if (!geminiModelsAvailable) {\n consola.info(\n \"gemini-3.1-pro-preview not found in your Copilot model catalog; gemini-critic persona will not be registered.\",\n )\n }\n\n // Which scoped MCP servers to register. `peers` + `search` are\n // always on; `workers` / `decide` / `browser` only when their gate\n // passes at launch (avoids registering a server whose tools would\n // all be filtered out of tools/list). Resolve each group's\n // `.claude.json` config key ONCE (bare, or `gh-router-<group>` on\n // collision) and thread it into every channel — the mcpServers\n // entries, the persona .md routing strings, and the awareness\n // snippet — so a user-side `browser`/`search` MCP never silently\n // hijacks or drops one of ours.\n const enabledGroups: Array<McpGroup> = [\"peers\", \"search\", \"orchestrate\"]\n if (workerToolsEnabled()) enabledGroups.push(\"workers\")\n if (standInToolEnabled()) enabledGroups.push(\"decide\")\n if (browserToolsEnabled()) enabledGroups.push(\"browser\")\n if (fleetToolsEnabled()) enabledGroups.push(\"fleet\")\n if (agentToolsEnabled()) enabledGroups.push(\"first-mate\")\n const { keys: groupKeys, skipped: skippedGroups } =\n await resolveGroupKeysFromMirror(enabledGroups)\n\n const runtime = await writePeerMcpRuntimeFiles(serverUrl, {\n codexCli: backend === \"cli\",\n geminiAvailable: geminiModelsAvailable,\n groupKeys,\n workerToolsAvailable: workerToolsEnabled(),\n browseAvailable: browseAgentEnabled(),\n nativeSubagentModel: nativeSubagentModel(),\n reviewerModel: reviewerModel(),\n brainstormModel: brainstormModel(),\n scoutModel: scoutModel(),\n scribeModel: scribeModel(),\n genericModel: genericModel(),\n genericFastModel: genericFastModel(),\n genericCheapModel: genericCheapModel(),\n })\n state.peerMcpNonce = runtime.nonce\n // Reach-back channel for the advisory-review hooks (hook V2): the\n // detached Stop reviewer and the UserPromptSubmit hook run as separate\n // processes with no in-proc state, so they call the proxy over loopback\n // HTTP using this URL + the per-launch nonce (the same nonce the /mcp\n // handler validates). Set on the spawned child's env so the hooks — and\n // the detached reviewer they spawn — inherit it.\n envVars.GH_ROUTER_HOOK_MCP_URL = serverUrl\n envVars.GH_ROUTER_HOOK_NONCE = runtime.nonce\n onShutdown = async (): Promise<void> => {\n await runtime.cleanup()\n await baseShutdown()\n }\n\n // Subagent MCP visibility: inject `gh-router-peers` (and the\n // `codex-cli` stdio entry when enabled) into the mirrored\n // `<CLAUDE_CONFIG_DIR>/.claude.json` so subagents — Agent-tool\n // subagents, forks, agent-teams subprocesses — discover the peer\n // MCP from persistent (user-scope) config rather than the parent's\n // ephemeral --mcp-config CLI flag. Same nonce as runtime files\n // (the proxy validates Authorization against the launch nonce\n // regardless of which channel the request came through).\n //\n // On collision with a user-side entry of the same name, this\n // returns ok:false; we then keep --mcp-config as the fallback so\n // at least the parent session retains the peer tools (subagents\n // remain blind in that case, by design — explicit branch, not\n // silent precedence).\n const injected = await injectPeerMcpIntoMirror(serverUrl, {\n codexCli: backend === \"cli\",\n geminiAvailable: geminiModelsAvailable,\n groupKeys,\n nonce: runtime.nonce,\n })\n\n // Channel selection: prefer the mirror (subagent-visible) when\n // injection succeeded. Only fall back to --mcp-config when\n // injection refused due to a user-side collision. Pushing BOTH\n // would register the same server name twice (mirror + CLI flag),\n // which is ambiguous across Claude Code versions.\n if (!injected.ok) {\n extraArgs.push(\"--mcp-config\", runtime.mcpConfigPath)\n if ((args as Record<string, unknown>)[\"codex-mcp-only\"] === true) {\n extraArgs.push(\"--strict-mcp-config\")\n }\n } else if ((args as Record<string, unknown>)[\"codex-mcp-only\"] === true) {\n // User asked for strict-MCP-only but the mirror inject path\n // can't enforce that (other user-scope MCPs already in the\n // mirror's snapshot are visible). Warn so the flag's mismatch\n // with the new behavior is obvious.\n consola.warn(\n \"--codex-mcp-only has no effect when peer MCP is wired via the \"\n + \"mirrored .claude.json (the user's existing user-scope MCPs in \"\n + \"the snapshot are still visible). Pass --no-codex-mcp to skip \"\n + \"peer-MCP wiring entirely.\",\n )\n }\n\n const personaNames = runtime.personas.map((p) => p.agentName).join(\", \")\n const subagentVisibility = injected.ok\n ? `subagent-visible (mirrored mcpServers: [${injected.serversAdded.join(\", \")}])`\n : `subagent-INVISIBLE (collision on user-side mcpServers: [${injected.conflictingServers.join(\", \")}]; parent-only via --mcp-config)`\n const skippedNote =\n skippedGroups.length > 0\n ? ` WARNING: groups [${skippedGroups.join(\", \")}] skipped — both the bare and \\`gh-router-<group>\\` keys collide with your own mcpServers; those tools are unavailable this session (rename the user-side server to re-enable).`\n : \"\"\n process.stderr.write(\n `Peer MCP wired (backend=${backend}, personas=[${personaNames}], `\n + `subagent .md files=${runtime.agentMdPaths.length}, ${subagentVisibility}).${skippedNote}\\n`,\n )\n\n // ── Floor-raising agent surface (docs/floor-raising-agent-surface.md) ──\n // NOTE (intentional coupling, gemini integration review): this block is\n // nested in `if (codexMcpEnabled)` — the github-router \"enhancement\n // layer\" master switch — alongside the MCP server injection above. The\n // skills REQUIRE that injection (they call mcp__workers__* /\n // mcp__orchestrate__* / mcp__search__* tools), so coupling them here is\n // correct. The Stop-gate is MCP-independent and could live outside;\n // keeping it here means `--no-codex-mcp` opts out of the whole layer\n // (consistent: that flag also hides --trust-gate, so the gate was never\n // enabled in such a session anyway). Decoupling the Stop-gate + moving\n // the per-prompt reset out is a clean follow-up.\n // When the worker/orchestrate backend is available, materialize the\n // injected skills into the mirror and register the front-end\n // UserPromptSubmit hook (per-prompt Stop-gate budget reset + advisory\n // goal steer). gh-first-mate is additionally gated on the first-mate MCP\n // server so the slash command does not advertise an unavailable tool.\n // The hooks scope themselves to the top-level session via the payload's\n // agent_type, so subagents/teammates are untouched.\n const sessionCwd = process.cwd()\n if (workerToolsEnabled()) {\n const skillsToWrite = INJECTED_SKILLS.filter(\n (s) => !isFirstMateSkillName(s.name) || agentToolsEnabled(),\n )\n let skillsWritten = 0\n for (const s of skillsToWrite) {\n const r = await writeInjectedSkill(s.name, s.md).catch(() => ({ written: false }))\n if (r.written) skillsWritten++\n }\n try {\n const settingsPath = nodePath.join(PATHS.CLAUDE_CONFIG_DIR, \"settings.json\")\n const cmd = buildPromptSubmitHookCommand(process.execPath, process.argv[1])\n // Raise the host hook timeout to 45s (default 30s): the V2 path may\n // make one gpt-5.6-sol scope call + a parallel code search. The hook's\n // own enrichment is bounded well under this (≈22s) and fails open,\n // so 45s is headroom, not a tax the user routinely pays.\n await injectStopHookIntoSettingsFile(settingsPath, cmd, \"UserPromptSubmit\", 45)\n } catch (err) {\n consola.warn(`Could not register the UserPromptSubmit hook: ${String(err)}`)\n }\n // Auto-approve github-router's own injected MCP servers so their tools\n // run WITHOUT a permission prompt in every mode — including plan mode,\n // where the research tools (search / peers / workers-explore / stand_in /\n // orchestrate) are exactly what the model reaches for. MCP tools aren't\n // subject to plan mode's file-edit/shell-write restriction, so a bare\n // `mcp__<server>` allow rule (on the RESOLVED keys) auto-runs them there\n // too. deny/ask and the user's own allow entries are preserved.\n // Auto-approve github-router's own injected MCP servers PLUS the native\n // read-only research tools (WebSearch/WebFetch) so they run WITHOUT a\n // permission prompt in every mode — including plan mode, where that\n // research surface is exactly what the model reaches for. MCP tools and\n // read-only natives aren't subject to plan mode's file-edit/shell-write\n // restriction, so an allow rule auto-runs them there too. deny/ask and\n // the user's own allow entries are preserved.\n try {\n const settingsPath = nodePath.join(PATHS.CLAUDE_CONFIG_DIR, \"settings.json\")\n // Authoritative list of the servers we actually injected — on the\n // mirror path that's `injected.serversAdded` (includes the `codex-cli`\n // stdio server, not just the HTTP groups); on the collision fallback,\n // reconstruct it from the resolved group keys + codex-cli.\n const injectedServerKeys = injected.ok\n ? injected.serversAdded\n : [\n ...Object.values(groupKeys).filter((k): k is string => Boolean(k)),\n ...(backend === \"cli\" ? [\"codex-cli\"] : []),\n ]\n const res = await injectAllowRules(settingsPath, planModeAllowRules(injectedServerKeys))\n if (res.added.length) {\n consola.debug(`Auto-approved for plan mode: ${res.added.join(\", \")}`)\n }\n } catch (err) {\n consola.warn(`Could not auto-approve injected tools: ${String(err)}`)\n }\n // Workers non-blocking guard: a PreToolUse hook scoped (matcher) to the\n // active worker tools that DENIES a raw `mcp__<workersKey>__<mode>` call\n // from the main agent (redirecting it to the `worker-<mode>` background\n // dispatcher) and ALLOWS it only from that dispatcher subagent. The\n // resolved workersKey + active modes are baked into the command\n // (dedup-distinct) and the matcher. Local + instant, so a short 10s\n // timeout is ample.\n //\n // GATED ON `injected.ok`: the guard only makes sense when subagents can\n // SEE the workers MCP (mirror injection succeeded). On the collision\n // fallback (parent-only --mcp-config, subagents blind), a `worker-*`\n // dispatcher could not reach the worker tool — registering the guard\n // then would deny the main agent AND leave it no working dispatcher, a\n // fully-broken state. So when injection failed we skip the guard and\n // leave raw (blocking) workers usable on the main thread.\n //\n // HYBRID opt-out (`GH_ROUTER_DISABLE_WORKER_GUARD=1`): keeps the\n // non-blocking guarantee ON by default, but lets an operator restore the\n // raw `mcp__workers__*` escape hatch on demand (e.g. for guaranteed\n // structured-arg fidelity, or when a blocking call is acceptable) — so\n // the worker-* agents COMPLEMENT rather than hard-replace the raw tools.\n if (!injected.ok) {\n consola.warn(\n \"Workers non-blocking guard NOT registered: subagent MCP injection \"\n + \"fell back to parent-only (--mcp-config), so worker-* dispatchers \"\n + \"cannot reach the workers server. Raw (blocking) worker tools remain \"\n + \"usable on the main thread this session.\",\n )\n } else if (process.env.GH_ROUTER_DISABLE_WORKER_GUARD === \"1\") {\n consola.info(\n \"Workers non-blocking guard disabled via GH_ROUTER_DISABLE_WORKER_GUARD=1 \"\n + \"— raw mcp__workers__* is callable on the main thread (blocking); the \"\n + \"worker-* background agents remain the steered, non-blocking default.\",\n )\n } else {\n try {\n const settingsPath = nodePath.join(PATHS.CLAUDE_CONFIG_DIR, \"settings.json\")\n const workersKey = workersKeyOf(groupKeys)\n const modes = activeDispatchModes({ browse: browseAgentEnabled() })\n const cmd = buildWorkerGuardHookCommand(\n process.execPath,\n process.argv[1],\n workersKey,\n modes,\n )\n const matcher = guardToolMatcher(workersKey, modes)\n await injectStopHookIntoSettingsFile(settingsPath, cmd, \"PreToolUse\", 10, matcher)\n } catch (err) {\n consola.warn(`Could not register the workers PreToolUse guard hook: ${String(err)}`)\n }\n }\n if (skillsWritten > 0) {\n const skillNames = skillsToWrite.map((s) => `/${s.name}`).join(\", \")\n process.stderr.write(\n `Injected skills (${skillsWritten}/${skillsToWrite.length}): ${skillNames}.\\n`,\n )\n }\n }\n\n // In operator/`--agents` mode, keep local worker/orchestrate MCP tools\n // subagent-only for the MAIN operator. File-authoring tools and Bash stay\n // available; steering to cloud-agent delegation lives in the banner and\n // /gh-first-mate skill. Fail-closed only for this reduced PreToolUse guard.\n if (agentToolsEnabled()) {\n let shapingInstalled = false\n try {\n const settingsPath = nodePath.join(PATHS.CLAUDE_CONFIG_DIR, \"settings.json\")\n const guardCmd = buildFirstMateGuardHookCommand(process.execPath, process.argv[1])\n await injectStopHookIntoSettingsFile(\n settingsPath,\n guardCmd,\n \"PreToolUse\",\n undefined,\n FIRST_MATE_GUARD_MATCHER,\n )\n shapingInstalled = true\n } catch (err) {\n consola.error(\n `Could not register the operator worker/orchestrate guard hook: ${String(err)}`,\n )\n }\n assertShapingInstalled(true, shapingInstalled)\n\n // Surface the scaffold allowlist AT STARTUP rather than only as a\n // refusal deep inside a tool call. `scaffold_repo` creates branches,\n // commits files and opens PRs, so it is deny-by-default — but a user\n // upgrading into that gate would otherwise discover it as an opaque\n // mid-mission failure naming an env var they have never seen. One\n // line at launch turns a surprise into a setting.\n if (!process.env.GH_ROUTER_FM_SCAFFOLD_REPOS?.trim()) {\n consola.info(\n \"first-mate: scaffold_repo is disabled (it writes to real repositories). \"\n + \"Set GH_ROUTER_FM_SCAFFOLD_REPOS to a comma-separated list of owner/name \"\n + \"entries, or owner/* for a whole org, to enable it.\",\n )\n }\n }\n\n // ai-or-die session binding. When launched inside an ai-or-die Terminal\n // tab, ai-or-die sets AIORDIE_CLAUDE_BIND to a per-tab sidecar path.\n // Register a SessionStart/SessionEnd hook that records the active claude\n // session id + transcript path to that sidecar on every startup / resume\n // / clear / compact, so ai-or-die can bind the tab's sticky-note\n // summariser to the exact transcript (surviving /resume, /clear and\n // exit→relaunch). The path is baked into the hook command (not env), and\n // AIORDIE_CLAUDE_BIND is stripped from the child's env (sanitizeParentEnv),\n // so a nested `github-router claude` can't hijack the parent tab; the hook\n // itself also skips subagent/teammate payloads (agent_id/agent_type).\n const aiordieSidecar = (process.env.AIORDIE_CLAUDE_BIND ?? \"\").trim()\n // The path is baked into the hook command string (double-quoted, like\n // buildStopHookCommand). It is app-controlled (ai-or-die's data dir + a\n // uuid), so it never contains a double-quote; refuse defensively if it\n // somehow does rather than emit a malformed command (ai-or-die then falls\n // back to its inference path).\n if (aiordieSidecar.length > 0 && !aiordieSidecar.includes('\"')) {\n try {\n const settingsPath = nodePath.join(PATHS.CLAUDE_CONFIG_DIR, \"settings.json\")\n const command = buildSessionBindHookCommand(process.execPath, process.argv[1], aiordieSidecar)\n await injectStopHookIntoSettingsFile(settingsPath, command, \"SessionStart\")\n await injectStopHookIntoSettingsFile(settingsPath, command, \"SessionEnd\")\n } catch (err) {\n consola.warn(`Could not register the ai-or-die session-bind hook: ${String(err)}`)\n }\n }\n\n // Default-on plan review in the artifact panel: when inside an ai-or-die\n // tab (AIORDIE_SESSION_ID set), the artifact_* tools reach a real review\n // panel, so materialize the gh-artifact-review skill and steer the agent\n // to open plans there by default. Skill is written here (not in\n // INJECTED_SKILLS) so it only appears inside a tab; the CLAUDE.md\n // directive reaches descendants. Both best-effort.\n if (artifactToolsEnabled()) {\n await writeInjectedSkill(ARTIFACT_REVIEW_SKILL.name, ARTIFACT_REVIEW_SKILL.md)\n .catch(() => ({ written: false }))\n try {\n await prependArtifactPanelDirectiveToMirroredClaudeMd(groupKeys.peers)\n } catch (err) {\n consola.warn(`Artifact-panel directive prepend failed: ${String(err)}`)\n }\n // Hands-off auto-open: write the artifact creds to a mode-600 mirror\n // file (AIORDIE_TOKEN is stripped from the child env, and argv leaks to\n // ps), then register a PostToolUse(ExitPlanMode) hook so the finalized\n // plan opens in the panel without the model calling artifact_open.\n try {\n await writeArtifactCredsToMirror(shouldUseInsecureTls(process.env.AIORDIE_BASE_URL ?? \"\"))\n const settingsPath = nodePath.join(PATHS.CLAUDE_CONFIG_DIR, \"settings.json\")\n const cmd = buildArtifactOpenHookCommand(process.execPath, process.argv[1])\n await injectStopHookIntoSettingsFile(settingsPath, cmd, \"PostToolUse\", undefined, \"ExitPlanMode\")\n } catch (err) {\n consola.warn(`Could not register the artifact auto-open hook: ${String(err)}`)\n }\n }\n\n // Advisory plan review: a SECOND PostToolUse(ExitPlanMode) hook, separate\n // from artifact auto-open. It is default-on when the hook MCP runtime is\n // wired, top-level-only at runtime, and never blocks ExitPlanMode; material\n // findings are written to the shared findings store for the next prompt.\n if (hookMcpRuntimeFromEnv(envVars) && planReviewEnabled()) {\n try {\n const settingsPath = nodePath.join(PATHS.CLAUDE_CONFIG_DIR, \"settings.json\")\n const command = buildPlanReviewHookCommand(process.execPath, process.argv[1])\n await injectStopHookIntoSettingsFile(settingsPath, command, \"PostToolUse\", undefined, \"ExitPlanMode\")\n } catch (err) {\n consola.warn(`Could not register the advisory plan-review hook: ${String(err)}`)\n }\n }\n\n // Structural-gate Stop hook — CONSENT-GATED per repo (default OFF; it\n // never runs an UNTRUSTED repo's scripts). `--trust-gate` records consent\n // for this repo; GH_ROUTER_ENABLE_STOP_GATE force-enables. The hook also\n // re-checks trust at runtime and scopes to the top-level session.\n if ((args as Record<string, unknown>)[\"trust-gate\"] === true) {\n try {\n const root = await trustRepo(sessionCwd)\n process.stderr.write(\n `Structural gate trusted for this repo (${root}); it will run on launch here from now on.\\n`,\n )\n } catch (err) {\n consola.warn(`Could not record gate trust: ${String(err)}`)\n }\n }\n const gateDisabled = stopGateDisabled(args as Record<string, unknown>)\n const forceEnabled = parseBoolEnv(process.env.GH_ROUTER_ENABLE_STOP_GATE) === true\n const includeTests = parseBoolEnv(process.env.GH_ROUTER_STOP_GATE_RUN_TESTS) === true\n const gateRoot = await repoRoot(sessionCwd).catch(() => sessionCwd)\n // Resolve the gate source: the deterministic parser first (the bun/TS\n // sealed fast-path OR a parsed multi-language check set), then the cached\n // model-discovered record. A cache MISS marks `discovering` (background\n // discovery arms the gate on the NEXT launch — no launch-time model\n // latency). Discovery is OPT-IN (GH_ROUTER_ENABLE_GATE_DISCOVERY=1): it\n // runs a read-only worker that reads repo files, so it stays off by\n // default; the parser covers the common ecosystems with no model at all.\n let descriptor: GateDescriptor | null = null\n let discovering = false\n if (!gateDisabled) {\n descriptor = await parseGateDescriptor(gateRoot, { includeTests }).catch(() => null)\n if (!descriptor) {\n const cached = await readDiscoveredGate(gateRoot).catch(() => null)\n if (cached) {\n descriptor = {\n kind: \"discovered\",\n checks: cached.checks,\n ecosystem: cached.ecosystem,\n workdir: gateRoot,\n evidence: cached.evidence,\n }\n // Force-enable wants a gate NOW (not a next-launch background discovery);\n // when nothing parsed/cached, it falls back to the sealed default below.\n } else if (!forceEnabled && workerToolsEnabled() && parseBoolEnv(process.env.GH_ROUTER_ENABLE_GATE_DISCOVERY) === true) {\n discovering = true\n }\n }\n }\n // Enable when forced-on, already trusted, or auto-trust-on-detection (a\n // resolved descriptor OR a kicked discovery is consent-by-launching). The\n // gate only ever runs the launched repo's own checks and is\n // baseline-isolated; opt out entirely with GH_ROUTER_DISABLE_STOP_GATE=1.\n let gateEnabled = gateDisabled ? false : await stopGateEnabledForRepo(sessionCwd).catch(() => false)\n let autoTrusted = false\n if (!gateEnabled && !gateDisabled && (descriptor || discovering)) {\n try {\n await trustRepo(sessionCwd)\n gateEnabled = true\n autoTrusted = true\n } catch (err) {\n consola.warn(`Could not auto-trust this repo for the structural gate: ${String(err)}`)\n }\n }\n // Force-enable (GH_ROUTER_ENABLE_STOP_GATE / --trust-gate) with no parsed\n // or discovered descriptor falls back to the sealed default gate, matching\n // the legacy behavior of explicit enablement (takes precedence over a\n // background discovery, which `forceEnabled` already suppressed above).\n if (gateEnabled && !descriptor && !discovering) {\n descriptor = { kind: \"sealed\", gateId: stopGateId(), workdir: gateRoot }\n }\n if (gateEnabled && descriptor) {\n const armed = descriptor\n try {\n const settingsPath = nodePath.join(PATHS.CLAUDE_CONFIG_DIR, \"settings.json\")\n const command = buildStopHookCommand(process.execPath, process.argv[1])\n // Arm the matching runtime resolver (env reaches the child via envVars).\n if (armed.kind === \"sealed\") envVars.GH_ROUTER_STOP_GATE_ID = armed.gateId\n else if (armed.kind === \"parsed\") envVars.GH_ROUTER_STOP_GATE_PARSED = \"1\"\n else envVars.GH_ROUTER_STOP_GATE_DISCOVERED = \"1\"\n if (includeTests) envVars.GH_ROUTER_STOP_GATE_RUN_TESTS = \"1\"\n await injectStopHookIntoSettingsFile(settingsPath, command)\n // Capture the baseline at LAUNCH (pre-mutation) for the dynamic paths,\n // so an agent-introduced failure is a regression — not silently adopted\n // as the baseline at first stop. Sealed keeps its legacy first-stop\n // baseline (unchanged). Fire-and-forget: never blocks the launch.\n if (armed.kind !== \"sealed\") {\n const checks = checksForDescriptor(armed)\n const descriptorKey = descriptorHash(armed)\n // Per-launch token isolates concurrent sessions' baselines; the child\n // reads it back to recompute the same launch-baseline key.\n const token = randomBytes(8).toString(\"hex\")\n envVars.GH_ROUTER_STOP_GATE_BASELINE_TOKEN = token\n void (async () => {\n await captureLaunchBaseline({\n checks,\n workdir: armed.workdir,\n exec: liveExec,\n descriptorKey,\n token,\n baseline: fileBaselineStore(nodePath.join(tmpdir(), \"gh-router-stopgate-baseline\")),\n })\n })().catch(() => {})\n }\n const tail =\n \"A regression or a gate-weakening diff blocks stopping until fixed (per-prompt, max 2). \"\n + \"Opt out with GH_ROUTER_DISABLE_STOP_GATE=1.\\n\"\n if (armed.kind === \"sealed\") {\n process.stderr.write(\n (autoTrusted\n ? `Structural-gate Stop hook enabled by default for this repo (gate=${armed.gateId}; runs typecheck/test/lint at stop). `\n : `Structural-gate Stop hook enabled (gate=${armed.gateId}). `) + tail,\n )\n } else {\n const ids = checksForDescriptor(armed).map((c) => c.id).join(\", \")\n const testNote = includeTests\n ? \"Full test suite ON. \"\n : \"Full test suite off by default (enable with GH_ROUTER_STOP_GATE_RUN_TESTS=1). \"\n process.stderr.write(\n `Structural-gate Stop hook armed for this ${armed.ecosystem} repo (checks: ${ids}; `\n + `from ${armed.evidence.join(\", \")}). ${testNote}${tail}`,\n )\n }\n } catch (err) {\n // Never fail the launch over the hook; surface and continue.\n consola.warn(`Could not register the structural-gate Stop hook: ${String(err)}`)\n }\n } else if (gateEnabled && discovering) {\n // Kick a background discovery; the gate arms on the next launch.\n process.stderr.write(\n \"Structural Stop-gate: no checks parsed for this repo — discovering check commands in the \"\n + \"background (arms on the next launch). Disable with GH_ROUTER_ENABLE_GATE_DISCOVERY=0.\\n\",\n )\n void (async () => {\n try {\n const disc = await discoverGateCommands(gateRoot, {\n includeTests,\n signal: AbortSignal.timeout(120_000),\n })\n if (disc) {\n const [fingerprint, sh] = await Promise.all([\n repoFingerprint(gateRoot).catch(() => \"\"),\n sourcesHash(gateRoot).catch(() => \"\"),\n ])\n await writeDiscoveredGate({\n root: gateRoot,\n fingerprint,\n sourcesHash: sh,\n discoveredAt: new Date().toISOString(),\n model: \"gemini-3.5-flash\",\n ecosystem: disc.ecosystem,\n checks: disc.checks,\n confidence: disc.confidence,\n evidence: disc.evidence,\n })\n }\n } catch {\n /* best-effort: discovery never blocks or fails the launch. */\n }\n })().catch(() => {})\n } else if (!gateDisabled && !descriptor && !discovering) {\n // The original \"silent skip\" fix: say WHY the gate is off.\n process.stderr.write(\n \"Structural Stop-gate not enabled: no checks found in this repo \"\n + \"(looked at package.json scripts / CI workflows / Makefile / Cargo.toml / go.mod / pyproject). \"\n + \"Force a sealed gate with GH_ROUTER_ENABLE_STOP_GATE=1\"\n + (workerToolsEnabled() ? \", or let a model discover them with GH_ROUTER_ENABLE_GATE_DISCOVERY=1\" : \"\")\n + \".\\n\",\n )\n }\n\n // Awareness snippet: append a short, descriptive system-prompt\n // section telling Claude *what* peer-review tools exist — Claude\n // decides *whether* to call them based on each tool's own\n // `description` (the routing layer). This is the awareness layer.\n //\n // Delivery is dual-surface, both unconditional:\n // 1. --append-system-prompt — system-turn position, strongest\n // attention weight; reaches the main agent only.\n // 2. <CLAUDE_CONFIG_DIR>/CLAUDE.md (appended) — user-turn\n // <claudeMd> wrapper; reaches Agent-tool subagents and\n // agent-teams teammates that inherit CLAUDE_CONFIG_DIR but\n // not --append-system-prompt.\n //\n // Capability gates: worker_* and stand_in mentions are gated on\n // the same predicates the /mcp tools/list uses (workerToolsEnabled\n // / standInToolEnabled), so the snippet never names a tool that\n // is missing from the live catalog.\n //\n // Previously gated by GH_ROUTER_PEER_AWARENESS (default-on).\n // The flag was dropped (per plan: it served no purpose now that\n // the snippet is default-on across both surfaces). Existing\n // `GH_ROUTER_PEER_AWARENESS=0` shell exports are silent no-ops.\n const peerSnippet = buildPeerAwarenessSnippet({\n codexCli: backend === \"cli\",\n geminiAvailable: geminiModelsAvailable,\n workerToolsAvailable: workerToolsEnabled(),\n standInAvailable: standInToolEnabled(),\n browseAvailable: browserToolsEnabled(),\n compoundBrowseAvailable: browserCompoundToolsEnabled(),\n powerBrowseAvailable: state.powerBrowseEnabled,\n fleetAvailable: fleetToolsEnabled(),\n agentToolsAvailable: agentToolsEnabled(),\n ...nativeAvailability,\n groupKeys,\n })\n // Capture the peer-awareness snippet; the always-on operating-defaults\n // injection after this block composes the single --append-system-prompt\n // (operating-defaults first, then this summary).\n peerAwarenessSnippet = peerSnippet\n peerAwarenessSummary = buildPeerAwarenessSummary({\n workerToolsAvailable: workerToolsEnabled(),\n standInAvailable: standInToolEnabled(),\n browseAvailable: browserToolsEnabled(),\n fleetAvailable: fleetToolsEnabled(),\n agentToolsAvailable: agentToolsEnabled(),\n ...nativeAvailability,\n groupKeys,\n })\n // Ordering invariant: this MUST run AFTER ensureClaudeConfigMirror()\n // has resolved (above in this same handler), so the snapshot of\n // the user's ~/.claude/CLAUDE.md is already in place before we\n // append our marker block. The helper's own mirror-only safety\n // guard rejects writes outside CLAUDE_CONFIG_DIR as defence in\n // depth. Failures warn-and-continue: this CLAUDE.md copy is now the\n // primary full-inventory surface (the system prompt carries only a\n // summary of it), so on the rare write failure the agent falls back to\n // the tool descriptions in tools/list, not a launch-blocker.\n try {\n await appendPeerAwarenessToMirroredClaudeMd(peerSnippet)\n } catch (err) {\n consola.warn(\n `Peer-awareness CLAUDE.md append failed (agent keeps tool descriptions via tools/list, loses the inventory overview): ${\n err instanceof Error ? err.message : String(err)\n }`,\n )\n }\n // Style directive: prepend a short writing/communication style\n // directive at the TOP of the mirrored CLAUDE.md so every\n // spawned agent reads it first. Independent marker fence from\n // the peer-awareness block, so the two coexist (style at top,\n // user content in the middle, peer-awareness at the bottom).\n // Best-effort like the append above.\n try {\n await prependStyleDirectiveToMirroredClaudeMd()\n } catch (err) {\n consola.warn(\n `Style-directive CLAUDE.md prepend failed: ${\n err instanceof Error ? err.message : String(err)\n }`,\n )\n }\n } catch (err) {\n consola.warn(\n `Peer MCP wiring failed (claude will launch without it): ${\n err instanceof Error ? err.message : String(err)\n }`,\n )\n }\n }\n\n // Operating-defaults directive (orchestrator posture + hybrid excellence\n // lens): injected by DEFAULT, NOT gated on codex-mcp, so the posture always\n // reaches the agent even under --no-codex-mcp. Two surfaces:\n // 1. --append-system-prompt (main agent, highest salience): the single\n // such arg for the session carries a condensed digest, with a compact\n // summary of the peer-awareness inventory riding along after it.\n // 2. the mirrored CLAUDE.md TOP (main agent and descendant agents): the\n // full directive is prepended after the style directive when that ran.\n // ensureClaudeConfigMirror() ran unconditionally above, so the mirror exists\n // regardless of codex-mcp; the prepend is best-effort (warn-and-continue).\n // Peer-awareness inventory lives ONCE, in the mirrored CLAUDE.md (appended\n // above), which the main agent and descendants both read. The system prompt\n // carries only a compact summary, not the full ~1.1k-token snippet, so\n // the inventory is not duplicated in the context window on every turn. The\n // full operating-defaults directive lives in CLAUDE.md, while a condensed\n // digest keeps those defaults at top salience in --append-system-prompt.\n // If the CLAUDE.md append happened to fail, the agent still has every tool's\n // own description in tools/list (the real routing signal); it just loses this\n // higher-level overview.\n extraArgs.push(\n \"--append-system-prompt\",\n peerAwarenessSnippet && peerAwarenessSummary\n ? `${OPERATING_DEFAULTS_DIGEST}\\n\\n${peerAwarenessSummary}`\n : OPERATING_DEFAULTS_DIGEST,\n )\n try {\n await prependOperatingDefaultsToMirroredClaudeMd(\n buildOperatingDefaultsDirective(nativeAvailability),\n )\n } catch (err) {\n consola.warn(\n `Operating-defaults CLAUDE.md prepend failed: ${\n err instanceof Error ? err.message : String(err)\n }`,\n )\n }\n\n // Deterministic backstop for the injected no-attribution style directive:\n // write `attribution: {commit:\"\", pr:\"\"}` into the mirrored settings.json so\n // Claude Code's harness suppresses the commit/PR bylines at the source, not\n // just via the advisory CLAUDE.md prose. Presence-guarded (an existing\n // user `attribution`/`includeCoAuthoredBy` wins) and best-effort. Opt out\n // with GH_ROUTER_DISABLE_NO_ATTRIBUTION=1.\n if (process.env.GH_ROUTER_DISABLE_NO_ATTRIBUTION !== \"1\") {\n try {\n const settingsPath = nodePath.join(PATHS.CLAUDE_CONFIG_DIR, \"settings.json\")\n await injectAttributionSuppressionIntoSettingsFile(settingsPath)\n } catch (err) {\n consola.warn(\n `No-attribution settings backstop skipped: ${\n err instanceof Error ? err.message : String(err)\n }`,\n )\n }\n }\n\n launchChild(\n { kind: \"claude-code\", envVars, extraArgs, model: chosenSlug },\n server,\n { onShutdown },\n )\n },\n})\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAAA,MAAa,wBAAwB;CACnC,MAAM;CACN,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoEN;;;ACyCA,SAAS,qBAAqB,MAAuB;CACnD,OACE,SAAS,mBACT,SAAS,4BACT,SAAS,2BACT,SAAS;AAEb;AAEA,MAAa,aAAa;CACxB,GAAG;CACH,OAAO;EACL,OAAO;EACP,MAAM;EACN,aACE;CACJ;CACA,aAAa;EACX,MAAM;EACN,SAAS;EACT,aACE;CACJ;CACA,aAAa;EACX,MAAM;EACN,SAAS;EACT,aACE;CACJ;CACA,kBAAkB;EAChB,MAAM;EACN,SAAS;EACT,aACE;CACJ;CACA,SAAS;EACP,MAAM;EACN,SAAS;EACT,aACE;CACJ;CACA,cAAc;EACZ,MAAM;EACN,SAAS;EACT,aACE;CACJ;CACA,gBAAgB;EACd,MAAM;EACN,SAAS;EACT,aACE;CACJ;CACA,eAAe;EACb,MAAM;EACN,SAAS;EACT,aACE;CACJ;CACA,gBAAgB;EACd,MAAM;EACN,SAAS;EACT,aACE;CACJ;AACF;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,4BACd,SACA,SACU;CACV,MAAM,wBAAQ,IAAI,IAAY;CAC9B,MAAM,8BAAc,IAAI,IAAY;CACpC,KAAK,MAAM,CAAC,MAAM,QAAQ,OAAO,QAAQ,OAAO,GAAG;EACjD,MAAM,WAAW,WAAW,MAAM,IAAI,QAAQ,KAAA;EAC9C,MAAM,UACJ,aAAa,KAAA,IACT,CAAC,IACD,MAAM,QAAQ,QAAQ,IACpB,WACA,CAAC,QAAQ;EACjB,KAAK,MAAM,KAAK,CAAC,MAAM,GAAG,OAAO,GAAG;GAClC,MAAM,IAAI,CAAC;GACX,IAAI,IAAI,SAAS,UAAU,YAAY,IAAI,CAAC;EAC9C;CACF;CAEA,MAAM,YAAsB,CAAC;CAC7B,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;EACvC,MAAM,MAAM,QAAQ;EAIpB,IAAI,QAAQ,MAAM;GAChB,UAAU,KAAK,GAAG,QAAQ,MAAM,IAAI,CAAC,CAAC;GACtC;EACF;EAEA,IAAI,QAAQ,OAAO,CAAC,IAAI,WAAW,GAAG,GAAG;GACvC,UAAU,KAAK,GAAG;GAClB;EACF;EACA,MAAM,aAAa,IAAI,WAAW,IAAI;EACtC,MAAM,cAAc,IAAI,MAAM,aAAa,IAAI,CAAC;EAChD,MAAM,KAAK,YAAY,QAAQ,GAAG;EAClC,MAAM,UAAU,MAAM,IAAI,YAAY,MAAM,GAAG,EAAE,IAAI;EACrD,MAAM,iBAAiB,MAAM;EAC7B,MAAM,UAAU,cAAc,QAAQ,WAAW,KAAK;EACtD,MAAM,WAAW,UAAU,QAAQ,MAAM,CAAC,IAAI;EAG9C,IAAI,EADY,MAAM,IAAI,OAAO,KAAM,WAAW,MAAM,IAAI,QAAQ,IACtD;GAEZ,UAAU,KAAK,GAAG;GAClB;EACF;EAYA,IANE,CAAC,WACE,CAAC,kBACD,YAAY,IAAI,OAAO,KACvB,IAAI,IAAI,QAAQ,UAChB,QAAQ,IAAI,OAAO,QACnB,CAAC,QAAQ,IAAI,EAAE,CAAC,WAAW,GAAG,GAChB;CACrB;CACA,OAAO;AACT;AAEA,MAAa,SAAS,cAAc;CAClC,MAAM;EACJ,MAAM;EACN,aAAa;CACf;CACA,MAAM;CACN,MAAM,IAAI,EAAE,MAAM,WAAW;EAC3B,IAAI,CAAC,QAAQ,OAAO,OAAO;GACzB,QAAQ,MAAM,8DAA8D;GAC5E,QAAQ,KAAK,CAAC;EAChB;EAEA,MAAM,SAAS,gBAAgB,IAA0C;EAkBzE,IAAI,KAAK,SAAS;GAEhB,OAAO,gBAAgB;GACvB,QAAQ,KACN,wEACF;EACF,OAAO,IAAI,CAAC,KAAK,mBAEf,OAAO,gBAAgB;EAYzB,IAAI,KAAK,oBAAoB,OAC3B,IAAI;GACF,MAAM,eAAe,MAAM,mBAAmB,EAC5C,SAAS,MACX,CAAC;GACD,IAAI,aAAa,WAAW,aAAa,eAAe,aAGtD,QAAQ,MACN,sDACF;QACK,IAAI,aAAa,WAAW,aAAa,eAAe,UAE7D,QAAQ,MACN,yFACF;QACK,IACL,aAAa,eACV,aAAa,oBACb,aAAa,eAEhB,IAAI,KAAK,mBAAmB,OAC1B,IAAI;IACF,MAAM,aAAa,aAAa,aAAa;GAC/C,SAAS,KAAK;IACZ,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;IAC3D,QAAQ,KACN,mCAAmC,aAAa,iBAAiB,MAAM,aAAa,cAAc,WAAW,IAAI,wIACnH;GACF;QAEA,QAAQ,KACN,gBAAgB,aAAa,iBAAiB,kBAAkB,aAAa,cAAc,yGAC7F;EAGN,SAAS,KAAK;GAEZ,QAAQ,MAAM,gCAAgC,GAAG;EACnD;EAGF,IAAI;EACJ,IAAI;EACJ,IAAI;GACF,MAAM,SAAS,MAAM,cAAc;IACjC,GAAG;IACH,MAAM,OAAO;IACb,QAAQ;GACV,CAAC;GACD,SAAS,OAAO;GAChB,YAAY,OAAO;EACrB,SAAS,OAAO;GACd,QAAQ,MAAM,2BAA2B,iBAAiB,QAAQ,MAAM,UAAU,KAAK;GACvF,QAAQ,KAAK,CAAC;EAChB;EAKA,cAAmB,EAAE,YAAY,KAAK,mBAAmB,MAAM,CAAC;EAchE,IAAI;GACF,MAAM,yBAAyB;EACjC,SAAS,KAAK;GACZ,QAAQ,MACN,iDACE,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAChD,yDACH;GACA,QAAQ,KAAK,CAAC;EAChB;EAEA,kBAAkB;EAyBlB,MAAM,eAAe,CAAC,KAAK;EAC3B,MAAM,sBAAsB,KAAK,OAAO,MAAM,cAAc,CAAC,GAAG;EAIhE,IAAI,aAHkB,sBAClB,kBAAkB,mBAAmB,IACpC,KAAK,SAAS,kBAAkB;EAErC,IAAI,eAAe,aAAa,UAAU;EAE1C,IAAI,gBAAgB,MAAM,QAAQ;GAChC,MAAM,WAAW,SACf,MAAM,QAAQ,KAAK,MAAM,MAAM,EAAE,OAAO,aAAa,IAAI,CAAC,KAAK;GACjE,IAAI,CAAC,QAAQ,UAAU,GAChB;SAAA,MAAM,YAAY,gCACrB,IAAI,QAAQ,QAAQ,GAAG;KAQrB,MAAM,eAAe,kBACnB,SAAS,QAAQ,iBAAiB,EAAE,CACtC;KACA,QAAQ,KACN,kBAAkB,WAAW,qDAAqD,aAAa,GACjG;KACA,aAAa;KACb,eAAe,aAAa,YAAY;KACxC;IACF;;EAGN;EAEA,IAAI,iBAAiB,YACnB,QAAQ,KAAK,UAAU,WAAW,iBAAiB,aAAa,EAAE;EAGpE,IAAI,CADe,MAAM,QAAQ,KAAK,MAAM,MAAM,EAAE,OAAO,YAAY,GACtD;GACf,MAAM,YAAY,sBAAsB,cAAc;GACtD,QAAQ,KACN,UAAU,aAAa,wCAAwC,UAAU,KAAK,IAAI,GACpF;EACF;EAIA,MAAM,SACJ,eAAe,eACX,aACA,GAAG,WAAW,KAAK;EAEzB,QAAQ,OAAO,MAAM,mBAAmB,UAAU,2BAA2B,OAAO,OAAO;EAE3F,MAAM,UAAU,qBAAqB,WAAW,UAAU;EAO1D,MAAM,YAAY,4BAA4B,SAAS,UAAU;EASjE,IAAI,gBAAgB,GAAG;GACrB,kBAAuB,CAAC,CAAC,OAAO,QAC9B,QAAQ,MAAM,iCAAiC,GAAG,CACpD;GACA,MAAM,eAAe,uBAAuB,sBAAsB,CAAC;GACnE,IAAI,cACF,IAAI;IACF,MAAM,0CAA0C,YAAY;GAC9D,SAAS,KAAK;IACZ,QAAQ,KACN,qCACE,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,GAEnD;GACF;EAEJ;EAKA,yBAA8B;EAM9B,uBAA4B,KAAA,GAAW,EAAE,YAAY,KAAK,CAAC,CAAC,CACzD,MAAM,YAAY;GACjB,IAAI,SAAS,QAAQ,OAAO,MAAM,GAAG,QAAQ,GAAG;EAClD,CAAC,CAAC,CACD,YAAY,CAAC,CAAC;EAIjB,mBAAwB;EAMxB,eAAe;EAMf,IAAI,oBAAoB,GACtB,uBAA4B,CAAC,CAAC,OAAO,QACnC,QAAQ,MAAM,0CAA0C,GAAG,CAC7D;EA4BF,MAAM,eAAe,YAA2B;GAC9C,MAAM,cAAc;GACpB,MAAM,4BAA4B;EACpC;EACA,IAAI,aAAkC;EAKtC,IAAI;EACJ,IAAI;EAOJ,MAAM,qBAA8C;GAClD,gBAAgB,WAAW,KAAK;GAChC,kBAAkB,aAAa,KAAK;GACpC,sBAAsB,iBAAiB,KAAK;GAC5C,uBAAuB,kBAAkB,KAAK;EAChD;EAEA,IADyB,KAAiC,iBAAiB,OAEzE,IAAI;GACF,MAAM,eACF,KAAiC,gBAAwC;GAC7E,MAAM,UAAU,uBAAuB;IACrC,WAAW;IACX,WAAW,eAAe,gBAAgB,IAAI;GAChD,CAAC;GACD,MAAM,wBAAwB,gBAAgB;GAC9C,IAAI,CAAC,uBACH,QAAQ,KACN,+GACF;GAYF,MAAM,gBAAiC;IAAC;IAAS;IAAU;GAAa;GACxE,IAAI,mBAAmB,GAAG,cAAc,KAAK,SAAS;GACtD,IAAI,mBAAmB,GAAG,cAAc,KAAK,QAAQ;GACrD,IAAI,oBAAoB,GAAG,cAAc,KAAK,SAAS;GACvD,IAAI,kBAAkB,GAAG,cAAc,KAAK,OAAO;GACnD,IAAI,kBAAkB,GAAG,cAAc,KAAK,YAAY;GACxD,MAAM,EAAE,MAAM,WAAW,SAAS,kBAChC,MAAM,2BAA2B,aAAa;GAEhD,MAAM,UAAU,MAAM,yBAAyB,WAAW;IACxD,UAAU,YAAY;IACtB,iBAAiB;IACjB;IACA,sBAAsB,mBAAmB;IACzC,iBAAiB,mBAAmB;IACpC,qBAAqB,oBAAoB;IACzC,eAAe,cAAc;IAC7B,iBAAiB,gBAAgB;IACjC,YAAY,WAAW;IACvB,aAAa,YAAY;IACzB,cAAc,aAAa;IAC3B,kBAAkB,iBAAiB;IACnC,mBAAmB,kBAAkB;GACvC,CAAC;GACD,MAAM,eAAe,QAAQ;GAO7B,QAAQ,yBAAyB;GACjC,QAAQ,uBAAuB,QAAQ;GACvC,aAAa,YAA2B;IACtC,MAAM,QAAQ,QAAQ;IACtB,MAAM,aAAa;GACrB;GAgBA,MAAM,WAAW,MAAM,wBAAwB,WAAW;IACxD,UAAU,YAAY;IACtB,iBAAiB;IACjB;IACA,OAAO,QAAQ;GACjB,CAAC;GAOD,IAAI,CAAC,SAAS,IAAI;IAChB,UAAU,KAAK,gBAAgB,QAAQ,aAAa;IACpD,IAAK,KAAiC,sBAAsB,MAC1D,UAAU,KAAK,qBAAqB;GAExC,OAAO,IAAK,KAAiC,sBAAsB,MAKjE,QAAQ,KACN,oNAIF;GAGF,MAAM,eAAe,QAAQ,SAAS,KAAK,MAAM,EAAE,SAAS,CAAC,CAAC,KAAK,IAAI;GACvE,MAAM,qBAAqB,SAAS,KAChC,2CAA2C,SAAS,aAAa,KAAK,IAAI,EAAE,MAC5E,2DAA2D,SAAS,mBAAmB,KAAK,IAAI,EAAE;GACtG,MAAM,cACJ,cAAc,SAAS,IACnB,qBAAqB,cAAc,KAAK,IAAI,EAAE,mLAC9C;GACN,QAAQ,OAAO,MACb,2BAA2B,QAAQ,cAAc,aAAa,wBACpC,QAAQ,aAAa,OAAO,IAAI,mBAAmB,IAAI,YAAY,GAC/F;GAoBA,MAAM,aAAa,QAAQ,IAAI;GAC/B,IAAI,mBAAmB,GAAG;IACxB,MAAM,gBAAgB,gBAAgB,QACnC,MAAM,CAAC,qBAAqB,EAAE,IAAI,KAAK,kBAAkB,CAC5D;IACA,IAAI,gBAAgB;IACpB,KAAK,MAAM,KAAK,eAEd,KAAI,MADY,mBAAmB,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,EAAA,CAC3E,SAAS;IAEjB,IAAI;KACF,MAAM,eAAeA,KAAS,KAAK,MAAM,mBAAmB,eAAe;KAC3E,MAAM,MAAM,6BAA6B,QAAQ,UAAU,QAAQ,KAAK,EAAE;KAK1E,MAAM,+BAA+B,cAAc,KAAK,oBAAoB,EAAE;IAChF,SAAS,KAAK;KACZ,QAAQ,KAAK,iDAAiD,OAAO,GAAG,GAAG;IAC7E;IAeA,IAAI;KACF,MAAM,eAAeA,KAAS,KAAK,MAAM,mBAAmB,eAAe;KAK3E,MAAM,qBAAqB,SAAS,KAChC,SAAS,eACT,CACE,GAAG,OAAO,OAAO,SAAS,CAAC,CAAC,QAAQ,MAAmB,QAAQ,CAAC,CAAC,GACjE,GAAI,YAAY,QAAQ,CAAC,WAAW,IAAI,CAAC,CAC3C;KACJ,MAAM,MAAM,MAAM,iBAAiB,cAAc,mBAAmB,kBAAkB,CAAC;KACvF,IAAI,IAAI,MAAM,QACZ,QAAQ,MAAM,gCAAgC,IAAI,MAAM,KAAK,IAAI,GAAG;IAExE,SAAS,KAAK;KACZ,QAAQ,KAAK,0CAA0C,OAAO,GAAG,GAAG;IACtE;IAsBA,IAAI,CAAC,SAAS,IACZ,QAAQ,KACN,gPAIF;SACK,IAAI,QAAQ,IAAI,mCAAmC,KACxD,QAAQ,KACN,oNAGF;SAEA,IAAI;KACF,MAAM,eAAeA,KAAS,KAAK,MAAM,mBAAmB,eAAe;KAC3E,MAAM,aAAa,aAAa,SAAS;KACzC,MAAM,QAAQ,oBAAoB,EAAE,QAAQ,mBAAmB,EAAE,CAAC;KAClE,MAAM,MAAM,4BACV,QAAQ,UACR,QAAQ,KAAK,IACb,YACA,KACF;KACA,MAAM,UAAU,iBAAiB,YAAY,KAAK;KAClD,MAAM,+BAA+B,cAAc,KAAK,cAAc,IAAI,OAAO;IACnF,SAAS,KAAK;KACZ,QAAQ,KAAK,yDAAyD,OAAO,GAAG,GAAG;IACrF;IAEF,IAAI,gBAAgB,GAAG;KACrB,MAAM,aAAa,cAAc,KAAK,MAAM,IAAI,EAAE,MAAM,CAAC,CAAC,KAAK,IAAI;KACnE,QAAQ,OAAO,MACb,oBAAoB,cAAc,GAAG,cAAc,OAAO,KAAK,WAAW,IAC5E;IACF;GACF;GAMA,IAAI,kBAAkB,GAAG;IACvB,IAAI,mBAAmB;IACvB,IAAI;KACF,MAAM,eAAeA,KAAS,KAAK,MAAM,mBAAmB,eAAe;KAC3E,MAAM,WAAW,+BAA+B,QAAQ,UAAU,QAAQ,KAAK,EAAE;KACjF,MAAM,+BACJ,cACA,UACA,cACA,KAAA,GACA,wBACF;KACA,mBAAmB;IACrB,SAAS,KAAK;KACZ,QAAQ,MACN,kEAAkE,OAAO,GAAG,GAC9E;IACF;IACA,uBAAuB,MAAM,gBAAgB;IAQ7C,IAAI,CAAC,QAAQ,IAAI,6BAA6B,KAAK,GACjD,QAAQ,KACN,oMAGF;GAEJ;GAYA,MAAM,kBAAkB,QAAQ,IAAI,uBAAuB,GAAA,CAAI,KAAK;GAMpE,IAAI,eAAe,SAAS,KAAK,CAAC,eAAe,SAAS,IAAG,GAC3D,IAAI;IACF,MAAM,eAAeA,KAAS,KAAK,MAAM,mBAAmB,eAAe;IAC3E,MAAM,UAAU,4BAA4B,QAAQ,UAAU,QAAQ,KAAK,IAAI,cAAc;IAC7F,MAAM,+BAA+B,cAAc,SAAS,cAAc;IAC1E,MAAM,+BAA+B,cAAc,SAAS,YAAY;GAC1E,SAAS,KAAK;IACZ,QAAQ,KAAK,uDAAuD,OAAO,GAAG,GAAG;GACnF;GASF,IAAI,qBAAqB,GAAG;IAC1B,MAAM,mBAAmB,sBAAsB,MAAM,sBAAsB,EAAE,CAAC,CAC3E,aAAa,EAAE,SAAS,MAAM,EAAE;IACnC,IAAI;KACF,MAAM,gDAAgD,UAAU,KAAK;IACvE,SAAS,KAAK;KACZ,QAAQ,KAAK,4CAA4C,OAAO,GAAG,GAAG;IACxE;IAKA,IAAI;KACF,MAAM,2BAA2B,qBAAqB,QAAQ,IAAI,oBAAoB,EAAE,CAAC;KACzF,MAAM,eAAeA,KAAS,KAAK,MAAM,mBAAmB,eAAe;KAC3E,MAAM,MAAM,6BAA6B,QAAQ,UAAU,QAAQ,KAAK,EAAE;KAC1E,MAAM,+BAA+B,cAAc,KAAK,eAAe,KAAA,GAAW,cAAc;IAClG,SAAS,KAAK;KACZ,QAAQ,KAAK,mDAAmD,OAAO,GAAG,GAAG;IAC/E;GACF;GAMA,IAAI,sBAAsB,OAAO,KAAK,kBAAkB,GACtD,IAAI;IACF,MAAM,eAAeA,KAAS,KAAK,MAAM,mBAAmB,eAAe;IAC3E,MAAM,UAAU,2BAA2B,QAAQ,UAAU,QAAQ,KAAK,EAAE;IAC5E,MAAM,+BAA+B,cAAc,SAAS,eAAe,KAAA,GAAW,cAAc;GACtG,SAAS,KAAK;IACZ,QAAQ,KAAK,qDAAqD,OAAO,GAAG,GAAG;GACjF;GAOF,IAAK,KAAiC,kBAAkB,MACtD,IAAI;IACF,MAAM,OAAO,MAAM,UAAU,UAAU;IACvC,QAAQ,OAAO,MACb,0CAA0C,KAAK,6CACjD;GACF,SAAS,KAAK;IACZ,QAAQ,KAAK,gCAAgC,OAAO,GAAG,GAAG;GAC5D;GAEF,MAAM,eAAe,iBAAiB,IAA+B;GACrE,MAAM,eAAe,aAAa,QAAQ,IAAI,0BAA0B,MAAM;GAC9E,MAAM,eAAe,aAAa,QAAQ,IAAI,6BAA6B,MAAM;GACjF,MAAM,WAAW,MAAM,SAAS,UAAU,CAAC,CAAC,YAAY,UAAU;GAQlE,IAAI,aAAoC;GACxC,IAAI,cAAc;GAClB,IAAI,CAAC,cAAc;IACjB,aAAa,MAAM,oBAAoB,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC,YAAY,IAAI;IACnF,IAAI,CAAC,YAAY;KACf,MAAM,SAAS,MAAM,mBAAmB,QAAQ,CAAC,CAAC,YAAY,IAAI;KAClE,IAAI,QACF,aAAa;MACX,MAAM;MACN,QAAQ,OAAO;MACf,WAAW,OAAO;MAClB,SAAS;MACT,UAAU,OAAO;KACnB;UAGK,IAAI,CAAC,gBAAgB,mBAAmB,KAAK,aAAa,QAAQ,IAAI,+BAA+B,MAAM,MAChH,cAAc;IAElB;GACF;GAKA,IAAI,cAAc,eAAe,QAAQ,MAAM,uBAAuB,UAAU,CAAC,CAAC,YAAY,KAAK;GACnG,IAAI,cAAc;GAClB,IAAI,CAAC,eAAe,CAAC,iBAAiB,cAAc,cAClD,IAAI;IACF,MAAM,UAAU,UAAU;IAC1B,cAAc;IACd,cAAc;GAChB,SAAS,KAAK;IACZ,QAAQ,KAAK,2DAA2D,OAAO,GAAG,GAAG;GACvF;GAMF,IAAI,eAAe,CAAC,cAAc,CAAC,aACjC,aAAa;IAAE,MAAM;IAAU,QAAQ,WAAW;IAAG,SAAS;GAAS;GAEzE,IAAI,eAAe,YAAY;IAC7B,MAAM,QAAQ;IACd,IAAI;KACF,MAAM,eAAeA,KAAS,KAAK,MAAM,mBAAmB,eAAe;KAC3E,MAAM,UAAU,qBAAqB,QAAQ,UAAU,QAAQ,KAAK,EAAE;KAEtE,IAAI,MAAM,SAAS,UAAU,QAAQ,yBAAyB,MAAM;UAC/D,IAAI,MAAM,SAAS,UAAU,QAAQ,6BAA6B;UAClE,QAAQ,iCAAiC;KAC9C,IAAI,cAAc,QAAQ,gCAAgC;KAC1D,MAAM,+BAA+B,cAAc,OAAO;KAK1D,IAAI,MAAM,SAAS,UAAU;MAC3B,MAAM,SAAS,oBAAoB,KAAK;MACxC,MAAM,gBAAgB,eAAe,KAAK;MAG1C,MAAM,QAAQ,YAAY,CAAC,CAAC,CAAC,SAAS,KAAK;MAC3C,QAAQ,qCAAqC;MAC7C,CAAM,YAAY;OAChB,MAAM,sBAAsB;QAC1B;QACA,SAAS,MAAM;QACf,MAAM;QACN;QACA;QACA,UAAU,kBAAkBA,KAAS,KAAK,OAAO,GAAG,6BAA6B,CAAC;OACpF,CAAC;MACH,EAAA,CAAG,CAAC,CAAC,YAAY,CAAC,CAAC;KACrB;KACA,MAAM,OACJ;KAEF,IAAI,MAAM,SAAS,UACjB,QAAQ,OAAO,OACZ,cACG,oEAAoE,MAAM,OAAO,yCACjF,2CAA2C,MAAM,OAAO,QAAQ,IACtE;UACK;MACL,MAAM,MAAM,oBAAoB,KAAK,CAAC,CAAC,KAAK,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI;MACjE,MAAM,WAAW,eACb,yBACA;MACJ,QAAQ,OAAO,MACb,4CAA4C,MAAM,UAAU,iBAAiB,IAAI,SACrE,MAAM,SAAS,KAAK,IAAI,EAAE,KAAK,WAAW,MACxD;KACF;IACF,SAAS,KAAK;KAEZ,QAAQ,KAAK,qDAAqD,OAAO,GAAG,GAAG;IACjF;GACF,OAAO,IAAI,eAAe,aAAa;IAErC,QAAQ,OAAO,MACb,kLAEF;IACA,CAAM,YAAY;KAChB,IAAI;MACF,MAAM,OAAO,MAAM,qBAAqB,UAAU;OAChD;OACA,QAAQ,YAAY,QAAQ,IAAO;MACrC,CAAC;MACD,IAAI,MAAM;OACR,MAAM,CAAC,aAAa,MAAM,MAAM,QAAQ,IAAI,CAC1C,gBAAgB,QAAQ,CAAC,CAAC,YAAY,EAAE,GACxC,YAAY,QAAQ,CAAC,CAAC,YAAY,EAAE,CACtC,CAAC;OACD,MAAM,oBAAoB;QACxB,MAAM;QACN;QACA,aAAa;QACb,+BAAc,IAAI,KAAK,EAAA,CAAE,YAAY;QACrC,OAAO;QACP,WAAW,KAAK;QAChB,QAAQ,KAAK;QACb,YAAY,KAAK;QACjB,UAAU,KAAK;OACjB,CAAC;MACH;KACF,QAAQ,CAER;IACF,EAAA,CAAG,CAAC,CAAC,YAAY,CAAC,CAAC;GACrB,OAAO,IAAI,CAAC,gBAAgB,CAAC,cAAc,CAAC,aAE1C,QAAQ,OAAO,MACb,wNAGK,mBAAmB,IAAI,0EAA0E,MAClG,KACN;GAyBF,MAAM,cAAc,0BAA0B;IAC5C,UAAU,YAAY;IACtB,iBAAiB;IACjB,sBAAsB,mBAAmB;IACzC,kBAAkB,mBAAmB;IACrC,iBAAiB,oBAAoB;IACrC,yBAAyB,4BAA4B;IACrD,sBAAsB,MAAM;IAC5B,gBAAgB,kBAAkB;IAClC,qBAAqB,kBAAkB;IACvC,GAAG;IACH;GACF,CAAC;GAID,uBAAuB;GACvB,uBAAuB,0BAA0B;IAC/C,sBAAsB,mBAAmB;IACzC,kBAAkB,mBAAmB;IACrC,iBAAiB,oBAAoB;IACrC,gBAAgB,kBAAkB;IAClC,qBAAqB,kBAAkB;IACvC,GAAG;IACH;GACF,CAAC;GAUD,IAAI;IACF,MAAM,sCAAsC,WAAW;GACzD,SAAS,KAAK;IACZ,QAAQ,KACN,wHACE,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,GAEnD;GACF;GAOA,IAAI;IACF,MAAM,wCAAwC;GAChD,SAAS,KAAK;IACZ,QAAQ,KACN,6CACE,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,GAEnD;GACF;EACF,SAAS,KAAK;GACZ,QAAQ,KACN,2DACE,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,GAEnD;EACF;EAsBF,UAAU,KACR,0BACA,wBAAwB,uBACpB,GAAG,0BAA0B,MAAM,yBACnC,yBACN;EACA,IAAI;GACF,MAAM,2CACJ,gCAAgC,kBAAkB,CACpD;EACF,SAAS,KAAK;GACZ,QAAQ,KACN,gDACE,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,GAEnD;EACF;EAQA,IAAI,QAAQ,IAAI,qCAAqC,KACnD,IAAI;GACF,MAAM,eAAeA,KAAS,KAAK,MAAM,mBAAmB,eAAe;GAC3E,MAAM,6CAA6C,YAAY;EACjE,SAAS,KAAK;GACZ,QAAQ,KACN,6CACE,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,GAEnD;EACF;EAGF,YACE;GAAE,MAAM;GAAe;GAAS;GAAW,OAAO;EAAW,GAC7D,QACA,EAAE,WAAW,CACf;CACF;AACF,CAAC"}
|
package/dist/engine-BKJKMAzh.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"internal-plan-review-jRMpHu68.js","names":["fs"],"sources":["../src/internal-plan-review.ts"],"sourcesContent":["/**\n * The internal `internal-plan-review` subcommand: a PostToolUse(ExitPlanMode)\n * hook that spawns a bounded advisory critic review when the main agent finalizes\n * a substantive plan.\n *\n * It never blocks ExitPlanMode: the hook process exits 0 quickly after deciding\n * whether to spawn a detached reviewer. The detached child calls a cross-lab plan\n * critic and writes material findings to the SAME per-session findings store used\n * by the Stop review, so the next UserPromptSubmit surfaces them with the existing\n * non-authoritative findings path.\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 { promises as fs } from \"node:fs\"\nimport path from \"node:path\"\n\nimport { hookMcpRuntimeFromEnv } from \"./lib/orchestration/hook-mcp-client\"\nimport {\n decidePlanReviewHook,\n filePlanReviewDebounce,\n runPlanReview,\n type PlanReviewSpawnContext,\n} from \"./lib/orchestration/plan-review-hook\"\nimport { fileFindingsStore, stopReviewStateDir } from \"./lib/orchestration/stop-gate-policy\"\n\nfunction readStdin(): string {\n try {\n if (process.stdin.isTTY) return \"\"\n return readFileSync(0, \"utf8\")\n } catch {\n return \"\"\n }\n}\n\nasync function readDetachedPayload(): Promise<PlanReviewSpawnContext | undefined> {\n const payloadPath = (process.env.GH_ROUTER_PLAN_REVIEW_PAYLOAD ?? \"\").trim()\n if (payloadPath.length === 0) return undefined\n try {\n const raw = await fs.readFile(payloadPath, \"utf8\")\n await fs.unlink(payloadPath).catch(() => {})\n const parsed: unknown = JSON.parse(raw)\n if (!parsed || typeof parsed !== \"object\") return undefined\n const p = parsed as Partial<PlanReviewSpawnContext>\n if (!p.sessionId || !p.cwd || !p.plan || !p.planHash) return undefined\n return { sessionId: p.sessionId, cwd: p.cwd, plan: p.plan, planHash: p.planHash }\n } catch {\n await fs.unlink(payloadPath).catch(() => {})\n return undefined\n }\n}\n\nfunction spawnDetachedPlanReview(ctx: PlanReviewSpawnContext): void {\n let payloadPath: string | undefined\n try {\n const dir = stopReviewStateDir()\n mkdirSync(dir, { recursive: true })\n payloadPath = path.join(dir, `plan-review-${process.pid}-${randomBytes(4).toString(\"hex\")}.json`)\n writeFileSync(payloadPath, JSON.stringify(ctx), { mode: 0o600 })\n const scriptArgs = process.argv[1] && process.argv[1] !== process.execPath ? [process.argv[1]] : []\n const child = spawn(process.execPath, [...scriptArgs, \"internal-plan-review\"], {\n detached: true,\n windowsHide: true,\n stdio: \"ignore\",\n env: { ...process.env, GH_ROUTER_PLAN_REVIEW_PAYLOAD: payloadPath },\n })\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 if (payloadPath) {\n try {\n unlinkSync(payloadPath)\n } catch {\n /* best-effort */\n }\n }\n }\n}\n\nexport const internalPlanReview = defineCommand({\n meta: {\n name: \"internal-plan-review\",\n description:\n \"Internal: advisory ExitPlanMode plan reviewer. Spawns a bounded detached critic review \"\n + \"for substantive plans and writes findings for the next prompt. Always exit 0.\",\n },\n async run() {\n try {\n const runtime = hookMcpRuntimeFromEnv()\n if (!runtime) return\n\n const detached = await readDetachedPayload()\n if (detached) {\n await runPlanReview({\n runtime,\n sessionId: detached.sessionId,\n cwd: detached.cwd,\n plan: detached.plan,\n findingsStore: fileFindingsStore(stopReviewStateDir()),\n })\n return\n }\n\n const decision = await decidePlanReviewHook({\n stdin: readStdin(),\n runtimeAvailable: true,\n debounce: filePlanReviewDebounce(stopReviewStateDir()),\n fallbackCwd: process.cwd(),\n })\n if (decision.kind === \"spawn\") spawnDetachedPlanReview(decision)\n } catch {\n /* side-effect only: never block ExitPlanMode */\n }\n process.exitCode = 0\n },\n})\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA6BA,SAAS,YAAoB;CAC3B,IAAI;EACF,IAAI,QAAQ,MAAM,OAAO,OAAO;EAChC,OAAO,aAAa,GAAG,MAAM;CAC/B,QAAQ;EACN,OAAO;CACT;AACF;AAEA,eAAe,sBAAmE;CAChF,MAAM,eAAe,QAAQ,IAAI,iCAAiC,GAAA,CAAI,KAAK;CAC3E,IAAI,YAAY,WAAW,GAAG,OAAO,KAAA;CACrC,IAAI;EACF,MAAM,MAAM,MAAMA,SAAG,SAAS,aAAa,MAAM;EACjD,MAAMA,SAAG,OAAO,WAAW,CAAC,CAAC,YAAY,CAAC,CAAC;EAC3C,MAAM,SAAkB,KAAK,MAAM,GAAG;EACtC,IAAI,CAAC,UAAU,OAAO,WAAW,UAAU,OAAO,KAAA;EAClD,MAAM,IAAI;EACV,IAAI,CAAC,EAAE,aAAa,CAAC,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,EAAE,UAAU,OAAO,KAAA;EAC7D,OAAO;GAAE,WAAW,EAAE;GAAW,KAAK,EAAE;GAAK,MAAM,EAAE;GAAM,UAAU,EAAE;EAAS;CAClF,QAAQ;EACN,MAAMA,SAAG,OAAO,WAAW,CAAC,CAAC,YAAY,CAAC,CAAC;EAC3C;CACF;AACF;AAEA,SAAS,wBAAwB,KAAmC;CAClE,IAAI;CACJ,IAAI;EACF,MAAM,MAAM,mBAAmB;EAC/B,UAAU,KAAK,EAAE,WAAW,KAAK,CAAC;EAClC,cAAc,KAAK,KAAK,KAAK,eAAe,QAAQ,IAAI,GAAG,YAAY,CAAC,CAAC,CAAC,SAAS,KAAK,EAAE,MAAM;EAChG,cAAc,aAAa,KAAK,UAAU,GAAG,GAAG,EAAE,MAAM,IAAM,CAAC;EAC/D,MAAM,aAAa,QAAQ,KAAK,MAAM,QAAQ,KAAK,OAAO,QAAQ,WAAW,CAAC,QAAQ,KAAK,EAAE,IAAI,CAAC;EAClG,MAAM,QAAQ,MAAM,QAAQ,UAAU,CAAC,GAAG,YAAY,sBAAsB,GAAG;GAC7E,UAAU;GACV,aAAa;GACb,OAAO;GACP,KAAK;IAAE,GAAG,QAAQ;IAAK,+BAA+B;GAAY;EACpE,CAAC;EACD,MAAM,SAAS;EACf,MAAM,GAAG,eAAe;GACtB,IAAI,QACF,IAAI;IACF,WAAW,MAAM;GACnB,QAAQ,CAER;EAEJ,CAAC;EACD,MAAM,MAAM;CACd,QAAQ;EACN,IAAI,aACF,IAAI;GACF,WAAW,WAAW;EACxB,QAAQ,CAER;CAEJ;AACF;AAEA,MAAa,qBAAqB,cAAc;CAC9C,MAAM;EACJ,MAAM;EACN,aACE;CAEJ;CACA,MAAM,MAAM;EACV,IAAI;GACF,MAAM,UAAU,sBAAsB;GACtC,IAAI,CAAC,SAAS;GAEd,MAAM,WAAW,MAAM,oBAAoB;GAC3C,IAAI,UAAU;IACZ,MAAM,cAAc;KAClB;KACA,WAAW,SAAS;KACpB,KAAK,SAAS;KACd,MAAM,SAAS;KACf,eAAe,kBAAkB,mBAAmB,CAAC;IACvD,CAAC;IACD;GACF;GAEA,MAAM,WAAW,MAAM,qBAAqB;IAC1C,OAAO,UAAU;IACjB,kBAAkB;IAClB,UAAU,uBAAuB,mBAAmB,CAAC;IACrD,aAAa,QAAQ,IAAI;GAC3B,CAAC;GACD,IAAI,SAAS,SAAS,SAAS,wBAAwB,QAAQ;EACjE,QAAQ,CAER;EACA,QAAQ,WAAW;CACrB;AACF,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"internal-stop-hook-Ds9LwmZA.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 { 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 stopGatePlanMode,\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/** Capture the working-tree diff WITHOUT mutating the user's index (no\n * `git add -N`): `git diff HEAD` covers modified tracked files, which is where\n * gate-weakening edits live. A git failure rejects, so the decision layer can run\n * checks with an empty weakening scan instead of treating an unknown diff as a\n * genuine no-diff turn. Capped. */\nasync function captureDiff(cwd: string): Promise<string> {\n const r = await runCommandCapture([\"git\", \"diff\", \"HEAD\"], { cwd, timeoutMs: 5_000 })\n if (r.code !== 0) throw new Error(`git diff failed with exit ${r.code}`)\n const out = r.stdout\n return out.length > MAX_DIFF_BYTES ? out.slice(0, MAX_DIFF_BYTES) : out\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. Mirror\n // buildStopHookCommand's resolution: pass the script path only when it\n // differs from the node/bun executable (a packaged single-file build).\n const scriptArgs = process.argv[1] && process.argv[1] !== process.execPath ? [process.argv[1]] : []\n const child = spawn(process.execPath, [...scriptArgs, \"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,\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 planMode: stopGatePlanMode(),\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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-workspace-header-CGJbNeHb.js","names":[],"sources":["../src/lib/mcp-workspace-header.ts"],"sourcesContent":["/** Header the per-session MCP headersHelper emits, carrying the calling Claude\n * session's working directory. The /mcp handler uses it as the default\n * `workspace` for repo-scoped tools so a machine-wide `serve` targets the\n * active repo, not the proxy launch cwd. */\nexport const MCP_WORKSPACE_HEADER = \"X-GH-Workspace\"\n\n/** stdout JSON the headersHelper prints; Claude merges it into the connection\n * headers. `cwd` is the helper's own process cwd = the session's project dir. */\nexport function buildWorkspaceHeaderJson(cwd: string): string {\n return JSON.stringify({ [MCP_WORKSPACE_HEADER]: cwd })\n}\n\n/** Command string Claude runs as the headersHelper. Mirrors\n * buildPromptSubmitHookCommand's quoting (src/lib/orchestration/prompt-submit-hook.ts). */\nexport function buildWorkspaceHeaderHelperCommand(execPath: string, scriptPath: string | undefined): string {\n const q = (s: string): string => `\"${s}\"`\n return scriptPath && scriptPath !== execPath\n ? `${q(execPath)} ${q(scriptPath)} internal-workspace-header`\n : `${q(execPath)} internal-workspace-header`\n}\n"],"mappings":";;;;;AAIA,MAAa,uBAAuB;;;AAIpC,SAAgB,yBAAyB,KAAqB;CAC5D,OAAO,KAAK,UAAU,GAAG,uBAAuB,IAAI,CAAC;AACvD;;;AAIA,SAAgB,kCAAkC,UAAkB,YAAwC;CAC1G,MAAM,KAAK,MAAsB,IAAI,EAAE;CACvC,OAAO,cAAc,eAAe,WAChC,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,UAAU,EAAE,8BAChC,GAAG,EAAE,QAAQ,EAAE;AACrB"}
|
package/dist/paths-DN3Nio42.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"paths-j2B7b0DZ.js","names":[],"sources":["../src/lib/paths.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\"\nimport fs from \"node:fs/promises\"\nimport os from \"node:os\"\nimport path from \"node:path\"\n\nimport consola from \"consola\"\n\nfunction appDir(): string {\n return path.join(os.homedir(), \".local\", \"share\", \"github-router\")\n}\n\nexport const PATHS = {\n get APP_DIR() {\n return appDir()\n },\n get GITHUB_TOKEN_PATH() {\n return path.join(appDir(), \"github_token\")\n },\n /**\n * Second, WRITE-capable GitHub token for the first-mate\n * agent-orchestration surface (`--agents`), stored apart from\n * `github_token` (the Copilot App token). Minted by\n * `setupGitHubAgentToken()` via a device-flow login against the GitHub\n * CLI OAuth client. Mode 0o600.\n */\n get GITHUB_AGENT_TOKEN_PATH() {\n return path.join(appDir(), \"github_agent_token\")\n },\n /**\n * Root of the durable first-mate fleet registry (`--agents`): the\n * global `missions.json` mission index + per-repo `<owner>__<repo>.json`\n * unit ledgers + the decisions/approvals ledger. Cross-launch and\n * OUTSIDE the per-launch CLAUDE_CONFIG_DIR mirror (which is swept each\n * launch) — the first mate must survive proxy restarts / context\n * clears. Modeled on the worker-agent lifecycle ledger.\n */\n get FIRST_MATE_DIR() {\n return (\n process.env.GH_ROUTER_FIRST_MATE_DIR ?? path.join(appDir(), \"first-mate\")\n )\n },\n get ERROR_LOG_PATH() {\n return path.join(appDir(), \"error.log\")\n },\n /**\n * Durable, router-owned directory for worker OVERFLOW artifacts. Two\n * kinds land here, both kept OUT of the MCP result relay so they don't\n * overflow Claude Code's 25k-token cap:\n * - `<pid>-<8hex>.patch` — a `worker_implement`/`worker_test` worktree\n * diff larger than `PREVIEW_CAP` (worker-agent/worktree.ts): the FULL\n * `git diff --binary --full-index` patch, written BEFORE the worktree\n * is removed so the (possibly binary) change stays `git apply`-able.\n * - `<pid>-<8hex>.txt` — any worker result over the relay byte cap\n * (worker-agent/relay-cap.ts `relaySafeText`): the full result text,\n * with a bounded preview + this path returned in its place.\n * `sweepAgedWorkerDiffs()` reaps entries older than 7 days on each write.\n * Cross-launch cache (like TOOLBELT_BIN_DIR) — deliberately OUTSIDE the\n * per-launch CLAUDE_CONFIG_DIR mirror and OUTSIDE the user's repo.\n */\n get WORKER_DIFFS_DIR() {\n return path.join(appDir(), \"worker-diffs\")\n },\n /**\n * Isolated CODEX_HOME for the spawned Codex CLI. Masks any cached\n * ChatGPT subscription login (openai/codex#2733 — cached login can\n * override OPENAI_API_KEY) so the proxy's dummy key is authoritative.\n */\n get CODEX_HOME() {\n return path.join(appDir(), \"codex-isolated\")\n },\n /**\n * Router-owned install + data dir for CloudCLI (the `serve` browser control\n * plane). Holds the pinned local `npm install` (node_modules/), an isolated\n * `auth.db` (so we never collide with a user's standalone CloudCLI DB), and\n * the persisted `.serve-secret.json` login. See docs/serve-control-plane.md.\n */\n get CLOUDCLI_HOME() {\n return path.join(appDir(), \"cloudcli\")\n },\n /**\n * Runtime tempfiles for the per-launch peer-MCP wiring (the\n * `--mcp-config` JSON and `--agents` JSON written before spawning\n * Claude Code). Mode 0o700 to match the security review's mandate;\n * cleaned on shutdown via the per-launch `cleanup()`, plus a\n * boot-time sweep of stale files (dead PIDs, >24h old).\n */\n get CLAUDE_RUNTIME_DIR() {\n return path.join(appDir(), \"runtime\")\n },\n /**\n * Router-owned CLAUDE_CONFIG_DIR. The spawned Claude Code (and any\n * teammates it spawns via the agent-teams primitive) reads its\n * config — including `.credentials.json` — from this dir. We\n * snapshot-copy the user's `~/.claude/` here at startup (excluding\n * `.credentials.json` and volatile state), then write our own\n * synthetic Console OAuth credential. The teammate-spawn allowlist\n * propagates `CLAUDE_CONFIG_DIR` to children, so teammates find the\n * synthetic credential and authenticate instead of falling into the\n * \"Not logged in · Run /login\" gate that would otherwise leave\n * them mute. See `ensureClaudeConfigMirror` below.\n *\n * Per-launch dir: `<appDir>/claude-config/<pid>-<8 hex>`. Two\n * concurrent `github-router claude` launches each get their own\n * isolated mirror, so per-launch state (synthetic credential,\n * snapshot copy of `~/.claude/`, future per-launch `.claude.json`\n * mutation with the peer-MCP entry) cannot cross-talk. The\n * per-launch suffix is cached on first access (see\n * `claudeConfigDirSuffix()`) so all callers within a single proxy\n * lifetime see the same value. Boot-time `sweepStaleClaudeConfigMirrors`\n * reaps mirrors from crashed prior PIDs.\n */\n get CLAUDE_CONFIG_DIR() {\n return path.join(appDir(), \"claude-config\", claudeConfigDirSuffix())\n },\n /**\n * Durable router-owned `bin/` for the LLM toolbelt — curated CLI\n * tools (rg/fd/jq/sd/sg/yq) materialized here and prepended to the\n * spawned agent's PATH. Intentionally OUTSIDE the per-launch\n * CLAUDE_CONFIG_DIR mirror (which is swept per launch): the toolbelt\n * is a cross-launch cache, like `last-update-check`.\n */\n get TOOLBELT_BIN_DIR() {\n return path.join(appDir(), \"bin\")\n },\n /**\n * Root of the github-router-managed ColBERT semantic-search sidecar\n * (the `--semantic-search` opt-in). Everything heavy that the\n * `colgrep` child needs is provisioned, SHA-verified, and stored here\n * by the router — never in the user's repo, never in colgrep's own\n * global `%APPDATA%\\colgrep` / `~/.config/colgrep`. Cross-launch cache\n * (like TOOLBELT_BIN_DIR), deliberately OUTSIDE the per-launch\n * CLAUDE_CONFIG_DIR mirror.\n *\n * Layout (see docs/research/colbert-sidecar-design.md §4):\n * colbert/\n * bin/colgrep[.exe] # provisioned binary\n * models/LateOn-Code-edge/<rev>/ # 5 model files\n * onnxruntime/1.23.0/cpu/<libname> # ORT dylib (ORT_DYLIB_PATH)\n * indices/ # === COLGREP_DATA_DIR ===\n * <project>-<xxh3(path|model)>/ # colgrep-owned per-(path,model)\n * .gh-router-meta/<hash>.json # router-owned sidecar metadata\n */\n get COLBERT_DIR() {\n return path.join(appDir(), \"colbert\")\n },\n get COLBERT_BIN_DIR() {\n return path.join(appDir(), \"colbert\", \"bin\")\n },\n get COLBERT_MODELS_DIR() {\n return path.join(appDir(), \"colbert\", \"models\")\n },\n get COLBERT_ORT_DIR() {\n return path.join(appDir(), \"colbert\", \"onnxruntime\")\n },\n /**\n * Value passed to colgrep as `COLGREP_DATA_DIR`. colgrep keys the\n * physical index dir by `xxh3(canonical_project_path | model)` under\n * here, so every distinct workspace gets its own index dir for free.\n * The router-owned freshness sidecar lives at\n * `<COLBERT_INDICES_DIR>/.gh-router-meta/<hash>.json`.\n */\n get COLBERT_INDICES_DIR() {\n return path.join(appDir(), \"colbert\", \"indices\")\n },\n get COLBERT_META_DIR() {\n return path.join(appDir(), \"colbert\", \"indices\", \".gh-router-meta\")\n },\n}\n\n/**\n * Per-launch suffix for `PATHS.CLAUDE_CONFIG_DIR`. Lazily generated on\n * first access and cached for the lifetime of the process so every\n * caller (env-var injection in `getClaudeCodeEnvVars`,\n * `ensureClaudeConfigMirror` provisioning, peer-agent `.md` writes\n * under `<dir>/agents/`, the shutdown cleanup) resolves the same path.\n *\n * Shape: `<pid>-<8 hex>`. The PID prefix is what\n * `sweepStaleClaudeConfigMirrors` keys off to drop orphans from\n * crashed prior sessions; the 8-hex random suffix prevents collision\n * if a future caller (tests, internal relaunch) ever clears the cache\n * within a single PID lifetime.\n *\n * NOT exported — every consumer should go through `PATHS.CLAUDE_CONFIG_DIR`\n * so the homedir-mock pattern used in the test suite keeps working.\n */\nlet _claudeConfigDirSuffix: string | undefined\nfunction claudeConfigDirSuffix(): string {\n if (_claudeConfigDirSuffix === undefined) {\n _claudeConfigDirSuffix = `${process.pid}-${randomBytes(4).toString(\"hex\")}`\n }\n return _claudeConfigDirSuffix\n}\n\n/**\n * Predicate: is `target` inside the current launch's `CLAUDE_CONFIG_DIR`?\n *\n * Used as a defence-in-depth safety guard by helpers that mutate files\n * under the mirror dir (e.g. `appendPeerAwarenessToMirroredClaudeMd`).\n * If `PATHS.CLAUDE_CONFIG_DIR` has somehow been pointed at the user's\n * real `~/.claude/` (configuration drift, future code regression), the\n * helper refuses to write — the proxy should never mutate user files.\n *\n * Pure-prefix `startsWith` is unsafe (`/a/foo` matches `/a/foobar`), so\n * this requires either equality or `<root><sep>...` strictly.\n * Synchronous because we don't need real-path resolution; symlink\n * refusal lives at the consuming helper.\n */\nexport function isUnderClaudeConfigMirror(target: string): boolean {\n const resolvedRoot = path.resolve(PATHS.CLAUDE_CONFIG_DIR)\n const resolvedTarget = path.resolve(target)\n if (resolvedTarget === resolvedRoot) return true\n return resolvedTarget.startsWith(resolvedRoot + path.sep)\n}\n\nexport async function ensurePaths(): Promise<void> {\n await fs.mkdir(PATHS.APP_DIR, { recursive: true })\n await fs.mkdir(PATHS.CODEX_HOME, { recursive: true })\n await fs.mkdir(PATHS.CLAUDE_RUNTIME_DIR, { recursive: true })\n // mkdir({recursive: true}) does NOT chmod an existing directory, so\n // explicitly tighten in case the dir was created by an older version.\n await chmodIfPossible(PATHS.CLAUDE_RUNTIME_DIR, 0o700)\n await ensureFile(PATHS.GITHUB_TOKEN_PATH)\n // Second (write-capable) GitHub token file for `--agents`. Always\n // ensured so the path exists; only populated by a device-flow login\n // when the agent surface is opted in. Tightened to 0o600 (it holds a\n // repo/workflow-scoped credential).\n await ensureFile(PATHS.GITHUB_AGENT_TOKEN_PATH)\n await chmodIfPossible(PATHS.GITHUB_AGENT_TOKEN_PATH, 0o600)\n await sweepStaleRuntimeFiles().catch((err) => {\n consola.debug(\"Runtime sweep skipped:\", err)\n })\n // Sweep stale per-launch CLAUDE_CONFIG_DIR mirrors left behind by\n // crashed prior proxy sessions BEFORE peer-agent .md sweep, since\n // the .md sweep is scoped to THIS launch's mirror and the per-launch\n // dir sweep is the parent cleanup for the same orphan class.\n await sweepStaleClaudeConfigMirrors().catch((err) => {\n consola.debug(\"Per-launch claude-config sweep skipped:\", err)\n })\n // Phase 2.5: also sweep stale peer-* subagent .md files from this\n // launch's CLAUDE_CONFIG_DIR/agents/ (defense-in-depth — should be\n // a no-op since the per-launch dir didn't exist before this PID\n // started; keeps the safety net in case a future change ever shares\n // an agents/ dir across launches).\n await sweepStalePeerAgentMdFiles().catch((err) => {\n consola.debug(\"Peer-agent .md sweep skipped:\", err)\n })\n // Worker-agent boot-time PID+instance safety net. Walks the\n // worker-repos.json ledger and removes any worktree dir whose\n // <pid> is dead OR whose <instance> UUID doesn't match this proxy.\n // Catches SIGKILL/OOM/host-crash escapees from prior sessions.\n // Lazy-imported so the worker-agent module doesn't get loaded by\n // every consumer of `paths.ts`.\n await (async () => {\n const mod = await import(\"./worker-agent/lifecycle\")\n await mod.sweepStaleWorktreesAtBoot()\n })().catch((err) => {\n consola.debug(\"Worker worktree boot sweep skipped:\", err)\n })\n // ColBERT sidecar boot sweep: reclassify any `.gh-router-meta/*.json`\n // entry stuck in `building` with a dead `buildPid` → `failed`, so the\n // next `code` semantic-mode call re-kicks a build instead of routing to\n // a never-finishing one. NEVER kills a PID from a prior boot. Lazy-\n // imported so paths.ts doesn't drag the colbert module into every\n // consumer.\n await (async () => {\n const mod = await import(\"./colbert/lifecycle\")\n await mod.sweepStaleColbertMetaAtBoot()\n })().catch((err) => {\n consola.debug(\"ColBERT meta boot sweep skipped:\", err)\n })\n}\n\n/**\n * Per-entry mirror policy. Every top-level entry under `~/.claude/` falls\n * into exactly one bucket; unlisted names default to `MIRRORED` so a future\n * Claude-Code-side addition flows through as a snapshot copy rather than\n * being silently lost.\n *\n * Three policies:\n *\n * - `ISOLATED` — not present in the mirror at all. The proxy owns its\n * own copy (synthetic `.credentials.json`, the `.github-router-managed`\n * marker) or the entry has no place in a proxy session\n * (`.credentials.json.lock`, `.oauth_refresh.lock` couple refresh loops\n * across sessions; `statsig/` is write-heavy and would constantly\n * re-copy; `cache/` and `logs/` are ephemeral; `paste-cache/` holds\n * sensitive clipboard extracts and shouldn't leak across sessions —\n * gemini-critic finding).\n *\n * - `SHARED` — symlink `<mirror>/<X>` → `~/.claude/<X>` so writes made\n * during the proxy session land in the user's real `~/.claude/` and\n * chat history is visible in both proxy and plain-`claude` sessions.\n * **Directories only.** Never use this for individual files: Claude\n * Code's atomic-write pattern (`fs.writeFile(temp); fs.rename(temp,\n * target)`) does NOT follow symlinks — a `rename` over the symlink\n * replaces it with a regular file, silently severing the connection\n * to `~/.claude/<X>`. Gemini-critic finding from the 3-lab review.\n *\n * - `MIRRORED` (default) — snapshot-copy with mtime skip. Use for static\n * or settings-shaped state where proxy-session writes should NOT flow\n * back to `~/.claude/` (e.g. `settings.json`, `.claude.json`,\n * `teams/`, `session-env/`) and for `agents/` — the proxy itself\n * writes per-launch `peer-<pid>-*.md` files into the mirror's `agents/`\n * and `sweepStalePeerAgentMdFiles` deletes them; a symlink would route\n * those writes/deletes into the user's real `~/.claude/agents/` and\n * destroy the user's own subagent files. **Hard regression test**:\n * `policyFor(\"agents\") === \"MIRRORED\"` is asserted in\n * `tests/isolated/lib-paths.test.ts` to prevent accidental reclassification.\n *\n * Sub-paths within MIRRORED dirs cascade recursively (existing behavior).\n */\ntype MirrorPolicy = \"ISOLATED\" | \"SHARED\" | \"MIRRORED\"\n\nconst CLAUDE_HOME_POLICY: ReadonlyMap<string, MirrorPolicy> = new Map<\n string,\n MirrorPolicy\n>([\n // ISOLATED\n [\".credentials.json\", \"ISOLATED\"],\n [\".credentials.json.lock\", \"ISOLATED\"],\n [\".oauth_refresh.lock\", \"ISOLATED\"],\n // Defense-in-depth: don't let a user-side file/symlink with the same\n // name as our marker collide with what we write. The marker write\n // logic also lstat-checks before writing (refuses if a non-regular\n // file exists at the path), but excluding it here removes the\n // attack vector entirely.\n [\".github-router-managed\", \"ISOLATED\"],\n [\"statsig\", \"ISOLATED\"],\n [\"cache\", \"ISOLATED\"],\n [\"logs\", \"ISOLATED\"],\n [\"paste-cache\", \"ISOLATED\"],\n [\"jobs\", \"ISOLATED\"],\n [\"daemon\", \"ISOLATED\"],\n [\"daemon.log\", \"ISOLATED\"],\n // SHARED — directories only (see policy doc above)\n [\"projects\", \"SHARED\"],\n [\"sessions\", \"SHARED\"],\n [\"tasks\", \"SHARED\"],\n [\"todos\", \"SHARED\"],\n [\"transcripts\", \"SHARED\"],\n [\"shell-snapshots\", \"SHARED\"],\n // The underscored variant is the historical exclude-list name; some\n // Claude Code versions may still use it. Classify SHARED so either\n // spelling resolves correctly.\n [\"shell_snapshots\", \"SHARED\"],\n [\"plans\", \"SHARED\"],\n [\"file-history\", \"SHARED\"],\n [\"backups\", \"SHARED\"],\n])\n\nfunction policyFor(name: string): MirrorPolicy {\n return CLAUDE_HOME_POLICY.get(name) ?? \"MIRRORED\"\n}\n\n/**\n * Test-only export: lets the test suite assert hard regression guards\n * such as `policyFor(\"agents\") === \"MIRRORED\"` (preventing accidental\n * reclassification that would let `sweepStalePeerAgentMdFiles` delete\n * files in the user's real `~/.claude/agents/`).\n */\nexport const __testing = {\n policyFor,\n ensureSharedSymlink,\n // The mirror walk runs concurrently under a bounded semaphore; both are\n // exposed so tests can prove the copy stays byte-identical to the serial\n // version and that the bound actually bounds.\n mirrorDirRecursive: (sourceDir: string, targetDir: string, relPath: string) =>\n mirrorDirRecursive(sourceDir, targetDir, relPath),\n createSemaphore,\n get MIRROR_COPY_CONCURRENCY() {\n return MIRROR_COPY_CONCURRENCY\n },\n}\n\n/**\n * Names with `SHARED` policy, materialized once for iteration in\n * `ensureClaudeConfigMirror`'s post-copy phase.\n */\nconst SHARED_TOPLEVEL_NAMES: ReadonlyArray<string> = Array.from(\n CLAUDE_HOME_POLICY.entries(),\n)\n .filter(([, kind]) => kind === \"SHARED\")\n .map(([name]) => name)\n\n/**\n * Marker file written into the router-owned CLAUDE_CONFIG_DIR so users\n * (and our own future sweeps) can identify that the dir is managed by\n * github-router. Content is informational only; no logic depends on\n * its presence.\n */\nconst MANAGED_MARKER_FILENAME = \".github-router-managed\"\n\n/**\n * Synthetic Console OAuth credential the router writes into its own\n * `CLAUDE_CONFIG_DIR/.credentials.json` so spawned Claude Code (and\n * any teammates it spawns) can authenticate without a real user\n * `/login`.\n *\n * Schema verified verbatim from `claude` v2.1.140 binary, function\n * `guH` (the credentials-save mutation). Fields:\n * - `accessToken` — sent as `Authorization: Bearer ...` to the\n * proxy. Proxy accepts any bearer (per CLAUDE.md \"doesn't enforce\n * auth\").\n * - `refreshToken` — only used by Claude Code's reactive refresh\n * path (function `nH8`), which fires on 401 from upstream. The\n * proxy maintains the no-401 invariant on the Anthropic-shape\n * boundary, so this is never invoked. Synthetic value is fine.\n * - `expiresAt` — far-future (2099-01-01 ms epoch). Sidesteps the\n * proactive refresh path (`R8H(expiresAt)` returns false).\n * - `scopes` — claude-ai-shaped so `tB(scopes)` returns true,\n * making `Hq()` true (full feature surface, not \"inference only\").\n * - `subscriptionType` — `\"max\"`. Pure client-side label\n * (`e7()` / `Zc_()` / `CZ1()`); no server validation since\n * `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1` suppresses\n * subscription-validation calls. Picks the most-permissive gating.\n * - `rateLimitTier` — `\"default_claude_max_20x\"`. Paired with\n * `subscriptionType:\"max\"` this is the real Max-20x tier, so the\n * credential is internally consistent (vs the prior odd `max`+`null`).\n * Verified live (claude v2.1.158) call-sites are cosmetic billing /\n * upsell-suppression UI plus the `getPlanModeV2AgentCount` (`bGK`)\n * `max && 20x → 3` branch — which `CLAUDE_CODE_PLAN_V2_AGENT_COUNT`\n * (set to 7 in server-setup) already overrides, so this is\n * belt-and-suspenders for the natural code path. No client-side quota\n * enforcement keys off the tier (rate-limit UI reads server\n * `x-ratelimit-*` headers; the proxy holds the no-429 invariant).\n */\nconst SYNTHETIC_CREDENTIAL = {\n claudeAiOauth: {\n accessToken: \"github-router-synthetic\",\n refreshToken: \"github-router-synthetic\",\n expiresAt: 4_070_908_800_000,\n scopes: [\"user:inference\", \"user:profile\"],\n subscriptionType: \"max\",\n rateLimitTier: \"default_claude_max_20x\",\n clientId: \"github-router\",\n },\n} as const\n\n/**\n * Router-owned fields injected into `<CLAUDE_CONFIG_DIR>/.claude.json` so\n * Claude Code skips its first-launch onboarding wizard. The synthetic\n * `.credentials.json` covers the OAuth *token*, but on a fresh machine\n * the wizard runs anyway — and one of its steps is the browser-OAuth\n * \"Sign in to your Anthropic account\" prompt that the user sees today.\n *\n * Schema verified against `claude` v2.1.158 binary strings:\n *\n * - `hasCompletedOnboarding: true` — single load-bearing gate.\n * Binary code path: `if (!E_().hasCompletedOnboarding) { ... await\n * Onboarding.default(...) }`. Setting true causes the wizard\n * (including the OAuth login step) to be skipped entirely.\n *\n * - `bypassPermissionsModeAccepted: true` — skips the\n * `--dangerously-skip-permissions` trust disclaimer. Binary gate:\n * `K = Ip() || Boolean(E_().bypassPermissionsModeAccepted)`. The\n * proxy passes `--dangerously-skip-permissions` so `Ip()` typically\n * suffices, but pinning the field covers edge code paths (e.g.\n * `--bg --dangerously-skip-permissions`) that still consult it\n * directly and would otherwise throw \"requires accepting the\n * disclaimer first.\"\n *\n * - `oauthAccount` — nice-to-have for UI consistency. Claude Code\n * reads sub-fields with optional chaining (`oauthAccount?.accountUuid`,\n * `oauthAccount?.organizationUuid`, `oauthAccount?.organizationRole`)\n * so undefined doesn't trigger login — but populating gives any\n * \"logged in as X\" / status-line UI something deterministic and\n * obviously-synthetic to display. Values are intentionally\n * non-credible (`github-router@local`, all-zero UUIDs) so any\n * leak into logs is self-documenting, not impersonation.\n *\n * Merge semantics: the two boolean gates are **force-overridden** —\n * even an existing `false` is flipped to `true`. (A user logging out\n * via the Claude Code UI cannot defeat the proxy-session bypass.)\n * `oauthAccount` is overwritten only when the existing value isn't a\n * *structurally usable* one (presence-only would preserve `{}` or\n * `{foo:1}` shapes that defeat the fix); a real OAuth identity with\n * non-empty `accountUuid` and `organizationUuid` is preserved as-is.\n * Every OTHER top-level field the user brought in via the mirror walk\n * (settings, project history, user-side MCP entries, etc.) is preserved\n * verbatim. The mirror is a router-controlled view, not a faithful\n * copy — same trade-off the synthetic credential already makes.\n */\nconst SYNTHETIC_CLAUDE_JSON_FIELDS = {\n hasCompletedOnboarding: true,\n bypassPermissionsModeAccepted: true,\n oauthAccount: {\n accountUuid: \"00000000-0000-0000-0000-000000000000\",\n organizationUuid: \"00000000-0000-0000-0000-000000000000\",\n organizationRole: \"member\",\n emailAddress: \"github-router@local\",\n organizationName: \"github-router\",\n },\n} as const\n\n/**\n * Snapshot-copy the user's `~/.claude/` into the router-owned\n * CLAUDE_CONFIG_DIR (real files, not symlinks — symlinks don't isolate\n * writes), classifying each top-level entry per `CLAUDE_HOME_POLICY`:\n * ISOLATED entries are skipped, MIRRORED entries are copied, and\n * SHARED entries become directory symlinks back to `~/.claude/<X>` so\n * chat history (in `projects/<cwd-hash>/<session-uuid>.jsonl`) and\n * other durable user state flow between proxy and plain-`claude`\n * sessions. Then writes the synthetic `.credentials.json` so spawned\n * Claude Code (and teammates that inherit `CLAUDE_CONFIG_DIR`)\n * authenticate.\n *\n * Idempotent: only re-copies files whose source `mtime` is newer than\n * target; SHARED-symlink creation no-ops when the symlink already\n * points at the right target. Concurrent-safe: `mkdir({recursive:true})`\n * is idempotent; symlinks are created via atomic temp+rename so two\n * parallel github-router-claude startups can't race to EEXIST; the\n * credentials write uses temp-file + atomic rename so Claude Code's\n * `EZ1()` mtime watcher never sees a partial write.\n *\n * Walks with `lstat` (does NOT follow symlinks during traversal — a\n * symlink-into-`/` would otherwise let the walk escape). Symlink leaves\n * in the source tree are skipped during the MIRRORED copy walk (per the\n * symlink-confused-deputy security finding); SHARED symlinks are\n * created on the mirror side only, pointing at predetermined targets\n * inside the user's real `~/.claude/`.\n *\n * Caller is expected to invoke this after `ensurePaths()` and before\n * spawning Claude Code (`launchChild`). The mirror must exist before\n * the child reads it. Currently called from the `claude` subcommand\n * entry point only; `start` and `codex` subcommands don't need it.\n */\nexport async function ensureClaudeConfigMirror(opts: {\n realHome?: string\n} = {}): Promise<void> {\n const realHome = opts.realHome ?? os.homedir()\n const sourceDir = path.join(realHome, \".claude\")\n const targetDir = PATHS.CLAUDE_CONFIG_DIR\n\n // 1. Create our config dir (idempotent, mode 0o700)\n await fs.mkdir(targetDir, { recursive: true, mode: 0o700 })\n await chmodIfPossible(targetDir, 0o700)\n\n // 2. Snapshot-copy from ~/.claude if it exists. Only MIRRORED entries\n // flow through this walk; ISOLATED and SHARED entries are filtered\n // in `mirrorDirRecursive` and handled separately.\n let sourceExists = false\n try {\n const sourceStat = await fs.stat(sourceDir)\n sourceExists = sourceStat.isDirectory()\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n consola.debug(`ensureClaudeConfigMirror: cannot stat ${sourceDir}:`, err)\n }\n }\n if (sourceExists) {\n await mirrorDirRecursive(sourceDir, targetDir, \"\")\n }\n\n // 3. Always ensure agents/ exists (even if user has none) so the\n // peer-agent .md emission has a place to write. Empty dir is fine.\n // agents/ is MIRRORED, not SHARED — the proxy writes per-launch\n // `peer-<pid>-*.md` files here and `sweepStalePeerAgentMdFiles`\n // deletes them; routing those operations into the user's real\n // `~/.claude/agents/` would destroy their custom subagent files.\n await fs.mkdir(path.join(targetDir, \"agents\"), { recursive: true })\n\n // 4. Create symlinks for SHARED entries so chat history (and other\n // durable user state) is visible in both proxy and plain-`claude`.\n for (const name of SHARED_TOPLEVEL_NAMES) {\n await ensureSharedSymlink(name, sourceDir, targetDir).catch((err) => {\n consola.debug(\n `ensureClaudeConfigMirror: SHARED symlink for ${name} skipped:`,\n err,\n )\n })\n }\n\n // 5. Write synthetic .credentials.json (only if content differs)\n const credentialsPath = path.join(targetDir, \".credentials.json\")\n const desiredJson = JSON.stringify(SYNTHETIC_CREDENTIAL, null, 2)\n let needsWrite = true\n try {\n const existing = await fs.readFile(credentialsPath, \"utf8\")\n needsWrite = existing.trim() !== desiredJson.trim()\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n consola.debug(`ensureClaudeConfigMirror: cannot read existing credentials:`, err)\n }\n }\n if (needsWrite) {\n // Atomic temp-file + rename so EZ1()'s mtime watcher doesn't see\n // a partial write. wx flag ensures we don't clobber a concurrent\n // writer's tempfile.\n const tempPath = `${credentialsPath}.${process.pid}.tmp`\n try {\n await fs.writeFile(tempPath, desiredJson + \"\\n\", { mode: 0o600, flag: \"wx\" })\n await fs.rename(tempPath, credentialsPath)\n } catch (err) {\n // EEXIST on the tempfile means another concurrent startup is\n // mid-write. Best-effort: skip — the other writer will produce\n // identical content (deterministic constant blob).\n if ((err as NodeJS.ErrnoException).code === \"EEXIST\") {\n consola.debug(\n \"ensureClaudeConfigMirror: concurrent credentials-write detected, skipping\",\n )\n } else {\n await fs.unlink(tempPath).catch(() => {})\n throw err\n }\n }\n }\n await chmodIfPossible(credentialsPath, 0o600)\n\n // 6. Inject onboarding-skip fields into <CLAUDE_CONFIG_DIR>/.claude.json.\n // Runs AFTER the mirror walk (step 2) so a user's existing\n // .claude.json content (settings, projects, user-side mcpServers)\n // is preserved, and BEFORE `injectPeerMcpIntoMirror` (called later\n // from `src/claude.ts`) which read-merge-writes the same file and\n // will preserve our fields naturally.\n //\n // Without this step, on a brand-new machine where ~/.claude/.claude.json\n // doesn't exist, nothing lands in the mirror, Claude Code's\n // `hasCompletedOnboarding` gate defaults to falsy, and the\n // first-launch wizard runs — including the \"Sign in to your\n // Anthropic account\" OAuth step that defeats the synthetic\n // credential. See SYNTHETIC_CLAUDE_JSON_FIELDS for the gate\n // rationale + binary-verified field shape.\n await injectSyntheticClaudeJsonFields(targetDir, sourceDir, realHome)\n\n // 6a. Postcondition: verify the gate fields actually landed in the\n // mirror's .claude.json. The helper has several \"warn and\n // return\" branches (non-regular target, non-ENOENT read failure)\n // where falling through silently would let Claude Code's\n // first-launch wizard fire — the exact bug this whole\n // subsystem exists to prevent. Re-reading the file and\n // asserting the booleans converts every silent-fail path into a\n // fail-loud launch error, which `src/claude.ts` surfaces to the\n // user with `process.exit(1)`. Cheap (one extra readFile +\n // JSON.parse on a small file we just wrote) and gives the\n // no-silent-degradation invariant teeth even if a future\n // contributor adds a new bail-out branch.\n await assertOnboardingGateInjected(targetDir)\n\n // 7. Write/refresh marker file. Use lstat (not access) to detect\n // symlinks at the marker path — a previously-mirrored or\n // user-placed symlink could otherwise let our `fs.writeFile`\n // follow through to an arbitrary target. With the symlink-skip\n // policy in `mirrorDirRecursive` this is defense-in-depth, but\n // cheap and definitive.\n const markerPath = path.join(targetDir, MANAGED_MARKER_FILENAME)\n let markerExists = false\n try {\n const markerStat = await fs.lstat(markerPath)\n if (markerStat.isFile()) {\n markerExists = true\n } else {\n // Anything non-regular (symlink, dir, special file) is a red flag —\n // refuse to overwrite, log loudly. The user can investigate.\n consola.warn(\n `ensureClaudeConfigMirror: ${markerPath} exists but is not a regular file (mode=${markerStat.mode.toString(8)}); refusing to overwrite. Inspect and remove manually if safe.`,\n )\n markerExists = true\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n consola.debug(`ensureClaudeConfigMirror: cannot lstat marker:`, err)\n markerExists = true\n }\n }\n if (!markerExists) {\n const body = `Managed by github-router. Created ${new Date().toISOString()}. Safe to delete (will be recreated).\\n`\n // wx flag (O_CREAT | O_EXCL) refuses to clobber an existing\n // file or symlink (POSIX O_EXCL behavior) — additional protection\n // against the marker-symlink confused-deputy vector.\n await fs\n .writeFile(markerPath, body, { mode: 0o600, flag: \"wx\" })\n .catch((err) => {\n consola.debug(`ensureClaudeConfigMirror: marker write skipped:`, err)\n })\n }\n}\n\n/**\n * Read-merge-atomic-write the router-required onboarding-skip fields\n * (see `SYNTHETIC_CLAUDE_JSON_FIELDS`) into `<targetDir>/.claude.json`.\n *\n * Inputs:\n * - `targetDir` — the per-launch mirror dir we own.\n * - `sourceDir` — the user's `~/.claude/` (subdir-level legacy path).\n * - `realHome` — the user's `$HOME`. Used to locate the **canonical**\n * `~/.claude.json` (HOME level), which is what Claude Code's path\n * resolver `qG` actually reads when `CLAUDE_CONFIG_DIR` is unset:\n * `path.join(process.env.CLAUDE_CONFIG_DIR || homedir(),\n * \".claude.json\")`. The mirror walk operates on `~/.claude/` and\n * thus can only see the subdir-level `~/.claude/.claude.json`\n * (often a stale shadow); the canonical HOME-level file — where\n * `claude mcp add` writes and Claude Code reads on default\n * invocations — would otherwise be invisible to the proxy session.\n *\n * Behaviour:\n * - Mirror's `.claude.json` is a non-regular file (symlink, dir,\n * etc.) → log warn, refuse to touch. The postcondition check in\n * `ensureClaudeConfigMirror` then throws, blocking launch — the\n * user investigates the warn-flagged path rather than landing in\n * the OAuth wizard.\n * - Read existing content from the first VALID source, in priority\n * order:\n * (a) `~/.claude.json` HOME-level (canonical Claude Code path)\n * (b) The mirror file itself (snapshot-walk's product; only\n * consulted if it's a regular file per step 1)\n * (c) `~/.claude/.claude.json` subdir-level (legacy)\n * A candidate that's missing (ENOENT), too large (> 50 MiB cap),\n * or fails to parse as a non-empty object is **skipped** — the\n * loop falls through to the next candidate, so a malformed\n * higher-priority source can't drop valid lower-priority content\n * on the floor. HOME-level non-ENOENT read failures warn\n * (user-visible — corp perms / OneDrive). All other failures\n * debug-log. `fs.readFile` follows symlinks; reading user-owned\n * content into a per-launch user-owned dir is not a privilege\n * escalation (same uid).\n * - Merge: **force-override** all three synthetic fields\n * unconditionally — `hasCompletedOnboarding`,\n * `bypassPermissionsModeAccepted`, AND `oauthAccount`. A\n * within-proxy \"log out\" is impossible by design. `oauthAccount`\n * is overwritten even when the user has a real one, because\n * pairing the synthetic OAuth token (from `.credentials.json`)\n * with a real-user identity blob creates a split-brain auth\n * state that Claude Code's identity-vs-token cross-checks may\n * treat as session corruption (triggering re-login → defeats\n * the fix). The user's real identity remains intact in their\n * own `~/.claude.json`; only the proxy session sees synthetic.\n * `Object.assign(Object.create(null), existing)` instead of spread\n * defuses the `__proto__` prototype-pollution sink from JSON.parse.\n * - Idempotency: skip the write IFF the source we read FROM is the\n * mirror itself AND the current mirror content is byte-equivalent\n * to what we'd write. Bytes-on-disk comparison is robust against\n * filesystem mtime resolution (FAT/exFAT 2 s buckets) and the\n * source-presence-vs-mirror-presence conflation that broke\n * round-2's `needsWrite = !hadExisting` heuristic.\n *\n * Atomic write: tempfile (per-PID + 4-byte random suffix matching\n * `injectPeerMcpIntoMirror`'s pattern, so collision is effectively\n * impossible) + rename, mode 0o600. Pre-chmod the destination before\n * rename to defuse Windows read-only attribute issues. Any write\n * error throws to the caller — `src/claude.ts` fails the launch\n * loudly. Combined with the postcondition check, every silent-\n * degradation path is converted to a fail-loud launch error.\n */\nasync function injectSyntheticClaudeJsonFields(\n targetDir: string,\n sourceDir: string,\n realHome: string,\n): Promise<void> {\n const claudeJsonPath = path.join(targetDir, \".claude.json\")\n\n // 1. lstat the mirror path before touching it (defense-in-depth,\n // matching the marker-write at step 7). If something other than a\n // regular file occupies the slot, refuse — `fs.readFile` /\n // `chmodIfPossible` / `fs.rename` would otherwise follow / clobber\n // in surprising ways. The postcondition then throws.\n let mirrorIsRegularFile = false\n try {\n const mirrorStat = await fs.lstat(claudeJsonPath)\n if (mirrorStat.isFile()) {\n mirrorIsRegularFile = true\n } else {\n consola.warn(\n `ensureClaudeConfigMirror: ${claudeJsonPath} exists but is not a regular file (mode=${mirrorStat.mode.toString(8)}); refusing to inject synthetic fields. Inspect and remove manually if safe.`,\n )\n return\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n consola.warn(\n `ensureClaudeConfigMirror: cannot lstat ${claudeJsonPath}; refusing to inject synthetic fields (overwriting an unreadable file would risk destroying user content):`,\n err,\n )\n return\n }\n // ENOENT: mirror file doesn't exist yet — fine; step 2 skips the\n // mirror candidate and step 4 creates a new file.\n }\n\n // 2. Read existing content from the first VALID source. Priority:\n // (a) `~/.claude.json` HOME-level (canonical Claude Code path)\n // (b) Mirror file (the snapshot-walk's product; only if regular)\n // (c) `~/.claude/.claude.json` subdir-level (legacy)\n // A candidate that's missing (ENOENT) or fails to parse as a\n // non-empty object is SKIPPED — the loop falls through to the\n // next candidate. This is the load-bearing fix for \"corrupt HOME\n // blocks valid subdir\": a zero-byte or malformed HOME file no\n // longer drops the user's good subdir content on the floor.\n // Size cap (`MAX_CLAUDE_JSON_BYTES`) skips absurdly large files\n // that would risk OOM. HOME read failures (non-ENOENT) warn\n // loudly — the canonical path failing is user-visible. Mirror /\n // subdir read failures debug-log and fall through.\n // `fs.readFile` follows symlinks; reading user-owned content\n // into a per-launch user-owned dir is not a privilege escalation.\n let existing: Record<string, unknown> = {}\n let selectedSource: string | null = null\n const homeLevelClaudeJson = path.join(realHome, \".claude.json\")\n const subdirClaudeJson = path.join(sourceDir, \".claude.json\")\n const sourceCandidates: Array<string | null> = [\n homeLevelClaudeJson,\n mirrorIsRegularFile ? claudeJsonPath : null,\n subdirClaudeJson,\n ]\n for (const sourceCandidate of sourceCandidates) {\n if (sourceCandidate === null) continue\n try {\n const stat = await fs.stat(sourceCandidate)\n if (stat.size > MAX_CLAUDE_JSON_BYTES) {\n consola.warn(\n `ensureClaudeConfigMirror: ${sourceCandidate} is ${stat.size} bytes (> ${MAX_CLAUDE_JSON_BYTES} cap); skipping this source to avoid OOM. Inspect for accidental log/state accumulation.`,\n )\n continue\n }\n const raw = await fs.readFile(sourceCandidate, \"utf8\")\n const parsed = parseExistingClaudeJson(raw, sourceCandidate)\n if (parsed === null) {\n // Malformed (parse failure or non-object). Skip; try next candidate.\n continue\n }\n existing = parsed\n selectedSource = sourceCandidate\n consola.debug(\n `ensureClaudeConfigMirror: read .claude.json content from ${sourceCandidate}`,\n )\n break\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code\n if (code === \"ENOENT\") {\n // Normal case — skip silently.\n continue\n }\n // Non-ENOENT read/stat failure. Severity depends on which source:\n // - HOME-level (canonical): warn (user-visible — corp perms,\n // OneDrive cloud-only, EACCES, etc.). Fall through, since\n // the gate-injection still needs to happen.\n // - Mirror / subdir: debug. Mirror failures used to abort,\n // but that locked out the still-valid subdir-level fallback;\n // we can always re-create the mirror from any valid source.\n if (sourceCandidate === homeLevelClaudeJson) {\n consola.warn(\n `ensureClaudeConfigMirror: cannot read canonical ${homeLevelClaudeJson}; falling back to other sources. User-scope MCPs added via 'claude mcp add' may be invisible to the proxy session:`,\n err,\n )\n } else {\n consola.debug(\n `ensureClaudeConfigMirror: cannot read source ${sourceCandidate}:`,\n err,\n )\n }\n // Fall through to next candidate. If all miss/fail, proceed\n // with synthetic-only content (existing stays {}).\n }\n }\n\n // 3. Build merged content. `Object.assign(Object.create(null), ...)`\n // instead of `{...existing}` avoids the `__proto__` prototype-\n // pollution sink (`JSON.parse('{\"__proto__\":{...}}')` sets the\n // object's prototype via defineProperty; spreading then triggers\n // the standard `__proto__` setter and mutates merged's prototype\n // chain. Cheap defense, prevents the entire class.\n //\n // All three gate fields are FORCE-OVERRIDDEN regardless of\n // existing value:\n // - `hasCompletedOnboarding` and `bypassPermissionsModeAccepted`\n // are the load-bearing gates we exist to set; a within-proxy\n // \"log out\" is impossible by design.\n // - `oauthAccount` is overwritten unconditionally with the\n // synthetic identity (NOT preserved even when real). Pairing\n // the synthetic OAuth token (from `.credentials.json`) with\n // a real-user `accountUuid` / `organizationUuid` creates a\n // split-brain auth state where Claude Code's identity-vs-\n // token cross-checks (binary `oauthAccount?.accountUuid`\n // consumers) may treat the mismatch as session corruption\n // and trigger re-login — the exact bug we're preventing.\n // The user's real identity remains intact in their own\n // `~/.claude.json`; only the proxy session sees synthetic.\n const merged: Record<string, unknown> = Object.assign(\n Object.create(null) as Record<string, unknown>,\n existing,\n )\n merged.hasCompletedOnboarding = SYNTHETIC_CLAUDE_JSON_FIELDS.hasCompletedOnboarding\n merged.bypassPermissionsModeAccepted = SYNTHETIC_CLAUDE_JSON_FIELDS.bypassPermissionsModeAccepted\n merged.oauthAccount = SYNTHETIC_CLAUDE_JSON_FIELDS.oauthAccount\n\n // 4. Decide if a write is needed. Idempotency contract: skip the\n // write IFF we read FROM the mirror itself AND the current mirror\n // content is byte-equivalent to what we'd write. Bytes-on-disk\n // comparison (vs. inferring from source state) is robust against:\n // - source-presence ≠ mirror-presence (a fully-onboarded HOME\n // file with no mirror yet must still trigger a mirror write)\n // - HOME content newer than mirror (HOME wins → mirror is\n // always rewritten when source was HOME)\n // - filesystem mtime resolution (FAT/exFAT 2s buckets)\n const desiredJson = JSON.stringify(merged, null, 2) + \"\\n\"\n if (selectedSource === claudeJsonPath) {\n try {\n const currentMirrorJson = await fs.readFile(claudeJsonPath, \"utf8\")\n if (currentMirrorJson === desiredJson) {\n await chmodIfPossible(claudeJsonPath, 0o600).catch(() => {})\n return\n }\n } catch {\n // Mirror read failed between step 2's success and now — fall\n // through to write. Worst case we rewrite identical content.\n }\n }\n\n // 5. Atomic temp + rename. Mode 0o600. Per-PID + 4-byte random\n // suffix matches `injectPeerMcpIntoMirror`'s pattern. Any write\n // error throws to the caller — `src/claude.ts` exits the launch\n // with a loud error message rather than silently degrading to\n // the OAuth wizard.\n //\n // Pre-chmod the destination before rename to defuse Windows\n // read-only attribute issues (FAT/exFAT volumes, corp-managed\n // perms): `fs.rename` over a read-only file can fail with EPERM\n // on Windows even though POSIX would silently succeed.\n //\n // Concurrent-racer tolerance: when multiple `ensureClaudeConfigMirror()`\n // calls run in parallel (e.g., from tests, or a future caller),\n // Windows `fs.rename` can fail with EBUSY/EPERM/EACCES if\n // another racer has the destination open or mid-write. The\n // write content is fully deterministic (same SYNTHETIC fields,\n // same force-override merge of the same source-priority list),\n // so a successful rename by ANY racer produces the same bytes\n // we'd produce. On rename failure, verify the on-disk content\n // matches `desiredJson` — if so, accept silently (another\n // racer won the race; the file is correct).\n const tempPath = `${claudeJsonPath}.${process.pid}.${randomBytes(4).toString(\"hex\")}.tmp`\n try {\n await fs.writeFile(tempPath, desiredJson, { mode: 0o600, flag: \"wx\" })\n if (mirrorIsRegularFile) {\n await chmodIfPossible(claudeJsonPath, 0o600).catch(() => {})\n }\n await fs.rename(tempPath, claudeJsonPath)\n } catch (err) {\n await fs.unlink(tempPath).catch(() => {})\n try {\n const observed = await fs.readFile(claudeJsonPath, \"utf8\")\n if (observed === desiredJson) {\n consola.debug(\n `ensureClaudeConfigMirror: rename failed but mirror already holds expected content (concurrent racer won the race):`,\n err,\n )\n await chmodIfPossible(claudeJsonPath, 0o600).catch(() => {})\n return\n }\n } catch {\n // Fall through to rethrow the original error.\n }\n throw err\n }\n await chmodIfPossible(claudeJsonPath, 0o600).catch(() => {})\n}\n\n/**\n * Size cap on candidate `.claude.json` reads in\n * `injectSyntheticClaudeJsonFields`. Real-world files are KB-MB scale;\n * if a `.claude.json` has grown to hundreds of MB (corruption, runaway\n * project history accumulation, accidental log redirect) reading it\n * blocks the event loop and risks OOM. 50 MiB is comfortably above\n * any legitimate Claude Code state and well below typical RSS budgets.\n */\nconst MAX_CLAUDE_JSON_BYTES = 50 * 1024 * 1024\n\n/**\n * Structural check for `oauthAccount`: must be a non-null, non-array\n * object whose `accountUuid` AND `organizationUuid` are non-empty\n * strings. Empty objects / partial blobs (missing UUIDs) do NOT count\n * as \"usable\" — preserving them would let Claude Code's various\n * \"logged in as X\" UIs read undefined sub-fields and (in some flows)\n * fall back to a re-login prompt that defeats the synthetic-credential\n * fix. Field names match the binary's optional-chain reads\n * (`oauthAccount?.accountUuid`, `oauthAccount?.organizationUuid`).\n */\nfunction isUsableOauthAccount(value: unknown): boolean {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return false\n const obj = value as Record<string, unknown>\n const accountUuid = obj.accountUuid\n const organizationUuid = obj.organizationUuid\n return (\n typeof accountUuid === \"string\"\n && accountUuid.length > 0\n && typeof organizationUuid === \"string\"\n && organizationUuid.length > 0\n )\n}\n\n/**\n * Parse a JSON blob expected to be an object. Returns `null` on parse\n * failure or non-object/array value (so callers can SKIP this source\n * rather than treating an empty object as a successful read — a\n * round-1 finding from gemini-critic). Logs a warn so the user can\n * investigate. Used in `injectSyntheticClaudeJsonFields`'s source-\n * priority loop: a malformed HOME-level file falls through to the\n * mirror or subdir candidate instead of dropping valid lower-priority\n * content on the floor.\n */\nfunction parseExistingClaudeJson(\n raw: string,\n contextPath: string,\n): Record<string, unknown> | null {\n try {\n const parsed = JSON.parse(raw) as unknown\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>\n }\n consola.warn(\n `ensureClaudeConfigMirror: ${contextPath} parsed to non-object `\n + `(typeof=${typeof parsed}); skipping this source.`,\n )\n return null\n } catch (err) {\n consola.warn(\n `ensureClaudeConfigMirror: cannot parse ${contextPath} as JSON; `\n + `skipping this source:`,\n err,\n )\n return null\n }\n}\n\n/**\n * Postcondition for `ensureClaudeConfigMirror`: re-reads the mirror's\n * `.claude.json` and verifies that both boolean gate fields actually\n * landed. Converts every \"warn and return\" branch in\n * `injectSyntheticClaudeJsonFields` (non-regular target, non-ENOENT\n * read failure, postcondition lstat race) into a launch-failing\n * throw — `src/claude.ts:197`'s catch path turns it into a clear\n * `process.exit(1)` rather than silently spawning Claude Code into\n * the OAuth wizard. The whole point of this subsystem is preventing\n * that silent degradation; a warn that scrolls by in the launch log\n * is not enough.\n *\n * Cheap (one extra readFile + JSON.parse on a small file we just\n * wrote) and load-bearing: every future contributor who adds a new\n * bail-out branch in the helper above automatically gets fail-loud\n * for free instead of having to remember to thread the failure\n * upward by hand.\n */\nasync function assertOnboardingGateInjected(targetDir: string): Promise<void> {\n const claudeJsonPath = path.join(targetDir, \".claude.json\")\n\n // 1. lstat first — never follow a symlink. A planted symlink at the\n // mirror path pointing at an attacker-controlled file with valid\n // gate fields would otherwise pass a plain readFile check and let\n // the helper's \"non-regular target\" warn-and-return slip through.\n let lst: import(\"node:fs\").Stats\n try {\n lst = await fs.lstat(claudeJsonPath)\n } catch (err) {\n throw new Error(\n `ensureClaudeConfigMirror: postcondition failed — cannot lstat ${claudeJsonPath} after injection (synthetic onboarding-skip fields are required to prevent Claude Code's first-launch wizard, which would defeat the synthetic credential): ${(err as Error).message}`,\n { cause: err },\n )\n }\n if (!lst.isFile()) {\n throw new Error(\n `ensureClaudeConfigMirror: postcondition failed — ${claudeJsonPath} exists but is not a regular file (mode=${lst.mode.toString(8)}). Check the preceding warn-level log lines for the underlying cause; without a regular file holding the gate fields, Claude Code's first-launch wizard fires and defeats the synthetic credential.`,\n )\n }\n\n let raw: string\n try {\n raw = await fs.readFile(claudeJsonPath, \"utf8\")\n } catch (err) {\n throw new Error(\n `ensureClaudeConfigMirror: postcondition failed — cannot read ${claudeJsonPath} after injection: ${(err as Error).message}`,\n { cause: err },\n )\n }\n let parsed: Record<string, unknown>\n try {\n const json = JSON.parse(raw) as unknown\n if (!json || typeof json !== \"object\" || Array.isArray(json)) {\n throw new Error(`parsed to non-object (typeof=${typeof json})`)\n }\n parsed = json as Record<string, unknown>\n } catch (err) {\n throw new Error(\n `ensureClaudeConfigMirror: postcondition failed — ${claudeJsonPath} is not a valid JSON object after injection: ${(err as Error).message}`,\n { cause: err },\n )\n }\n if (\n parsed.hasCompletedOnboarding !== true\n || parsed.bypassPermissionsModeAccepted !== true\n ) {\n throw new Error(\n `ensureClaudeConfigMirror: postcondition failed — ${claudeJsonPath} is missing required gate fields after injection (hasCompletedOnboarding=${String(parsed.hasCompletedOnboarding)}, bypassPermissionsModeAccepted=${String(parsed.bypassPermissionsModeAccepted)}). Check the preceding warn-level log lines for the underlying cause; without these fields Claude Code's first-launch wizard fires and defeats the synthetic credential.`,\n )\n }\n // Reuse `isUsableOauthAccount` so a future change to the structural\n // definition stays consistent across helper write-decision and\n // postcondition. The synthetic blob we always write satisfies this\n // by construction; a regression that drops oauthAccount during the\n // merge would otherwise slip past (per the round-2 codex/opus/\n // gemini-3-lab finding).\n if (!isUsableOauthAccount(parsed.oauthAccount)) {\n throw new Error(\n `ensureClaudeConfigMirror: postcondition failed — ${claudeJsonPath} has invalid oauthAccount after injection (got ${JSON.stringify(parsed.oauthAccount)}). The synthetic identity blob is required so Claude Code's identity-vs-token cross-checks don't trigger a re-login that defeats the synthetic credential.`,\n )\n }\n}\n\n/**\n * Recursive snapshot-copy helper for `ensureClaudeConfigMirror`. Walks\n * `sourceDir/relPath` and mirrors each entry into `targetDir/relPath`.\n * - Top-level entries are dispatched on `policyFor(name)`:\n * - `ISOLATED` → skipped entirely (no presence in mirror).\n * - `SHARED` → skipped from the copy walk; handled by\n * `ensureSharedSymlink` in the post-copy phase.\n * - `MIRRORED` → copied as today.\n * - Symlinks are skipped (not recreated) so the walk never follows out\n * of `sourceDir` and we don't reintroduce a confused-deputy vector.\n * - Files copy only if source mtime > target mtime (idempotent).\n */\n/**\n * Max file operations in flight across the WHOLE mirror walk.\n *\n * The walk is I/O-bound over hundreds of independent small files, so it\n * parallelizes well: measured 1021ms serial vs 311ms at this limit for a real\n * 394-file / 4.7MB `~/.claude` (Windows 11, 16 cores). It must be BOUNDED\n * rather than a bare `Promise.all` over every entry — the mirror is re-copied\n * on every launch (the target is a fresh per-launch dir, so the mtime skip\n * below never fires across launches), and firing hundreds of simultaneous\n * `copyFile`s just trades one bottleneck for file-handle exhaustion.\n *\n * The limit is shared across the recursion via a semaphore rather than applied\n * per-directory: a per-directory bound would multiply with nesting depth and\n * stop being a bound at all.\n */\nconst MIRROR_COPY_CONCURRENCY = 16\n\n/** Counting semaphore. FIFO, so a deep subtree cannot starve a shallow one. */\nfunction createSemaphore(limit: number) {\n let active = 0\n const waiters: Array<() => void> = []\n return async function withSlot<T>(fn: () => Promise<T>): Promise<T> {\n if (active >= limit) {\n await new Promise<void>((resolve) => waiters.push(resolve))\n // Woken by a HAND-OFF: the releaser kept the slot counted on our behalf\n // rather than decrementing, so `active` is already correct here. Doing\n // `active++` would double-count — and worse, decrementing first would\n // open a window where a fresh caller sees a free slot and takes it too,\n // letting the pool exceed `limit`.\n } else {\n active++\n }\n try {\n return await fn()\n } finally {\n const next = waiters.shift()\n // Hand the slot straight to the next waiter (keeping `active` as-is), or\n // give it back to the pool when nobody is queued.\n if (next) next()\n else active--\n }\n }\n}\n\nasync function mirrorDirRecursive(\n sourceDir: string,\n targetDir: string,\n relPath: string,\n withSlot: <T>(fn: () => Promise<T>) => Promise<T> = createSemaphore(\n MIRROR_COPY_CONCURRENCY,\n ),\n): Promise<void> {\n const sourcePath = path.join(sourceDir, relPath)\n let entries: Array<string>\n try {\n // Through the semaphore like every other syscall here. Subdirectories are\n // traversed concurrently, so an unslotted readdir would let a wide or deep\n // tree dispatch hundreds at once — defeating the file-handle bound the\n // semaphore exists to enforce.\n entries = await withSlot(() => fs.readdir(sourcePath))\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return\n consola.debug(`mirrorDirRecursive: cannot readdir ${sourcePath}:`, err)\n return\n }\n // Entries within a directory are independent, so they run concurrently under\n // the shared slot budget. Semantics per entry are unchanged from the serial\n // version; only the scheduling differs.\n await Promise.all(\n entries.map(async (name) => {\n // Policy dispatch at top-level only. Sub-paths within MIRRORED\n // dirs always cascade as MIRRORED.\n if (relPath === \"\") {\n const policy = policyFor(name)\n if (policy === \"ISOLATED\" || policy === \"SHARED\") return\n }\n const childRel = relPath === \"\" ? name : path.join(relPath, name)\n // Never mirror a proxy-authored agent `.md` out of the user's real\n // `~/.claude/agents/`. A pre-mirror release wrote `peer-<pid>-<hex>-<name>.md`\n // files there; left alone they get copied into every new mirror and register\n // alongside the current launch's files under the SAME frontmatter `name:`,\n // so Claude Code resolves between a live definition and a dead one\n // nondeterministically. Filtering at the COPY step fixes that without\n // deleting anything we do not own: the user's directory is left exactly as\n // it is, and the current launch writes its own files into the mirror after\n // this walk. Deleting them instead would be unsound, because the filename\n // shape is evidence of provenance, not proof of it, and a user who copied a\n // generated file to customize it would lose it.\n if (relPath === \"agents\" && PEER_AGENT_MD_FILENAME.test(name)) {\n return\n }\n const childSource = path.join(sourceDir, childRel)\n const childTarget = path.join(targetDir, childRel)\n let stats: Awaited<ReturnType<typeof fs.lstat>>\n try {\n stats = await withSlot(() => fs.lstat(childSource))\n } catch (err) {\n consola.debug(`mirrorDirRecursive: cannot lstat ${childSource}:`, err)\n return\n }\n if (stats.isSymbolicLink()) {\n // Skip symlinks during mirror copy. gemini-critic security finding:\n // recreating user symlinks in our mirror creates a confused-deputy\n // vector — a previously prompt-injected process could place\n // `~/.claude/<X>` → `/some/sensitive/file`, our walker would mirror\n // it, and any subsequent write to `<mirror>/<X>` (by us or by\n // Claude Code) would follow the symlink and overwrite the target.\n // Snapshot-copy semantics make symlink preservation moot anyway:\n // a snapshot is a point-in-time content copy, and a symlink\n // recreated in the mirror points at exactly the same target as\n // the original would have — the user-side symlink is sufficient.\n // If a user has a legitimate need for a symlink to be visible\n // through the proxy session, they can create the equivalent\n // symlink in their `~/.claude/` directly and it'll be reachable\n // — they just won't see it in our mirror dir.\n consola.debug(`mirrorDirRecursive: skipping symlink ${childSource} (security policy)`)\n return\n }\n if (stats.isDirectory()) {\n await withSlot(() => fs.mkdir(childTarget, { recursive: true }))\n await mirrorDirRecursive(sourceDir, targetDir, childRel, withSlot)\n return\n }\n if (stats.isFile()) {\n // mtime-based skip — only copy if source is newer than target.\n let needsCopy = true\n try {\n const targetStat = await withSlot(() => fs.lstat(childTarget))\n if (targetStat.isFile() && targetStat.mtimeMs >= stats.mtimeMs) {\n needsCopy = false\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n consola.debug(`mirrorDirRecursive: lstat target ${childTarget}:`, err)\n }\n }\n if (!needsCopy) return\n try {\n await withSlot(() =>\n fs.copyFile(childSource, childTarget, fs.constants.COPYFILE_FICLONE),\n )\n } catch (err) {\n consola.debug(`mirrorDirRecursive: copy ${childSource} → ${childTarget}:`, err)\n }\n return\n }\n // Skip other inode types (sockets, devices, fifos) silently.\n }),\n )\n}\n\n/**\n * Create or refresh a directory symlink `<mirrorDir>/<name>` →\n * `<sourceDir>/<name>` (i.e. `~/.local/share/github-router/claude-config/<X>`\n * → `~/.claude/<X>`). Idempotent and concurrent-safe.\n *\n * Behavior depending on what's already at `<mirrorDir>/<name>`:\n * - Symlink with the correct target → no-op.\n * - Symlink with the wrong target → replace atomically.\n * - Empty real directory (legacy mirror leftover with no proxy-session\n * writes accumulated yet) → `rmdir` and replace with the symlink.\n * Safe by definition: `fs.rmdir` only succeeds on empty dirs (POSIX),\n * so there is nothing to lose. Smooths the upgrade path for users\n * whose legacy mirror dirs were never written to.\n * - Non-empty real directory or regular file → loud-warn and skip.\n * Auto-deleting would destroy proxy-session writes from the prior\n * version. The user is told the exact path and remediation.\n * - ENOENT → create symlink atomically.\n *\n * Atomic-creation: symlinks are first written at a unique side-path\n * (`<mirrorDir>/<name>.tmp.<pid>.<8 hex>`) and then `fs.rename()`d into\n * place. POSIX `rename` is atomic and replaces an existing symlink in\n * a single step, so two concurrent `github-router claude` startups can't\n * race to `EEXIST` — the loser's rename just overwrites the winner's\n * symlink with an identical one. Gemini-critic 3-lab-review finding.\n *\n * Pre-creates `~/.claude/<name>/` as a real directory if missing so\n * Claude Code's writes through the symlink don't fail with ENOENT.\n */\nasync function ensureSharedSymlink(\n name: string,\n sourceDir: string,\n mirrorDir: string,\n): Promise<void> {\n const sourcePath = path.join(sourceDir, name)\n const mirrorPath = path.join(mirrorDir, name)\n\n // 1. Ensure the source directory exists. Without this, Claude Code's\n // writes through the symlink (e.g. `projects/<hash>/foo.jsonl`)\n // fail with ENOENT on the parent dir.\n try {\n await fs.mkdir(sourcePath, { recursive: true })\n } catch (err) {\n // Escalated from debug → warn per the CLAUDE.md \"smoking gun\"\n // rule (consistent with the symlink and rename catches below):\n // if the source dir cannot be created (e.g. a stray regular file\n // sitting at `~/.claude/projects`, perms blocking mkdir on a\n // corp-managed Windows box, OneDrive cloud-only reparse point),\n // ensureSharedSymlink returns without creating a junction. The\n // spawned Claude Code child then writes to the REAL `~/.claude`\n // while the proxy reads from the mirror — exactly the split-brain\n // pattern this whole function exists to prevent. Silent debug-log\n // hid this from us once already; warn so the user sees the cause.\n consola.warn(\n `ensureSharedSymlink(${name}): cannot mkdir source ${sourcePath}:`,\n err,\n )\n return\n }\n\n // 2. Inspect the mirror-side slot.\n let existing: Awaited<ReturnType<typeof fs.lstat>> | null = null\n try {\n existing = await fs.lstat(mirrorPath)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n // Escalated from debug → warn per the CLAUDE.md \"smoking gun\"\n // rule (consistent with the other fs catches in this function):\n // ENOENT is the only expected-and-benign failure mode here\n // (slot doesn't exist yet — falls through to create). Any other\n // lstat failure (EACCES, ELOOP, EIO from a sketchy reparse\n // point) means we bail without creating the junction, which\n // silently leaves the proxy and child diverged. A visible warn\n // surfaces the root cause instead of a mysteriously missing\n // junction.\n consola.warn(\n `ensureSharedSymlink(${name}): cannot lstat ${mirrorPath}:`,\n err,\n )\n return\n }\n }\n\n if (existing?.isSymbolicLink()) {\n // Resolve both sides to their canonical absolute paths and compare.\n // We use `fs.realpath` rather than the raw `fs.readlink()` output\n // because Windows junctions resolve via readlink to `\\\\?\\`-prefixed\n // device-namespace paths (e.g. `\\\\?\\C:\\Users\\foo\\.claude\\projects`)\n // while we wrote the plain absolute `sourcePath` (e.g.\n // `C:\\Users\\foo\\.claude\\projects`) with `fs.symlink`. A literal\n // `===` on the raw readlink output never matched on Windows, so\n // the fast path silently failed and every startup tore down +\n // recreated all 9 SHARED junctions — masked locally because NTFS\n // File System Tunneling forges the creation timestamp for a name\n // deleted and recreated within 15 s (the per-startup churn was\n // real, the ctime-stable assertion was a false negative). The\n // realpath comparison canonicalizes both forms to the same string\n // on POSIX and Windows alike, and as a bonus handles drive-letter\n // casing / trailing-slash differences too. The extra two syscalls\n // per slot are negligible at proxy startup (runs once per launch).\n //\n // CRITICAL: sourceReal and currentReal are NOT treated symmetrically.\n // If `sourceReal` is null (we just mkdir'd it above, but realpath\n // failed — OneDrive cloud-only reparse point, EACCES on parent,\n // EXDEV mount oddity), we WARN AND RETURN rather than fall through.\n // Falling through would do unlink+symlink+rename with the same\n // failing realpath next launch — silent every-startup churn, the\n // exact bug class round-3 G2 fixed in a different code path.\n // `currentReal === null` is benign (broken/wrong slot — replace).\n const sourceReal = await fs.realpath(sourcePath).catch(() => null)\n if (sourceReal === null) {\n consola.warn(\n `ensureSharedSymlink(${name}): cannot resolve source ${sourcePath} ` +\n `— skipping junction creation to avoid silent every-startup churn. ` +\n `Inspect the source dir's permissions / OneDrive sync state and re-launch.`,\n )\n return\n }\n const currentReal = await fs.realpath(mirrorPath).catch(() => null)\n if (currentReal !== null && currentReal === sourceReal) {\n return\n }\n // Wrong target (or unresolvable mirror) — fall through to the\n // atomic-rename replace path.\n } else if (existing?.isDirectory()) {\n // Legacy real directory at the slot. Try `fs.rmdir` — on POSIX it\n // succeeds ONLY if the directory is empty, so there's nothing to\n // lose. If it's non-empty (ENOTEMPTY) or any other failure occurs,\n // fall back to the warn-and-skip path so we never auto-clobber\n // user data.\n try {\n await fs.rmdir(mirrorPath)\n // Empty dir reaped — fall through to the atomic-rename create path.\n } catch (err) {\n consola.warn(\n `ensureClaudeConfigMirror: ${mirrorPath} is a non-empty real directory ` +\n `from an older github-router version; refusing to clobber. ` +\n `If you want chat-history continuity for \"${name}\", move its ` +\n `contents into ${sourcePath}/ then delete ${mirrorPath}; the ` +\n `mirror will create a symlink (junction on Windows) on next launch. ` +\n `(rmdir error: ${(err as NodeJS.ErrnoException).code ?? \"unknown\"})`,\n )\n return\n }\n } else if (existing) {\n // Regular file (or special inode like a socket) — never auto-clobber.\n consola.warn(\n `ensureClaudeConfigMirror: ${mirrorPath} is a regular file at a ` +\n `SHARED symlink slot; refusing to clobber. Inspect and remove ` +\n `manually if safe; the mirror will create a symlink on next launch.`,\n )\n return\n }\n\n // 3. Atomic-rename creation: symlink to a unique temp path, then\n // rename over the slot. `fs.rename` replaces existing symlinks\n // atomically on POSIX and is safe against concurrent racers.\n // On Windows, MoveFileEx with MOVEFILE_REPLACE_EXISTING does NOT\n // replace an existing directory or junction destination\n // (npm/cli#9021), so when the slot already holds a wrong-target\n // junction we must explicitly unlink it first. The sub-millisecond\n // window of no-link is acceptable: ensureClaudeConfigMirror is\n // idempotent under concurrency and only runs at proxy startup,\n // before any spawned Claude Code child has been launched.\n const tempPath = `${mirrorPath}.tmp.${process.pid}.${randomBytes(4).toString(\"hex\")}`\n try {\n await fs.symlink(\n sourcePath,\n tempPath,\n process.platform === \"win32\" ? \"junction\" : \"dir\",\n )\n } catch (err) {\n // Escalated from debug → warn per the CLAUDE.md \"smoking gun\" rule:\n // the rule applies to ALL fs catches in this function, not just the\n // rename one. The temp path is per-pid + 8-hex random so EEXIST is\n // essentially impossible — any failure here (EPERM on Windows\n // without DevMode, EXDEV cross-volume, ENOSPC, …) is a real\n // operational problem the user needs to see.\n consola.warn(\n `ensureSharedSymlink(${name}): symlink ${tempPath} failed:`,\n err,\n )\n return\n }\n if (process.platform === \"win32\" && existing?.isSymbolicLink()) {\n // Windows-only: clear the wrong-target junction so the rename\n // below can land. Best-effort — if a concurrent racer already\n // unlinked it, the rename succeeds as a CREATE; if a concurrent\n // racer already replaced it with a fresh junction, the rename\n // hits the catch below and we surface a warn.\n await fs.unlink(mirrorPath).catch(() => {})\n }\n try {\n await fs.rename(tempPath, mirrorPath)\n } catch (err) {\n // Escalated from debug → warn per the CLAUDE.md \"smoking gun\"\n // rule (consistent with the fs.symlink catch above): a silent\n // debug log here previously hid the Windows rename-replace bug\n // (junction-over-junction MoveFileEx EPERM). Post-fix, rename\n // failures should be rare and visible.\n consola.warn(\n `ensureSharedSymlink(${name}): rename ${tempPath} → ${mirrorPath} failed:`,\n err,\n )\n await fs.unlink(tempPath).catch(() => {})\n }\n}\n\nasync function ensureFile(filePath: string): Promise<void> {\n try {\n await fs.access(filePath, fs.constants.W_OK)\n } catch {\n await fs.writeFile(filePath, \"\")\n await fs.chmod(filePath, 0o600)\n }\n}\n\nasync function chmodIfPossible(target: string, mode: number): Promise<void> {\n if (process.platform === \"win32\") return // Windows chmod is no-op-ish\n try {\n await fs.chmod(target, mode)\n } catch (err) {\n consola.debug(`chmod ${target} ${mode.toString(8)} failed:`, err)\n }\n}\n\n/**\n * Write a runtime tempfile securely.\n *\n * - Mode `0o600` so other local users (multi-tenant boxes, shared\n * dev containers) can't read the per-launch nonce or runtime URL.\n * - `flag: \"wx\"` (O_CREAT | O_EXCL | O_WRONLY) refuses to overwrite\n * an existing path. POSIX open(2) with O_EXCL also rejects\n * pre-placed symlinks, killing the symlink-clobber attack vector.\n * - The caller's responsibility to pick a path NOT yet in use.\n * We intentionally do NOT pre-unlink: an `lstat` + `unlink` +\n * `open(O_EXCL)` sequence still has a TOCTOU window where an\n * attacker can drop a symlink between unlink and open. Letting\n * `wx` fail is the safer behavior — surfaces the conflict\n * instead of silently following.\n */\nexport async function writeRuntimeFileSecure(\n filePath: string,\n content: string,\n): Promise<void> {\n await fs.writeFile(filePath, content, { mode: 0o600, flag: \"wx\" })\n}\n\n/**\n * Write the ai-or-die artifact creds (base/token/sessionId/insecureTLS) to a\n * mode-600 file in the per-launch mirror so the `internal-artifact-open` hook can\n * authenticate: AIORDIE_TOKEN is stripped from the spawned child env, and passing\n * a token via argv would leak it to `ps`. Insecure-TLS mirrors the artifact\n * client's loopback-only fail-closed policy. Best-effort; atomic temp+rename.\n */\nexport async function writeArtifactCredsToMirror(\n insecureTLS: boolean,\n env: NodeJS.ProcessEnv = process.env,\n): Promise<void> {\n const baseUrl = env.AIORDIE_BASE_URL\n const token = env.AIORDIE_TOKEN\n const sessionId = env.AIORDIE_SESSION_ID\n if (!baseUrl || !token || !sessionId) return\n const target = path.join(PATHS.CLAUDE_CONFIG_DIR, \".aiordie-artifact.json\")\n const tempPath = `${target}.${process.pid}.tmp`\n const body = JSON.stringify({ baseUrl, token, sessionId, insecureTLS }) + \"\\n\"\n await fs.writeFile(tempPath, body, { mode: 0o600, flag: \"wx\" }).catch(async (err) => {\n if ((err as NodeJS.ErrnoException).code === \"EEXIST\") return\n throw err\n })\n await fs.rename(tempPath, target).catch(() => {})\n await chmodIfPossible(target, 0o600)\n}\n\n/**\n * Sweep stale runtime tempfiles. Removes files whose embedded PID is no\n * longer a live process. A proxy crash (`kill -9`, OS reboot) leaves\n * orphans that would otherwise accumulate forever — and worse, a stale\n * config pointing at a now-recycled port could route MCP traffic to\n * whatever process bound that port next.\n *\n * Naming convention: `peer-mcp-<pid>.json` and `peer-agents-<pid>.json`.\n * Files not matching either pattern are left alone — this directory\n * is shared with future runtime artifacts.\n *\n * We deliberately do NOT age-prune files whose PID is alive. A\n * legitimately long-running proxy can have a tempfile older than any\n * arbitrary threshold; deleting it out from under the live process\n * breaks the spawned Claude Code child's MCP/agent wiring with no clean\n * recovery. PID-wraparound risk is mitigated by (a) PID reuse on Linux\n * being slow under typical loads, and (b) the file is only consulted by\n * github-router itself — an unrelated process that inherits the PID\n * never reads it.\n */\nexport async function sweepStaleRuntimeFiles(): Promise<void> {\n const dir = PATHS.CLAUDE_RUNTIME_DIR\n let entries: Array<string>\n try {\n entries = await fs.readdir(dir)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return\n throw err\n }\n\n for (const name of entries) {\n // Match both legacy `peer-mcp-<pid>.json` and current\n // `peer-mcp-<pid>-<rand>.json` filenames so we can clean up either.\n const match = /^peer-(?:mcp|agents)-(\\d+)(?:-[0-9a-f]+)?\\.json$/.exec(name)\n if (!match) continue\n const pid = Number.parseInt(match[1], 10)\n const filePath = path.join(dir, name)\n\n if (isPidAlive(pid)) continue\n\n await fs.unlink(filePath).catch(() => {\n // already gone or unreadable, fine\n })\n }\n}\n\nfunction isPidAlive(pid: number): boolean {\n if (!Number.isInteger(pid) || pid <= 0) return false\n try {\n // signal 0 = check existence without delivering a signal. EPERM\n // means the process exists but we can't signal it (which is still\n // \"alive\" for our purposes); ESRCH means it's gone.\n process.kill(pid, 0)\n return true\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code\n if (code === \"EPERM\") return true\n return false\n }\n}\n\n/**\n * Sweep stale peer-* subagent .md files from the router-owned\n * `CLAUDE_CONFIG_DIR/agents/`. Phase 2.5 writes one .md per peer agent\n * into Claude Code's agents directory (now our config dir's `agents/`\n * subdir, since `getClaudeCodeEnvVars` points `CLAUDE_CONFIG_DIR` at\n * `PATHS.CLAUDE_CONFIG_DIR`) so they appear in Claude Code's Task\n * `subagent_type` enum. Files are named `peer-<pid>-<rand>-<agentName>.md`\n * so this sweep can drop orphans from crashed prior proxy sessions\n * without touching the user's own .md files (which were copied into\n * the same dir during `ensureClaudeConfigMirror`).\n *\n * Same liveness rule as `sweepStaleRuntimeFiles`: only delete when the\n * file's embedded PID is no longer alive. Live PIDs keep their files —\n * a long-running proxy doesn't lose its agent registrations.\n *\n * Regex tightening (Phase 2.6, codex-critic + gemini-critic 2-lab finding):\n * the original sweep regex `^peer-(\\d+)(?:-[0-9a-f]+)?-.+\\.md$` was too\n * permissive — a user-authored `peer-12345-meeting-notes.md` matches\n * (`12345` = \"PID\", `-meeting-notes` = trailing `.+`) and would be\n * silently unlinked when 12345 happens to be a dead PID (overwhelmingly\n * likely). Tightened to require BOTH the 8-hex-char random suffix AND\n * an exact-match persona name suffix, eliminating the risk for any\n * realistic user filename.\n *\n * SCOPE IS THE MIRROR ONLY, deliberately. This deletes on filename shape plus\n * PID liveness, and neither establishes ownership: a user who copied a generated\n * file to customize it would have a file this cannot distinguish from ours. That\n * ambiguity is acceptable inside the router-owned mirror, where the worst case\n * is dropping a snapshot that the next launch regenerates. It would NOT be\n * acceptable in the user's real `~/.claude/agents/`, where the same false\n * positive is permanent data loss, which is exactly why `agents` is MIRRORED\n * rather than SHARED (see `policyFor`). Orphans a pre-mirror release left in the\n * user's directory are handled non-destructively at the copy step in\n * `mirrorDirRecursive`, which skips them so they never register.\n */\nexport async function sweepStalePeerAgentMdFiles(): Promise<void> {\n const dir = path.join(PATHS.CLAUDE_CONFIG_DIR, \"agents\")\n let entries: Array<string>\n try {\n entries = await fs.readdir(dir)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return\n throw err\n }\n for (const name of entries) {\n const match = PEER_AGENT_MD_FILENAME.exec(name)\n if (!match) continue\n const pid = Number.parseInt(match[1], 10)\n if (isPidAlive(pid)) continue\n await fs.unlink(path.join(dir, name)).catch(() => {\n // already gone or unreadable, fine\n })\n }\n}\n\n/**\n * Strict regex matching only files this proxy writes:\n * peer-<pid>-<8 hex>-<exact persona/coordinator/native/dispatcher name>.md\n * The name allowlist is the load-bearing protection against deleting user\n * files. Update this list whenever a new persona is added to `PERSONAS_READ` /\n * `PERSONAS_WRITE` in `peer-mcp-personas.ts`, a new coordinator-style or native\n * subagent is added in `codex-mcp-config.ts`, or a new `worker-*` dispatcher mode\n * is added in `worker-dispatch.ts` (a drift test in tests/worker-dispatch pins\n * that the `ALL_DISPATCHER_AGENT_NAMES` are all covered here).\n *\n * NAMES ARE ADDED HERE, NEVER REMOVED — this alternation is a permanent superset\n * of every agent name the proxy has ever shipped. A name dropped on a rename\n * stops matching, so `.md` files left behind by a crashed session bearing the old\n * name are never swept: they survive every later launch and load as ghost\n * subagents with a stale prompt and a stale model. That outlives any release\n * window, so \"keep the old name for one version\" is not sufficient. `debugger`\n * and `qa-engineer` are retained for exactly this reason after being renamed to\n * `brainstorm` and `reviewer`.\n *\n * `general-purpose|Explore|Plan` are the serve-only `BUILTIN_SUBAGENT_DEFINITIONS`,\n * which are written through the same `peer-<suffix>-<name>.md` path and were\n * previously absent here, so their stale files never got reaped. Note the capitals:\n * the alternation is case-sensitive and those two are capitalized at the source.\n */\nexport const PEER_AGENT_MD_FILENAME =\n /^peer-(\\d+)-[0-9a-f]{8}-(?:codex-critic|codex-reviewer|gemini-critic|gemini-reviewer|opus-critic|codex-implementer|peer-review-coordinator|implementer|reviewer|brainstorm|scout|scribe|generic-fast|generic-cheap|generic|debugger|qa-engineer|general-purpose|Explore|Plan|worker-explore|worker-implement|worker-review|worker-plan|worker-test|worker-browse)\\.md$/\n\n/**\n * Strict regex matching only per-launch claude-config mirror dirs this\n * proxy creates: `<pid>-<8 hex>`. Anchored to the entire entry name so\n * user-authored siblings under `<appDir>/claude-config/` (if any) are\n * untouchable. The PID prefix is what `sweepStaleClaudeConfigMirrors`\n * keys off; the 8-hex random suffix matches `randomBytes(4)` exactly\n * (no `?` — files created by a different shape are not ours).\n */\nconst CLAUDE_CONFIG_MIRROR_DIR = /^(\\d+)-[0-9a-f]{8}$/\n\n/**\n * Sweep stale per-launch CLAUDE_CONFIG_DIR mirrors left behind by\n * crashed prior proxy sessions. Symmetric to `sweepStalePeerAgentMdFiles`\n * — same liveness rule (only delete when the embedded PID is dead),\n * same strict regex (the dir-name allowlist is the load-bearing\n * protection against deleting user-authored siblings).\n *\n * Scans `<appDir>/claude-config/` (the parent of the per-launch dirs).\n * Each entry whose name matches `<pid>-<8 hex>` AND whose PID is no\n * longer alive is removed recursively. `fs.rm({recursive: true})`\n * walks the tree calling `unlink` on symlinks/junctions rather than\n * following them, so the SHARED junctions back to `~/.claude/<X>`\n * are removed without touching their targets.\n *\n * Tolerates missing parent dir (first-ever launch, or user wiped it).\n */\nexport async function sweepStaleClaudeConfigMirrors(): Promise<void> {\n const parent = path.join(appDir(), \"claude-config\")\n let entries: Array<string>\n try {\n entries = await fs.readdir(parent)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return\n throw err\n }\n for (const name of entries) {\n const match = CLAUDE_CONFIG_MIRROR_DIR.exec(name)\n if (!match) continue\n const pid = Number.parseInt(match[1], 10)\n if (isPidAlive(pid)) continue\n await fs\n .rm(path.join(parent, name), { recursive: true, force: true })\n .catch((err) => {\n // Best-effort: stale-dir cleanup must never block startup.\n // Common failure modes (worth surviving silently): an EBUSY/EPERM\n // on Windows if a leftover handle is still open, or a stray\n // root-owned file inside the dir from a previous run with\n // different permissions.\n consola.debug(\n `sweepStaleClaudeConfigMirrors: cannot rm ${name}:`,\n err,\n )\n })\n }\n}\n\n/**\n * Remove THIS launch's per-launch CLAUDE_CONFIG_DIR on shutdown.\n * Best-effort: a failure here must not block process exit (the caller\n * wraps this in a `.catch`-equivalent via `launchChild`'s onShutdown\n * try/catch). Symmetric to `writePeerMcpRuntimeFiles`'s `cleanup()`:\n * we own this dir for the lifetime of the proxy, so removing it on\n * normal shutdown is correct; the boot-time sweep handles the\n * abnormal-exit case.\n *\n * `fs.rm({recursive: true})` removes SHARED junctions via unlink\n * (does NOT follow them into the user's real `~/.claude/<X>`).\n */\nexport async function removeOwnClaudeConfigMirror(): Promise<void> {\n const dir = PATHS.CLAUDE_CONFIG_DIR\n await fs.rm(dir, { recursive: true, force: true }).catch((err) => {\n consola.debug(`removeOwnClaudeConfigMirror: rm ${dir} skipped:`, err)\n })\n}\n"],"mappings":"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"}
|