github-router 0.3.321 → 0.3.322

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/{aic-ledger-BrDqsuge.js → aic-ledger-BW3zMiCx.js} +19 -2
  2. package/dist/aic-ledger-BW3zMiCx.js.map +1 -0
  3. package/dist/{attribution-settings-DHWo0lyG.js → attribution-settings-z-U2L7ab.js} +4 -4
  4. package/dist/{attribution-settings-DHWo0lyG.js.map → attribution-settings-z-U2L7ab.js.map} +1 -1
  5. package/dist/browser-ext/manifest.json +1 -1
  6. package/dist/{claude-uAFPKvuS.js → claude-DtWf7DEw.js} +8 -8
  7. package/dist/{claude-uAFPKvuS.js.map → claude-DtWf7DEw.js.map} +1 -1
  8. package/dist/{codex-CVHdye0h.js → codex-CDtFT-Qp.js} +4 -4
  9. package/dist/{codex-CVHdye0h.js.map → codex-CDtFT-Qp.js.map} +1 -1
  10. package/dist/{copilot-discount-BnJkEojk.js → copilot-discount-I6BrT7QE.js} +2 -2
  11. package/dist/{copilot-discount-BnJkEojk.js.map → copilot-discount-I6BrT7QE.js.map} +1 -1
  12. package/dist/engine-DA5laGIh.js +2 -0
  13. package/dist/{gate-discovery-DUBuxiLb.js → gate-discovery-j_hQ1IPc.js} +2 -2
  14. package/dist/{gate-discovery-DUBuxiLb.js.map → gate-discovery-j_hQ1IPc.js.map} +1 -1
  15. package/dist/{internal-aic-status-Dc2JQDsX.js → internal-aic-status-CKXxxb30.js} +3 -3
  16. package/dist/{internal-aic-status-Dc2JQDsX.js.map → internal-aic-status-CKXxxb30.js.map} +1 -1
  17. package/dist/{internal-stop-hook-DCjYE2x0.js → internal-stop-hook-BGTPRCtO.js} +2 -2
  18. package/dist/{internal-stop-hook-DCjYE2x0.js.map → internal-stop-hook-BGTPRCtO.js.map} +1 -1
  19. package/dist/main.js +6 -6
  20. package/dist/{peer-mcp-personas-BerVEkZ9.js → peer-mcp-personas-Gb0LfII0.js} +10 -7
  21. package/dist/{peer-mcp-personas-BerVEkZ9.js.map → peer-mcp-personas-Gb0LfII0.js.map} +1 -1
  22. package/dist/{provision-o4bXbsvB.js → provision-DagZEkdZ.js} +2 -2
  23. package/dist/{provision-o4bXbsvB.js.map → provision-DagZEkdZ.js.map} +1 -1
  24. package/dist/{serve-DSL2Fcnc.js → serve-DOMsmIk-.js} +5 -5
  25. package/dist/{serve-DSL2Fcnc.js.map → serve-DOMsmIk-.js.map} +1 -1
  26. package/dist/{server-setup-CXXKmPIE.js → server-setup-DQVRJluz.js} +23 -12
  27. package/dist/server-setup-DQVRJluz.js.map +1 -0
  28. package/dist/{start-Cl_1UMg_.js → start-CqSFR_s8.js} +3 -3
  29. package/dist/{start-Cl_1UMg_.js.map → start-CqSFR_s8.js.map} +1 -1
  30. package/package.json +1 -1
  31. package/dist/aic-ledger-BrDqsuge.js.map +0 -1
  32. package/dist/engine-57iIqICe.js +0 -2
  33. package/dist/server-setup-CXXKmPIE.js.map +0 -1
@@ -1,8 +1,8 @@
1
- import { D as toolbeltEnabled, _n as DEFAULT_CODEX_MODEL, in as provisionAndIndexColbert, nn as hasSupportedBrowserInstalled, rn as colbertDegradedWarning, sn as warmTreeSitterPool, tn as provisionBrowserAssets, vn as DEFAULT_CODEX_MODEL_FALLBACKS } from "./peer-mcp-personas-BerVEkZ9.js";
1
+ import { D as toolbeltEnabled, _n as DEFAULT_CODEX_MODEL, in as provisionAndIndexColbert, nn as hasSupportedBrowserInstalled, rn as colbertDegradedWarning, sn as warmTreeSitterPool, tn as provisionBrowserAssets, vn as DEFAULT_CODEX_MODEL_FALLBACKS } from "./peer-mcp-personas-Gb0LfII0.js";
2
2
  import { t as state } from "./state-BlMpEFAt.js";
3
3
  import { d as resolveModel, u as resolveCodexModel } from "./token-86uk6y4P.js";
4
- import { P as runSelfUpdate, a as sharedServerArgs, d as enableFileLogging, i as setupAndServe, n as getCodexEnvVars, r as parseSharedArgs, s as startKeepAwake, u as listModelsForEndpoint } from "./server-setup-CXXKmPIE.js";
5
- import { i as launchChild, t as provisionToolbelt } from "./provision-o4bXbsvB.js";
4
+ import { P as runSelfUpdate, a as sharedServerArgs, d as enableFileLogging, i as setupAndServe, n as getCodexEnvVars, r as parseSharedArgs, s as startKeepAwake, u as listModelsForEndpoint } from "./server-setup-DQVRJluz.js";
5
+ import { i as launchChild, t as provisionToolbelt } from "./provision-DagZEkdZ.js";
6
6
  import { defineCommand } from "citty";
7
7
  import consola from "consola";
8
8
  import process from "node:process";
@@ -85,4 +85,4 @@ const codex = defineCommand({
85
85
  //#endregion
86
86
  export { codex };
87
87
 
88
- //# sourceMappingURL=codex-CVHdye0h.js.map
88
+ //# sourceMappingURL=codex-CDtFT-Qp.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"codex-CVHdye0h.js","names":[],"sources":["../src/codex.ts"],"sourcesContent":["import process from \"node:process\"\n\nimport { defineCommand } from \"citty\"\nimport consola from \"consola\"\n\nimport { enableFileLogging } from \"./lib/file-log-reporter\"\nimport { launchChild } from \"./lib/launch\"\nimport { listModelsForEndpoint } from \"./lib/model-validation\"\nimport {\n DEFAULT_CODEX_MODEL,\n DEFAULT_CODEX_MODEL_FALLBACKS,\n} from \"./lib/port\"\nimport {\n getCodexEnvVars,\n parseSharedArgs,\n setupAndServe,\n sharedServerArgs,\n} from \"./lib/server-setup\"\nimport { runSelfUpdate } from \"./lib/self-update\"\nimport { state } from \"./lib/state\"\nimport { toolbeltEnabled } from \"./lib/toolbelt\"\nimport { provisionToolbelt } from \"./lib/toolbelt/provision\"\nimport { colbertDegradedWarning, provisionAndIndexColbert } from \"./lib/colbert\"\nimport { startKeepAwake } from \"./lib/keep-awake\"\nimport { warmTreeSitterPool } from \"./lib/tree-sitter-pool/pool\"\nimport { provisionBrowserAssets } from \"./lib/browser-mcp/provision\"\nimport { hasSupportedBrowserInstalled } from \"./lib/browser-mcp/browser-detect\"\nimport { resolveCodexModel, resolveModel } from \"./lib/utils\"\n\nexport const codex = defineCommand({\n meta: {\n name: \"codex\",\n description: \"Start the proxy server and launch Codex CLI\",\n },\n args: {\n ...sharedServerArgs,\n model: {\n alias: \"m\",\n type: \"string\",\n description: \"Override the default model for Codex CLI\",\n },\n },\n async run({ args }) {\n if (!process.stdout.isTTY) {\n consola.error(\"The codex 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 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 // Best-effort self-update (detached, applies next launch).\n void runSelfUpdate({ selfUpdate: args[\"self-update\"] !== false })\n\n // Materialize the LLM toolbelt in the background (PATH prepend is in\n // getCodexEnvVars). Best-effort; never blocks launch.\n if (toolbeltEnabled()) {\n void provisionToolbelt().catch(() => {})\n }\n\n // Best-effort ColBERT semantic-search provision + background index of\n // the launch cwd. Opt-IN via --search (no-op otherwise); never blocks\n // launch, never throws.\n void provisionAndIndexColbert()\n\n // Surface a terminally-failed semantic index to the HUMAN (see the note\n // in claude.ts). Fire-and-forget; lexical search still works.\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/Codex session\n // runs (win32 default-on; opt out GH_ROUTER_DISABLE_KEEP_AWAKE=1).\n // Self-registered SIGINT/SIGTERM/exit reaper releases the assertion.\n startKeepAwake()\n\n // Best-effort: materialize the browser extension + bridge into the\n // stable app-dir and stamp the running version. Gated inline (browse\n // opt-in + a supported browser) rather than via browserToolsEnabled()\n // so the codex command path doesn't eagerly pull the compressor /\n // worker-agent graph. Never blocks launch, never throws.\n const browseOptIn =\n state.browseEnabled || process.env.GH_ROUTER_ENABLE_BROWSE === \"1\"\n if (browseOptIn && hasSupportedBrowserInstalled()) {\n void provisionBrowserAssets().catch((err) =>\n consola.debug(\"Browser extension provisioning failed:\", err),\n )\n }\n\n const usingDefault = !args.model\n const requestedModel = args.model ?? DEFAULT_CODEX_MODEL\n\n // Resolve model before printing success message (so we show the actual model)\n // but enable file logging first so resolution warnings go to file, not terminal\n enableFileLogging()\n\n let codexModel = resolveCodexModel(requestedModel)\n if (codexModel !== requestedModel) {\n consola.info(`Model \"${requestedModel}\" resolved to \"${codexModel}\"`)\n }\n\n // For the implicit-default path only: honor the explicit fallback order\n // (DEFAULT_CODEX_MODEL, then DEFAULT_CODEX_MODEL_FALLBACKS led by gpt-5.5)\n // by cache presence BEFORE resolveCodexModel's codex-slug-preferring\n // \"best /responses model\" safety net. `resolveModel` (NOT resolveCodexModel)\n // walks the chain, so the codex-slug preference can't shadow an\n // earlier-in-chain named fallback (e.g. gpt-5.5) that IS present. The\n // resolveCodexModel result above stays as the FINAL safety net, used only\n // when none of the named chain entries is in the catalog.\n if (usingDefault && state.models) {\n const inCache = (id: string) =>\n state.models?.data.some((m) => m.id === id) ?? false\n const firstPresent = [DEFAULT_CODEX_MODEL, ...DEFAULT_CODEX_MODEL_FALLBACKS]\n .map((id) => resolveModel(id))\n .find((id) => inCache(id))\n if (firstPresent) {\n if (firstPresent !== codexModel) {\n consola.info(\n `Default model \"${DEFAULT_CODEX_MODEL}\" not in your Copilot model list; falling back to \"${firstPresent}\".`,\n )\n }\n codexModel = firstPresent\n }\n }\n\n // Validate model exists in Copilot model list\n const modelEntry = state.models?.data.find((m) => m.id === codexModel)\n if (!modelEntry) {\n const available = listModelsForEndpoint(\"/responses\")\n consola.warn(\n `Model \"${codexModel}\" not found. Available codex models: ${available.join(\", \")}`,\n )\n } else {\n const ctx = modelEntry.capabilities?.limits?.max_context_window_tokens\n if (ctx) consola.info(`Model context window: ${ctx.toLocaleString()} tokens`)\n }\n\n // Print to stderr directly — consola's terminal reporter is already gone\n process.stderr.write(`Server ready on ${serverUrl}, launching Codex CLI (${codexModel})...\\n`)\n\n const envVars = getCodexEnvVars(serverUrl)\n const extraArgs = ((args as unknown as Record<string, unknown>)._ as string[]) ?? []\n\n launchChild(\n {\n kind: \"codex\",\n envVars,\n extraArgs,\n model: codexModel,\n serverUrl,\n },\n server,\n )\n },\n})\n"],"mappings":";;;;;;;;;AA6BA,MAAa,QAAQ,cAAc;CACjC,MAAM;EACJ,MAAM;EACN,aAAa;CACf;CACA,MAAM;EACJ,GAAG;EACH,OAAO;GACL,OAAO;GACP,MAAM;GACN,aAAa;EACf;CACF;CACA,MAAM,IAAI,EAAE,QAAQ;EAClB,IAAI,CAAC,QAAQ,OAAO,OAAO;GACzB,QAAQ,MAAM,6DAA6D;GAC3E,QAAQ,KAAK,CAAC;EAChB;EAEA,MAAM,SAAS,gBAAgB,IAA0C;EAEzE,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;EAGA,cAAmB,EAAE,YAAY,KAAK,mBAAmB,MAAM,CAAC;EAIhE,IAAI,gBAAgB,GAClB,kBAAuB,CAAC,CAAC,YAAY,CAAC,CAAC;EAMzC,yBAA8B;EAI9B,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;EAKxB,eAAe;EASf,KADE,MAAM,iBAAiB,QAAQ,IAAI,4BAA4B,QAC9C,6BAA6B,GAC9C,uBAA4B,CAAC,CAAC,OAAO,QACnC,QAAQ,MAAM,0CAA0C,GAAG,CAC7D;EAGF,MAAM,eAAe,CAAC,KAAK;EAC3B,MAAM,iBAAiB,KAAK,SAAA;EAI5B,kBAAkB;EAElB,IAAI,aAAa,kBAAkB,cAAc;EACjD,IAAI,eAAe,gBACjB,QAAQ,KAAK,UAAU,eAAe,iBAAiB,WAAW,EAAE;EAWtE,IAAI,gBAAgB,MAAM,QAAQ;GAChC,MAAM,WAAW,OACf,MAAM,QAAQ,KAAK,MAAM,MAAM,EAAE,OAAO,EAAE,KAAK;GACjD,MAAM,eAAe,CAAC,qBAAqB,GAAG,6BAA6B,CAAC,CACzE,KAAK,OAAO,aAAa,EAAE,CAAC,CAAC,CAC7B,MAAM,OAAO,QAAQ,EAAE,CAAC;GAC3B,IAAI,cAAc;IAChB,IAAI,iBAAiB,YACnB,QAAQ,KACN,kBAAkB,oBAAoB,qDAAqD,aAAa,GAC1G;IAEF,aAAa;GACf;EACF;EAGA,MAAM,aAAa,MAAM,QAAQ,KAAK,MAAM,MAAM,EAAE,OAAO,UAAU;EACrE,IAAI,CAAC,YAAY;GACf,MAAM,YAAY,sBAAsB,YAAY;GACpD,QAAQ,KACN,UAAU,WAAW,uCAAuC,UAAU,KAAK,IAAI,GACjF;EACF,OAAO;GACL,MAAM,MAAM,WAAW,cAAc,QAAQ;GAC7C,IAAI,KAAK,QAAQ,KAAK,yBAAyB,IAAI,eAAe,EAAE,QAAQ;EAC9E;EAGA,QAAQ,OAAO,MAAM,mBAAmB,UAAU,yBAAyB,WAAW,OAAO;EAE7F,MAAM,UAAU,gBAAgB,SAAS;EACzC,MAAM,YAAc,KAA4C,KAAkB,CAAC;EAEnF,YACE;GACE,MAAM;GACN;GACA;GACA,OAAO;GACP;EACF,GACA,MACF;CACF;AACF,CAAC"}
1
+ {"version":3,"file":"codex-CDtFT-Qp.js","names":[],"sources":["../src/codex.ts"],"sourcesContent":["import process from \"node:process\"\n\nimport { defineCommand } from \"citty\"\nimport consola from \"consola\"\n\nimport { enableFileLogging } from \"./lib/file-log-reporter\"\nimport { launchChild } from \"./lib/launch\"\nimport { listModelsForEndpoint } from \"./lib/model-validation\"\nimport {\n DEFAULT_CODEX_MODEL,\n DEFAULT_CODEX_MODEL_FALLBACKS,\n} from \"./lib/port\"\nimport {\n getCodexEnvVars,\n parseSharedArgs,\n setupAndServe,\n sharedServerArgs,\n} from \"./lib/server-setup\"\nimport { runSelfUpdate } from \"./lib/self-update\"\nimport { state } from \"./lib/state\"\nimport { toolbeltEnabled } from \"./lib/toolbelt\"\nimport { provisionToolbelt } from \"./lib/toolbelt/provision\"\nimport { colbertDegradedWarning, provisionAndIndexColbert } from \"./lib/colbert\"\nimport { startKeepAwake } from \"./lib/keep-awake\"\nimport { warmTreeSitterPool } from \"./lib/tree-sitter-pool/pool\"\nimport { provisionBrowserAssets } from \"./lib/browser-mcp/provision\"\nimport { hasSupportedBrowserInstalled } from \"./lib/browser-mcp/browser-detect\"\nimport { resolveCodexModel, resolveModel } from \"./lib/utils\"\n\nexport const codex = defineCommand({\n meta: {\n name: \"codex\",\n description: \"Start the proxy server and launch Codex CLI\",\n },\n args: {\n ...sharedServerArgs,\n model: {\n alias: \"m\",\n type: \"string\",\n description: \"Override the default model for Codex CLI\",\n },\n },\n async run({ args }) {\n if (!process.stdout.isTTY) {\n consola.error(\"The codex 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 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 // Best-effort self-update (detached, applies next launch).\n void runSelfUpdate({ selfUpdate: args[\"self-update\"] !== false })\n\n // Materialize the LLM toolbelt in the background (PATH prepend is in\n // getCodexEnvVars). Best-effort; never blocks launch.\n if (toolbeltEnabled()) {\n void provisionToolbelt().catch(() => {})\n }\n\n // Best-effort ColBERT semantic-search provision + background index of\n // the launch cwd. Opt-IN via --search (no-op otherwise); never blocks\n // launch, never throws.\n void provisionAndIndexColbert()\n\n // Surface a terminally-failed semantic index to the HUMAN (see the note\n // in claude.ts). Fire-and-forget; lexical search still works.\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/Codex session\n // runs (win32 default-on; opt out GH_ROUTER_DISABLE_KEEP_AWAKE=1).\n // Self-registered SIGINT/SIGTERM/exit reaper releases the assertion.\n startKeepAwake()\n\n // Best-effort: materialize the browser extension + bridge into the\n // stable app-dir and stamp the running version. Gated inline (browse\n // opt-in + a supported browser) rather than via browserToolsEnabled()\n // so the codex command path doesn't eagerly pull the compressor /\n // worker-agent graph. Never blocks launch, never throws.\n const browseOptIn =\n state.browseEnabled || process.env.GH_ROUTER_ENABLE_BROWSE === \"1\"\n if (browseOptIn && hasSupportedBrowserInstalled()) {\n void provisionBrowserAssets().catch((err) =>\n consola.debug(\"Browser extension provisioning failed:\", err),\n )\n }\n\n const usingDefault = !args.model\n const requestedModel = args.model ?? DEFAULT_CODEX_MODEL\n\n // Resolve model before printing success message (so we show the actual model)\n // but enable file logging first so resolution warnings go to file, not terminal\n enableFileLogging()\n\n let codexModel = resolveCodexModel(requestedModel)\n if (codexModel !== requestedModel) {\n consola.info(`Model \"${requestedModel}\" resolved to \"${codexModel}\"`)\n }\n\n // For the implicit-default path only: honor the explicit fallback order\n // (DEFAULT_CODEX_MODEL, then DEFAULT_CODEX_MODEL_FALLBACKS led by gpt-5.5)\n // by cache presence BEFORE resolveCodexModel's codex-slug-preferring\n // \"best /responses model\" safety net. `resolveModel` (NOT resolveCodexModel)\n // walks the chain, so the codex-slug preference can't shadow an\n // earlier-in-chain named fallback (e.g. gpt-5.5) that IS present. The\n // resolveCodexModel result above stays as the FINAL safety net, used only\n // when none of the named chain entries is in the catalog.\n if (usingDefault && state.models) {\n const inCache = (id: string) =>\n state.models?.data.some((m) => m.id === id) ?? false\n const firstPresent = [DEFAULT_CODEX_MODEL, ...DEFAULT_CODEX_MODEL_FALLBACKS]\n .map((id) => resolveModel(id))\n .find((id) => inCache(id))\n if (firstPresent) {\n if (firstPresent !== codexModel) {\n consola.info(\n `Default model \"${DEFAULT_CODEX_MODEL}\" not in your Copilot model list; falling back to \"${firstPresent}\".`,\n )\n }\n codexModel = firstPresent\n }\n }\n\n // Validate model exists in Copilot model list\n const modelEntry = state.models?.data.find((m) => m.id === codexModel)\n if (!modelEntry) {\n const available = listModelsForEndpoint(\"/responses\")\n consola.warn(\n `Model \"${codexModel}\" not found. Available codex models: ${available.join(\", \")}`,\n )\n } else {\n const ctx = modelEntry.capabilities?.limits?.max_context_window_tokens\n if (ctx) consola.info(`Model context window: ${ctx.toLocaleString()} tokens`)\n }\n\n // Print to stderr directly — consola's terminal reporter is already gone\n process.stderr.write(`Server ready on ${serverUrl}, launching Codex CLI (${codexModel})...\\n`)\n\n const envVars = getCodexEnvVars(serverUrl)\n const extraArgs = ((args as unknown as Record<string, unknown>)._ as string[]) ?? []\n\n launchChild(\n {\n kind: \"codex\",\n envVars,\n extraArgs,\n model: codexModel,\n serverUrl,\n },\n server,\n )\n },\n})\n"],"mappings":";;;;;;;;;AA6BA,MAAa,QAAQ,cAAc;CACjC,MAAM;EACJ,MAAM;EACN,aAAa;CACf;CACA,MAAM;EACJ,GAAG;EACH,OAAO;GACL,OAAO;GACP,MAAM;GACN,aAAa;EACf;CACF;CACA,MAAM,IAAI,EAAE,QAAQ;EAClB,IAAI,CAAC,QAAQ,OAAO,OAAO;GACzB,QAAQ,MAAM,6DAA6D;GAC3E,QAAQ,KAAK,CAAC;EAChB;EAEA,MAAM,SAAS,gBAAgB,IAA0C;EAEzE,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;EAGA,cAAmB,EAAE,YAAY,KAAK,mBAAmB,MAAM,CAAC;EAIhE,IAAI,gBAAgB,GAClB,kBAAuB,CAAC,CAAC,YAAY,CAAC,CAAC;EAMzC,yBAA8B;EAI9B,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;EAKxB,eAAe;EASf,KADE,MAAM,iBAAiB,QAAQ,IAAI,4BAA4B,QAC9C,6BAA6B,GAC9C,uBAA4B,CAAC,CAAC,OAAO,QACnC,QAAQ,MAAM,0CAA0C,GAAG,CAC7D;EAGF,MAAM,eAAe,CAAC,KAAK;EAC3B,MAAM,iBAAiB,KAAK,SAAA;EAI5B,kBAAkB;EAElB,IAAI,aAAa,kBAAkB,cAAc;EACjD,IAAI,eAAe,gBACjB,QAAQ,KAAK,UAAU,eAAe,iBAAiB,WAAW,EAAE;EAWtE,IAAI,gBAAgB,MAAM,QAAQ;GAChC,MAAM,WAAW,OACf,MAAM,QAAQ,KAAK,MAAM,MAAM,EAAE,OAAO,EAAE,KAAK;GACjD,MAAM,eAAe,CAAC,qBAAqB,GAAG,6BAA6B,CAAC,CACzE,KAAK,OAAO,aAAa,EAAE,CAAC,CAAC,CAC7B,MAAM,OAAO,QAAQ,EAAE,CAAC;GAC3B,IAAI,cAAc;IAChB,IAAI,iBAAiB,YACnB,QAAQ,KACN,kBAAkB,oBAAoB,qDAAqD,aAAa,GAC1G;IAEF,aAAa;GACf;EACF;EAGA,MAAM,aAAa,MAAM,QAAQ,KAAK,MAAM,MAAM,EAAE,OAAO,UAAU;EACrE,IAAI,CAAC,YAAY;GACf,MAAM,YAAY,sBAAsB,YAAY;GACpD,QAAQ,KACN,UAAU,WAAW,uCAAuC,UAAU,KAAK,IAAI,GACjF;EACF,OAAO;GACL,MAAM,MAAM,WAAW,cAAc,QAAQ;GAC7C,IAAI,KAAK,QAAQ,KAAK,yBAAyB,IAAI,eAAe,EAAE,QAAQ;EAC9E;EAGA,QAAQ,OAAO,MAAM,mBAAmB,UAAU,yBAAyB,WAAW,OAAO;EAE7F,MAAM,UAAU,gBAAgB,SAAS;EACzC,MAAM,YAAc,KAA4C,KAAkB,CAAC;EAEnF,YACE;GACE,MAAM;GACN;GACA;GACA,OAAO;GACP;EACF,GACA,MACF;CACF;AACF,CAAC"}
@@ -1,4 +1,4 @@
1
- import { r as aicTotalCredits } from "./aic-ledger-BrDqsuge.js";
1
+ import { r as aicTotalCredits } from "./aic-ledger-BW3zMiCx.js";
2
2
  //#region src/lib/copilot-discount.ts
3
3
  /** USD value of one AI credit (GitHub's published conversion). */
4
4
  const USD_PER_AIC_CREDIT = .01;
@@ -217,4 +217,4 @@ function formatCount(n) {
217
217
  //#endregion
218
218
  export { formatDiscountedCostTable as n, discountedUsdForSnapshot as t };
219
219
 
220
- //# sourceMappingURL=copilot-discount-BnJkEojk.js.map
220
+ //# sourceMappingURL=copilot-discount-I6BrT7QE.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"copilot-discount-BnJkEojk.js","names":[],"sources":["../src/lib/copilot-discount.ts"],"sourcesContent":["/**\n * Approximate discounted USD for a session, from static per-model discount\n * factors applied to the AIC ledger's billed nano-AIU.\n *\n * Background: GitHub's portal lists per-token rates, but the spend dashboard\n * bills several models well below portal (gpt-5.6-sol ≈ 0.28x, the Opus /\n * Sonnet / Codex cluster ≈ 0.6x, gemini-3.7-flash ≈ 0.29x since Sep 2026),\n * while Luna bills long-context portal tiers and Grok / 3.8-flash bill\n * portal-default exactly. No uniform per-lab factor exists (verified across\n * 28d / MTD / 7d / daily windows), so each model carries its own factor.\n *\n * The factors scale NANO-AIU, not tokens × portal rates: nano is already\n * tier-correct upstream (long-context tiers, cache writes), so no tier\n * engine or portal-rate table is needed here. Displayed total is capped:\n * `min(factored, totalCredits × $0.01)` — the cap can only ever lower the\n * number (all factors ≤ 1), so a typo like 2.8-instead-of-0.28 degrades to\n * the unfactored total rather than overstating spend.\n *\n * Staleness: rates move (3.7-flash flipped 1.00x → 0.29x overnight once\n * already). Re-derive monthly from the portal + spend dashboard; drift\n * degrades to cap-bound display, never to overstatement.\n */\n\nimport type { AicSnapshot } from \"./aic-ledger\"\nimport { aicTotalCredits } from \"./aic-ledger\"\n\n/** USD value of one AI credit (GitHub's published conversion). */\nexport const USD_PER_AIC_CREDIT = 0.01\n\nexport interface DiscountFactorRow {\n /** Multiply nano-implied USD by this. */\n factor: number\n /** Why this value: which snapshot window(s) it was derived from. */\n source: string\n}\n\nfunction row(factor: number, source: string): DiscountFactorRow {\n return { factor, source }\n}\n\n/**\n * Static per-model discount factors, keyed by resolved (Copilot-side) model\n * id — the same key the ledger's `perModel` uses. Derived Sep 2026 from the\n * 28d / MTD / 7d / daily spend-dashboard windows vs portal rates.\n */\nexport const DISCOUNT_FACTORS: Readonly<Record<string, DiscountFactorRow>> =\n Object.freeze({\n \"gpt-5.6-sol\": row(0.28, \"0.27-0.29 across 28d/MTD/7d/pre/9-9; write-mix refuted\"),\n \"claude-opus-5\": row(0.62, \"0.61-0.64 across windows; 9/7 1.02 excluded as single-day artifact\"),\n \"claude-sonnet-5\": row(0.66, \"0.62-0.71 across windows\"),\n \"gpt-5.3-codex\": row(0.60, \"0.59-0.62 across windows\"),\n \"gpt-5.4\": row(0.60, \"single snapshot 0.598\"),\n // Regime break: billed 1.00x pre-Sep, 0.29x since. Recheck FIRST at\n // every refresh — likeliest row to have moved again.\n \"gemini-3.7-flash\": row(0.29, \"September regime; pre-Sep 1.00x — volatile\"),\n // No discount: Luna's heat is long-context tiers + ~2% cache writes\n // (both already in nano); Grok / 3.8-flash bill portal-default exactly.\n \"gpt-5.6-luna\": row(1.0, \"long-tier portal + writes, no discount\"),\n \"grok-4.6\": row(1.0, \"portal default, six windows\"),\n \"gemini-3.8-flash\": row(1.0, \"portal exact, six windows to the dollar\"),\n })\n\n/** Discount factor for a model id; 1.0 (no discount) when unlisted. */\nexport function discountFactorFor(modelId: string): number {\n return sanitizeFactor(DISCOUNT_FACTORS[modelId]?.factor)\n}\n\n/**\n * Clamp a factor to usable range. Non-finite or negative factors (corrupt\n * table edit, bad injection) degrade to 1.0 — unfactored but never NaN,\n * never negative on screen.\n */\nfunction sanitizeFactor(raw: unknown): number {\n return typeof raw === \"number\" && Number.isFinite(raw) && raw >= 0 ? raw : 1.0\n}\n\nexport interface DiscountedUsd {\n /** Capped total actually displayed (`~$`). */\n total: number\n /** Per-model factored USD, scaled to the capped total when the cap binds. */\n byModel: Record<string, number>\n /** True when the cap (not the factors) decided the total. */\n capped: boolean\n}\n\n/**\n * Session discounted USD from a ledger snapshot. Total function: never\n * throws (guards non-finite/negative ledger values defensively — a corrupt\n * snapshot must degrade the status line, not crash the hook).\n */\nexport function discountedUsdForSnapshot(snapshot: AicSnapshot): DiscountedUsd {\n return discountedUsdWithFactors(snapshot, DISCOUNT_FACTORS)\n}\n\n/**\n * Factor-injectable core (exported for unit tests — e.g. proving a\n * fat-finger factor can never overstate spend). `factors` falls back to\n * 1.0 per model exactly like the table lookup.\n */\nexport function discountedUsdWithFactors(\n snapshot: AicSnapshot,\n factors: Readonly<Record<string, DiscountFactorRow>>,\n): DiscountedUsd {\n const byModel: Record<string, number> = {}\n let sum = 0\n for (const [model, entry] of Object.entries(snapshot.perModel)) {\n const nano =\n typeof entry.nanoAiu === \"number\" && Number.isFinite(entry.nanoAiu)\n ? Math.max(0, entry.nanoAiu)\n : 0\n const factor = sanitizeFactor(factors[model]?.factor)\n const usd = (nano / 1_000_000_000) * USD_PER_AIC_CREDIT * factor\n byModel[model] = usd\n sum += usd\n }\n const cap = aicTotalCredits(snapshot) * USD_PER_AIC_CREDIT\n // Relative epsilon: per-model float summation can exceed the cap by a\n // final-ulp hair without any factor being wrong — only bind on a real gap.\n if (!(sum > cap * (1 + 1e-9)) || sum <= 0) {\n return { total: sum, byModel, capped: false }\n }\n // Cap binds (fat-finger factor, volatile row, unknown-model overstatement):\n // scale rows down proportionally so the table still sums to its Total.\n const scale = cap / sum\n const scaled: Record<string, number> = {}\n for (const [model, usd] of Object.entries(byModel)) {\n scaled[model] = usd * scale\n }\n return { total: cap, byModel: scaled, capped: true }\n}\n\n/** Format table dollars: always cents (`~$` added by callers that want it). */\nexport function formatTableUsd(usd: number): string {\n return usd.toFixed(2)\n}\n\ninterface CostTableRow {\n model: string\n uncached: number\n cached: number\n writes: number\n output: number\n usd: number\n}\n\n/**\n * Beautiful per-model session cost table (plain text, no ANSI — it lands on\n * stderr / logs). Columns mirror the spend dashboard: Uncached / Cached /\n * [Write] / Cache% / Output / $. The Write column appears only when the\n * session actually wrote cache. `Cache% = cache_read / (cache_read + input)`;\n * writes are excluded (they are new tokens, not hits).\n */\nexport function formatDiscountedCostTable(\n snapshot: AicSnapshot,\n discounted: DiscountedUsd,\n): string {\n const tokens = snapshot.tokensByModel ?? {}\n const models = new Set([...Object.keys(snapshot.perModel), ...Object.keys(tokens)])\n const rows: Array<CostTableRow> = []\n for (const model of models) {\n const t = tokens[model]\n rows.push({\n model,\n uncached: t?.input ?? 0,\n cached: t?.cache_read ?? 0,\n writes: t?.cache_write ?? 0,\n output: t?.output ?? 0,\n usd: discounted.byModel[model] ?? 0,\n })\n }\n if (rows.length === 0) return \"\"\n rows.sort((a, b) => b.usd - a.usd || a.model.localeCompare(b.model))\n\n const showWrites = rows.some((r) => r.writes > 0)\n const cachePct = (cached: number, uncached: number): string => {\n const denom = cached + uncached\n if (denom <= 0) return \"—\"\n return `${((cached / denom) * 100).toFixed(1)}%`\n }\n\n type Cell = { text: string; align: \"left\" | \"right\" }\n const header: Array<Cell> = [\n { text: \"Model\", align: \"left\" },\n { text: \"Uncached\", align: \"right\" },\n { text: \"Cached\", align: \"right\" },\n ]\n if (showWrites) header.push({ text: \"Write\", align: \"right\" })\n header.push(\n { text: \"Cache%\", align: \"right\" },\n { text: \"Output\", align: \"right\" },\n { text: \"$\", align: \"right\" },\n )\n\n const body: Array<Array<Cell>> = rows.map((r) => {\n const cells: Array<Cell> = [\n { text: r.model, align: \"left\" },\n { text: formatCount(r.uncached), align: \"right\" },\n { text: formatCount(r.cached), align: \"right\" },\n ]\n if (showWrites) cells.push({ text: formatCount(r.writes), align: \"right\" })\n cells.push(\n { text: cachePct(r.cached, r.uncached), align: \"right\" },\n { text: formatCount(r.output), align: \"right\" },\n { text: `~$${formatTableUsd(r.usd)}`, align: \"right\" },\n )\n return cells\n })\n\n const totalUncached = rows.reduce((n, r) => n + r.uncached, 0)\n const totalCached = rows.reduce((n, r) => n + r.cached, 0)\n const totalWrites = rows.reduce((n, r) => n + r.writes, 0)\n const totalOutput = rows.reduce((n, r) => n + r.output, 0)\n const totalCells: Array<Cell> = [\n { text: \"Total\", align: \"left\" },\n { text: formatCount(totalUncached), align: \"right\" },\n { text: formatCount(totalCached), align: \"right\" },\n ]\n if (showWrites) {\n totalCells.push({ text: formatCount(totalWrites), align: \"right\" })\n }\n totalCells.push(\n { text: cachePct(totalCached, totalUncached), align: \"right\" },\n { text: formatCount(totalOutput), align: \"right\" },\n { text: `~$${formatTableUsd(discounted.total)}`, align: \"right\" },\n )\n\n const widths = header.map((h, i) => {\n let w = h.text.length\n for (const row of [...body, totalCells]) {\n w = Math.max(w, row[i].text.length)\n }\n return w\n })\n const render = (cells: Array<Cell>): string =>\n cells\n .map((c, i) =>\n c.align === \"left\" ? c.text.padEnd(widths[i]) : c.text.padStart(widths[i]),\n )\n .join(\" \")\n const lines = [\n \"Session cost (discounted actuals, ~$):\",\n render(header),\n ...body.map(render),\n render(totalCells),\n ]\n return lines.join(\"\\n\")\n}\n\n/** Compact count for table cells: `999` → `999`, `15234` → `15.2k`. */\nfunction formatCount(n: number): string {\n if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`\n if (n >= 1000) return `${(n / 1000).toFixed(1)}k`\n return `${n}`\n}\n"],"mappings":";;;AA2BA,MAAa,qBAAqB;AASlC,SAAS,IAAI,QAAgB,QAAmC;CAC9D,OAAO;EAAE;EAAQ;CAAO;AAC1B;;;;;;AAOA,MAAa,mBACX,OAAO,OAAO;CACZ,eAAe,IAAI,KAAM,wDAAwD;CACjF,iBAAiB,IAAI,KAAM,oEAAoE;CAC/F,mBAAmB,IAAI,KAAM,0BAA0B;CACvD,iBAAiB,IAAI,IAAM,0BAA0B;CACrD,WAAW,IAAI,IAAM,uBAAuB;CAG5C,oBAAoB,IAAI,KAAM,4CAA4C;CAG1E,gBAAgB,IAAI,GAAK,wCAAwC;CACjE,YAAY,IAAI,GAAK,6BAA6B;CAClD,oBAAoB,IAAI,GAAK,yCAAyC;AACxE,CAAC;;;;;;AAYH,SAAS,eAAe,KAAsB;CAC5C,OAAO,OAAO,QAAQ,YAAY,OAAO,SAAS,GAAG,KAAK,OAAO,IAAI,MAAM;AAC7E;;;;;;AAgBA,SAAgB,yBAAyB,UAAsC;CAC7E,OAAO,yBAAyB,UAAU,gBAAgB;AAC5D;;;;;;AAOA,SAAgB,yBACd,UACA,SACe;CACf,MAAM,UAAkC,CAAC;CACzC,IAAI,MAAM;CACV,KAAK,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,SAAS,QAAQ,GAAG;EAC9D,MAAM,OACJ,OAAO,MAAM,YAAY,YAAY,OAAO,SAAS,MAAM,OAAO,IAC9D,KAAK,IAAI,GAAG,MAAM,OAAO,IACzB;EACN,MAAM,SAAS,eAAe,QAAQ,MAAM,EAAE,MAAM;EACpD,MAAM,MAAO,OAAO,MAAiB,qBAAqB;EAC1D,QAAQ,SAAS;EACjB,OAAO;CACT;CACA,MAAM,MAAM,gBAAgB,QAAQ,IAAI;CAGxC,IAAI,EAAE,MAAM,OAAO,IAAI,UAAU,OAAO,GACtC,OAAO;EAAE,OAAO;EAAK;EAAS,QAAQ;CAAM;CAI9C,MAAM,QAAQ,MAAM;CACpB,MAAM,SAAiC,CAAC;CACxC,KAAK,MAAM,CAAC,OAAO,QAAQ,OAAO,QAAQ,OAAO,GAC/C,OAAO,SAAS,MAAM;CAExB,OAAO;EAAE,OAAO;EAAK,SAAS;EAAQ,QAAQ;CAAK;AACrD;;AAGA,SAAgB,eAAe,KAAqB;CAClD,OAAO,IAAI,QAAQ,CAAC;AACtB;;;;;;;;AAkBA,SAAgB,0BACd,UACA,YACQ;CACR,MAAM,SAAS,SAAS,iBAAiB,CAAC;CAC1C,MAAM,yBAAS,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,SAAS,QAAQ,GAAG,GAAG,OAAO,KAAK,MAAM,CAAC,CAAC;CAClF,MAAM,OAA4B,CAAC;CACnC,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,IAAI,OAAO;EACjB,KAAK,KAAK;GACR;GACA,UAAU,GAAG,SAAS;GACtB,QAAQ,GAAG,cAAc;GACzB,QAAQ,GAAG,eAAe;GAC1B,QAAQ,GAAG,UAAU;GACrB,KAAK,WAAW,QAAQ,UAAU;EACpC,CAAC;CACH;CACA,IAAI,KAAK,WAAW,GAAG,OAAO;CAC9B,KAAK,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,cAAc,EAAE,KAAK,CAAC;CAEnE,MAAM,aAAa,KAAK,MAAM,MAAM,EAAE,SAAS,CAAC;CAChD,MAAM,YAAY,QAAgB,aAA6B;EAC7D,MAAM,QAAQ,SAAS;EACvB,IAAI,SAAS,GAAG,OAAO;EACvB,OAAO,IAAK,SAAS,QAAS,IAAA,CAAK,QAAQ,CAAC,EAAE;CAChD;CAGA,MAAM,SAAsB;EAC1B;GAAE,MAAM;GAAS,OAAO;EAAO;EAC/B;GAAE,MAAM;GAAY,OAAO;EAAQ;EACnC;GAAE,MAAM;GAAU,OAAO;EAAQ;CACnC;CACA,IAAI,YAAY,OAAO,KAAK;EAAE,MAAM;EAAS,OAAO;CAAQ,CAAC;CAC7D,OAAO,KACL;EAAE,MAAM;EAAU,OAAO;CAAQ,GACjC;EAAE,MAAM;EAAU,OAAO;CAAQ,GACjC;EAAE,MAAM;EAAK,OAAO;CAAQ,CAC9B;CAEA,MAAM,OAA2B,KAAK,KAAK,MAAM;EAC/C,MAAM,QAAqB;GACzB;IAAE,MAAM,EAAE;IAAO,OAAO;GAAO;GAC/B;IAAE,MAAM,YAAY,EAAE,QAAQ;IAAG,OAAO;GAAQ;GAChD;IAAE,MAAM,YAAY,EAAE,MAAM;IAAG,OAAO;GAAQ;EAChD;EACA,IAAI,YAAY,MAAM,KAAK;GAAE,MAAM,YAAY,EAAE,MAAM;GAAG,OAAO;EAAQ,CAAC;EAC1E,MAAM,KACJ;GAAE,MAAM,SAAS,EAAE,QAAQ,EAAE,QAAQ;GAAG,OAAO;EAAQ,GACvD;GAAE,MAAM,YAAY,EAAE,MAAM;GAAG,OAAO;EAAQ,GAC9C;GAAE,MAAM,KAAK,eAAe,EAAE,GAAG;GAAK,OAAO;EAAQ,CACvD;EACA,OAAO;CACT,CAAC;CAED,MAAM,gBAAgB,KAAK,QAAQ,GAAG,MAAM,IAAI,EAAE,UAAU,CAAC;CAC7D,MAAM,cAAc,KAAK,QAAQ,GAAG,MAAM,IAAI,EAAE,QAAQ,CAAC;CACzD,MAAM,cAAc,KAAK,QAAQ,GAAG,MAAM,IAAI,EAAE,QAAQ,CAAC;CACzD,MAAM,cAAc,KAAK,QAAQ,GAAG,MAAM,IAAI,EAAE,QAAQ,CAAC;CACzD,MAAM,aAA0B;EAC9B;GAAE,MAAM;GAAS,OAAO;EAAO;EAC/B;GAAE,MAAM,YAAY,aAAa;GAAG,OAAO;EAAQ;EACnD;GAAE,MAAM,YAAY,WAAW;GAAG,OAAO;EAAQ;CACnD;CACA,IAAI,YACF,WAAW,KAAK;EAAE,MAAM,YAAY,WAAW;EAAG,OAAO;CAAQ,CAAC;CAEpE,WAAW,KACT;EAAE,MAAM,SAAS,aAAa,aAAa;EAAG,OAAO;CAAQ,GAC7D;EAAE,MAAM,YAAY,WAAW;EAAG,OAAO;CAAQ,GACjD;EAAE,MAAM,KAAK,eAAe,WAAW,KAAK;EAAK,OAAO;CAAQ,CAClE;CAEA,MAAM,SAAS,OAAO,KAAK,GAAG,MAAM;EAClC,IAAI,IAAI,EAAE,KAAK;EACf,KAAK,MAAM,OAAO,CAAC,GAAG,MAAM,UAAU,GACpC,IAAI,KAAK,IAAI,GAAG,IAAI,EAAE,CAAC,KAAK,MAAM;EAEpC,OAAO;CACT,CAAC;CACD,MAAM,UAAU,UACd,MACG,KAAK,GAAG,MACP,EAAE,UAAU,SAAS,EAAE,KAAK,OAAO,OAAO,EAAE,IAAI,EAAE,KAAK,SAAS,OAAO,EAAE,CAC3E,CAAC,CACA,KAAK,IAAI;CAOd,OAAO;EALL;EACA,OAAO,MAAM;EACb,GAAG,KAAK,IAAI,MAAM;EAClB,OAAO,UAAU;CAER,CAAC,CAAC,KAAK,IAAI;AACxB;;AAGA,SAAS,YAAY,GAAmB;CACtC,IAAI,KAAK,KAAW,OAAO,IAAI,IAAI,IAAA,CAAW,QAAQ,CAAC,EAAE;CACzD,IAAI,KAAK,KAAM,OAAO,IAAI,IAAI,IAAA,CAAM,QAAQ,CAAC,EAAE;CAC/C,OAAO,GAAG;AACZ"}
1
+ {"version":3,"file":"copilot-discount-I6BrT7QE.js","names":[],"sources":["../src/lib/copilot-discount.ts"],"sourcesContent":["/**\n * Approximate discounted USD for a session, from static per-model discount\n * factors applied to the AIC ledger's billed nano-AIU.\n *\n * Background: GitHub's portal lists per-token rates, but the spend dashboard\n * bills several models well below portal (gpt-5.6-sol ≈ 0.28x, the Opus /\n * Sonnet / Codex cluster ≈ 0.6x, gemini-3.7-flash ≈ 0.29x since Sep 2026),\n * while Luna bills long-context portal tiers and Grok / 3.8-flash bill\n * portal-default exactly. No uniform per-lab factor exists (verified across\n * 28d / MTD / 7d / daily windows), so each model carries its own factor.\n *\n * The factors scale NANO-AIU, not tokens × portal rates: nano is already\n * tier-correct upstream (long-context tiers, cache writes), so no tier\n * engine or portal-rate table is needed here. Displayed total is capped:\n * `min(factored, totalCredits × $0.01)` — the cap can only ever lower the\n * number (all factors ≤ 1), so a typo like 2.8-instead-of-0.28 degrades to\n * the unfactored total rather than overstating spend.\n *\n * Staleness: rates move (3.7-flash flipped 1.00x → 0.29x overnight once\n * already). Re-derive monthly from the portal + spend dashboard; drift\n * degrades to cap-bound display, never to overstatement.\n */\n\nimport type { AicSnapshot } from \"./aic-ledger\"\nimport { aicTotalCredits } from \"./aic-ledger\"\n\n/** USD value of one AI credit (GitHub's published conversion). */\nexport const USD_PER_AIC_CREDIT = 0.01\n\nexport interface DiscountFactorRow {\n /** Multiply nano-implied USD by this. */\n factor: number\n /** Why this value: which snapshot window(s) it was derived from. */\n source: string\n}\n\nfunction row(factor: number, source: string): DiscountFactorRow {\n return { factor, source }\n}\n\n/**\n * Static per-model discount factors, keyed by resolved (Copilot-side) model\n * id — the same key the ledger's `perModel` uses. Derived Sep 2026 from the\n * 28d / MTD / 7d / daily spend-dashboard windows vs portal rates.\n */\nexport const DISCOUNT_FACTORS: Readonly<Record<string, DiscountFactorRow>> =\n Object.freeze({\n \"gpt-5.6-sol\": row(0.28, \"0.27-0.29 across 28d/MTD/7d/pre/9-9; write-mix refuted\"),\n \"claude-opus-5\": row(0.62, \"0.61-0.64 across windows; 9/7 1.02 excluded as single-day artifact\"),\n \"claude-sonnet-5\": row(0.66, \"0.62-0.71 across windows\"),\n \"gpt-5.3-codex\": row(0.60, \"0.59-0.62 across windows\"),\n \"gpt-5.4\": row(0.60, \"single snapshot 0.598\"),\n // Regime break: billed 1.00x pre-Sep, 0.29x since. Recheck FIRST at\n // every refresh — likeliest row to have moved again.\n \"gemini-3.7-flash\": row(0.29, \"September regime; pre-Sep 1.00x — volatile\"),\n // No discount: Luna's heat is long-context tiers + ~2% cache writes\n // (both already in nano); Grok / 3.8-flash bill portal-default exactly.\n \"gpt-5.6-luna\": row(1.0, \"long-tier portal + writes, no discount\"),\n \"grok-4.6\": row(1.0, \"portal default, six windows\"),\n \"gemini-3.8-flash\": row(1.0, \"portal exact, six windows to the dollar\"),\n })\n\n/** Discount factor for a model id; 1.0 (no discount) when unlisted. */\nexport function discountFactorFor(modelId: string): number {\n return sanitizeFactor(DISCOUNT_FACTORS[modelId]?.factor)\n}\n\n/**\n * Clamp a factor to usable range. Non-finite or negative factors (corrupt\n * table edit, bad injection) degrade to 1.0 — unfactored but never NaN,\n * never negative on screen.\n */\nfunction sanitizeFactor(raw: unknown): number {\n return typeof raw === \"number\" && Number.isFinite(raw) && raw >= 0 ? raw : 1.0\n}\n\nexport interface DiscountedUsd {\n /** Capped total actually displayed (`~$`). */\n total: number\n /** Per-model factored USD, scaled to the capped total when the cap binds. */\n byModel: Record<string, number>\n /** True when the cap (not the factors) decided the total. */\n capped: boolean\n}\n\n/**\n * Session discounted USD from a ledger snapshot. Total function: never\n * throws (guards non-finite/negative ledger values defensively — a corrupt\n * snapshot must degrade the status line, not crash the hook).\n */\nexport function discountedUsdForSnapshot(snapshot: AicSnapshot): DiscountedUsd {\n return discountedUsdWithFactors(snapshot, DISCOUNT_FACTORS)\n}\n\n/**\n * Factor-injectable core (exported for unit tests — e.g. proving a\n * fat-finger factor can never overstate spend). `factors` falls back to\n * 1.0 per model exactly like the table lookup.\n */\nexport function discountedUsdWithFactors(\n snapshot: AicSnapshot,\n factors: Readonly<Record<string, DiscountFactorRow>>,\n): DiscountedUsd {\n const byModel: Record<string, number> = {}\n let sum = 0\n for (const [model, entry] of Object.entries(snapshot.perModel)) {\n const nano =\n typeof entry.nanoAiu === \"number\" && Number.isFinite(entry.nanoAiu)\n ? Math.max(0, entry.nanoAiu)\n : 0\n const factor = sanitizeFactor(factors[model]?.factor)\n const usd = (nano / 1_000_000_000) * USD_PER_AIC_CREDIT * factor\n byModel[model] = usd\n sum += usd\n }\n const cap = aicTotalCredits(snapshot) * USD_PER_AIC_CREDIT\n // Relative epsilon: per-model float summation can exceed the cap by a\n // final-ulp hair without any factor being wrong — only bind on a real gap.\n if (!(sum > cap * (1 + 1e-9)) || sum <= 0) {\n return { total: sum, byModel, capped: false }\n }\n // Cap binds (fat-finger factor, volatile row, unknown-model overstatement):\n // scale rows down proportionally so the table still sums to its Total.\n const scale = cap / sum\n const scaled: Record<string, number> = {}\n for (const [model, usd] of Object.entries(byModel)) {\n scaled[model] = usd * scale\n }\n return { total: cap, byModel: scaled, capped: true }\n}\n\n/** Format table dollars: always cents (`~$` added by callers that want it). */\nexport function formatTableUsd(usd: number): string {\n return usd.toFixed(2)\n}\n\ninterface CostTableRow {\n model: string\n uncached: number\n cached: number\n writes: number\n output: number\n usd: number\n}\n\n/**\n * Beautiful per-model session cost table (plain text, no ANSI — it lands on\n * stderr / logs). Columns mirror the spend dashboard: Uncached / Cached /\n * [Write] / Cache% / Output / $. The Write column appears only when the\n * session actually wrote cache. `Cache% = cache_read / (cache_read + input)`;\n * writes are excluded (they are new tokens, not hits).\n */\nexport function formatDiscountedCostTable(\n snapshot: AicSnapshot,\n discounted: DiscountedUsd,\n): string {\n const tokens = snapshot.tokensByModel ?? {}\n const models = new Set([...Object.keys(snapshot.perModel), ...Object.keys(tokens)])\n const rows: Array<CostTableRow> = []\n for (const model of models) {\n const t = tokens[model]\n rows.push({\n model,\n uncached: t?.input ?? 0,\n cached: t?.cache_read ?? 0,\n writes: t?.cache_write ?? 0,\n output: t?.output ?? 0,\n usd: discounted.byModel[model] ?? 0,\n })\n }\n if (rows.length === 0) return \"\"\n rows.sort((a, b) => b.usd - a.usd || a.model.localeCompare(b.model))\n\n const showWrites = rows.some((r) => r.writes > 0)\n const cachePct = (cached: number, uncached: number): string => {\n const denom = cached + uncached\n if (denom <= 0) return \"—\"\n return `${((cached / denom) * 100).toFixed(1)}%`\n }\n\n type Cell = { text: string; align: \"left\" | \"right\" }\n const header: Array<Cell> = [\n { text: \"Model\", align: \"left\" },\n { text: \"Uncached\", align: \"right\" },\n { text: \"Cached\", align: \"right\" },\n ]\n if (showWrites) header.push({ text: \"Write\", align: \"right\" })\n header.push(\n { text: \"Cache%\", align: \"right\" },\n { text: \"Output\", align: \"right\" },\n { text: \"$\", align: \"right\" },\n )\n\n const body: Array<Array<Cell>> = rows.map((r) => {\n const cells: Array<Cell> = [\n { text: r.model, align: \"left\" },\n { text: formatCount(r.uncached), align: \"right\" },\n { text: formatCount(r.cached), align: \"right\" },\n ]\n if (showWrites) cells.push({ text: formatCount(r.writes), align: \"right\" })\n cells.push(\n { text: cachePct(r.cached, r.uncached), align: \"right\" },\n { text: formatCount(r.output), align: \"right\" },\n { text: `~$${formatTableUsd(r.usd)}`, align: \"right\" },\n )\n return cells\n })\n\n const totalUncached = rows.reduce((n, r) => n + r.uncached, 0)\n const totalCached = rows.reduce((n, r) => n + r.cached, 0)\n const totalWrites = rows.reduce((n, r) => n + r.writes, 0)\n const totalOutput = rows.reduce((n, r) => n + r.output, 0)\n const totalCells: Array<Cell> = [\n { text: \"Total\", align: \"left\" },\n { text: formatCount(totalUncached), align: \"right\" },\n { text: formatCount(totalCached), align: \"right\" },\n ]\n if (showWrites) {\n totalCells.push({ text: formatCount(totalWrites), align: \"right\" })\n }\n totalCells.push(\n { text: cachePct(totalCached, totalUncached), align: \"right\" },\n { text: formatCount(totalOutput), align: \"right\" },\n { text: `~$${formatTableUsd(discounted.total)}`, align: \"right\" },\n )\n\n const widths = header.map((h, i) => {\n let w = h.text.length\n for (const row of [...body, totalCells]) {\n w = Math.max(w, row[i].text.length)\n }\n return w\n })\n const render = (cells: Array<Cell>): string =>\n cells\n .map((c, i) =>\n c.align === \"left\" ? c.text.padEnd(widths[i]) : c.text.padStart(widths[i]),\n )\n .join(\" \")\n const lines = [\n \"Session cost (discounted actuals, ~$):\",\n render(header),\n ...body.map(render),\n render(totalCells),\n ]\n return lines.join(\"\\n\")\n}\n\n/** Compact count for table cells: `999` → `999`, `15234` → `15.2k`. */\nfunction formatCount(n: number): string {\n if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`\n if (n >= 1000) return `${(n / 1000).toFixed(1)}k`\n return `${n}`\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { C as runWorkerAgent } from "./peer-mcp-personas-Gb0LfII0.js";
2
+ export { runWorkerAgent };
@@ -606,7 +606,7 @@ async function discoverGateCommands(cwd, opts) {
606
606
  if (files.length === 0) return null;
607
607
  let result;
608
608
  try {
609
- const { runWorkerAgent } = await import("./engine-57iIqICe.js");
609
+ const { runWorkerAgent } = await import("./engine-DA5laGIh.js");
610
610
  result = await runWorkerAgent({
611
611
  mode: "explore",
612
612
  workspace: root,
@@ -632,4 +632,4 @@ async function discoverGateCommands(cwd, opts) {
632
632
  //#endregion
633
633
  export { checksForDescriptor as a, writeDiscoveredGate as i, readDiscoveredGate as n, descriptorHash as o, sourcesHash as r, parseGateDescriptor as s, discoverGateCommands as t };
634
634
 
635
- //# sourceMappingURL=gate-discovery-DUBuxiLb.js.map
635
+ //# sourceMappingURL=gate-discovery-j_hQ1IPc.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"gate-discovery-DUBuxiLb.js","names":["fs","nodePath","fs","nodePath"],"sources":["../src/lib/orchestration/harness-parse.ts","../src/lib/orchestration/gate-discovery.ts"],"sourcesContent":["/**\n * Deterministic, language-agnostic harness PARSER for the structural Stop-gate.\n *\n * The gate runs a repo's own checks (typecheck/lint/test/build) when the agent\n * tries to finish. Historically it only auto-enabled for Bun/TS repos via a\n * hard-coded sealed gate. This module generalizes detection by PARSING the\n * project's own authoritative config — `package.json` scripts, CI `run:` steps,\n * Make/just/task targets, and language manifests — and emitting the canonical\n * check commands it finds.\n *\n * Design properties (a cross-lab panel chose a parser over a model for these):\n * - EVIDENCE-PINNED BY CONSTRUCTION: every emitted command is either lifted\n * verbatim from a parsed source file (a package.json script, a CI `run:`\n * line, a Make target invocation) or is a fixed canonical manifest command\n * for a detected ecosystem (`cargo check`, `go vet ./...`). The parser never\n * invents a command, so it has no hallucination / prompt-injection surface.\n * - PURE + DETERMINISTIC: a function of the repo's files + the live PATH. The\n * runtime Stop hook re-derives it at each stop (no cache), so it always\n * reflects the current tree and fails open for free when a tool vanished.\n * - SHELL-SAFE: `liveExec` runs `command.trim().split(/\\s+/)` (naive argv\n * split, no shell), so every emitted command is validated to be plain\n * space-separated tokens with no shell operators and no `%` (cmd.exe quoting\n * throws on `%`). See `isSafeCommand`.\n *\n * This is consumed ONLY by the local Stop hook. The sealed-gate kernel\n * (`run_workflow`) is never fed a parsed command — `GateDescriptor.kind` is\n * `\"parsed\"`, never a sealed id, and `sealedGateIds()` is unchanged.\n */\n\nimport { createHash } from \"node:crypto\"\nimport { existsSync, promises as fs } from \"node:fs\"\nimport nodePath from \"node:path\"\n\nimport { resolveExecutable } from \"~/lib/exec\"\n\nimport { resolveSealedGate } from \"./gate-registry\"\nimport { type CheckSpec } from \"./gate-runner\"\n\n/** Canonical check ids — stable across ecosystems so baseline isolation and the\n * selector key on the same names regardless of language. `build` is deliberately\n * NOT a check: build scripts are too variable in cost to auto-run on every stop\n * (a full bundle/SEA build is the slow-command / false-red footgun the design\n * avoids). Compile-checking is covered under `typecheck` (`go vet`, `cargo\n * check`, `tsc`). */\nexport type CheckId = \"typecheck\" | \"lint\" | \"test\"\n\n/** The fast static checks that are always-on; `test` is opt-in (it runs project\n * code and can be slow on every stop). */\nconst STATIC_IDS: ReadonlySet<CheckId> = new Set([\"typecheck\", \"lint\"])\n\n/**\n * The resolved gate source for a repo.\n * - `sealed` — the bun/TS fast-path (a sealed gate id, byte-identical to\n * the legacy behavior; the runtime hook resolves it via the\n * sealed registry).\n * - `parsed` — deterministic parser output (this module).\n * - `discovered` — the evidence-pinned model fallback (see `gate-discovery`).\n * `workdir` is the directory the checks run in (the repo/package root where the\n * evidence was found), NOT the Stop payload's cwd — so a monorepo stop from a\n * nested dir still runs the root's checks.\n */\nexport type GateDescriptor =\n | { kind: \"sealed\"; gateId: string; workdir: string }\n | { kind: \"parsed\"; checks: CheckSpec[]; ecosystem: string; workdir: string; evidence: string[] }\n | { kind: \"discovered\"; checks: CheckSpec[]; ecosystem: string; workdir: string; evidence: string[] }\n\ninterface Candidate {\n id: CheckId\n command: string\n /** The source file the command was lifted from (for evidence + messages). */\n source: string\n}\n\n/** Reject anything that is not a plain, shell-free, `%`-free argv line. This is\n * the load-bearing guard for `liveExec`'s naive whitespace split. */\nexport function isSafeCommand(command: string): boolean {\n const c = command.trim()\n if (c.length === 0 || c.length > 200) return false\n if (/[\\r\\n]/.test(c)) return false\n // Each token may contain only these chars: letters/digits and a small set of\n // path/flag punctuation. This rejects shell metacharacters (; & | < > ( ) $\n // backtick \" ' * ? ! ^ \\ %), env expansion, and quoting in one shot.\n const tokens = c.split(/\\s+/).filter(Boolean)\n if (tokens.length === 0) return false\n return tokens.every((t) => /^[A-Za-z0-9._/:@=+-]+$/.test(t))\n}\n\n/** First token of a command (the executable), for a PATH-presence probe. */\nfunction firstToken(command: string): string {\n return command.trim().split(/\\s+/)[0] ?? \"\"\n}\n\n/** True when a command would MUTATE the tree and so must never be a gate: a\n * `--fix`/`--write` flag or a `:fix`/`:write` script-name suffix (e.g.\n * `npm run lint:fix`, `eslint --fix`). Whitespace is normalized first so a\n * tab/double-space can't slip a flag past the scan. Exported + shared with the\n * discovered-command sanitizer. */\nexport function isMutatingCommand(command: string): boolean {\n const c = command.trim().replace(/\\s+/g, \" \")\n if (/(^| )(--fix|--write|-w|--watch|--serve|--interactive|-i)( |=|$)/i.test(c)) return true\n if (/(^| |:|-)(fix|write)\\b/i.test(c)) return true\n return false\n}\n\n/** True when the command is safe AND its executable resolves on PATH (a missing\n * tool → drop the check rather than guarantee a false-red). */\nfunction commandRunnable(command: string): boolean {\n if (!isSafeCommand(command)) return false\n return resolveExecutable(firstToken(command), { env: process.env }) !== null\n}\n\nasync function readJsonFile(file: string): Promise<unknown | undefined> {\n try {\n return JSON.parse(await fs.readFile(file, \"utf8\")) as unknown\n } catch {\n return undefined\n }\n}\n\nasync function readTextFile(file: string): Promise<string | undefined> {\n try {\n return await fs.readFile(file, \"utf8\")\n } catch {\n return undefined\n }\n}\n\n/** package.json `scripts` as a plain string→string map (best-effort). */\nasync function readScripts(root: string): Promise<Record<string, string>> {\n const pkg = await readJsonFile(nodePath.join(root, \"package.json\"))\n const scripts = pkg && typeof pkg === \"object\" ? (pkg as { scripts?: unknown }).scripts : undefined\n const out: Record<string, string> = {}\n if (scripts && typeof scripts === \"object\") {\n for (const [k, v] of Object.entries(scripts as Record<string, unknown>)) {\n if (typeof v === \"string\") out[k] = v\n }\n }\n return out\n}\n\n/** Pick the JS package runner from the lockfile present at the root. */\nfunction nodeRunner(root: string): \"bun\" | \"pnpm\" | \"yarn\" | \"npm\" {\n if (existsSync(nodePath.join(root, \"bun.lockb\")) || existsSync(nodePath.join(root, \"bun.lock\"))) return \"bun\"\n if (existsSync(nodePath.join(root, \"pnpm-lock.yaml\"))) return \"pnpm\"\n if (existsSync(nodePath.join(root, \"yarn.lock\"))) return \"yarn\"\n return \"npm\"\n}\n\n/** Map a package.json script NAME to a canonical id (read-only checks only — a\n * `:fix`/`:write` variant mutates the tree and is never a gate). */\nfunction scriptNameToId(name: string): CheckId | null {\n if (/:(fix|write)$/i.test(name)) return null\n if (/^(typecheck|type-check|check-types|tsc|types)$/i.test(name)) return \"typecheck\"\n if (/^(lint|lint:check|eslint)$/i.test(name)) return \"lint\"\n if (/^test$/i.test(name)) return \"test\"\n return null\n}\n\nasync function collectNodeChecks(root: string, includeTests: boolean): Promise<Candidate[]> {\n if (!existsSync(nodePath.join(root, \"package.json\"))) return []\n const runner = nodeRunner(root)\n if (resolveExecutable(runner, { env: process.env }) === null) return []\n const scripts = await readScripts(root)\n const out: Candidate[] = []\n for (const [name] of Object.entries(scripts)) {\n const id = scriptNameToId(name)\n if (!id) continue\n if (id === \"test\" && !includeTests) continue\n const command = `${runner} run ${name}`\n if (isSafeCommand(command)) out.push({ id, command, source: \"package.json\" })\n }\n return out\n}\n\n/**\n * Best-effort CI gap-filler: scan GitHub Actions / GitLab CI YAML for single-line\n * `run:` static checks (typecheck/lint/build) the project actually runs. We do\n * NOT lift `test` from CI — CI test jobs frequently need services/secrets and\n * would false-red on every stop. Multi-line / scripted `run:` blocks are skipped\n * (they can't be a single argv). Any parse failure yields nothing.\n */\nasync function collectCiChecks(root: string): Promise<Candidate[]> {\n const files: string[] = []\n const wfDir = nodePath.join(root, \".github\", \"workflows\")\n try {\n for (const name of await fs.readdir(wfDir)) {\n if (/\\.ya?ml$/i.test(name)) files.push(nodePath.join(wfDir, name))\n }\n } catch {\n /* no workflows dir */\n }\n const gitlab = nodePath.join(root, \".gitlab-ci.yml\")\n if (existsSync(gitlab)) files.push(gitlab)\n if (files.length === 0) return []\n\n let parseYaml: (s: string) => unknown\n try {\n // Lazy import so the parser module has no hard dep when CI files are absent.\n ;({ parse: parseYaml } = await import(\"yaml\"))\n } catch {\n return []\n }\n\n const out: Candidate[] = []\n const seen = new Set<CheckId>()\n const classify = (cmd: string): CheckId | null => {\n if (/\\b(typecheck|type-check|tsc|mypy|pyright)\\b/i.test(cmd)) return \"typecheck\"\n if (/\\b(lint|eslint|ruff|clippy|golangci|vet)\\b/i.test(cmd)) return \"lint\"\n return null\n }\n const consider = (run: unknown, source: string): void => {\n if (typeof run !== \"string\") return\n const cmd = run.trim()\n if (cmd.includes(\"\\n\")) return // multi-line script block — not a single argv.\n if (!isSafeCommand(cmd) || isMutatingCommand(cmd) || !commandRunnable(cmd)) return\n const id = classify(cmd)\n if (!id || seen.has(id)) return\n seen.add(id)\n out.push({ id, command: cmd, source })\n }\n // Walk the parsed YAML for any `run:` string anywhere (GH `steps[].run`, GitLab\n // `<job>.script[]`). A generic deep walk tolerates both schemas + future shapes.\n const walk = (node: unknown, source: string): void => {\n if (!node || typeof node !== \"object\") return\n if (Array.isArray(node)) {\n for (const v of node) walk(v, source)\n return\n }\n for (const [k, v] of Object.entries(node as Record<string, unknown>)) {\n if (k === \"run\" && typeof v === \"string\") consider(v, source)\n else if (k === \"script\") {\n // GitLab `script:` is a string or string[].\n if (typeof v === \"string\") consider(v, source)\n else if (Array.isArray(v)) for (const s of v) consider(s, source)\n } else walk(v, source)\n }\n }\n for (const f of files) {\n const text = await readTextFile(f)\n if (text === undefined) continue\n try {\n walk(parseYaml(text), nodePath.relative(root, f) || nodePath.basename(f))\n } catch {\n /* unparseable workflow → skip */\n }\n }\n return out\n}\n\n/** Make / just / Taskfile targets matching canonical ids → `<tool> <target>`. */\nasync function collectTaskChecks(root: string, includeTests: boolean): Promise<Candidate[]> {\n const out: Candidate[] = []\n const wantId = (target: string): CheckId | null => scriptNameToId(target)\n // Makefile / justfile: a line `target:` (Make) or `target:` (just recipe head).\n for (const [file, tool] of [\n [\"Makefile\", \"make\"],\n [\"makefile\", \"make\"],\n [\"justfile\", \"just\"],\n [\".justfile\", \"just\"],\n ] as const) {\n const text = await readTextFile(nodePath.join(root, file))\n if (text === undefined) continue\n if (resolveExecutable(tool, { env: process.env }) === null) continue\n for (const line of text.split(/\\r?\\n/)) {\n const m = /^([A-Za-z0-9._-]+)\\s*:/.exec(line)\n if (!m) continue\n const id = wantId(m[1])\n if (!id || (id === \"test\" && !includeTests)) continue\n const command = `${tool} ${m[1]}`\n if (isSafeCommand(command)) out.push({ id, command, source: file })\n }\n }\n // Taskfile.yml: top-level `tasks:` keys.\n const taskfile = [\"Taskfile.yml\", \"Taskfile.yaml\"].map((n) => nodePath.join(root, n)).find((p) => existsSync(p))\n if (taskfile && resolveExecutable(\"task\", { env: process.env }) !== null) {\n const text = await readTextFile(taskfile)\n if (text !== undefined) {\n try {\n const { parse } = await import(\"yaml\")\n const doc = parse(text) as { tasks?: Record<string, unknown> } | undefined\n for (const name of Object.keys(doc?.tasks ?? {})) {\n const id = wantId(name)\n if (!id || (id === \"test\" && !includeTests)) continue\n const command = `task ${name}`\n if (isSafeCommand(command)) out.push({ id, command, source: nodePath.basename(taskfile) })\n }\n } catch {\n /* skip */\n }\n }\n }\n return out\n}\n\nasync function collectRustChecks(root: string, includeTests: boolean): Promise<Candidate[]> {\n if (!existsSync(nodePath.join(root, \"Cargo.toml\"))) return []\n if (resolveExecutable(\"cargo\", { env: process.env }) === null) return []\n const out: Candidate[] = [{ id: \"typecheck\", command: \"cargo check\", source: \"Cargo.toml\" }]\n // clippy is a separate component; only emit it if `cargo-clippy` resolves.\n if (resolveExecutable(\"cargo-clippy\", { env: process.env }) !== null) {\n out.push({ id: \"lint\", command: \"cargo clippy\", source: \"Cargo.toml\" })\n }\n if (includeTests) out.push({ id: \"test\", command: \"cargo test\", source: \"Cargo.toml\" })\n return out\n}\n\nasync function collectGoChecks(root: string, includeTests: boolean): Promise<Candidate[]> {\n if (!existsSync(nodePath.join(root, \"go.mod\"))) return []\n if (resolveExecutable(\"go\", { env: process.env }) === null) return []\n // `go vet` compiles + reports suspect constructs — the typecheck-equivalent.\n const out: Candidate[] = [{ id: \"typecheck\", command: \"go vet ./...\", source: \"go.mod\" }]\n if (includeTests) out.push({ id: \"test\", command: \"go test ./...\", source: \"go.mod\" })\n return out\n}\n\nasync function collectPythonChecks(root: string, includeTests: boolean): Promise<Candidate[]> {\n const configFiles = [\"pyproject.toml\", \"setup.cfg\", \"pytest.ini\", \"tox.ini\", \"ruff.toml\", \"mypy.ini\"]\n const present = configFiles.filter((f) => existsSync(nodePath.join(root, f)))\n if (present.length === 0) return []\n const evidence = present[0]\n const out: Candidate[] = []\n const configText = (await readTextFile(nodePath.join(root, present[0]))) ?? \"\"\n const mentions = (tool: string): boolean => present.some((f) => f.startsWith(tool)) || configText.includes(tool)\n // Only emit a Python tool when BOTH config evidence AND the tool resolve.\n if (mentions(\"mypy\") && resolveExecutable(\"mypy\", { env: process.env }) !== null) {\n out.push({ id: \"typecheck\", command: \"mypy .\", source: evidence })\n }\n if (mentions(\"ruff\") && resolveExecutable(\"ruff\", { env: process.env }) !== null) {\n out.push({ id: \"lint\", command: \"ruff check .\", source: evidence })\n }\n if (includeTests && (mentions(\"pytest\") || existsSync(nodePath.join(root, \"pytest.ini\")))) {\n if (resolveExecutable(\"pytest\", { env: process.env }) !== null) {\n out.push({ id: \"test\", command: \"pytest -q\", source: evidence })\n }\n }\n return out\n}\n\n/** Pick the first runnable candidate per id, in source-priority order. */\nfunction pickByPriority(candidates: Candidate[]): { checks: CheckSpec[]; evidence: string[] } {\n const byId = new Map<CheckId, Candidate>()\n for (const c of candidates) {\n if (!byId.has(c.id) && commandRunnable(c.command)) byId.set(c.id, c)\n }\n const order: CheckId[] = [\"typecheck\", \"lint\", \"test\"]\n const checks: CheckSpec[] = []\n const evidence = new Set<string>()\n for (const id of order) {\n const c = byId.get(id)\n if (c) {\n checks.push({ id: c.id, command: c.command })\n evidence.add(c.source)\n }\n }\n return { checks, evidence: [...evidence] }\n}\n\n/**\n * Resolve the gate descriptor for an already-resolved repo `root`.\n * 1. bun/TS sealed fast-path — byte-identical to the legacy `detectHarnessGateId`\n * (bun on PATH + a `typecheck` script → sealed `default-ci`/`typecheck-test`).\n * 2. else the deterministic parser: collect candidates from package.json\n * scripts (primary, self-contained), Make/just/task, language manifests, and\n * a CI gap-filler for static checks; pick one per id by priority. A `parsed`\n * descriptor is returned only when at least one STATIC check survives (a\n * test-only set would either be off-by-default or risk false-reds).\n * 3. else null (the launcher prints why and the gate stays off).\n */\nexport async function parseGateDescriptor(\n root: string,\n opts: { includeTests: boolean },\n): Promise<GateDescriptor | null> {\n // (1) bun/TS sealed parity.\n if (resolveExecutable(\"bun\", { env: process.env }) !== null) {\n const scripts = await readScripts(root)\n if (typeof scripts.typecheck === \"string\") {\n const gateId = typeof scripts.lint === \"string\" ? \"default-ci\" : \"typecheck-test\"\n return { kind: \"sealed\", gateId, workdir: root }\n }\n }\n\n // (2) deterministic parser. package.json scripts first (self-contained), then\n // task runners + manifests, then a CI gap-filler for static checks.\n const groups = await Promise.all([\n collectNodeChecks(root, opts.includeTests),\n collectTaskChecks(root, opts.includeTests),\n collectRustChecks(root, opts.includeTests),\n collectGoChecks(root, opts.includeTests),\n collectPythonChecks(root, opts.includeTests),\n collectCiChecks(root),\n ])\n const candidates = groups.flat()\n if (candidates.length === 0) return null\n\n const ecosystem =\n candidates.find((c) => c.source === \"package.json\") ? \"node\"\n : candidates.find((c) => c.source === \"Cargo.toml\") ? \"rust\"\n : candidates.find((c) => c.source === \"go.mod\") ? \"go\"\n : candidates.find((c) => /^(pyproject|setup|pytest|tox|ruff|mypy)/.test(c.source)) ? \"python\"\n : candidates.find((c) => /^(Makefile|makefile|justfile|\\.justfile|Taskfile)/.test(c.source)) ? \"make\"\n : \"ci\"\n\n const { checks, evidence } = pickByPriority(candidates)\n if (checks.length === 0) return null\n // Require at least one STATIC check (typecheck/lint/build) unless the caller\n // opted into running the full test suite — a test-only set is otherwise either\n // off-by-default or a slow/false-red risk on every stop.\n const hasStatic = checks.some((c) => STATIC_IDS.has(c.id as CheckId))\n if (!hasStatic && !opts.includeTests) return null\n return { kind: \"parsed\", checks, ecosystem, workdir: root, evidence }\n}\n\n/** The checks a descriptor runs: a sealed descriptor resolves its sealed command\n * set from the registry; parsed/discovered carry their own. Fresh array. */\nexport function checksForDescriptor(d: GateDescriptor): CheckSpec[] {\n if (d.kind === \"sealed\") {\n const sealed = resolveSealedGate(d.gateId)\n return sealed ? sealed.checks.map((c) => ({ id: c.id, command: c.command })) : []\n }\n return d.checks.map((c) => ({ id: c.id, command: c.command }))\n}\n\n/** A stable key over a descriptor's effective check set, for baseline isolation.\n * Sealed descriptors key on their gate id (preserving legacy baseline keys);\n * parsed/discovered key on the canonicalized (id,command) set, so a changed\n * command set yields a fresh baseline instead of masking/inventing regressions. */\nexport function descriptorHash(d: GateDescriptor): string {\n if (d.kind === \"sealed\") return `sealed:${d.gateId}`\n const canon = [...d.checks]\n .map((c) => `${c.id}\u0000${c.command.trim().replace(/\\s+/g, \" \")}`)\n .sort()\n .join(\"\u0001\")\n return `${d.kind}:${createHash(\"sha256\").update(canon).digest(\"hex\").slice(0, 32)}`\n}\n","/**\n * Evidence-pinned model FALLBACK for the structural Stop-gate — the last resort\n * when the deterministic parser (`harness-parse`) finds no runnable checks.\n *\n * A read-only worker reads the repo's own config/docs and proposes the canonical\n * check commands. Two guards keep this safe despite being model-authored:\n * 1. SANITIZE — `sanitizeDiscoveredCheck` rejects shell metacharacters (so a\n * command can never chain/redirect/expand), destructive/stateful verbs,\n * interactive/watch shapes, and an executable that isn't on PATH. What\n * survives is a single plain argv line, safe for `liveExec`'s naive split.\n * 2. EVIDENCE-PIN — a surviving command must appear (whitespace-normalized)\n * VERBATIM in one of the collected source files. The model cannot invent a\n * command or be prompt-injected into emitting one that isn't already a real\n * command in the repo (and a real command in a user-trusted repo is the\n * same authority the existing gate already runs).\n *\n * Discovery runs ONCE at launch and the result is cached per (repoFingerprint,\n * sourcesHash) in a human-readable record. The runtime Stop hook only READS the\n * cached record (no model call at stop). This is consumed ONLY by the local Stop\n * hook; the sealed-gate kernel never sees a discovered command.\n */\n\nimport { createHash } from \"node:crypto\"\nimport { existsSync, promises as fs } from \"node:fs\"\nimport nodePath from \"node:path\"\n\nimport { resolveExecutable } from \"~/lib/exec\"\nimport { PATHS } from \"~/lib/paths\"\n\nimport { type CheckSpec } from \"./gate-runner\"\nimport { isMutatingCommand, isSafeCommand, type CheckId } from \"./harness-parse\"\nimport { repoFingerprint, repoRoot } from \"./stop-gate-policy\"\n\n/** The allowlist of files the discovery worker is steered to read — config +\n * docs that legitimately describe how to check a project. Secret files are\n * additionally blocked at the worker IO layer (`.env*`/`*.pem`/`id_*`/…). */\nconst SIGNAL_FILES: ReadonlyArray<string> = [\n \"package.json\",\n \"Makefile\",\n \"makefile\",\n \"justfile\",\n \".justfile\",\n \"Taskfile.yml\",\n \"Taskfile.yaml\",\n \"Cargo.toml\",\n \"go.mod\",\n \"pyproject.toml\",\n \"setup.cfg\",\n \"tox.ini\",\n \"pytest.ini\",\n \"CONTRIBUTING.md\",\n \"CONTRIBUTING\",\n \"README.md\",\n \"README\",\n \"DEVELOPING.md\",\n \"mix.exs\",\n \"build.gradle\",\n \"pom.xml\",\n \"composer.json\",\n]\nconst SIGNAL_DIRS: ReadonlyArray<string> = [\".github/workflows\"]\n\n/** A discovered check command after sanitization + evidence-pinning. */\nexport interface DiscoveredRecord {\n root: string\n fingerprint: string\n sourcesHash: string\n discoveredAt: string\n model: string\n ecosystem: string\n checks: CheckSpec[]\n confidence: string\n evidence: string[]\n}\n\nconst MAX_SIGNAL_BYTES = 64 * 1024\n/** Global caps so a repo with many workflow files can't inflate discovery /\n * hashing cost: at most this many files and this much total text. */\nconst MAX_SIGNAL_FILES = 40\nconst MAX_TOTAL_SIGNAL_BYTES = 512 * 1024\n\n/** Words that mark a command as destructive, stateful, or non-terminating — a\n * \"check\" must never do any of these. (Shell operators are already rejected by\n * `isSafeCommand`, so chaining can't smuggle them past this word scan.) */\nconst DENY_WORD =\n /\\b(rm|rmdir|mv|dd|mkfs|sudo|chmod|chown|publish|push|deploy|migrate|kubectl|terraform|docker|curl|wget|ssh|scp|rsync|nc|eval|npm i|npm install|yarn add|pip install|apt|brew|watch|serve|repl|dev|start)\\b/i\n\n/**\n * True when `command` is safe to auto-run as a check: a plain argv line (no\n * shell metacharacters), no destructive/stateful verb, no mutating/interactive\n * shape, and its executable resolves on PATH. The deny-word scan runs on a\n * whitespace-NORMALIZED copy so a tab / double-space can't split `npm install`\n * past the literal-space patterns.\n */\nexport function sanitizeDiscoveredCheck(command: string): boolean {\n if (!isSafeCommand(command)) return false\n const norm = normalizeWs(command)\n if (DENY_WORD.test(norm)) return false\n if (isMutatingCommand(norm)) return false // --fix/--write/--watch + :fix/:write scripts\n const first = norm.split(\" \")[0] ?? \"\"\n return resolveExecutable(first, { env: process.env }) !== null\n}\n\nfunction normalizeWs(s: string): string {\n return s.replace(/\\s+/g, \" \").trim()\n}\n\n/** Collect the text of the allowlisted signal files (capped), for evidence-pin +\n * the sources hash. Returns the concatenated text and the relative file list. */\nasync function collectSignals(root: string): Promise<{ text: string; files: string[] }> {\n const parts: string[] = []\n const files: string[] = []\n let total = 0\n const readCapped = async (abs: string, rel: string): Promise<void> => {\n if (files.length >= MAX_SIGNAL_FILES || total >= MAX_TOTAL_SIGNAL_BYTES) return\n try {\n const raw = await fs.readFile(abs, \"utf8\")\n const slice = raw.length > MAX_SIGNAL_BYTES ? raw.slice(0, MAX_SIGNAL_BYTES) : raw\n parts.push(slice)\n files.push(rel)\n total += slice.length\n } catch {\n /* unreadable → skip */\n }\n }\n for (const f of SIGNAL_FILES) {\n const abs = nodePath.join(root, f)\n if (existsSync(abs)) await readCapped(abs, f)\n }\n for (const d of SIGNAL_DIRS) {\n const dir = nodePath.join(root, d)\n try {\n for (const name of await fs.readdir(dir)) {\n if (/\\.(ya?ml)$/i.test(name)) await readCapped(nodePath.join(dir, name), nodePath.join(d, name))\n }\n } catch {\n /* no dir */\n }\n }\n return { text: parts.join(\"\\n\"), files }\n}\n\n/** A freshness hash over the signal files' contents + relative paths. A change\n * to how the project checks itself flips this → re-discovery on next launch. */\nexport async function sourcesHash(root: string): Promise<string> {\n const { text, files } = await collectSignals(root)\n return createHash(\"sha256\")\n .update(files.sort().join(\"\\n\"))\n .update(\"\\0\")\n .update(text)\n .digest(\"hex\")\n}\n\nfunction discoveredDir(): string {\n return nodePath.join(PATHS.APP_DIR, \"stop-gate\", \"discovered\")\n}\nfunction recordPathFor(root: string): string {\n return nodePath.join(discoveredDir(), createHash(\"sha256\").update(nodePath.resolve(root)).digest(\"hex\").slice(0, 32))\n}\n\n/** Read the cached discovered record for `root`, verifying it still matches the\n * live repo identity AND the live sources hash. Any mismatch / unreadable /\n * empty-checks record → null (re-discover or stay off; never run a stale set). */\nexport async function readDiscoveredGate(root: string): Promise<DiscoveredRecord | null> {\n let rec: DiscoveredRecord\n try {\n rec = JSON.parse(await fs.readFile(recordPathFor(root), \"utf8\")) as DiscoveredRecord\n } catch {\n return null\n }\n if (!rec || !Array.isArray(rec.checks) || rec.checks.length === 0) return null\n const fp = await repoFingerprint(root).catch(() => \"\")\n if (fp.length === 0 || fp !== rec.fingerprint) return null // identity drift → deny.\n const sh = await sourcesHash(root).catch(() => \"\")\n if (sh.length === 0 || sh !== rec.sourcesHash) return null // config changed → stale.\n // Re-validate id AND re-sanitize the command at read time (defense-in-depth\n // against a tampered/corrupt record).\n const checks = rec.checks.filter(\n (c) => c && typeof c.id === \"string\" && VALID_IDS.has(c.id) && typeof c.command === \"string\" && sanitizeDiscoveredCheck(c.command),\n )\n if (checks.length === 0) return null\n return { ...rec, checks }\n}\n\nexport async function writeDiscoveredGate(rec: DiscoveredRecord): Promise<void> {\n await fs.mkdir(discoveredDir(), { recursive: true })\n const tmp = `${recordPathFor(rec.root)}.${process.pid}.tmp`\n await fs.writeFile(tmp, `${JSON.stringify(rec, null, 2)}\\n`, { mode: 0o600 })\n await fs.rename(tmp, recordPathFor(rec.root))\n}\n\nconst DISCOVERY_PROMPT = (files: string[]): string =>\n `You are configuring an automated pre-finish CHECK gate for this repository. Read ONLY these `\n + `already-present config/doc files to learn how the project checks itself: ${files.join(\", \")}. `\n + `Identify the canonical FAST static check command (typecheck and/or lint) and, if obvious, the test `\n + `command. Rules: (1) return a command ONLY if it appears VERBATIM in one of those files — never invent `\n + `one; (2) commands must be non-interactive, self-terminating, read-only verification (NO install / `\n + `publish / push / deploy / migrate / format-in-place / watch / serve / dev-server / delete); (3) at most `\n + `3 commands; (4) if unsure, return an empty list. Respond with ONLY a fenced \\`\\`\\`json block of the shape `\n + `{\"ecosystem\":\"<label>\",\"checks\":[{\"id\":\"typecheck|lint|test\",\"command\":\"<single-line command>\"}],`\n + `\"confidence\":\"high|low\"} and NOTHING else.`\n\ninterface DiscoverResult {\n ecosystem: string\n checks: CheckSpec[]\n confidence: string\n evidence: string[]\n}\n\n/** Extract the first fenced/bare JSON object from the worker's text. */\nfunction extractJson(text: string): unknown {\n const fenced = /```(?:json)?\\s*([\\s\\S]*?)```/i.exec(text)\n const body = fenced ? fenced[1] : text\n const start = body.indexOf(\"{\")\n const end = body.lastIndexOf(\"}\")\n if (start < 0 || end <= start) return undefined\n try {\n return JSON.parse(body.slice(start, end + 1)) as unknown\n } catch {\n return undefined\n }\n}\n\nconst VALID_IDS: ReadonlySet<string> = new Set<CheckId>([\"typecheck\", \"lint\", \"test\"])\n\n/**\n * Sanitize + EVIDENCE-PIN the model's raw checks against the collected source\n * text. Pure (no IO) so it is unit-testable without the worker. A check survives\n * only if: its id is canonical, it isn't a `test` while tests are off, its id\n * hasn't already been taken, it passes `sanitizeDiscoveredCheck`, AND its command\n * appears (whitespace-normalized) VERBATIM in `evidenceText`. The evidence-pin is\n * the load-bearing guard: the model cannot invent a command or be prompt-injected\n * into one that isn't already a real command in the (user-trusted) repo.\n */\nexport function filterDiscoveredChecks(\n rawChecks: unknown,\n evidenceText: string,\n includeTests: boolean,\n): CheckSpec[] {\n const evidenceNorm = normalizeWs(evidenceText)\n const seen = new Set<string>()\n const checks: CheckSpec[] = []\n for (const c of Array.isArray(rawChecks) ? rawChecks : []) {\n if (!c || typeof c !== \"object\") continue\n const id = (c as { id?: unknown }).id\n const command = (c as { command?: unknown }).command\n if (typeof id !== \"string\" || !VALID_IDS.has(id)) continue\n if (typeof command !== \"string\") continue\n if (id === \"test\" && !includeTests) continue\n if (seen.has(id)) continue\n if (!sanitizeDiscoveredCheck(command)) continue\n if (!evidenceNorm.includes(normalizeWs(command))) continue\n seen.add(id)\n checks.push({ id, command: command.trim() })\n }\n return checks\n}\n\n/**\n * Run the read-only worker to discover check commands for `cwd`. Returns the\n * sanitized + evidence-pinned result, or null (worker unavailable / errored /\n * nothing survived). NEVER throws.\n */\nexport async function discoverGateCommands(\n cwd: string,\n opts: { signal?: AbortSignal; includeTests: boolean },\n): Promise<DiscoverResult | null> {\n const root = await repoRoot(cwd).catch(() => cwd)\n const { text: evidenceText, files } = await collectSignals(root)\n if (files.length === 0) return null\n\n let result: { text: string; isError?: boolean }\n try {\n // Lazy-import the heavy worker-agent engine ONLY when discovery actually\n // runs — keeping it out of the module-load graph of `claude.ts` (which\n // imports this module), so the mock.module-isolated CLI tests don't deadlock\n // pulling the Pi runtime at load time.\n const { runWorkerAgent } = await import(\"~/lib/worker-agent/engine\")\n // Discovery is advisory setup, not a verified workflow producer/checker,\n // so honoring the operator's explore default here is intentional.\n result = await runWorkerAgent({\n mode: \"explore\",\n workspace: root,\n prompt: DISCOVERY_PROMPT(files),\n signal: opts.signal,\n })\n } catch {\n return null\n }\n if (result.isError) return null\n const parsed = extractJson(result.text)\n if (!parsed || typeof parsed !== \"object\") return null\n const obj = parsed as { ecosystem?: unknown; checks?: unknown; confidence?: unknown }\n const checks = filterDiscoveredChecks(obj.checks, evidenceText, opts.includeTests)\n if (checks.length === 0) return null\n const ecosystem = typeof obj.ecosystem === \"string\" && obj.ecosystem.length > 0 ? obj.ecosystem : \"discovered\"\n const confidence = typeof obj.confidence === \"string\" ? obj.confidence : \"low\"\n return { ecosystem, checks, confidence, evidence: files }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"gate-discovery-j_hQ1IPc.js","names":["fs","nodePath","fs","nodePath"],"sources":["../src/lib/orchestration/harness-parse.ts","../src/lib/orchestration/gate-discovery.ts"],"sourcesContent":["/**\n * Deterministic, language-agnostic harness PARSER for the structural Stop-gate.\n *\n * The gate runs a repo's own checks (typecheck/lint/test/build) when the agent\n * tries to finish. Historically it only auto-enabled for Bun/TS repos via a\n * hard-coded sealed gate. This module generalizes detection by PARSING the\n * project's own authoritative config — `package.json` scripts, CI `run:` steps,\n * Make/just/task targets, and language manifests — and emitting the canonical\n * check commands it finds.\n *\n * Design properties (a cross-lab panel chose a parser over a model for these):\n * - EVIDENCE-PINNED BY CONSTRUCTION: every emitted command is either lifted\n * verbatim from a parsed source file (a package.json script, a CI `run:`\n * line, a Make target invocation) or is a fixed canonical manifest command\n * for a detected ecosystem (`cargo check`, `go vet ./...`). The parser never\n * invents a command, so it has no hallucination / prompt-injection surface.\n * - PURE + DETERMINISTIC: a function of the repo's files + the live PATH. The\n * runtime Stop hook re-derives it at each stop (no cache), so it always\n * reflects the current tree and fails open for free when a tool vanished.\n * - SHELL-SAFE: `liveExec` runs `command.trim().split(/\\s+/)` (naive argv\n * split, no shell), so every emitted command is validated to be plain\n * space-separated tokens with no shell operators and no `%` (cmd.exe quoting\n * throws on `%`). See `isSafeCommand`.\n *\n * This is consumed ONLY by the local Stop hook. The sealed-gate kernel\n * (`run_workflow`) is never fed a parsed command — `GateDescriptor.kind` is\n * `\"parsed\"`, never a sealed id, and `sealedGateIds()` is unchanged.\n */\n\nimport { createHash } from \"node:crypto\"\nimport { existsSync, promises as fs } from \"node:fs\"\nimport nodePath from \"node:path\"\n\nimport { resolveExecutable } from \"~/lib/exec\"\n\nimport { resolveSealedGate } from \"./gate-registry\"\nimport { type CheckSpec } from \"./gate-runner\"\n\n/** Canonical check ids — stable across ecosystems so baseline isolation and the\n * selector key on the same names regardless of language. `build` is deliberately\n * NOT a check: build scripts are too variable in cost to auto-run on every stop\n * (a full bundle/SEA build is the slow-command / false-red footgun the design\n * avoids). Compile-checking is covered under `typecheck` (`go vet`, `cargo\n * check`, `tsc`). */\nexport type CheckId = \"typecheck\" | \"lint\" | \"test\"\n\n/** The fast static checks that are always-on; `test` is opt-in (it runs project\n * code and can be slow on every stop). */\nconst STATIC_IDS: ReadonlySet<CheckId> = new Set([\"typecheck\", \"lint\"])\n\n/**\n * The resolved gate source for a repo.\n * - `sealed` — the bun/TS fast-path (a sealed gate id, byte-identical to\n * the legacy behavior; the runtime hook resolves it via the\n * sealed registry).\n * - `parsed` — deterministic parser output (this module).\n * - `discovered` — the evidence-pinned model fallback (see `gate-discovery`).\n * `workdir` is the directory the checks run in (the repo/package root where the\n * evidence was found), NOT the Stop payload's cwd — so a monorepo stop from a\n * nested dir still runs the root's checks.\n */\nexport type GateDescriptor =\n | { kind: \"sealed\"; gateId: string; workdir: string }\n | { kind: \"parsed\"; checks: CheckSpec[]; ecosystem: string; workdir: string; evidence: string[] }\n | { kind: \"discovered\"; checks: CheckSpec[]; ecosystem: string; workdir: string; evidence: string[] }\n\ninterface Candidate {\n id: CheckId\n command: string\n /** The source file the command was lifted from (for evidence + messages). */\n source: string\n}\n\n/** Reject anything that is not a plain, shell-free, `%`-free argv line. This is\n * the load-bearing guard for `liveExec`'s naive whitespace split. */\nexport function isSafeCommand(command: string): boolean {\n const c = command.trim()\n if (c.length === 0 || c.length > 200) return false\n if (/[\\r\\n]/.test(c)) return false\n // Each token may contain only these chars: letters/digits and a small set of\n // path/flag punctuation. This rejects shell metacharacters (; & | < > ( ) $\n // backtick \" ' * ? ! ^ \\ %), env expansion, and quoting in one shot.\n const tokens = c.split(/\\s+/).filter(Boolean)\n if (tokens.length === 0) return false\n return tokens.every((t) => /^[A-Za-z0-9._/:@=+-]+$/.test(t))\n}\n\n/** First token of a command (the executable), for a PATH-presence probe. */\nfunction firstToken(command: string): string {\n return command.trim().split(/\\s+/)[0] ?? \"\"\n}\n\n/** True when a command would MUTATE the tree and so must never be a gate: a\n * `--fix`/`--write` flag or a `:fix`/`:write` script-name suffix (e.g.\n * `npm run lint:fix`, `eslint --fix`). Whitespace is normalized first so a\n * tab/double-space can't slip a flag past the scan. Exported + shared with the\n * discovered-command sanitizer. */\nexport function isMutatingCommand(command: string): boolean {\n const c = command.trim().replace(/\\s+/g, \" \")\n if (/(^| )(--fix|--write|-w|--watch|--serve|--interactive|-i)( |=|$)/i.test(c)) return true\n if (/(^| |:|-)(fix|write)\\b/i.test(c)) return true\n return false\n}\n\n/** True when the command is safe AND its executable resolves on PATH (a missing\n * tool → drop the check rather than guarantee a false-red). */\nfunction commandRunnable(command: string): boolean {\n if (!isSafeCommand(command)) return false\n return resolveExecutable(firstToken(command), { env: process.env }) !== null\n}\n\nasync function readJsonFile(file: string): Promise<unknown | undefined> {\n try {\n return JSON.parse(await fs.readFile(file, \"utf8\")) as unknown\n } catch {\n return undefined\n }\n}\n\nasync function readTextFile(file: string): Promise<string | undefined> {\n try {\n return await fs.readFile(file, \"utf8\")\n } catch {\n return undefined\n }\n}\n\n/** package.json `scripts` as a plain string→string map (best-effort). */\nasync function readScripts(root: string): Promise<Record<string, string>> {\n const pkg = await readJsonFile(nodePath.join(root, \"package.json\"))\n const scripts = pkg && typeof pkg === \"object\" ? (pkg as { scripts?: unknown }).scripts : undefined\n const out: Record<string, string> = {}\n if (scripts && typeof scripts === \"object\") {\n for (const [k, v] of Object.entries(scripts as Record<string, unknown>)) {\n if (typeof v === \"string\") out[k] = v\n }\n }\n return out\n}\n\n/** Pick the JS package runner from the lockfile present at the root. */\nfunction nodeRunner(root: string): \"bun\" | \"pnpm\" | \"yarn\" | \"npm\" {\n if (existsSync(nodePath.join(root, \"bun.lockb\")) || existsSync(nodePath.join(root, \"bun.lock\"))) return \"bun\"\n if (existsSync(nodePath.join(root, \"pnpm-lock.yaml\"))) return \"pnpm\"\n if (existsSync(nodePath.join(root, \"yarn.lock\"))) return \"yarn\"\n return \"npm\"\n}\n\n/** Map a package.json script NAME to a canonical id (read-only checks only — a\n * `:fix`/`:write` variant mutates the tree and is never a gate). */\nfunction scriptNameToId(name: string): CheckId | null {\n if (/:(fix|write)$/i.test(name)) return null\n if (/^(typecheck|type-check|check-types|tsc|types)$/i.test(name)) return \"typecheck\"\n if (/^(lint|lint:check|eslint)$/i.test(name)) return \"lint\"\n if (/^test$/i.test(name)) return \"test\"\n return null\n}\n\nasync function collectNodeChecks(root: string, includeTests: boolean): Promise<Candidate[]> {\n if (!existsSync(nodePath.join(root, \"package.json\"))) return []\n const runner = nodeRunner(root)\n if (resolveExecutable(runner, { env: process.env }) === null) return []\n const scripts = await readScripts(root)\n const out: Candidate[] = []\n for (const [name] of Object.entries(scripts)) {\n const id = scriptNameToId(name)\n if (!id) continue\n if (id === \"test\" && !includeTests) continue\n const command = `${runner} run ${name}`\n if (isSafeCommand(command)) out.push({ id, command, source: \"package.json\" })\n }\n return out\n}\n\n/**\n * Best-effort CI gap-filler: scan GitHub Actions / GitLab CI YAML for single-line\n * `run:` static checks (typecheck/lint/build) the project actually runs. We do\n * NOT lift `test` from CI — CI test jobs frequently need services/secrets and\n * would false-red on every stop. Multi-line / scripted `run:` blocks are skipped\n * (they can't be a single argv). Any parse failure yields nothing.\n */\nasync function collectCiChecks(root: string): Promise<Candidate[]> {\n const files: string[] = []\n const wfDir = nodePath.join(root, \".github\", \"workflows\")\n try {\n for (const name of await fs.readdir(wfDir)) {\n if (/\\.ya?ml$/i.test(name)) files.push(nodePath.join(wfDir, name))\n }\n } catch {\n /* no workflows dir */\n }\n const gitlab = nodePath.join(root, \".gitlab-ci.yml\")\n if (existsSync(gitlab)) files.push(gitlab)\n if (files.length === 0) return []\n\n let parseYaml: (s: string) => unknown\n try {\n // Lazy import so the parser module has no hard dep when CI files are absent.\n ;({ parse: parseYaml } = await import(\"yaml\"))\n } catch {\n return []\n }\n\n const out: Candidate[] = []\n const seen = new Set<CheckId>()\n const classify = (cmd: string): CheckId | null => {\n if (/\\b(typecheck|type-check|tsc|mypy|pyright)\\b/i.test(cmd)) return \"typecheck\"\n if (/\\b(lint|eslint|ruff|clippy|golangci|vet)\\b/i.test(cmd)) return \"lint\"\n return null\n }\n const consider = (run: unknown, source: string): void => {\n if (typeof run !== \"string\") return\n const cmd = run.trim()\n if (cmd.includes(\"\\n\")) return // multi-line script block — not a single argv.\n if (!isSafeCommand(cmd) || isMutatingCommand(cmd) || !commandRunnable(cmd)) return\n const id = classify(cmd)\n if (!id || seen.has(id)) return\n seen.add(id)\n out.push({ id, command: cmd, source })\n }\n // Walk the parsed YAML for any `run:` string anywhere (GH `steps[].run`, GitLab\n // `<job>.script[]`). A generic deep walk tolerates both schemas + future shapes.\n const walk = (node: unknown, source: string): void => {\n if (!node || typeof node !== \"object\") return\n if (Array.isArray(node)) {\n for (const v of node) walk(v, source)\n return\n }\n for (const [k, v] of Object.entries(node as Record<string, unknown>)) {\n if (k === \"run\" && typeof v === \"string\") consider(v, source)\n else if (k === \"script\") {\n // GitLab `script:` is a string or string[].\n if (typeof v === \"string\") consider(v, source)\n else if (Array.isArray(v)) for (const s of v) consider(s, source)\n } else walk(v, source)\n }\n }\n for (const f of files) {\n const text = await readTextFile(f)\n if (text === undefined) continue\n try {\n walk(parseYaml(text), nodePath.relative(root, f) || nodePath.basename(f))\n } catch {\n /* unparseable workflow → skip */\n }\n }\n return out\n}\n\n/** Make / just / Taskfile targets matching canonical ids → `<tool> <target>`. */\nasync function collectTaskChecks(root: string, includeTests: boolean): Promise<Candidate[]> {\n const out: Candidate[] = []\n const wantId = (target: string): CheckId | null => scriptNameToId(target)\n // Makefile / justfile: a line `target:` (Make) or `target:` (just recipe head).\n for (const [file, tool] of [\n [\"Makefile\", \"make\"],\n [\"makefile\", \"make\"],\n [\"justfile\", \"just\"],\n [\".justfile\", \"just\"],\n ] as const) {\n const text = await readTextFile(nodePath.join(root, file))\n if (text === undefined) continue\n if (resolveExecutable(tool, { env: process.env }) === null) continue\n for (const line of text.split(/\\r?\\n/)) {\n const m = /^([A-Za-z0-9._-]+)\\s*:/.exec(line)\n if (!m) continue\n const id = wantId(m[1])\n if (!id || (id === \"test\" && !includeTests)) continue\n const command = `${tool} ${m[1]}`\n if (isSafeCommand(command)) out.push({ id, command, source: file })\n }\n }\n // Taskfile.yml: top-level `tasks:` keys.\n const taskfile = [\"Taskfile.yml\", \"Taskfile.yaml\"].map((n) => nodePath.join(root, n)).find((p) => existsSync(p))\n if (taskfile && resolveExecutable(\"task\", { env: process.env }) !== null) {\n const text = await readTextFile(taskfile)\n if (text !== undefined) {\n try {\n const { parse } = await import(\"yaml\")\n const doc = parse(text) as { tasks?: Record<string, unknown> } | undefined\n for (const name of Object.keys(doc?.tasks ?? {})) {\n const id = wantId(name)\n if (!id || (id === \"test\" && !includeTests)) continue\n const command = `task ${name}`\n if (isSafeCommand(command)) out.push({ id, command, source: nodePath.basename(taskfile) })\n }\n } catch {\n /* skip */\n }\n }\n }\n return out\n}\n\nasync function collectRustChecks(root: string, includeTests: boolean): Promise<Candidate[]> {\n if (!existsSync(nodePath.join(root, \"Cargo.toml\"))) return []\n if (resolveExecutable(\"cargo\", { env: process.env }) === null) return []\n const out: Candidate[] = [{ id: \"typecheck\", command: \"cargo check\", source: \"Cargo.toml\" }]\n // clippy is a separate component; only emit it if `cargo-clippy` resolves.\n if (resolveExecutable(\"cargo-clippy\", { env: process.env }) !== null) {\n out.push({ id: \"lint\", command: \"cargo clippy\", source: \"Cargo.toml\" })\n }\n if (includeTests) out.push({ id: \"test\", command: \"cargo test\", source: \"Cargo.toml\" })\n return out\n}\n\nasync function collectGoChecks(root: string, includeTests: boolean): Promise<Candidate[]> {\n if (!existsSync(nodePath.join(root, \"go.mod\"))) return []\n if (resolveExecutable(\"go\", { env: process.env }) === null) return []\n // `go vet` compiles + reports suspect constructs — the typecheck-equivalent.\n const out: Candidate[] = [{ id: \"typecheck\", command: \"go vet ./...\", source: \"go.mod\" }]\n if (includeTests) out.push({ id: \"test\", command: \"go test ./...\", source: \"go.mod\" })\n return out\n}\n\nasync function collectPythonChecks(root: string, includeTests: boolean): Promise<Candidate[]> {\n const configFiles = [\"pyproject.toml\", \"setup.cfg\", \"pytest.ini\", \"tox.ini\", \"ruff.toml\", \"mypy.ini\"]\n const present = configFiles.filter((f) => existsSync(nodePath.join(root, f)))\n if (present.length === 0) return []\n const evidence = present[0]\n const out: Candidate[] = []\n const configText = (await readTextFile(nodePath.join(root, present[0]))) ?? \"\"\n const mentions = (tool: string): boolean => present.some((f) => f.startsWith(tool)) || configText.includes(tool)\n // Only emit a Python tool when BOTH config evidence AND the tool resolve.\n if (mentions(\"mypy\") && resolveExecutable(\"mypy\", { env: process.env }) !== null) {\n out.push({ id: \"typecheck\", command: \"mypy .\", source: evidence })\n }\n if (mentions(\"ruff\") && resolveExecutable(\"ruff\", { env: process.env }) !== null) {\n out.push({ id: \"lint\", command: \"ruff check .\", source: evidence })\n }\n if (includeTests && (mentions(\"pytest\") || existsSync(nodePath.join(root, \"pytest.ini\")))) {\n if (resolveExecutable(\"pytest\", { env: process.env }) !== null) {\n out.push({ id: \"test\", command: \"pytest -q\", source: evidence })\n }\n }\n return out\n}\n\n/** Pick the first runnable candidate per id, in source-priority order. */\nfunction pickByPriority(candidates: Candidate[]): { checks: CheckSpec[]; evidence: string[] } {\n const byId = new Map<CheckId, Candidate>()\n for (const c of candidates) {\n if (!byId.has(c.id) && commandRunnable(c.command)) byId.set(c.id, c)\n }\n const order: CheckId[] = [\"typecheck\", \"lint\", \"test\"]\n const checks: CheckSpec[] = []\n const evidence = new Set<string>()\n for (const id of order) {\n const c = byId.get(id)\n if (c) {\n checks.push({ id: c.id, command: c.command })\n evidence.add(c.source)\n }\n }\n return { checks, evidence: [...evidence] }\n}\n\n/**\n * Resolve the gate descriptor for an already-resolved repo `root`.\n * 1. bun/TS sealed fast-path — byte-identical to the legacy `detectHarnessGateId`\n * (bun on PATH + a `typecheck` script → sealed `default-ci`/`typecheck-test`).\n * 2. else the deterministic parser: collect candidates from package.json\n * scripts (primary, self-contained), Make/just/task, language manifests, and\n * a CI gap-filler for static checks; pick one per id by priority. A `parsed`\n * descriptor is returned only when at least one STATIC check survives (a\n * test-only set would either be off-by-default or risk false-reds).\n * 3. else null (the launcher prints why and the gate stays off).\n */\nexport async function parseGateDescriptor(\n root: string,\n opts: { includeTests: boolean },\n): Promise<GateDescriptor | null> {\n // (1) bun/TS sealed parity.\n if (resolveExecutable(\"bun\", { env: process.env }) !== null) {\n const scripts = await readScripts(root)\n if (typeof scripts.typecheck === \"string\") {\n const gateId = typeof scripts.lint === \"string\" ? \"default-ci\" : \"typecheck-test\"\n return { kind: \"sealed\", gateId, workdir: root }\n }\n }\n\n // (2) deterministic parser. package.json scripts first (self-contained), then\n // task runners + manifests, then a CI gap-filler for static checks.\n const groups = await Promise.all([\n collectNodeChecks(root, opts.includeTests),\n collectTaskChecks(root, opts.includeTests),\n collectRustChecks(root, opts.includeTests),\n collectGoChecks(root, opts.includeTests),\n collectPythonChecks(root, opts.includeTests),\n collectCiChecks(root),\n ])\n const candidates = groups.flat()\n if (candidates.length === 0) return null\n\n const ecosystem =\n candidates.find((c) => c.source === \"package.json\") ? \"node\"\n : candidates.find((c) => c.source === \"Cargo.toml\") ? \"rust\"\n : candidates.find((c) => c.source === \"go.mod\") ? \"go\"\n : candidates.find((c) => /^(pyproject|setup|pytest|tox|ruff|mypy)/.test(c.source)) ? \"python\"\n : candidates.find((c) => /^(Makefile|makefile|justfile|\\.justfile|Taskfile)/.test(c.source)) ? \"make\"\n : \"ci\"\n\n const { checks, evidence } = pickByPriority(candidates)\n if (checks.length === 0) return null\n // Require at least one STATIC check (typecheck/lint/build) unless the caller\n // opted into running the full test suite — a test-only set is otherwise either\n // off-by-default or a slow/false-red risk on every stop.\n const hasStatic = checks.some((c) => STATIC_IDS.has(c.id as CheckId))\n if (!hasStatic && !opts.includeTests) return null\n return { kind: \"parsed\", checks, ecosystem, workdir: root, evidence }\n}\n\n/** The checks a descriptor runs: a sealed descriptor resolves its sealed command\n * set from the registry; parsed/discovered carry their own. Fresh array. */\nexport function checksForDescriptor(d: GateDescriptor): CheckSpec[] {\n if (d.kind === \"sealed\") {\n const sealed = resolveSealedGate(d.gateId)\n return sealed ? sealed.checks.map((c) => ({ id: c.id, command: c.command })) : []\n }\n return d.checks.map((c) => ({ id: c.id, command: c.command }))\n}\n\n/** A stable key over a descriptor's effective check set, for baseline isolation.\n * Sealed descriptors key on their gate id (preserving legacy baseline keys);\n * parsed/discovered key on the canonicalized (id,command) set, so a changed\n * command set yields a fresh baseline instead of masking/inventing regressions. */\nexport function descriptorHash(d: GateDescriptor): string {\n if (d.kind === \"sealed\") return `sealed:${d.gateId}`\n const canon = [...d.checks]\n .map((c) => `${c.id}\u0000${c.command.trim().replace(/\\s+/g, \" \")}`)\n .sort()\n .join(\"\u0001\")\n return `${d.kind}:${createHash(\"sha256\").update(canon).digest(\"hex\").slice(0, 32)}`\n}\n","/**\n * Evidence-pinned model FALLBACK for the structural Stop-gate — the last resort\n * when the deterministic parser (`harness-parse`) finds no runnable checks.\n *\n * A read-only worker reads the repo's own config/docs and proposes the canonical\n * check commands. Two guards keep this safe despite being model-authored:\n * 1. SANITIZE — `sanitizeDiscoveredCheck` rejects shell metacharacters (so a\n * command can never chain/redirect/expand), destructive/stateful verbs,\n * interactive/watch shapes, and an executable that isn't on PATH. What\n * survives is a single plain argv line, safe for `liveExec`'s naive split.\n * 2. EVIDENCE-PIN — a surviving command must appear (whitespace-normalized)\n * VERBATIM in one of the collected source files. The model cannot invent a\n * command or be prompt-injected into emitting one that isn't already a real\n * command in the repo (and a real command in a user-trusted repo is the\n * same authority the existing gate already runs).\n *\n * Discovery runs ONCE at launch and the result is cached per (repoFingerprint,\n * sourcesHash) in a human-readable record. The runtime Stop hook only READS the\n * cached record (no model call at stop). This is consumed ONLY by the local Stop\n * hook; the sealed-gate kernel never sees a discovered command.\n */\n\nimport { createHash } from \"node:crypto\"\nimport { existsSync, promises as fs } from \"node:fs\"\nimport nodePath from \"node:path\"\n\nimport { resolveExecutable } from \"~/lib/exec\"\nimport { PATHS } from \"~/lib/paths\"\n\nimport { type CheckSpec } from \"./gate-runner\"\nimport { isMutatingCommand, isSafeCommand, type CheckId } from \"./harness-parse\"\nimport { repoFingerprint, repoRoot } from \"./stop-gate-policy\"\n\n/** The allowlist of files the discovery worker is steered to read — config +\n * docs that legitimately describe how to check a project. Secret files are\n * additionally blocked at the worker IO layer (`.env*`/`*.pem`/`id_*`/…). */\nconst SIGNAL_FILES: ReadonlyArray<string> = [\n \"package.json\",\n \"Makefile\",\n \"makefile\",\n \"justfile\",\n \".justfile\",\n \"Taskfile.yml\",\n \"Taskfile.yaml\",\n \"Cargo.toml\",\n \"go.mod\",\n \"pyproject.toml\",\n \"setup.cfg\",\n \"tox.ini\",\n \"pytest.ini\",\n \"CONTRIBUTING.md\",\n \"CONTRIBUTING\",\n \"README.md\",\n \"README\",\n \"DEVELOPING.md\",\n \"mix.exs\",\n \"build.gradle\",\n \"pom.xml\",\n \"composer.json\",\n]\nconst SIGNAL_DIRS: ReadonlyArray<string> = [\".github/workflows\"]\n\n/** A discovered check command after sanitization + evidence-pinning. */\nexport interface DiscoveredRecord {\n root: string\n fingerprint: string\n sourcesHash: string\n discoveredAt: string\n model: string\n ecosystem: string\n checks: CheckSpec[]\n confidence: string\n evidence: string[]\n}\n\nconst MAX_SIGNAL_BYTES = 64 * 1024\n/** Global caps so a repo with many workflow files can't inflate discovery /\n * hashing cost: at most this many files and this much total text. */\nconst MAX_SIGNAL_FILES = 40\nconst MAX_TOTAL_SIGNAL_BYTES = 512 * 1024\n\n/** Words that mark a command as destructive, stateful, or non-terminating — a\n * \"check\" must never do any of these. (Shell operators are already rejected by\n * `isSafeCommand`, so chaining can't smuggle them past this word scan.) */\nconst DENY_WORD =\n /\\b(rm|rmdir|mv|dd|mkfs|sudo|chmod|chown|publish|push|deploy|migrate|kubectl|terraform|docker|curl|wget|ssh|scp|rsync|nc|eval|npm i|npm install|yarn add|pip install|apt|brew|watch|serve|repl|dev|start)\\b/i\n\n/**\n * True when `command` is safe to auto-run as a check: a plain argv line (no\n * shell metacharacters), no destructive/stateful verb, no mutating/interactive\n * shape, and its executable resolves on PATH. The deny-word scan runs on a\n * whitespace-NORMALIZED copy so a tab / double-space can't split `npm install`\n * past the literal-space patterns.\n */\nexport function sanitizeDiscoveredCheck(command: string): boolean {\n if (!isSafeCommand(command)) return false\n const norm = normalizeWs(command)\n if (DENY_WORD.test(norm)) return false\n if (isMutatingCommand(norm)) return false // --fix/--write/--watch + :fix/:write scripts\n const first = norm.split(\" \")[0] ?? \"\"\n return resolveExecutable(first, { env: process.env }) !== null\n}\n\nfunction normalizeWs(s: string): string {\n return s.replace(/\\s+/g, \" \").trim()\n}\n\n/** Collect the text of the allowlisted signal files (capped), for evidence-pin +\n * the sources hash. Returns the concatenated text and the relative file list. */\nasync function collectSignals(root: string): Promise<{ text: string; files: string[] }> {\n const parts: string[] = []\n const files: string[] = []\n let total = 0\n const readCapped = async (abs: string, rel: string): Promise<void> => {\n if (files.length >= MAX_SIGNAL_FILES || total >= MAX_TOTAL_SIGNAL_BYTES) return\n try {\n const raw = await fs.readFile(abs, \"utf8\")\n const slice = raw.length > MAX_SIGNAL_BYTES ? raw.slice(0, MAX_SIGNAL_BYTES) : raw\n parts.push(slice)\n files.push(rel)\n total += slice.length\n } catch {\n /* unreadable → skip */\n }\n }\n for (const f of SIGNAL_FILES) {\n const abs = nodePath.join(root, f)\n if (existsSync(abs)) await readCapped(abs, f)\n }\n for (const d of SIGNAL_DIRS) {\n const dir = nodePath.join(root, d)\n try {\n for (const name of await fs.readdir(dir)) {\n if (/\\.(ya?ml)$/i.test(name)) await readCapped(nodePath.join(dir, name), nodePath.join(d, name))\n }\n } catch {\n /* no dir */\n }\n }\n return { text: parts.join(\"\\n\"), files }\n}\n\n/** A freshness hash over the signal files' contents + relative paths. A change\n * to how the project checks itself flips this → re-discovery on next launch. */\nexport async function sourcesHash(root: string): Promise<string> {\n const { text, files } = await collectSignals(root)\n return createHash(\"sha256\")\n .update(files.sort().join(\"\\n\"))\n .update(\"\\0\")\n .update(text)\n .digest(\"hex\")\n}\n\nfunction discoveredDir(): string {\n return nodePath.join(PATHS.APP_DIR, \"stop-gate\", \"discovered\")\n}\nfunction recordPathFor(root: string): string {\n return nodePath.join(discoveredDir(), createHash(\"sha256\").update(nodePath.resolve(root)).digest(\"hex\").slice(0, 32))\n}\n\n/** Read the cached discovered record for `root`, verifying it still matches the\n * live repo identity AND the live sources hash. Any mismatch / unreadable /\n * empty-checks record → null (re-discover or stay off; never run a stale set). */\nexport async function readDiscoveredGate(root: string): Promise<DiscoveredRecord | null> {\n let rec: DiscoveredRecord\n try {\n rec = JSON.parse(await fs.readFile(recordPathFor(root), \"utf8\")) as DiscoveredRecord\n } catch {\n return null\n }\n if (!rec || !Array.isArray(rec.checks) || rec.checks.length === 0) return null\n const fp = await repoFingerprint(root).catch(() => \"\")\n if (fp.length === 0 || fp !== rec.fingerprint) return null // identity drift → deny.\n const sh = await sourcesHash(root).catch(() => \"\")\n if (sh.length === 0 || sh !== rec.sourcesHash) return null // config changed → stale.\n // Re-validate id AND re-sanitize the command at read time (defense-in-depth\n // against a tampered/corrupt record).\n const checks = rec.checks.filter(\n (c) => c && typeof c.id === \"string\" && VALID_IDS.has(c.id) && typeof c.command === \"string\" && sanitizeDiscoveredCheck(c.command),\n )\n if (checks.length === 0) return null\n return { ...rec, checks }\n}\n\nexport async function writeDiscoveredGate(rec: DiscoveredRecord): Promise<void> {\n await fs.mkdir(discoveredDir(), { recursive: true })\n const tmp = `${recordPathFor(rec.root)}.${process.pid}.tmp`\n await fs.writeFile(tmp, `${JSON.stringify(rec, null, 2)}\\n`, { mode: 0o600 })\n await fs.rename(tmp, recordPathFor(rec.root))\n}\n\nconst DISCOVERY_PROMPT = (files: string[]): string =>\n `You are configuring an automated pre-finish CHECK gate for this repository. Read ONLY these `\n + `already-present config/doc files to learn how the project checks itself: ${files.join(\", \")}. `\n + `Identify the canonical FAST static check command (typecheck and/or lint) and, if obvious, the test `\n + `command. Rules: (1) return a command ONLY if it appears VERBATIM in one of those files — never invent `\n + `one; (2) commands must be non-interactive, self-terminating, read-only verification (NO install / `\n + `publish / push / deploy / migrate / format-in-place / watch / serve / dev-server / delete); (3) at most `\n + `3 commands; (4) if unsure, return an empty list. Respond with ONLY a fenced \\`\\`\\`json block of the shape `\n + `{\"ecosystem\":\"<label>\",\"checks\":[{\"id\":\"typecheck|lint|test\",\"command\":\"<single-line command>\"}],`\n + `\"confidence\":\"high|low\"} and NOTHING else.`\n\ninterface DiscoverResult {\n ecosystem: string\n checks: CheckSpec[]\n confidence: string\n evidence: string[]\n}\n\n/** Extract the first fenced/bare JSON object from the worker's text. */\nfunction extractJson(text: string): unknown {\n const fenced = /```(?:json)?\\s*([\\s\\S]*?)```/i.exec(text)\n const body = fenced ? fenced[1] : text\n const start = body.indexOf(\"{\")\n const end = body.lastIndexOf(\"}\")\n if (start < 0 || end <= start) return undefined\n try {\n return JSON.parse(body.slice(start, end + 1)) as unknown\n } catch {\n return undefined\n }\n}\n\nconst VALID_IDS: ReadonlySet<string> = new Set<CheckId>([\"typecheck\", \"lint\", \"test\"])\n\n/**\n * Sanitize + EVIDENCE-PIN the model's raw checks against the collected source\n * text. Pure (no IO) so it is unit-testable without the worker. A check survives\n * only if: its id is canonical, it isn't a `test` while tests are off, its id\n * hasn't already been taken, it passes `sanitizeDiscoveredCheck`, AND its command\n * appears (whitespace-normalized) VERBATIM in `evidenceText`. The evidence-pin is\n * the load-bearing guard: the model cannot invent a command or be prompt-injected\n * into one that isn't already a real command in the (user-trusted) repo.\n */\nexport function filterDiscoveredChecks(\n rawChecks: unknown,\n evidenceText: string,\n includeTests: boolean,\n): CheckSpec[] {\n const evidenceNorm = normalizeWs(evidenceText)\n const seen = new Set<string>()\n const checks: CheckSpec[] = []\n for (const c of Array.isArray(rawChecks) ? rawChecks : []) {\n if (!c || typeof c !== \"object\") continue\n const id = (c as { id?: unknown }).id\n const command = (c as { command?: unknown }).command\n if (typeof id !== \"string\" || !VALID_IDS.has(id)) continue\n if (typeof command !== \"string\") continue\n if (id === \"test\" && !includeTests) continue\n if (seen.has(id)) continue\n if (!sanitizeDiscoveredCheck(command)) continue\n if (!evidenceNorm.includes(normalizeWs(command))) continue\n seen.add(id)\n checks.push({ id, command: command.trim() })\n }\n return checks\n}\n\n/**\n * Run the read-only worker to discover check commands for `cwd`. Returns the\n * sanitized + evidence-pinned result, or null (worker unavailable / errored /\n * nothing survived). NEVER throws.\n */\nexport async function discoverGateCommands(\n cwd: string,\n opts: { signal?: AbortSignal; includeTests: boolean },\n): Promise<DiscoverResult | null> {\n const root = await repoRoot(cwd).catch(() => cwd)\n const { text: evidenceText, files } = await collectSignals(root)\n if (files.length === 0) return null\n\n let result: { text: string; isError?: boolean }\n try {\n // Lazy-import the heavy worker-agent engine ONLY when discovery actually\n // runs — keeping it out of the module-load graph of `claude.ts` (which\n // imports this module), so the mock.module-isolated CLI tests don't deadlock\n // pulling the Pi runtime at load time.\n const { runWorkerAgent } = await import(\"~/lib/worker-agent/engine\")\n // Discovery is advisory setup, not a verified workflow producer/checker,\n // so honoring the operator's explore default here is intentional.\n result = await runWorkerAgent({\n mode: \"explore\",\n workspace: root,\n prompt: DISCOVERY_PROMPT(files),\n signal: opts.signal,\n })\n } catch {\n return null\n }\n if (result.isError) return null\n const parsed = extractJson(result.text)\n if (!parsed || typeof parsed !== \"object\") return null\n const obj = parsed as { ecosystem?: unknown; checks?: unknown; confidence?: unknown }\n const checks = filterDiscoveredChecks(obj.checks, evidenceText, opts.includeTests)\n if (checks.length === 0) return null\n const ecosystem = typeof obj.ecosystem === \"string\" && obj.ecosystem.length > 0 ? obj.ecosystem : \"discovered\"\n const confidence = typeof obj.confidence === \"string\" ? obj.confidence : \"low\"\n return { ecosystem, checks, confidence, evidence: files }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,MAAM,6BAAmC,IAAI,IAAI,CAAC,aAAa,MAAM,CAAC;;;AA2BtE,SAAgB,cAAc,SAA0B;CACtD,MAAM,IAAI,QAAQ,KAAK;CACvB,IAAI,EAAE,WAAW,KAAK,EAAE,SAAS,KAAK,OAAO;CAC7C,IAAI,SAAS,KAAK,CAAC,GAAG,OAAO;CAI7B,MAAM,SAAS,EAAE,MAAM,KAAK,CAAC,CAAC,OAAO,OAAO;CAC5C,IAAI,OAAO,WAAW,GAAG,OAAO;CAChC,OAAO,OAAO,OAAO,MAAM,yBAAyB,KAAK,CAAC,CAAC;AAC7D;;AAGA,SAAS,WAAW,SAAyB;CAC3C,OAAO,QAAQ,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;AAC3C;;;;;;AAOA,SAAgB,kBAAkB,SAA0B;CAC1D,MAAM,IAAI,QAAQ,KAAK,CAAC,CAAC,QAAQ,QAAQ,GAAG;CAC5C,IAAI,mEAAmE,KAAK,CAAC,GAAG,OAAO;CACvF,IAAI,0BAA0B,KAAK,CAAC,GAAG,OAAO;CAC9C,OAAO;AACT;;;AAIA,SAAS,gBAAgB,SAA0B;CACjD,IAAI,CAAC,cAAc,OAAO,GAAG,OAAO;CACpC,OAAO,kBAAkB,WAAW,OAAO,GAAG,EAAE,KAAK,QAAQ,IAAI,CAAC,MAAM;AAC1E;AAEA,eAAe,aAAa,MAA4C;CACtE,IAAI;EACF,OAAO,KAAK,MAAM,MAAMA,SAAG,SAAS,MAAM,MAAM,CAAC;CACnD,QAAQ;EACN;CACF;AACF;AAEA,eAAe,aAAa,MAA2C;CACrE,IAAI;EACF,OAAO,MAAMA,SAAG,SAAS,MAAM,MAAM;CACvC,QAAQ;EACN;CACF;AACF;;AAGA,eAAe,YAAY,MAA+C;CACxE,MAAM,MAAM,MAAM,aAAaC,KAAS,KAAK,MAAM,cAAc,CAAC;CAClE,MAAM,UAAU,OAAO,OAAO,QAAQ,WAAY,IAA8B,UAAU,KAAA;CAC1F,MAAM,MAA8B,CAAC;CACrC,IAAI,WAAW,OAAO,YAAY,UAC3B;OAAA,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,OAAkC,GACpE,IAAI,OAAO,MAAM,UAAU,IAAI,KAAK;CAAA;CAGxC,OAAO;AACT;;AAGA,SAAS,WAAW,MAA+C;CACjE,IAAI,WAAWA,KAAS,KAAK,MAAM,WAAW,CAAC,KAAK,WAAWA,KAAS,KAAK,MAAM,UAAU,CAAC,GAAG,OAAO;CACxG,IAAI,WAAWA,KAAS,KAAK,MAAM,gBAAgB,CAAC,GAAG,OAAO;CAC9D,IAAI,WAAWA,KAAS,KAAK,MAAM,WAAW,CAAC,GAAG,OAAO;CACzD,OAAO;AACT;;;AAIA,SAAS,eAAe,MAA8B;CACpD,IAAI,iBAAiB,KAAK,IAAI,GAAG,OAAO;CACxC,IAAI,kDAAkD,KAAK,IAAI,GAAG,OAAO;CACzE,IAAI,8BAA8B,KAAK,IAAI,GAAG,OAAO;CACrD,IAAI,UAAU,KAAK,IAAI,GAAG,OAAO;CACjC,OAAO;AACT;AAEA,eAAe,kBAAkB,MAAc,cAA6C;CAC1F,IAAI,CAAC,WAAWA,KAAS,KAAK,MAAM,cAAc,CAAC,GAAG,OAAO,CAAC;CAC9D,MAAM,SAAS,WAAW,IAAI;CAC9B,IAAI,kBAAkB,QAAQ,EAAE,KAAK,QAAQ,IAAI,CAAC,MAAM,MAAM,OAAO,CAAC;CACtE,MAAM,UAAU,MAAM,YAAY,IAAI;CACtC,MAAM,MAAmB,CAAC;CAC1B,KAAK,MAAM,CAAC,SAAS,OAAO,QAAQ,OAAO,GAAG;EAC5C,MAAM,KAAK,eAAe,IAAI;EAC9B,IAAI,CAAC,IAAI;EACT,IAAI,OAAO,UAAU,CAAC,cAAc;EACpC,MAAM,UAAU,GAAG,OAAO,OAAO;EACjC,IAAI,cAAc,OAAO,GAAG,IAAI,KAAK;GAAE;GAAI;GAAS,QAAQ;EAAe,CAAC;CAC9E;CACA,OAAO;AACT;;;;;;;;AASA,eAAe,gBAAgB,MAAoC;CACjE,MAAM,QAAkB,CAAC;CACzB,MAAM,QAAQA,KAAS,KAAK,MAAM,WAAW,WAAW;CACxD,IAAI;EACF,KAAK,MAAM,QAAQ,MAAMD,SAAG,QAAQ,KAAK,GACvC,IAAI,YAAY,KAAK,IAAI,GAAG,MAAM,KAAKC,KAAS,KAAK,OAAO,IAAI,CAAC;CAErE,QAAQ,CAER;CACA,MAAM,SAASA,KAAS,KAAK,MAAM,gBAAgB;CACnD,IAAI,WAAW,MAAM,GAAG,MAAM,KAAK,MAAM;CACzC,IAAI,MAAM,WAAW,GAAG,OAAO,CAAC;CAEhC,IAAI;CACJ,IAAI;EAED,CAAC,CAAE,OAAO,aAAc,MAAM,OAAO;CACxC,QAAQ;EACN,OAAO,CAAC;CACV;CAEA,MAAM,MAAmB,CAAC;CAC1B,MAAM,uBAAO,IAAI,IAAa;CAC9B,MAAM,YAAY,QAAgC;EAChD,IAAI,+CAA+C,KAAK,GAAG,GAAG,OAAO;EACrE,IAAI,8CAA8C,KAAK,GAAG,GAAG,OAAO;EACpE,OAAO;CACT;CACA,MAAM,YAAY,KAAc,WAAyB;EACvD,IAAI,OAAO,QAAQ,UAAU;EAC7B,MAAM,MAAM,IAAI,KAAK;EACrB,IAAI,IAAI,SAAS,IAAI,GAAG;EACxB,IAAI,CAAC,cAAc,GAAG,KAAK,kBAAkB,GAAG,KAAK,CAAC,gBAAgB,GAAG,GAAG;EAC5E,MAAM,KAAK,SAAS,GAAG;EACvB,IAAI,CAAC,MAAM,KAAK,IAAI,EAAE,GAAG;EACzB,KAAK,IAAI,EAAE;EACX,IAAI,KAAK;GAAE;GAAI,SAAS;GAAK;EAAO,CAAC;CACvC;CAGA,MAAM,QAAQ,MAAe,WAAyB;EACpD,IAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;EACvC,IAAI,MAAM,QAAQ,IAAI,GAAG;GACvB,KAAK,MAAM,KAAK,MAAM,KAAK,GAAG,MAAM;GACpC;EACF;EACA,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,IAA+B,GACjE,IAAI,MAAM,SAAS,OAAO,MAAM,UAAU,SAAS,GAAG,MAAM;OACvD,IAAI,MAAM,UAET;OAAA,OAAO,MAAM,UAAU,SAAS,GAAG,MAAM;QACxC,IAAI,MAAM,QAAQ,CAAC,GAAG,KAAK,MAAM,KAAK,GAAG,SAAS,GAAG,MAAM;EAAA,OAC3D,KAAK,GAAG,MAAM;CAEzB;CACA,KAAK,MAAM,KAAK,OAAO;EACrB,MAAM,OAAO,MAAM,aAAa,CAAC;EACjC,IAAI,SAAS,KAAA,GAAW;EACxB,IAAI;GACF,KAAK,UAAU,IAAI,GAAGA,KAAS,SAAS,MAAM,CAAC,KAAKA,KAAS,SAAS,CAAC,CAAC;EAC1E,QAAQ,CAER;CACF;CACA,OAAO;AACT;;AAGA,eAAe,kBAAkB,MAAc,cAA6C;CAC1F,MAAM,MAAmB,CAAC;CAC1B,MAAM,UAAU,WAAmC,eAAe,MAAM;CAExE,KAAK,MAAM,CAAC,MAAM,SAAS;EACzB,CAAC,YAAY,MAAM;EACnB,CAAC,YAAY,MAAM;EACnB,CAAC,YAAY,MAAM;EACnB,CAAC,aAAa,MAAM;CACtB,GAAY;EACV,MAAM,OAAO,MAAM,aAAaA,KAAS,KAAK,MAAM,IAAI,CAAC;EACzD,IAAI,SAAS,KAAA,GAAW;EACxB,IAAI,kBAAkB,MAAM,EAAE,KAAK,QAAQ,IAAI,CAAC,MAAM,MAAM;EAC5D,KAAK,MAAM,QAAQ,KAAK,MAAM,OAAO,GAAG;GACtC,MAAM,IAAI,yBAAyB,KAAK,IAAI;GAC5C,IAAI,CAAC,GAAG;GACR,MAAM,KAAK,OAAO,EAAE,EAAE;GACtB,IAAI,CAAC,MAAO,OAAO,UAAU,CAAC,cAAe;GAC7C,MAAM,UAAU,GAAG,KAAK,GAAG,EAAE;GAC7B,IAAI,cAAc,OAAO,GAAG,IAAI,KAAK;IAAE;IAAI;IAAS,QAAQ;GAAK,CAAC;EACpE;CACF;CAEA,MAAM,WAAW,CAAC,gBAAgB,eAAe,CAAC,CAAC,KAAK,MAAMA,KAAS,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,MAAM,WAAW,CAAC,CAAC;CAC/G,IAAI,YAAY,kBAAkB,QAAQ,EAAE,KAAK,QAAQ,IAAI,CAAC,MAAM,MAAM;EACxE,MAAM,OAAO,MAAM,aAAa,QAAQ;EACxC,IAAI,SAAS,KAAA,GACX,IAAI;GACF,MAAM,EAAE,UAAU,MAAM,OAAO;GAC/B,MAAM,MAAM,MAAM,IAAI;GACtB,KAAK,MAAM,QAAQ,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,GAAG;IAChD,MAAM,KAAK,OAAO,IAAI;IACtB,IAAI,CAAC,MAAO,OAAO,UAAU,CAAC,cAAe;IAC7C,MAAM,UAAU,QAAQ;IACxB,IAAI,cAAc,OAAO,GAAG,IAAI,KAAK;KAAE;KAAI;KAAS,QAAQA,KAAS,SAAS,QAAQ;IAAE,CAAC;GAC3F;EACF,QAAQ,CAER;CAEJ;CACA,OAAO;AACT;AAEA,eAAe,kBAAkB,MAAc,cAA6C;CAC1F,IAAI,CAAC,WAAWA,KAAS,KAAK,MAAM,YAAY,CAAC,GAAG,OAAO,CAAC;CAC5D,IAAI,kBAAkB,SAAS,EAAE,KAAK,QAAQ,IAAI,CAAC,MAAM,MAAM,OAAO,CAAC;CACvE,MAAM,MAAmB,CAAC;EAAE,IAAI;EAAa,SAAS;EAAe,QAAQ;CAAa,CAAC;CAE3F,IAAI,kBAAkB,gBAAgB,EAAE,KAAK,QAAQ,IAAI,CAAC,MAAM,MAC9D,IAAI,KAAK;EAAE,IAAI;EAAQ,SAAS;EAAgB,QAAQ;CAAa,CAAC;CAExE,IAAI,cAAc,IAAI,KAAK;EAAE,IAAI;EAAQ,SAAS;EAAc,QAAQ;CAAa,CAAC;CACtF,OAAO;AACT;AAEA,eAAe,gBAAgB,MAAc,cAA6C;CACxF,IAAI,CAAC,WAAWA,KAAS,KAAK,MAAM,QAAQ,CAAC,GAAG,OAAO,CAAC;CACxD,IAAI,kBAAkB,MAAM,EAAE,KAAK,QAAQ,IAAI,CAAC,MAAM,MAAM,OAAO,CAAC;CAEpE,MAAM,MAAmB,CAAC;EAAE,IAAI;EAAa,SAAS;EAAgB,QAAQ;CAAS,CAAC;CACxF,IAAI,cAAc,IAAI,KAAK;EAAE,IAAI;EAAQ,SAAS;EAAiB,QAAQ;CAAS,CAAC;CACrF,OAAO;AACT;AAEA,eAAe,oBAAoB,MAAc,cAA6C;CAE5F,MAAM,UAAU;EADK;EAAkB;EAAa;EAAc;EAAW;EAAa;CAChE,CAAC,CAAC,QAAQ,MAAM,WAAWA,KAAS,KAAK,MAAM,CAAC,CAAC,CAAC;CAC5E,IAAI,QAAQ,WAAW,GAAG,OAAO,CAAC;CAClC,MAAM,WAAW,QAAQ;CACzB,MAAM,MAAmB,CAAC;CAC1B,MAAM,aAAc,MAAM,aAAaA,KAAS,KAAK,MAAM,QAAQ,EAAE,CAAC,KAAM;CAC5E,MAAM,YAAY,SAA0B,QAAQ,MAAM,MAAM,EAAE,WAAW,IAAI,CAAC,KAAK,WAAW,SAAS,IAAI;CAE/G,IAAI,SAAS,MAAM,KAAK,kBAAkB,QAAQ,EAAE,KAAK,QAAQ,IAAI,CAAC,MAAM,MAC1E,IAAI,KAAK;EAAE,IAAI;EAAa,SAAS;EAAU,QAAQ;CAAS,CAAC;CAEnE,IAAI,SAAS,MAAM,KAAK,kBAAkB,QAAQ,EAAE,KAAK,QAAQ,IAAI,CAAC,MAAM,MAC1E,IAAI,KAAK;EAAE,IAAI;EAAQ,SAAS;EAAgB,QAAQ;CAAS,CAAC;CAEpE,IAAI,iBAAiB,SAAS,QAAQ,KAAK,WAAWA,KAAS,KAAK,MAAM,YAAY,CAAC,IACjF;MAAA,kBAAkB,UAAU,EAAE,KAAK,QAAQ,IAAI,CAAC,MAAM,MACxD,IAAI,KAAK;GAAE,IAAI;GAAQ,SAAS;GAAa,QAAQ;EAAS,CAAC;CAAA;CAGnE,OAAO;AACT;;AAGA,SAAS,eAAe,YAAsE;CAC5F,MAAM,uBAAO,IAAI,IAAwB;CACzC,KAAK,MAAM,KAAK,YACd,IAAI,CAAC,KAAK,IAAI,EAAE,EAAE,KAAK,gBAAgB,EAAE,OAAO,GAAG,KAAK,IAAI,EAAE,IAAI,CAAC;CAErE,MAAM,QAAmB;EAAC;EAAa;EAAQ;CAAM;CACrD,MAAM,SAAsB,CAAC;CAC7B,MAAM,2BAAW,IAAI,IAAY;CACjC,KAAK,MAAM,MAAM,OAAO;EACtB,MAAM,IAAI,KAAK,IAAI,EAAE;EACrB,IAAI,GAAG;GACL,OAAO,KAAK;IAAE,IAAI,EAAE;IAAI,SAAS,EAAE;GAAQ,CAAC;GAC5C,SAAS,IAAI,EAAE,MAAM;EACvB;CACF;CACA,OAAO;EAAE;EAAQ,UAAU,CAAC,GAAG,QAAQ;CAAE;AAC3C;;;;;;;;;;;;AAaA,eAAsB,oBACpB,MACA,MACgC;CAEhC,IAAI,kBAAkB,OAAO,EAAE,KAAK,QAAQ,IAAI,CAAC,MAAM,MAAM;EAC3D,MAAM,UAAU,MAAM,YAAY,IAAI;EACtC,IAAI,OAAO,QAAQ,cAAc,UAE/B,OAAO;GAAE,MAAM;GAAU,QADV,OAAO,QAAQ,SAAS,WAAW,eAAe;GAChC,SAAS;EAAK;CAEnD;CAYA,MAAM,cAAa,MARE,QAAQ,IAAI;EAC/B,kBAAkB,MAAM,KAAK,YAAY;EACzC,kBAAkB,MAAM,KAAK,YAAY;EACzC,kBAAkB,MAAM,KAAK,YAAY;EACzC,gBAAgB,MAAM,KAAK,YAAY;EACvC,oBAAoB,MAAM,KAAK,YAAY;EAC3C,gBAAgB,IAAI;CACtB,CAAC,EAAA,CACyB,KAAK;CAC/B,IAAI,WAAW,WAAW,GAAG,OAAO;CAEpC,MAAM,YACJ,WAAW,MAAM,MAAM,EAAE,WAAW,cAAc,IAAI,SACpD,WAAW,MAAM,MAAM,EAAE,WAAW,YAAY,IAAI,SACpD,WAAW,MAAM,MAAM,EAAE,WAAW,QAAQ,IAAI,OAChD,WAAW,MAAM,MAAM,0CAA0C,KAAK,EAAE,MAAM,CAAC,IAAI,WACnF,WAAW,MAAM,MAAM,oDAAoD,KAAK,EAAE,MAAM,CAAC,IAAI,SAC7F;CAEJ,MAAM,EAAE,QAAQ,aAAa,eAAe,UAAU;CACtD,IAAI,OAAO,WAAW,GAAG,OAAO;CAKhC,IAAI,CADc,OAAO,MAAM,MAAM,WAAW,IAAI,EAAE,EAAa,CACtD,KAAK,CAAC,KAAK,cAAc,OAAO;CAC7C,OAAO;EAAE,MAAM;EAAU;EAAQ;EAAW,SAAS;EAAM;CAAS;AACtE;;;AAIA,SAAgB,oBAAoB,GAAgC;CAClE,IAAI,EAAE,SAAS,UAAU;EACvB,MAAM,SAAS,kBAAkB,EAAE,MAAM;EACzC,OAAO,SAAS,OAAO,OAAO,KAAK,OAAO;GAAE,IAAI,EAAE;GAAI,SAAS,EAAE;EAAQ,EAAE,IAAI,CAAC;CAClF;CACA,OAAO,EAAE,OAAO,KAAK,OAAO;EAAE,IAAI,EAAE;EAAI,SAAS,EAAE;CAAQ,EAAE;AAC/D;;;;;AAMA,SAAgB,eAAe,GAA2B;CACxD,IAAI,EAAE,SAAS,UAAU,OAAO,UAAU,EAAE;CAC5C,MAAM,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CACxB,KAAK,MAAM,GAAG,EAAE,GAAG,GAAG,EAAE,QAAQ,KAAK,CAAC,CAAC,QAAQ,QAAQ,GAAG,GAAG,CAAC,CAC9D,KAAK,CAAC,CACN,KAAK,GAAG;CACX,OAAO,GAAG,EAAE,KAAK,GAAG,WAAW,QAAQ,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,EAAE;AAClF;;;;;;;;;;;;;;;;;;;;;;;;;;;AC7YA,MAAM,eAAsC;CAC1C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AACA,MAAM,cAAqC,CAAC,mBAAmB;AAe/D,MAAM,mBAAmB;;;AAGzB,MAAM,mBAAmB;AACzB,MAAM,yBAAyB;;;;AAK/B,MAAM,YACJ;;;;;;;;AASF,SAAgB,wBAAwB,SAA0B;CAChE,IAAI,CAAC,cAAc,OAAO,GAAG,OAAO;CACpC,MAAM,OAAO,YAAY,OAAO;CAChC,IAAI,UAAU,KAAK,IAAI,GAAG,OAAO;CACjC,IAAI,kBAAkB,IAAI,GAAG,OAAO;CACpC,MAAM,QAAQ,KAAK,MAAM,GAAG,CAAC,CAAC,MAAM;CACpC,OAAO,kBAAkB,OAAO,EAAE,KAAK,QAAQ,IAAI,CAAC,MAAM;AAC5D;AAEA,SAAS,YAAY,GAAmB;CACtC,OAAO,EAAE,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAK;AACrC;;;AAIA,eAAe,eAAe,MAA0D;CACtF,MAAM,QAAkB,CAAC;CACzB,MAAM,QAAkB,CAAC;CACzB,IAAI,QAAQ;CACZ,MAAM,aAAa,OAAO,KAAa,QAA+B;EACpE,IAAI,MAAM,UAAU,oBAAoB,SAAS,wBAAwB;EACzE,IAAI;GACF,MAAM,MAAM,MAAMC,SAAG,SAAS,KAAK,MAAM;GACzC,MAAM,QAAQ,IAAI,SAAS,mBAAmB,IAAI,MAAM,GAAG,gBAAgB,IAAI;GAC/E,MAAM,KAAK,KAAK;GAChB,MAAM,KAAK,GAAG;GACd,SAAS,MAAM;EACjB,QAAQ,CAER;CACF;CACA,KAAK,MAAM,KAAK,cAAc;EAC5B,MAAM,MAAMC,KAAS,KAAK,MAAM,CAAC;EACjC,IAAI,WAAW,GAAG,GAAG,MAAM,WAAW,KAAK,CAAC;CAC9C;CACA,KAAK,MAAM,KAAK,aAAa;EAC3B,MAAM,MAAMA,KAAS,KAAK,MAAM,CAAC;EACjC,IAAI;GACF,KAAK,MAAM,QAAQ,MAAMD,SAAG,QAAQ,GAAG,GACrC,IAAI,cAAc,KAAK,IAAI,GAAG,MAAM,WAAWC,KAAS,KAAK,KAAK,IAAI,GAAGA,KAAS,KAAK,GAAG,IAAI,CAAC;EAEnG,QAAQ,CAER;CACF;CACA,OAAO;EAAE,MAAM,MAAM,KAAK,IAAI;EAAG;CAAM;AACzC;;;AAIA,eAAsB,YAAY,MAA+B;CAC/D,MAAM,EAAE,MAAM,UAAU,MAAM,eAAe,IAAI;CACjD,OAAO,WAAW,QAAQ,CAAC,CACxB,OAAO,MAAM,KAAK,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAC/B,OAAO,IAAI,CAAC,CACZ,OAAO,IAAI,CAAC,CACZ,OAAO,KAAK;AACjB;AAEA,SAAS,gBAAwB;CAC/B,OAAOA,KAAS,KAAK,MAAM,SAAS,aAAa,YAAY;AAC/D;AACA,SAAS,cAAc,MAAsB;CAC3C,OAAOA,KAAS,KAAK,cAAc,GAAG,WAAW,QAAQ,CAAC,CAAC,OAAOA,KAAS,QAAQ,IAAI,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC;AACtH;;;;AAKA,eAAsB,mBAAmB,MAAgD;CACvF,IAAI;CACJ,IAAI;EACF,MAAM,KAAK,MAAM,MAAMD,SAAG,SAAS,cAAc,IAAI,GAAG,MAAM,CAAC;CACjE,QAAQ;EACN,OAAO;CACT;CACA,IAAI,CAAC,OAAO,CAAC,MAAM,QAAQ,IAAI,MAAM,KAAK,IAAI,OAAO,WAAW,GAAG,OAAO;CAC1E,MAAM,KAAK,MAAM,gBAAgB,IAAI,CAAC,CAAC,YAAY,EAAE;CACrD,IAAI,GAAG,WAAW,KAAK,OAAO,IAAI,aAAa,OAAO;CACtD,MAAM,KAAK,MAAM,YAAY,IAAI,CAAC,CAAC,YAAY,EAAE;CACjD,IAAI,GAAG,WAAW,KAAK,OAAO,IAAI,aAAa,OAAO;CAGtD,MAAM,SAAS,IAAI,OAAO,QACvB,MAAM,KAAK,OAAO,EAAE,OAAO,YAAY,UAAU,IAAI,EAAE,EAAE,KAAK,OAAO,EAAE,YAAY,YAAY,wBAAwB,EAAE,OAAO,CACnI;CACA,IAAI,OAAO,WAAW,GAAG,OAAO;CAChC,OAAO;EAAE,GAAG;EAAK;CAAO;AAC1B;AAEA,eAAsB,oBAAoB,KAAsC;CAC9E,MAAMA,SAAG,MAAM,cAAc,GAAG,EAAE,WAAW,KAAK,CAAC;CACnD,MAAM,MAAM,GAAG,cAAc,IAAI,IAAI,EAAE,GAAG,QAAQ,IAAI;CACtD,MAAMA,SAAG,UAAU,KAAK,GAAG,KAAK,UAAU,KAAK,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,IAAM,CAAC;CAC5E,MAAMA,SAAG,OAAO,KAAK,cAAc,IAAI,IAAI,CAAC;AAC9C;AAEA,MAAM,oBAAoB,UACxB,wKAC8E,MAAM,KAAK,IAAI,EAAE;;AAiBjG,SAAS,YAAY,MAAuB;CAC1C,MAAM,SAAS,gCAAgC,KAAK,IAAI;CACxD,MAAM,OAAO,SAAS,OAAO,KAAK;CAClC,MAAM,QAAQ,KAAK,QAAQ,GAAG;CAC9B,MAAM,MAAM,KAAK,YAAY,GAAG;CAChC,IAAI,QAAQ,KAAK,OAAO,OAAO,OAAO,KAAA;CACtC,IAAI;EACF,OAAO,KAAK,MAAM,KAAK,MAAM,OAAO,MAAM,CAAC,CAAC;CAC9C,QAAQ;EACN;CACF;AACF;AAEA,MAAM,4BAAiC,IAAI,IAAa;CAAC;CAAa;CAAQ;AAAM,CAAC;;;;;;;;;;AAWrF,SAAgB,uBACd,WACA,cACA,cACa;CACb,MAAM,eAAe,YAAY,YAAY;CAC7C,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,SAAsB,CAAC;CAC7B,KAAK,MAAM,KAAK,MAAM,QAAQ,SAAS,IAAI,YAAY,CAAC,GAAG;EACzD,IAAI,CAAC,KAAK,OAAO,MAAM,UAAU;EACjC,MAAM,KAAM,EAAuB;EACnC,MAAM,UAAW,EAA4B;EAC7C,IAAI,OAAO,OAAO,YAAY,CAAC,UAAU,IAAI,EAAE,GAAG;EAClD,IAAI,OAAO,YAAY,UAAU;EACjC,IAAI,OAAO,UAAU,CAAC,cAAc;EACpC,IAAI,KAAK,IAAI,EAAE,GAAG;EAClB,IAAI,CAAC,wBAAwB,OAAO,GAAG;EACvC,IAAI,CAAC,aAAa,SAAS,YAAY,OAAO,CAAC,GAAG;EAClD,KAAK,IAAI,EAAE;EACX,OAAO,KAAK;GAAE;GAAI,SAAS,QAAQ,KAAK;EAAE,CAAC;CAC7C;CACA,OAAO;AACT;;;;;;AAOA,eAAsB,qBACpB,KACA,MACgC;CAChC,MAAM,OAAO,MAAM,SAAS,GAAG,CAAC,CAAC,YAAY,GAAG;CAChD,MAAM,EAAE,MAAM,cAAc,UAAU,MAAM,eAAe,IAAI;CAC/D,IAAI,MAAM,WAAW,GAAG,OAAO;CAE/B,IAAI;CACJ,IAAI;EAKF,MAAM,EAAE,mBAAmB,MAAM,OAAO;EAGxC,SAAS,MAAM,eAAe;GAC5B,MAAM;GACN,WAAW;GACX,QAAQ,iBAAiB,KAAK;GAC9B,QAAQ,KAAK;EACf,CAAC;CACH,QAAQ;EACN,OAAO;CACT;CACA,IAAI,OAAO,SAAS,OAAO;CAC3B,MAAM,SAAS,YAAY,OAAO,IAAI;CACtC,IAAI,CAAC,UAAU,OAAO,WAAW,UAAU,OAAO;CAClD,MAAM,MAAM;CACZ,MAAM,SAAS,uBAAuB,IAAI,QAAQ,cAAc,KAAK,YAAY;CACjF,IAAI,OAAO,WAAW,GAAG,OAAO;CAGhC,OAAO;EAAE,WAFS,OAAO,IAAI,cAAc,YAAY,IAAI,UAAU,SAAS,IAAI,IAAI,YAAY;EAE9E;EAAQ,YADT,OAAO,IAAI,eAAe,WAAW,IAAI,aAAa;EACjC,UAAU;CAAM;AAC1D"}
@@ -1,6 +1,6 @@
1
1
  import { o as resolveExecutable, t as buildExecInvocation } from "./exec-COUT4QsS.js";
2
- import { c as readAicSnapshotFile, s as formatAicStatus } from "./aic-ledger-BrDqsuge.js";
3
- import { t as discountedUsdForSnapshot } from "./copilot-discount-BnJkEojk.js";
2
+ import { c as readAicSnapshotFile, s as formatAicStatus } from "./aic-ledger-BW3zMiCx.js";
3
+ import { t as discountedUsdForSnapshot } from "./copilot-discount-I6BrT7QE.js";
4
4
  import { defineCommand } from "citty";
5
5
  import { readFileSync } from "node:fs";
6
6
  import { spawnSync } from "node:child_process";
@@ -436,4 +436,4 @@ const internalAicStatus = defineCommand({
436
436
  //#endregion
437
437
  export { internalAicStatus };
438
438
 
439
- //# sourceMappingURL=internal-aic-status-Dc2JQDsX.js.map
439
+ //# sourceMappingURL=internal-aic-status-CKXxxb30.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"internal-aic-status-Dc2JQDsX.js","names":[],"sources":["../src/lib/default-statusline.ts","../src/internal-aic-status.ts"],"sourcesContent":["/**\n * Rich default status line for `github-router claude` sessions.\n *\n * Replaces the need for users to maintain their own PowerShell/bash status\n * script: `internal-aic-status` renders these segments natively from the\n * Claude Code stdin JSON, then pins the `[AIC x.xx]` ledger fragment ahead\n * of them. Zero dependencies, cross-platform (Windows 11 is the primary\n * target — no `powershell`/`bash`/`jq` required), and fast (no shell\n * startup; one bounded `git rev-parse` at most).\n *\n * Segment order (left = highest priority; narrow terminals drop from the\n * right, `AIC` is never dropped):\n *\n * `[AIC] | [ctx bar] % | model | dir (branch) | ~$actual | in/out | dur | +a -r`\n *\n * The `$` segment is the DISCOUNTED actual (`~$` = factor-discounted\n * session total from `copilot-discount.ts`, capped at AIC × $0.01) — NOT\n * Claude's `cost.total_cost_usd`, which prices at Anthropic list rates and\n * reads ~10-100x high against Copilot billing (a 28-day sample: 41.1B tokens\n * for $5,142 ≈ $0.13/1M blended vs $15-75/1M list). Rendering list price\n * next to the real `[AIC]` billing unit would show two contradictory\n * numbers, so list price is deliberately not rendered at all.\n *\n * Width comes from `COLUMNS` (set by Claude Code for status scripts since\n * v2.1.153); fallback is 120 when unset/unparseable so a missing `COLUMNS`\n * never needlessly drops segments.\n */\n\nimport { spawnSync } from \"node:child_process\"\n\nimport { USD_PER_AIC_CREDIT } from \"./copilot-discount\"\nimport {\n buildExecInvocation,\n resolveExecutable,\n} from \"./exec\"\n\nconst ESC = \"\\x1b\"\nconst RESET = `${ESC}[0m`\nconst DIM = `${ESC}[90m`\nconst GREEN = `${ESC}[32m`\nconst YELLOW = `${ESC}[33m`\nconst RED = `${ESC}[31m`\nconst ORANGE = `${ESC}[38;5;208m`\nconst CYAN = `${ESC}[36m`\nconst MAGENTA = `${ESC}[35m`\nconst BLUE = `${ESC}[34m`\nconst AIC_COLOR = `${ESC}[1;33m`\n\nconst CTX_BAR_LEN = 10\nconst GIT_TIMEOUT_MS = 300\nconst WIDTH_FALLBACK = 120\nconst SEP_PLAIN = \" | \"\nconst SEP_TEXT = `${DIM} | ${RESET}`\n\nexport interface StatusInput {\n usedPct?: number\n totalInputTokens?: number\n totalOutputTokens?: number\n totalDurationMs?: number\n linesAdded?: number\n linesRemoved?: number\n cwd?: string\n modelName?: string\n}\n\nexport interface StatusSegment {\n text: string\n plain: string\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return undefined\n }\n return value as Record<string, unknown>\n}\n\nfunction finiteNumber(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined\n}\n\nfunction nonEmptyString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined\n}\n\n/**\n * Parse Claude Code's status-line stdin JSON into the fields we render.\n * Total function: malformed JSON or unexpected shapes yield `{}` (every\n * segment then renders its `--` placeholder or is omitted) — never throws.\n */\nexport function parseStatusInput(raw: string): StatusInput {\n if (!raw.trim()) return {}\n let parsed: unknown\n try {\n parsed = JSON.parse(raw) as unknown\n } catch {\n return {}\n }\n const root = asRecord(parsed)\n if (!root) return {}\n const out: StatusInput = {}\n\n const ctx = asRecord(root.context_window)\n if (ctx) {\n const pct = finiteNumber(ctx.used_percentage)\n if (pct !== undefined) out.usedPct = pct\n const tIn = finiteNumber(ctx.total_input_tokens)\n if (tIn !== undefined && tIn >= 0) out.totalInputTokens = Math.floor(tIn)\n const tOut = finiteNumber(ctx.total_output_tokens)\n if (tOut !== undefined && tOut >= 0) out.totalOutputTokens = Math.floor(tOut)\n }\n\n const cost = asRecord(root.cost)\n if (cost) {\n const dur = finiteNumber(cost.total_duration_ms)\n if (dur !== undefined && dur >= 0) out.totalDurationMs = dur\n const la = finiteNumber(cost.total_lines_added)\n if (la !== undefined && la >= 0) out.linesAdded = Math.floor(la)\n const lr = finiteNumber(cost.total_lines_removed)\n if (lr !== undefined && lr >= 0) out.linesRemoved = Math.floor(lr)\n }\n\n const workspace = asRecord(root.workspace)\n const wsDir = workspace ? nonEmptyString(workspace.current_dir) : undefined\n const cwd = wsDir ?? nonEmptyString(root.cwd)\n if (cwd) out.cwd = cwd\n\n const model = asRecord(root.model)\n if (model) {\n const name =\n nonEmptyString(model.display_name) ?? nonEmptyString(model.id)\n if (name) out.modelName = name\n }\n\n return out\n}\n\n/** Compact token count: `999` → `999`, `15234` → `15.2k`, `2.1M`. */\nexport function compactTokens(n: number): string {\n if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`\n if (n >= 1000) return `${(n / 1000).toFixed(1)}k`\n return `${n}`\n}\n\n/** `45000` → `0m45s`, `198000` → `3m18s`, `3720000` → `1h02m`. */\nexport function formatDurationMs(ms: number): string {\n const totalSeconds = Math.floor(ms / 1000)\n const hours = Math.floor(totalSeconds / 3600)\n const minutes = Math.floor((totalSeconds % 3600) / 60)\n const seconds = totalSeconds % 60\n if (hours >= 1) {\n return `${hours}h${String(minutes).padStart(2, \"0\")}m`\n }\n return `${minutes}m${String(seconds).padStart(2, \"0\")}s`\n}\n\n/** Basename of a cwd, tolerant of `/` and `\\` separators + trailing slash. */\nexport function dirLeaf(cwd: string): string {\n const trimmed = cwd.replace(/[/\\\\]+$/, \"\")\n const parts = trimmed.split(/[/\\\\]/)\n return parts[parts.length - 1] ?? trimmed\n}\n\nexport function buildCtxSegment(usedPct: number | undefined): StatusSegment {\n if (usedPct === undefined || !Number.isFinite(usedPct)) {\n return {\n text: `${DIM}[----------] --%${RESET}`,\n plain: \"[----------] --%\",\n }\n }\n const u = Math.max(0, Math.min(100, Math.round(usedPct)))\n let filled = Math.round((u / 100) * CTX_BAR_LEN)\n if (filled > CTX_BAR_LEN) filled = CTX_BAR_LEN\n if (filled < 0) filled = 0\n const bar = `#`.repeat(filled) + `-`.repeat(CTX_BAR_LEN - filled)\n const color = u < 50 ? GREEN : u < 75 ? YELLOW : RED\n return {\n text: `${color}[${bar}] ${u}%${RESET}`,\n plain: `[${bar}] ${u}%`,\n }\n}\n\n/**\n * AIC credits → undiscounted USD. 1 credit ≈ $0.01 — the same convention as\n * the session exit summary (`aic-ledger.ts`). Kept for tests/callers that\n * need the raw conversion; the status line itself shows the\n * factor-discounted total from `copilot-discount.ts`.\n */\nexport function usdFromAicCredits(credits: number): number {\n return credits * USD_PER_AIC_CREDIT\n}\n\n/**\n * Discounted-actual `$` segment. Callers pass the capped total from\n * `discountedUsdForSnapshot` — this function only formats. Placeholder\n * `~$--` until the first priced upstream response lands in the ledger.\n * Thresholds are tuned for ACTUALS scale (a session runs ~$0.10-2.00) —\n * not the old list-price scale.\n */\nexport function buildUsdSegment(\n actualUsd: number | undefined,\n): StatusSegment {\n if (actualUsd === undefined || !Number.isFinite(actualUsd) || actualUsd < 0) {\n return { text: `${DIM}~$--${RESET}`, plain: \"~$--\" }\n }\n const c = actualUsd\n const color = c < 1 ? GREEN : c < 5 ? YELLOW : c < 20 ? ORANGE : RED\n const formatted = c < 10 ? c.toFixed(2) : c.toFixed(1)\n return {\n text: `${color}~$${formatted}${RESET}`,\n plain: `~$${formatted}`,\n }\n}\n\nexport function buildToksSegment(\n tIn: number | undefined,\n tOut: number | undefined,\n): StatusSegment {\n if (tIn === undefined || tOut === undefined) {\n return { text: `${DIM}--/--${RESET}`, plain: \"--/--\" }\n }\n const cin = compactTokens(tIn)\n const cout = compactTokens(tOut)\n return {\n text: `${CYAN}${cin}${RESET}${DIM}/${RESET}${MAGENTA}${cout}${RESET}`,\n plain: `${cin}/${cout}`,\n }\n}\n\nexport function buildDurSegment(durMs: number | undefined): StatusSegment {\n if (durMs === undefined || !Number.isFinite(durMs) || durMs < 0) {\n return { text: `${DIM}--${RESET}`, plain: \"--\" }\n }\n const formatted = formatDurationMs(durMs)\n return { text: `${DIM}${formatted}${RESET}`, plain: formatted }\n}\n\nexport function buildLinesSegment(\n added: number | undefined,\n removed: number | undefined,\n): StatusSegment {\n const a = added ?? 0\n const r = removed ?? 0\n return {\n text: `${GREEN}+${a}${RESET} ${RED}-${r}${RESET}`,\n plain: `+${a} -${r}`,\n }\n}\n\nexport function buildModelSegment(\n modelName: string | undefined,\n): StatusSegment | undefined {\n if (!modelName || modelName === \"--\") return undefined\n return {\n text: `${MAGENTA}${modelName}${RESET}`,\n plain: modelName,\n }\n}\n\nexport function buildDirGitSegment(\n cwd: string | undefined,\n branch: string | undefined,\n): StatusSegment | undefined {\n const leaf = cwd ? dirLeaf(cwd) : \"\"\n const br = branch && branch !== \"HEAD\" ? branch : \"\"\n if (!leaf && !br) return undefined\n if (leaf && br) {\n return {\n text: `${BLUE}${leaf}${RESET} ${CYAN}(${br})${RESET}`,\n plain: `${leaf} (${br})`,\n }\n }\n if (leaf) {\n return { text: `${BLUE}${leaf}${RESET}`, plain: leaf }\n }\n return { text: `${CYAN}${br}${RESET}`, plain: br }\n}\n\n/**\n * Resolve the current git branch for `cwd`. Bounded (`GIT_TIMEOUT_MS`) and\n * total: any failure (no git binary, not a repo, detached `HEAD`, timeout,\n * `%` in a Windows path that cmd.exe cannot safely quote) yields `\"\"`\n * (caller omits the branch) — never throws.\n */\nexport function resolveGitBranch(cwd: string | undefined): string {\n try {\n if (!cwd) return \"\"\n const gitPath = resolveExecutable(\"git\")\n if (!gitPath) return \"\"\n const { command, args, shell } = buildExecInvocation([\n gitPath,\n \"-C\",\n cwd,\n \"rev-parse\",\n \"--abbrev-ref\",\n \"HEAD\",\n ])\n const result = spawnSync(command, args, {\n shell,\n timeout: GIT_TIMEOUT_MS,\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n })\n if (result.error) return \"\"\n const out =\n typeof result.stdout === \"string\" ? result.stdout.trim() : \"\"\n if (!out || out === \"HEAD\") return \"\"\n return out.split(\"\\n\")[0]?.trim() ?? \"\"\n } catch {\n return \"\"\n }\n}\n\n/** Terminal width for the drop-right-to-left assembly. */\nexport function terminalWidth(env: NodeJS.ProcessEnv = process.env): number {\n const raw = env.COLUMNS\n if (raw !== undefined) {\n const n = Number(raw)\n if (Number.isInteger(n) && n > 0) return n\n }\n return WIDTH_FALLBACK\n}\n\n/**\n * Assemble the full status line. `aicFragment` (e.g. `[AIC 12.42]`) is\n * PINNED — it renders even when nothing else fits. `actualUsd` feeds the\n * `~$` segment (the capped factor-discounted total from\n * `discountedUsdForSnapshot`, same snapshot the fragment came from).\n * Droppable segments in priority order: ctx, model, dir/git, ~$, toks,\n * dur, lines.\n */\nexport function assembleStatusLine(\n aicFragment: string,\n input: StatusInput,\n opts: { width?: number; branch?: string; actualUsd?: number } = {},\n): string {\n const aic = aicFragment.trim()\n const aicText = aic ? `${AIC_COLOR}${aic}${RESET}` : \"\"\n\n const droppable: Array<StatusSegment> = [buildCtxSegment(input.usedPct)]\n const model = buildModelSegment(input.modelName)\n if (model) droppable.push(model)\n // Location context sits right after ctx/model: more useful than the\n // numbers when the bar is wide, still dropped before ctx/model.\n const dirGit = buildDirGitSegment(input.cwd, opts.branch)\n if (dirGit) droppable.push(dirGit)\n droppable.push(\n buildUsdSegment(opts.actualUsd),\n buildToksSegment(input.totalInputTokens, input.totalOutputTokens),\n buildDurSegment(input.totalDurationMs),\n buildLinesSegment(input.linesAdded, input.linesRemoved),\n )\n\n const width = opts.width ?? terminalWidth()\n const sepLen = SEP_PLAIN.length\n\n // Drop from the end (lowest priority) until the line fits. AIC is pinned:\n // it is excluded from the dropping loop entirely.\n let count = droppable.length\n while (count > 0) {\n let total = aic ? aic.length : 0\n for (let i = 0; i < count; i++) {\n if (aic || i > 0) total += sepLen\n total += droppable[i].plain.length\n }\n if (total <= width) break\n // Never drop the last segment when there is no AIC: an over-wide line\n // with real data beats an empty status line.\n if (count === 1 && !aic) break\n count--\n }\n\n const parts: Array<string> = []\n if (aic) parts.push(aicText)\n for (let i = 0; i < count; i++) parts.push(droppable[i].text)\n if (parts.length === 0) return \"\"\n return parts.join(SEP_TEXT)\n}\n\n/**\n * Convenience: parse stdin JSON + assemble with branch resolution.\n * `branchOverride` skips the `git` spawn (used by tests). `actualUsd`\n * comes from `discountedUsdForSnapshot` on the same ledger snapshot as\n * `aicFragment` (read by the caller — this module never touches the\n * ledger file).\n */\nexport function buildRichStatusLine(\n stdinRaw: string,\n aicFragment: string,\n opts: { width?: number; branchOverride?: string; actualUsd?: number } = {},\n): string {\n const input = parseStatusInput(stdinRaw)\n const branch =\n opts.branchOverride !== undefined\n ? opts.branchOverride\n : resolveGitBranch(input.cwd)\n return assembleStatusLine(aicFragment, input, {\n ...(opts.width !== undefined ? { width: opts.width } : {}),\n ...(branch ? { branch } : {}),\n ...(opts.actualUsd !== undefined ? { actualUsd: opts.actualUsd } : {}),\n })\n}\n","/**\n * The internal `internal-aic-status` subcommand: the executable a spawned\n * Claude Code session's `statusLine` invokes (registered into the mirrored\n * settings.json by the launcher — see `src/lib/aic-statusline-settings.ts`).\n *\n * Default-on rich status line: Claude Code's stdin JSON is rendered natively\n * (see `src/lib/default-statusline.ts`) as\n * `[ctx bar] % | model | dir (branch) | ~$actual | in/out | dur | +a -r`,\n * with this launch's AIC ledger total PINNED ahead of it: `[AIC 12.42] ...`.\n * The `~$` segment is the factor-discounted actual (per-model static\n * factors over ledger nano, capped at AIC × $0.01 — see\n * `src/lib/copilot-discount.ts`), not Claude's list-price `total_cost_usd`.\n * Narrow terminals drop segments right-to-left; AIC is never dropped.\n *\n * When the user already had a `statusLine` command, its text arrives via\n * `GH_ROUTER_AIC_USER_STATUSLINE` and is run instead of the rich default\n * (their script already renders ctx/cost/model itself — rendering both\n * would duplicate every segment), so the output stays one composed line:\n * `[AIC 12.42] <user line>`.\n *\n * Status-line contract: stdout is the rendered line (first line wins), fast\n * (<1s budget — ledger read is local, git branch has a 300ms cap, the user\n * command gets a 5s cap), and NEVER failing (any error → print whatever is\n * available → exit 0).\n */\n\nimport { defineCommand } from \"citty\"\n\nimport { spawnSync } from \"node:child_process\"\nimport { readFileSync } from \"node:fs\"\n\nimport {\n formatAicStatus,\n readAicSnapshotFile,\n} from \"./lib/aic-ledger\"\nimport { discountedUsdForSnapshot } from \"./lib/copilot-discount\"\nimport { buildRichStatusLine } from \"./lib/default-statusline\"\n\n/**\n * Read stdin synchronously. Claude Code pipes session JSON to the\n * statusLine command; an unread pipe can SIGPIPE the writer on some\n * platforms. Returns \"\" when stdin is a TTY or unreadable — callers render\n * placeholders/omit segments rather than failing.\n */\nfunction readStdin(): string {\n try {\n if (process.stdin.isTTY) return \"\"\n return readFileSync(0, \"utf8\")\n } catch {\n // Best-effort; the ledger read below does not depend on stdin.\n return \"\"\n }\n}\n\n/**\n * Pure core: compose the status line from an AIC fragment and a user line.\n * Exported for unit tests.\n */\nexport function composeAicStatusLine(\n aicFragment: string,\n userLine: string,\n): string {\n const aic = aicFragment.trim()\n const user = userLine.split(\"\\n\")[0]?.trim() ?? \"\"\n if (aic && user) return `${aic} ${user}`\n return aic || user\n}\n\n/**\n * Run the user's own statusLine command (from `GH_ROUTER_AIC_USER_STATUSLINE`),\n * returning its first stdout line or \"\" on any failure/timeout. Exported for\n * unit tests (command + timeout injectable).\n *\n * Claude Code pipes session JSON to the statusLine command's stdin, and\n * user scripts commonly block reading it to EOF — so the received\n * `stdinRaw` is forwarded via `input` (written then closed) rather than\n * leaving stdin disconnected, which would hang such scripts until the\n * timeout and silently drop their segment.\n */\nexport function runUserStatusLine(\n command: string,\n stdinRaw: string,\n timeoutMs = 5000,\n): string {\n if (!command.trim()) return \"\"\n try {\n const result = spawnSync(command, {\n shell: true,\n timeout: timeoutMs,\n encoding: \"utf8\",\n input: stdinRaw,\n stdio: [\"pipe\", \"pipe\", \"ignore\"],\n // Inherit env so user scripts see the same session context they would\n // under their own statusLine (cwd included — spawnSync inherits it).\n env: process.env,\n })\n if (result.error) return \"\"\n const out = typeof result.stdout === \"string\" ? result.stdout : \"\"\n return out.split(\"\\n\")[0]?.trim() ?? \"\"\n } catch {\n return \"\"\n }\n}\n\nfunction readAicSnapshot(): { fragment: string; actualUsd?: number } {\n try {\n const ledgerPath = process.env.GH_ROUTER_AIC_LEDGER\n if (!ledgerPath) return { fragment: \"\" }\n const snapshot = readAicSnapshotFile(ledgerPath)\n if (!snapshot) return { fragment: \"\" }\n // No priced responses yet → no fragment AND no USD: the `~$`\n // segment renders its `~$--` placeholder instead of `~$0.00`.\n if (snapshot.requests === 0 || snapshot.totalNanoAiu <= 0) {\n return { fragment: \"\" }\n }\n return {\n fragment: formatAicStatus(snapshot),\n actualUsd: discountedUsdForSnapshot(snapshot).total,\n }\n } catch {\n return { fragment: \"\" }\n }\n}\n\nexport const internalAicStatus = defineCommand({\n meta: {\n name: \"internal-aic-status\",\n description:\n \"Internal: Claude Code statusLine handler printing AIC total + rich session line.\",\n },\n async run() {\n const stdinRaw = readStdin()\n const { fragment: aic, actualUsd } = readAicSnapshot()\n const userCommand = process.env.GH_ROUTER_AIC_USER_STATUSLINE ?? \"\"\n // Wrap mode: the user's own script already renders ctx/cost/model from\n // the same stdin JSON — run it and prepend AIC only.\n if (userCommand.trim()) {\n const userLine = runUserStatusLine(userCommand, stdinRaw)\n const line = composeAicStatusLine(aic, userLine)\n if (line) process.stdout.write(`${line}\\n`)\n process.exitCode = 0\n return\n }\n // Default mode: native rich line with AIC pinned ahead of it. The\n // builder never throws; an empty result (no ledger yet AND no stdin)\n // prints nothing rather than a placeholder line.\n let line: string\n try {\n line = buildRichStatusLine(stdinRaw, aic, {\n ...(actualUsd !== undefined ? { actualUsd } : {}),\n })\n } catch {\n line = aic.trim()\n }\n if (line) process.stdout.write(`${line}\\n`)\n // Signal success WITHOUT tearing the loop down — same resolution as the\n // other internal hooks (see main.ts help-path comment): a hard\n // process.exit() can abort through a pipe on Windows.\n process.exitCode = 0\n },\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"internal-aic-status-CKXxxb30.js","names":[],"sources":["../src/lib/default-statusline.ts","../src/internal-aic-status.ts"],"sourcesContent":["/**\n * Rich default status line for `github-router claude` sessions.\n *\n * Replaces the need for users to maintain their own PowerShell/bash status\n * script: `internal-aic-status` renders these segments natively from the\n * Claude Code stdin JSON, then pins the `[AIC x.xx]` ledger fragment ahead\n * of them. Zero dependencies, cross-platform (Windows 11 is the primary\n * target — no `powershell`/`bash`/`jq` required), and fast (no shell\n * startup; one bounded `git rev-parse` at most).\n *\n * Segment order (left = highest priority; narrow terminals drop from the\n * right, `AIC` is never dropped):\n *\n * `[AIC] | [ctx bar] % | model | dir (branch) | ~$actual | in/out | dur | +a -r`\n *\n * The `$` segment is the DISCOUNTED actual (`~$` = factor-discounted\n * session total from `copilot-discount.ts`, capped at AIC × $0.01) — NOT\n * Claude's `cost.total_cost_usd`, which prices at Anthropic list rates and\n * reads ~10-100x high against Copilot billing (a 28-day sample: 41.1B tokens\n * for $5,142 ≈ $0.13/1M blended vs $15-75/1M list). Rendering list price\n * next to the real `[AIC]` billing unit would show two contradictory\n * numbers, so list price is deliberately not rendered at all.\n *\n * Width comes from `COLUMNS` (set by Claude Code for status scripts since\n * v2.1.153); fallback is 120 when unset/unparseable so a missing `COLUMNS`\n * never needlessly drops segments.\n */\n\nimport { spawnSync } from \"node:child_process\"\n\nimport { USD_PER_AIC_CREDIT } from \"./copilot-discount\"\nimport {\n buildExecInvocation,\n resolveExecutable,\n} from \"./exec\"\n\nconst ESC = \"\\x1b\"\nconst RESET = `${ESC}[0m`\nconst DIM = `${ESC}[90m`\nconst GREEN = `${ESC}[32m`\nconst YELLOW = `${ESC}[33m`\nconst RED = `${ESC}[31m`\nconst ORANGE = `${ESC}[38;5;208m`\nconst CYAN = `${ESC}[36m`\nconst MAGENTA = `${ESC}[35m`\nconst BLUE = `${ESC}[34m`\nconst AIC_COLOR = `${ESC}[1;33m`\n\nconst CTX_BAR_LEN = 10\nconst GIT_TIMEOUT_MS = 300\nconst WIDTH_FALLBACK = 120\nconst SEP_PLAIN = \" | \"\nconst SEP_TEXT = `${DIM} | ${RESET}`\n\nexport interface StatusInput {\n usedPct?: number\n totalInputTokens?: number\n totalOutputTokens?: number\n totalDurationMs?: number\n linesAdded?: number\n linesRemoved?: number\n cwd?: string\n modelName?: string\n}\n\nexport interface StatusSegment {\n text: string\n plain: string\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return undefined\n }\n return value as Record<string, unknown>\n}\n\nfunction finiteNumber(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined\n}\n\nfunction nonEmptyString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined\n}\n\n/**\n * Parse Claude Code's status-line stdin JSON into the fields we render.\n * Total function: malformed JSON or unexpected shapes yield `{}` (every\n * segment then renders its `--` placeholder or is omitted) — never throws.\n */\nexport function parseStatusInput(raw: string): StatusInput {\n if (!raw.trim()) return {}\n let parsed: unknown\n try {\n parsed = JSON.parse(raw) as unknown\n } catch {\n return {}\n }\n const root = asRecord(parsed)\n if (!root) return {}\n const out: StatusInput = {}\n\n const ctx = asRecord(root.context_window)\n if (ctx) {\n const pct = finiteNumber(ctx.used_percentage)\n if (pct !== undefined) out.usedPct = pct\n const tIn = finiteNumber(ctx.total_input_tokens)\n if (tIn !== undefined && tIn >= 0) out.totalInputTokens = Math.floor(tIn)\n const tOut = finiteNumber(ctx.total_output_tokens)\n if (tOut !== undefined && tOut >= 0) out.totalOutputTokens = Math.floor(tOut)\n }\n\n const cost = asRecord(root.cost)\n if (cost) {\n const dur = finiteNumber(cost.total_duration_ms)\n if (dur !== undefined && dur >= 0) out.totalDurationMs = dur\n const la = finiteNumber(cost.total_lines_added)\n if (la !== undefined && la >= 0) out.linesAdded = Math.floor(la)\n const lr = finiteNumber(cost.total_lines_removed)\n if (lr !== undefined && lr >= 0) out.linesRemoved = Math.floor(lr)\n }\n\n const workspace = asRecord(root.workspace)\n const wsDir = workspace ? nonEmptyString(workspace.current_dir) : undefined\n const cwd = wsDir ?? nonEmptyString(root.cwd)\n if (cwd) out.cwd = cwd\n\n const model = asRecord(root.model)\n if (model) {\n const name =\n nonEmptyString(model.display_name) ?? nonEmptyString(model.id)\n if (name) out.modelName = name\n }\n\n return out\n}\n\n/** Compact token count: `999` → `999`, `15234` → `15.2k`, `2.1M`. */\nexport function compactTokens(n: number): string {\n if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`\n if (n >= 1000) return `${(n / 1000).toFixed(1)}k`\n return `${n}`\n}\n\n/** `45000` → `0m45s`, `198000` → `3m18s`, `3720000` → `1h02m`. */\nexport function formatDurationMs(ms: number): string {\n const totalSeconds = Math.floor(ms / 1000)\n const hours = Math.floor(totalSeconds / 3600)\n const minutes = Math.floor((totalSeconds % 3600) / 60)\n const seconds = totalSeconds % 60\n if (hours >= 1) {\n return `${hours}h${String(minutes).padStart(2, \"0\")}m`\n }\n return `${minutes}m${String(seconds).padStart(2, \"0\")}s`\n}\n\n/** Basename of a cwd, tolerant of `/` and `\\` separators + trailing slash. */\nexport function dirLeaf(cwd: string): string {\n const trimmed = cwd.replace(/[/\\\\]+$/, \"\")\n const parts = trimmed.split(/[/\\\\]/)\n return parts[parts.length - 1] ?? trimmed\n}\n\nexport function buildCtxSegment(usedPct: number | undefined): StatusSegment {\n if (usedPct === undefined || !Number.isFinite(usedPct)) {\n return {\n text: `${DIM}[----------] --%${RESET}`,\n plain: \"[----------] --%\",\n }\n }\n const u = Math.max(0, Math.min(100, Math.round(usedPct)))\n let filled = Math.round((u / 100) * CTX_BAR_LEN)\n if (filled > CTX_BAR_LEN) filled = CTX_BAR_LEN\n if (filled < 0) filled = 0\n const bar = `#`.repeat(filled) + `-`.repeat(CTX_BAR_LEN - filled)\n const color = u < 50 ? GREEN : u < 75 ? YELLOW : RED\n return {\n text: `${color}[${bar}] ${u}%${RESET}`,\n plain: `[${bar}] ${u}%`,\n }\n}\n\n/**\n * AIC credits → undiscounted USD. 1 credit ≈ $0.01 — the same convention as\n * the session exit summary (`aic-ledger.ts`). Kept for tests/callers that\n * need the raw conversion; the status line itself shows the\n * factor-discounted total from `copilot-discount.ts`.\n */\nexport function usdFromAicCredits(credits: number): number {\n return credits * USD_PER_AIC_CREDIT\n}\n\n/**\n * Discounted-actual `$` segment. Callers pass the capped total from\n * `discountedUsdForSnapshot` — this function only formats. Placeholder\n * `~$--` until the first priced upstream response lands in the ledger.\n * Thresholds are tuned for ACTUALS scale (a session runs ~$0.10-2.00) —\n * not the old list-price scale.\n */\nexport function buildUsdSegment(\n actualUsd: number | undefined,\n): StatusSegment {\n if (actualUsd === undefined || !Number.isFinite(actualUsd) || actualUsd < 0) {\n return { text: `${DIM}~$--${RESET}`, plain: \"~$--\" }\n }\n const c = actualUsd\n const color = c < 1 ? GREEN : c < 5 ? YELLOW : c < 20 ? ORANGE : RED\n const formatted = c < 10 ? c.toFixed(2) : c.toFixed(1)\n return {\n text: `${color}~$${formatted}${RESET}`,\n plain: `~$${formatted}`,\n }\n}\n\nexport function buildToksSegment(\n tIn: number | undefined,\n tOut: number | undefined,\n): StatusSegment {\n if (tIn === undefined || tOut === undefined) {\n return { text: `${DIM}--/--${RESET}`, plain: \"--/--\" }\n }\n const cin = compactTokens(tIn)\n const cout = compactTokens(tOut)\n return {\n text: `${CYAN}${cin}${RESET}${DIM}/${RESET}${MAGENTA}${cout}${RESET}`,\n plain: `${cin}/${cout}`,\n }\n}\n\nexport function buildDurSegment(durMs: number | undefined): StatusSegment {\n if (durMs === undefined || !Number.isFinite(durMs) || durMs < 0) {\n return { text: `${DIM}--${RESET}`, plain: \"--\" }\n }\n const formatted = formatDurationMs(durMs)\n return { text: `${DIM}${formatted}${RESET}`, plain: formatted }\n}\n\nexport function buildLinesSegment(\n added: number | undefined,\n removed: number | undefined,\n): StatusSegment {\n const a = added ?? 0\n const r = removed ?? 0\n return {\n text: `${GREEN}+${a}${RESET} ${RED}-${r}${RESET}`,\n plain: `+${a} -${r}`,\n }\n}\n\nexport function buildModelSegment(\n modelName: string | undefined,\n): StatusSegment | undefined {\n if (!modelName || modelName === \"--\") return undefined\n return {\n text: `${MAGENTA}${modelName}${RESET}`,\n plain: modelName,\n }\n}\n\nexport function buildDirGitSegment(\n cwd: string | undefined,\n branch: string | undefined,\n): StatusSegment | undefined {\n const leaf = cwd ? dirLeaf(cwd) : \"\"\n const br = branch && branch !== \"HEAD\" ? branch : \"\"\n if (!leaf && !br) return undefined\n if (leaf && br) {\n return {\n text: `${BLUE}${leaf}${RESET} ${CYAN}(${br})${RESET}`,\n plain: `${leaf} (${br})`,\n }\n }\n if (leaf) {\n return { text: `${BLUE}${leaf}${RESET}`, plain: leaf }\n }\n return { text: `${CYAN}${br}${RESET}`, plain: br }\n}\n\n/**\n * Resolve the current git branch for `cwd`. Bounded (`GIT_TIMEOUT_MS`) and\n * total: any failure (no git binary, not a repo, detached `HEAD`, timeout,\n * `%` in a Windows path that cmd.exe cannot safely quote) yields `\"\"`\n * (caller omits the branch) — never throws.\n */\nexport function resolveGitBranch(cwd: string | undefined): string {\n try {\n if (!cwd) return \"\"\n const gitPath = resolveExecutable(\"git\")\n if (!gitPath) return \"\"\n const { command, args, shell } = buildExecInvocation([\n gitPath,\n \"-C\",\n cwd,\n \"rev-parse\",\n \"--abbrev-ref\",\n \"HEAD\",\n ])\n const result = spawnSync(command, args, {\n shell,\n timeout: GIT_TIMEOUT_MS,\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n })\n if (result.error) return \"\"\n const out =\n typeof result.stdout === \"string\" ? result.stdout.trim() : \"\"\n if (!out || out === \"HEAD\") return \"\"\n return out.split(\"\\n\")[0]?.trim() ?? \"\"\n } catch {\n return \"\"\n }\n}\n\n/** Terminal width for the drop-right-to-left assembly. */\nexport function terminalWidth(env: NodeJS.ProcessEnv = process.env): number {\n const raw = env.COLUMNS\n if (raw !== undefined) {\n const n = Number(raw)\n if (Number.isInteger(n) && n > 0) return n\n }\n return WIDTH_FALLBACK\n}\n\n/**\n * Assemble the full status line. `aicFragment` (e.g. `[AIC 12.42]`) is\n * PINNED — it renders even when nothing else fits. `actualUsd` feeds the\n * `~$` segment (the capped factor-discounted total from\n * `discountedUsdForSnapshot`, same snapshot the fragment came from).\n * Droppable segments in priority order: ctx, model, dir/git, ~$, toks,\n * dur, lines.\n */\nexport function assembleStatusLine(\n aicFragment: string,\n input: StatusInput,\n opts: { width?: number; branch?: string; actualUsd?: number } = {},\n): string {\n const aic = aicFragment.trim()\n const aicText = aic ? `${AIC_COLOR}${aic}${RESET}` : \"\"\n\n const droppable: Array<StatusSegment> = [buildCtxSegment(input.usedPct)]\n const model = buildModelSegment(input.modelName)\n if (model) droppable.push(model)\n // Location context sits right after ctx/model: more useful than the\n // numbers when the bar is wide, still dropped before ctx/model.\n const dirGit = buildDirGitSegment(input.cwd, opts.branch)\n if (dirGit) droppable.push(dirGit)\n droppable.push(\n buildUsdSegment(opts.actualUsd),\n buildToksSegment(input.totalInputTokens, input.totalOutputTokens),\n buildDurSegment(input.totalDurationMs),\n buildLinesSegment(input.linesAdded, input.linesRemoved),\n )\n\n const width = opts.width ?? terminalWidth()\n const sepLen = SEP_PLAIN.length\n\n // Drop from the end (lowest priority) until the line fits. AIC is pinned:\n // it is excluded from the dropping loop entirely.\n let count = droppable.length\n while (count > 0) {\n let total = aic ? aic.length : 0\n for (let i = 0; i < count; i++) {\n if (aic || i > 0) total += sepLen\n total += droppable[i].plain.length\n }\n if (total <= width) break\n // Never drop the last segment when there is no AIC: an over-wide line\n // with real data beats an empty status line.\n if (count === 1 && !aic) break\n count--\n }\n\n const parts: Array<string> = []\n if (aic) parts.push(aicText)\n for (let i = 0; i < count; i++) parts.push(droppable[i].text)\n if (parts.length === 0) return \"\"\n return parts.join(SEP_TEXT)\n}\n\n/**\n * Convenience: parse stdin JSON + assemble with branch resolution.\n * `branchOverride` skips the `git` spawn (used by tests). `actualUsd`\n * comes from `discountedUsdForSnapshot` on the same ledger snapshot as\n * `aicFragment` (read by the caller — this module never touches the\n * ledger file).\n */\nexport function buildRichStatusLine(\n stdinRaw: string,\n aicFragment: string,\n opts: { width?: number; branchOverride?: string; actualUsd?: number } = {},\n): string {\n const input = parseStatusInput(stdinRaw)\n const branch =\n opts.branchOverride !== undefined\n ? opts.branchOverride\n : resolveGitBranch(input.cwd)\n return assembleStatusLine(aicFragment, input, {\n ...(opts.width !== undefined ? { width: opts.width } : {}),\n ...(branch ? { branch } : {}),\n ...(opts.actualUsd !== undefined ? { actualUsd: opts.actualUsd } : {}),\n })\n}\n","/**\n * The internal `internal-aic-status` subcommand: the executable a spawned\n * Claude Code session's `statusLine` invokes (registered into the mirrored\n * settings.json by the launcher — see `src/lib/aic-statusline-settings.ts`).\n *\n * Default-on rich status line: Claude Code's stdin JSON is rendered natively\n * (see `src/lib/default-statusline.ts`) as\n * `[ctx bar] % | model | dir (branch) | ~$actual | in/out | dur | +a -r`,\n * with this launch's AIC ledger total PINNED ahead of it: `[AIC 12.42] ...`.\n * The `~$` segment is the factor-discounted actual (per-model static\n * factors over ledger nano, capped at AIC × $0.01 — see\n * `src/lib/copilot-discount.ts`), not Claude's list-price `total_cost_usd`.\n * Narrow terminals drop segments right-to-left; AIC is never dropped.\n *\n * When the user already had a `statusLine` command, its text arrives via\n * `GH_ROUTER_AIC_USER_STATUSLINE` and is run instead of the rich default\n * (their script already renders ctx/cost/model itself — rendering both\n * would duplicate every segment), so the output stays one composed line:\n * `[AIC 12.42] <user line>`.\n *\n * Status-line contract: stdout is the rendered line (first line wins), fast\n * (<1s budget — ledger read is local, git branch has a 300ms cap, the user\n * command gets a 5s cap), and NEVER failing (any error → print whatever is\n * available → exit 0).\n */\n\nimport { defineCommand } from \"citty\"\n\nimport { spawnSync } from \"node:child_process\"\nimport { readFileSync } from \"node:fs\"\n\nimport {\n formatAicStatus,\n readAicSnapshotFile,\n} from \"./lib/aic-ledger\"\nimport { discountedUsdForSnapshot } from \"./lib/copilot-discount\"\nimport { buildRichStatusLine } from \"./lib/default-statusline\"\n\n/**\n * Read stdin synchronously. Claude Code pipes session JSON to the\n * statusLine command; an unread pipe can SIGPIPE the writer on some\n * platforms. Returns \"\" when stdin is a TTY or unreadable — callers render\n * placeholders/omit segments rather than failing.\n */\nfunction readStdin(): string {\n try {\n if (process.stdin.isTTY) return \"\"\n return readFileSync(0, \"utf8\")\n } catch {\n // Best-effort; the ledger read below does not depend on stdin.\n return \"\"\n }\n}\n\n/**\n * Pure core: compose the status line from an AIC fragment and a user line.\n * Exported for unit tests.\n */\nexport function composeAicStatusLine(\n aicFragment: string,\n userLine: string,\n): string {\n const aic = aicFragment.trim()\n const user = userLine.split(\"\\n\")[0]?.trim() ?? \"\"\n if (aic && user) return `${aic} ${user}`\n return aic || user\n}\n\n/**\n * Run the user's own statusLine command (from `GH_ROUTER_AIC_USER_STATUSLINE`),\n * returning its first stdout line or \"\" on any failure/timeout. Exported for\n * unit tests (command + timeout injectable).\n *\n * Claude Code pipes session JSON to the statusLine command's stdin, and\n * user scripts commonly block reading it to EOF — so the received\n * `stdinRaw` is forwarded via `input` (written then closed) rather than\n * leaving stdin disconnected, which would hang such scripts until the\n * timeout and silently drop their segment.\n */\nexport function runUserStatusLine(\n command: string,\n stdinRaw: string,\n timeoutMs = 5000,\n): string {\n if (!command.trim()) return \"\"\n try {\n const result = spawnSync(command, {\n shell: true,\n timeout: timeoutMs,\n encoding: \"utf8\",\n input: stdinRaw,\n stdio: [\"pipe\", \"pipe\", \"ignore\"],\n // Inherit env so user scripts see the same session context they would\n // under their own statusLine (cwd included — spawnSync inherits it).\n env: process.env,\n })\n if (result.error) return \"\"\n const out = typeof result.stdout === \"string\" ? result.stdout : \"\"\n return out.split(\"\\n\")[0]?.trim() ?? \"\"\n } catch {\n return \"\"\n }\n}\n\nfunction readAicSnapshot(): { fragment: string; actualUsd?: number } {\n try {\n const ledgerPath = process.env.GH_ROUTER_AIC_LEDGER\n if (!ledgerPath) return { fragment: \"\" }\n const snapshot = readAicSnapshotFile(ledgerPath)\n if (!snapshot) return { fragment: \"\" }\n // No priced responses yet → no fragment AND no USD: the `~$`\n // segment renders its `~$--` placeholder instead of `~$0.00`.\n if (snapshot.requests === 0 || snapshot.totalNanoAiu <= 0) {\n return { fragment: \"\" }\n }\n return {\n fragment: formatAicStatus(snapshot),\n actualUsd: discountedUsdForSnapshot(snapshot).total,\n }\n } catch {\n return { fragment: \"\" }\n }\n}\n\nexport const internalAicStatus = defineCommand({\n meta: {\n name: \"internal-aic-status\",\n description:\n \"Internal: Claude Code statusLine handler printing AIC total + rich session line.\",\n },\n async run() {\n const stdinRaw = readStdin()\n const { fragment: aic, actualUsd } = readAicSnapshot()\n const userCommand = process.env.GH_ROUTER_AIC_USER_STATUSLINE ?? \"\"\n // Wrap mode: the user's own script already renders ctx/cost/model from\n // the same stdin JSON — run it and prepend AIC only.\n if (userCommand.trim()) {\n const userLine = runUserStatusLine(userCommand, stdinRaw)\n const line = composeAicStatusLine(aic, userLine)\n if (line) process.stdout.write(`${line}\\n`)\n process.exitCode = 0\n return\n }\n // Default mode: native rich line with AIC pinned ahead of it. The\n // builder never throws; an empty result (no ledger yet AND no stdin)\n // prints nothing rather than a placeholder line.\n let line: string\n try {\n line = buildRichStatusLine(stdinRaw, aic, {\n ...(actualUsd !== undefined ? { actualUsd } : {}),\n })\n } catch {\n line = aic.trim()\n }\n if (line) process.stdout.write(`${line}\\n`)\n // Signal success WITHOUT tearing the loop down — same resolution as the\n // other internal hooks (see main.ts help-path comment): a hard\n // process.exit() can abort through a pipe on Windows.\n process.exitCode = 0\n },\n})\n"],"mappings":"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"}
@@ -4,7 +4,7 @@ import { a as decideStopHook, c as launchBaselineKey, d as stopGateId, f as stop
4
4
  import { t as liveExec } from "./orchestration-B8kypXcZ.js";
5
5
  import { c as repoRoot, i as fileReviewDebounce, l as stopGateEnabledForRepo, r as fileLastPromptStore, t as fileBaselineStore, u as stopReviewStateDir } from "./stop-gate-policy-C0gt04R0.js";
6
6
  import { r as hookMcpRuntimeFromEnv } from "./hook-mcp-client-DBvm9608.js";
7
- import { a as checksForDescriptor, n as readDiscoveredGate, o as descriptorHash, s as parseGateDescriptor } from "./gate-discovery-DUBuxiLb.js";
7
+ import { a as checksForDescriptor, n as readDiscoveredGate, o as descriptorHash, s as parseGateDescriptor } from "./gate-discovery-j_hQ1IPc.js";
8
8
  import { defineCommand } from "citty";
9
9
  import { mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
10
10
  import path from "node:path";
@@ -257,4 +257,4 @@ const internalStopHook = defineCommand({
257
257
  //#endregion
258
258
  export { internalStopHook };
259
259
 
260
- //# sourceMappingURL=internal-stop-hook-DCjYE2x0.js.map
260
+ //# sourceMappingURL=internal-stop-hook-BGTPRCtO.js.map