@gajae-code/coding-agent 0.14.2 → 0.15.0

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 (345) hide show
  1. package/CHANGELOG.md +120 -1
  2. package/dist/types/async/job-manager.d.ts +4 -1
  3. package/dist/types/autoresearch/dashboard.d.ts +29 -4
  4. package/dist/types/autoresearch/data-context.d.ts +25 -0
  5. package/dist/types/autoresearch/git.d.ts +65 -7
  6. package/dist/types/autoresearch/harness.d.ts +58 -0
  7. package/dist/types/autoresearch/prompts.d.ts +51 -0
  8. package/dist/types/autoresearch/runs.d.ts +173 -0
  9. package/dist/types/autoresearch/types.d.ts +9 -139
  10. package/dist/types/capability/types.d.ts +20 -0
  11. package/dist/types/cli/accounts-cli.d.ts +1 -0
  12. package/dist/types/commands/{rlm.d.ts → autoresearch.d.ts} +2 -4
  13. package/dist/types/config/autorouting-contract.d.ts +146 -0
  14. package/dist/types/config/autorouting-generator.d.ts +40 -0
  15. package/dist/types/config/autorouting-tier-map.d.ts +16844 -0
  16. package/dist/types/config/autorouting.d.ts +35 -0
  17. package/dist/types/config/model-discovery-manager.d.ts +8 -1
  18. package/dist/types/config/model-registry.d.ts +30 -1
  19. package/dist/types/config/provider-selection-policy.d.ts +23 -0
  20. package/dist/types/config/settings-schema.d.ts +61 -3
  21. package/dist/types/config/settings.d.ts +3 -0
  22. package/dist/types/coordinator/contract.d.ts +1 -1
  23. package/dist/types/coordinator-mcp/durability.d.ts +29 -0
  24. package/dist/types/coordinator-mcp/event-webhook.d.ts +50 -0
  25. package/dist/types/coordinator-mcp/question-state.d.ts +1 -1
  26. package/dist/types/coordinator-mcp/server.d.ts +14 -0
  27. package/dist/types/crash/index-store.d.ts +3 -0
  28. package/dist/types/crash/record-loader.d.ts +2 -0
  29. package/dist/types/crash/upstream/relay.d.ts +5 -3
  30. package/dist/types/cursor.d.ts +12 -0
  31. package/dist/types/defaults/gjc-defaults.d.ts +35 -1
  32. package/dist/types/defaults/gjc-skills.generated.d.ts +1 -1
  33. package/dist/types/discovery/helpers.d.ts +1 -1
  34. package/dist/types/edit/index.d.ts +2 -1
  35. package/dist/types/eval/py/executor.d.ts +2 -0
  36. package/dist/types/extensibility/extensions/loader.d.ts +32 -0
  37. package/dist/types/extensibility/gjc-plugins/types.d.ts +1 -1
  38. package/dist/types/extensibility/skills.d.ts +2 -0
  39. package/dist/types/gjc-runtime/autoresearch-runtime.d.ts +194 -0
  40. package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
  41. package/dist/types/gjc-runtime/psmux-detect.d.ts +1 -1
  42. package/dist/types/gjc-runtime/python-transcript.d.ts +19 -0
  43. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +2 -8
  44. package/dist/types/gjc-runtime/session-layout.d.ts +6 -3
  45. package/dist/types/gjc-runtime/state-renderer.d.ts +0 -22
  46. package/dist/types/gjc-runtime/state-schema.d.ts +7 -7
  47. package/dist/types/gjc-runtime/tmux-common.d.ts +2 -2
  48. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +38 -1
  49. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +2 -24
  50. package/dist/types/gjc-runtime/ultragoal-validation-policy.d.ts +73 -0
  51. package/dist/types/gjc-runtime/workflow-recovery-projection.d.ts +100 -0
  52. package/dist/types/hooks/skill-keywords.d.ts +1 -1
  53. package/dist/types/main.d.ts +0 -10
  54. package/dist/types/modes/acp/acp-agent.d.ts +13 -6
  55. package/dist/types/modes/components/model-selector.d.ts +14 -0
  56. package/dist/types/modes/components/skill-hud/render.d.ts +6 -1
  57. package/dist/types/modes/components/smart-routing-panel.d.ts +82 -0
  58. package/dist/types/modes/controllers/selector-controller.d.ts +14 -0
  59. package/dist/types/modes/interactive-mode.d.ts +3 -0
  60. package/dist/types/modes/types.d.ts +9 -0
  61. package/dist/types/rlm/artifacts.d.ts +4 -4
  62. package/dist/types/runtime/optional-runtime-services.d.ts +2 -1
  63. package/dist/types/runtime/workspace-tree-service.d.ts +5 -1
  64. package/dist/types/runtime-mcp/config.d.ts +6 -0
  65. package/dist/types/runtime-mcp/manager.d.ts +10 -0
  66. package/dist/types/sdk/acp/adapter.d.ts +0 -4
  67. package/dist/types/sdk/broker/broker.d.ts +1 -1
  68. package/dist/types/sdk/broker/lifecycle.d.ts +57 -2
  69. package/dist/types/sdk/broker/session-index.d.ts +15 -0
  70. package/dist/types/sdk/bus/chat-daemon-control.d.ts +6 -0
  71. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -0
  72. package/dist/types/sdk/bus/index.d.ts +8 -0
  73. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +2 -0
  74. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -2
  75. package/dist/types/sdk/client/client.d.ts +39 -4
  76. package/dist/types/sdk/host/host.d.ts +1 -0
  77. package/dist/types/sdk/host/internal-autorouting-state.d.ts +3 -0
  78. package/dist/types/sdk/host/model-pin.d.ts +29 -0
  79. package/dist/types/sdk/host/session-runtime.d.ts +2 -1
  80. package/dist/types/sdk/lifecycle-session.d.ts +13 -0
  81. package/dist/types/sdk/router/session-router.d.ts +27 -1
  82. package/dist/types/sdk/session.d.ts +11 -1
  83. package/dist/types/session/agent-session.d.ts +36 -6
  84. package/dist/types/session/artifacts.d.ts +16 -1
  85. package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
  86. package/dist/types/session/session-manager.d.ts +84 -2
  87. package/dist/types/session/session-staging-paths.d.ts +3 -0
  88. package/dist/types/skill-state/canonical-skills.d.ts +1 -1
  89. package/dist/types/skill-state/workflow-hud.d.ts +12 -21
  90. package/dist/types/skill-state/workflow-mutation-guard.d.ts +1 -0
  91. package/dist/types/slash-commands/helpers/autorouting-status.d.ts +8 -0
  92. package/dist/types/system-prompt.d.ts +23 -0
  93. package/dist/types/task/executor.d.ts +35 -4
  94. package/dist/types/task/index.d.ts +17 -3
  95. package/dist/types/task/receipt.d.ts +3 -1
  96. package/dist/types/task/types.d.ts +144 -0
  97. package/dist/types/tools/atomic-file-write.d.ts +72 -0
  98. package/dist/types/tools/descriptors.d.ts +1 -0
  99. package/dist/types/tools/index.d.ts +36 -1
  100. package/dist/types/tools/move-session.d.ts +40 -0
  101. package/dist/types/tools/python.d.ts +12 -0
  102. package/dist/types/tools/resource-gc.d.ts +0 -3
  103. package/dist/types/tools/search.d.ts +8 -1
  104. package/dist/types/tools/skill.d.ts +9 -4
  105. package/dist/types/tools/subagent.d.ts +33 -3
  106. package/dist/types/utils/xml-escape.d.ts +2 -0
  107. package/dist/types/workflow/workflow-intent-diff.d.ts +2 -2
  108. package/dist/types/workflow/workflow-intent-report-consumer.d.ts +1 -1
  109. package/package.json +11 -12
  110. package/scripts/build-sdk-package-smoke.ts +7 -5
  111. package/scripts/check-autorouting-tier-map.ts +183 -0
  112. package/scripts/generate-sdk-operation-inventory.ts +12 -0
  113. package/scripts/generate-tool-catalog.ts +40 -5
  114. package/src/async/job-manager.ts +5 -2
  115. package/src/autoresearch/dashboard.ts +134 -314
  116. package/src/autoresearch/data-context.ts +35 -0
  117. package/src/autoresearch/git.ts +186 -26
  118. package/src/autoresearch/{helpers.ts → harness.ts} +65 -40
  119. package/src/autoresearch/{prompt-setup.md → prompts/prompt-setup.md} +7 -7
  120. package/src/autoresearch/prompts/prompt.md +103 -0
  121. package/src/autoresearch/prompts.ts +126 -0
  122. package/src/autoresearch/runs.ts +708 -0
  123. package/src/autoresearch/types.ts +9 -150
  124. package/src/capability/index.ts +4 -4
  125. package/src/capability/types.ts +20 -0
  126. package/src/cli/accounts-cli.ts +21 -5
  127. package/src/cli/config-cli.ts +25 -1
  128. package/src/cli/fast-help.ts +1 -2
  129. package/src/cli/list-models.ts +5 -8
  130. package/src/cli/setup-cli.ts +8 -0
  131. package/src/cli.ts +2 -21
  132. package/src/commands/autoresearch.ts +33 -0
  133. package/src/commands/coordinator.ts +15 -0
  134. package/src/commands/launch.ts +2 -4
  135. package/src/commands/quick-lane.ts +1 -1
  136. package/src/commands/sdk.ts +24 -0
  137. package/src/commands/state.ts +1 -1
  138. package/src/config/autorouting-contract.ts +474 -0
  139. package/src/config/autorouting-generator.ts +238 -0
  140. package/src/config/autorouting-tier-map.ts +6108 -0
  141. package/src/config/autorouting.ts +120 -0
  142. package/src/config/model-discovery-manager.ts +42 -11
  143. package/src/config/model-registry.ts +348 -68
  144. package/src/config/provider-selection-policy.ts +60 -9
  145. package/src/config/settings-schema.ts +195 -6
  146. package/src/config/settings.ts +62 -1
  147. package/src/config.ts +2 -3
  148. package/src/coordinator/contract.ts +0 -1
  149. package/src/coordinator-mcp/codex-handoff.ts +68 -42
  150. package/src/coordinator-mcp/durability.ts +200 -0
  151. package/src/coordinator-mcp/event-webhook.ts +278 -0
  152. package/src/coordinator-mcp/question-state.ts +109 -46
  153. package/src/coordinator-mcp/server.ts +932 -504
  154. package/src/crash/index-store.ts +126 -22
  155. package/src/crash/record-loader.ts +7 -0
  156. package/src/crash/upstream/relay.ts +82 -34
  157. package/src/cursor.ts +84 -1
  158. package/src/debug/index.ts +26 -0
  159. package/src/defaults/gjc/skills/autoresearch/SKILL.md +116 -0
  160. package/src/defaults/gjc/skills/autoresearch/auto-critic.md +46 -0
  161. package/src/defaults/gjc/skills/autoresearch/auto-iterate.md +42 -0
  162. package/src/defaults/gjc/skills/deep-interview/SKILL.md +17 -37
  163. package/src/defaults/gjc/skills/ralplan/SKILL.md +21 -18
  164. package/src/defaults/gjc/skills/ultragoal/SKILL.md +6 -17
  165. package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +2 -2
  166. package/src/defaults/gjc-defaults.ts +89 -2
  167. package/src/defaults/gjc-skills.generated.ts +24 -17
  168. package/src/discovery/builtin.ts +18 -5
  169. package/src/discovery/helpers.ts +6 -6
  170. package/src/edit/index.ts +5 -2
  171. package/src/eval/py/executor.ts +8 -0
  172. package/src/extensibility/extensions/loader.ts +135 -13
  173. package/src/extensibility/extensions/wrapper.ts +1 -2
  174. package/src/extensibility/gjc-plugins/schema.ts +1 -1
  175. package/src/extensibility/runtime-skill-discovery.ts +2 -2
  176. package/src/extensibility/skill-management.ts +2 -3
  177. package/src/extensibility/skills.ts +16 -8
  178. package/src/gjc-runtime/autoresearch-runtime.ts +1509 -0
  179. package/src/gjc-runtime/gc-render.ts +0 -1
  180. package/src/gjc-runtime/gc-runtime.ts +1 -11
  181. package/src/gjc-runtime/launch-tmux.ts +3 -3
  182. package/src/gjc-runtime/psmux-detect.ts +2 -2
  183. package/src/gjc-runtime/python-transcript.ts +58 -0
  184. package/src/gjc-runtime/ralplan-runtime.ts +6 -18
  185. package/src/gjc-runtime/session-layout.ts +18 -7
  186. package/src/gjc-runtime/session-state-sidecar.ts +58 -9
  187. package/src/gjc-runtime/state-renderer.ts +0 -38
  188. package/src/gjc-runtime/state-runtime.ts +43 -22
  189. package/src/gjc-runtime/state-schema.ts +1 -1
  190. package/src/gjc-runtime/tmux-common.ts +7 -7
  191. package/src/gjc-runtime/ultragoal-change-set.ts +232 -11
  192. package/src/gjc-runtime/ultragoal-runtime.ts +309 -114
  193. package/src/gjc-runtime/ultragoal-validation-policy.ts +247 -0
  194. package/src/gjc-runtime/workflow-command-ref.ts +15 -11
  195. package/src/gjc-runtime/workflow-manifest.generated.json +413 -532
  196. package/src/gjc-runtime/workflow-manifest.ts +43 -83
  197. package/src/gjc-runtime/workflow-recovery-projection.ts +616 -0
  198. package/src/hooks/skill-keywords.ts +3 -3
  199. package/src/hooks/skill-state.ts +2 -2
  200. package/src/internal-urls/docs-index.generated.ts +26 -24
  201. package/src/lsp/index.ts +25 -5
  202. package/src/main.ts +2 -27
  203. package/src/modes/acp/acp-agent.ts +200 -75
  204. package/src/modes/components/model-selector.ts +216 -13
  205. package/src/modes/components/oauth-selector.ts +5 -5
  206. package/src/modes/components/skill-hud/render.ts +173 -35
  207. package/src/modes/components/smart-routing-panel.ts +531 -0
  208. package/src/modes/components/tool-status-header.ts +6 -3
  209. package/src/modes/controllers/command-controller.ts +17 -7
  210. package/src/modes/controllers/input-controller.ts +44 -9
  211. package/src/modes/controllers/runtime-mcp-command-controller.ts +4 -0
  212. package/src/modes/controllers/selector-controller.ts +298 -65
  213. package/src/modes/interactive-mode.ts +8 -1
  214. package/src/modes/shared/agent-wire/workflow-gate-types.ts +1 -1
  215. package/src/modes/types.ts +9 -1
  216. package/src/prompts/agents/planner.md +1 -1
  217. package/src/prompts/system/system-prompt.md +1 -1
  218. package/src/prompts/system/volatile-project-context.md +2 -1
  219. package/src/prompts/tools/move-session.md +15 -0
  220. package/src/prompts/tools/python.md +12 -0
  221. package/src/prompts/tools/skill-discovery.md +1 -1
  222. package/src/prompts/tools/skill.md +2 -2
  223. package/src/prompts/tools/task-summary.md +1 -0
  224. package/src/prompts/tools/task.md +6 -0
  225. package/src/rlm/artifacts.ts +12 -10
  226. package/src/runtime/optional-runtime-services.ts +3 -2
  227. package/src/runtime/workspace-tree-service.ts +11 -3
  228. package/src/runtime-mcp/config.ts +8 -1
  229. package/src/runtime-mcp/manager.ts +33 -0
  230. package/src/sdk/acp/adapter.ts +48 -1
  231. package/src/sdk/broker/broker.ts +30 -2
  232. package/src/sdk/broker/discovery.ts +217 -2
  233. package/src/sdk/broker/lifecycle.ts +385 -59
  234. package/src/sdk/broker/session-index.ts +243 -48
  235. package/src/sdk/broker/transport.ts +1 -0
  236. package/src/sdk/bus/chat-daemon-control.ts +8 -2
  237. package/src/sdk/bus/chat-daemon-runtime.ts +5 -0
  238. package/src/sdk/bus/index.ts +208 -11
  239. package/src/sdk/bus/kind-aware-reconciliation.ts +16 -0
  240. package/src/sdk/bus/reconciliation-store.ts +27 -5
  241. package/src/sdk/bus/telegram-daemon-contract.ts +12 -2
  242. package/src/sdk/client/client.ts +204 -22
  243. package/src/sdk/host/host.ts +16 -0
  244. package/src/sdk/host/internal-autorouting-state.ts +13 -0
  245. package/src/sdk/host/model-pin.ts +101 -0
  246. package/src/sdk/host/reverse-leases.ts +2 -1
  247. package/src/sdk/host/session-runtime.test.ts +224 -1
  248. package/src/sdk/host/session-runtime.ts +125 -37
  249. package/src/sdk/lifecycle/service.ts +3 -1
  250. package/src/sdk/lifecycle-session.ts +42 -1
  251. package/src/sdk/protocol/operation-inventory.generated.json +66 -0
  252. package/src/sdk/router/session-router.ts +375 -36
  253. package/src/sdk/session.ts +547 -47
  254. package/src/session/account-inventory.ts +1 -1
  255. package/src/session/agent-session.ts +765 -189
  256. package/src/session/artifacts.ts +234 -4
  257. package/src/session/internal/managed-session-storage.ts +91 -4
  258. package/src/session/provider-safety-stop-hint.ts +3 -2
  259. package/src/session/session-manager.ts +775 -16
  260. package/src/session/session-staging-paths.ts +8 -0
  261. package/src/setup/hermes/templates/operator-instructions.v1.md +0 -1
  262. package/src/setup/host-plugin-setup.ts +2 -2
  263. package/src/skill-state/active-state.ts +2 -2
  264. package/src/skill-state/canonical-skills.ts +1 -1
  265. package/src/skill-state/initial-phase.ts +1 -1
  266. package/src/skill-state/workflow-hud.ts +41 -41
  267. package/src/skill-state/workflow-mutation-guard.ts +26 -7
  268. package/src/slash-commands/builtin-registry.ts +77 -0
  269. package/src/slash-commands/helpers/autorouting-status.ts +55 -0
  270. package/src/system-prompt.ts +102 -5
  271. package/src/task/discovery.ts +2 -3
  272. package/src/task/executor.ts +617 -43
  273. package/src/task/index.ts +262 -15
  274. package/src/task/receipt.ts +30 -0
  275. package/src/task/render.ts +11 -1
  276. package/src/task/types.ts +316 -0
  277. package/src/tools/atomic-file-write.ts +594 -0
  278. package/src/tools/bisect.ts +3 -2
  279. package/src/tools/descriptors.test.ts +3 -0
  280. package/src/tools/descriptors.ts +24 -0
  281. package/src/tools/image-gen.ts +5 -1
  282. package/src/tools/index.ts +62 -5
  283. package/src/tools/move-session.ts +107 -0
  284. package/src/tools/python.ts +205 -0
  285. package/src/tools/read.ts +88 -15
  286. package/src/tools/renderers.ts +2 -0
  287. package/src/tools/resource-gc.ts +8 -177
  288. package/src/tools/search.ts +30 -6
  289. package/src/tools/skill-discovery.ts +1 -0
  290. package/src/tools/skill.ts +37 -7
  291. package/src/tools/subagent-render.ts +119 -13
  292. package/src/tools/subagent.ts +91 -74
  293. package/src/tools/tool-catalog.generated.ts +64 -2
  294. package/src/tools/write.ts +16 -12
  295. package/src/utils/xml-escape.ts +14 -0
  296. package/src/web/search/providers/codex.ts +2 -2
  297. package/src/workflow/workflow-intent-diff.ts +13 -12
  298. package/src/workflow/workflow-intent-report-consumer.ts +6 -6
  299. package/dist/types/autoresearch/helpers.d.ts +0 -24
  300. package/dist/types/autoresearch/index.d.ts +0 -2
  301. package/dist/types/autoresearch/state.d.ts +0 -17
  302. package/dist/types/autoresearch/storage.d.ts +0 -142
  303. package/dist/types/autoresearch/tools/init-experiment.d.ts +0 -31
  304. package/dist/types/autoresearch/tools/log-experiment.d.ts +0 -23
  305. package/dist/types/autoresearch/tools/run-experiment.d.ts +0 -8
  306. package/dist/types/autoresearch/tools/update-notes.d.ts +0 -12
  307. package/dist/types/commands/team.d.ts +0 -24
  308. package/dist/types/gjc-runtime/team-gc.d.ts +0 -7
  309. package/dist/types/gjc-runtime/team-launch.d.ts +0 -61
  310. package/dist/types/gjc-runtime/team-notify.d.ts +0 -45
  311. package/dist/types/gjc-runtime/team-runtime.d.ts +0 -336
  312. package/dist/types/gjc-runtime/team-store.d.ts +0 -168
  313. package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +0 -43
  314. package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +0 -92
  315. package/dist/types/gjc-runtime/team-workers.d.ts +0 -83
  316. package/dist/types/rlm/index.d.ts +0 -47
  317. package/dist/types/rlm/preset.d.ts +0 -23
  318. package/dist/types/rlm/python-tool.d.ts +0 -16
  319. package/src/autoresearch/command-resume.md +0 -14
  320. package/src/autoresearch/index.ts +0 -536
  321. package/src/autoresearch/prompt.md +0 -103
  322. package/src/autoresearch/resume-message.md +0 -10
  323. package/src/autoresearch/state.ts +0 -273
  324. package/src/autoresearch/storage.ts +0 -699
  325. package/src/autoresearch/tools/init-experiment.ts +0 -272
  326. package/src/autoresearch/tools/log-experiment.ts +0 -524
  327. package/src/autoresearch/tools/run-experiment.ts +0 -475
  328. package/src/autoresearch/tools/update-notes.ts +0 -109
  329. package/src/commands/rlm.ts +0 -19
  330. package/src/commands/team.ts +0 -238
  331. package/src/defaults/gjc/skills/deep-interview/auto-research-greenfield.md +0 -42
  332. package/src/defaults/gjc/skills/team/SKILL.md +0 -467
  333. package/src/gjc-runtime/team-gc.ts +0 -49
  334. package/src/gjc-runtime/team-launch.ts +0 -404
  335. package/src/gjc-runtime/team-notify.ts +0 -326
  336. package/src/gjc-runtime/team-runtime.ts +0 -5923
  337. package/src/gjc-runtime/team-store.ts +0 -878
  338. package/src/gjc-runtime/team-worker-heartbeat.ts +0 -149
  339. package/src/gjc-runtime/team-worker-memory-guard.ts +0 -363
  340. package/src/gjc-runtime/team-workers.ts +0 -1123
  341. package/src/prompts/system/rlm-report-command.md +0 -1
  342. package/src/prompts/system/rlm-research.md +0 -23
  343. package/src/rlm/index.ts +0 -365
  344. package/src/rlm/preset.ts +0 -76
  345. package/src/rlm/python-tool.ts +0 -68
@@ -62,8 +62,51 @@ import {
62
62
  writeArtifact,
63
63
  writeGuardedJsonAtomic,
64
64
  } from "./state-writer";
65
+ import {
66
+ categorizeComputerChangePath,
67
+ ciDevChangedPathRows,
68
+ computeCheckpointChangeSet,
69
+ computeUltragoalReviewSourceHash,
70
+ mergeChangeSetPaths,
71
+ normalizeChangeSetPath,
72
+ parseGitNameStatus,
73
+ parseGitUntrackedPaths,
74
+ parseUnifiedDiffPaths,
75
+ resolveGitBase,
76
+ spawnText,
77
+ type UltragoalChangeCategory,
78
+ type UltragoalChangeSet,
79
+ type UltragoalChangeSetPath,
80
+ type UltragoalChangeStatus,
81
+ } from "./ultragoal-change-set";
82
+ import {
83
+ resolveUltragoalValidationApplicability,
84
+ type UltragoalValidationApplicability,
85
+ type UltragoalValidationLane,
86
+ type UltragoalValidationLaneSelection,
87
+ validationLaneSelectionFor,
88
+ } from "./ultragoal-validation-policy";
65
89
  import { resolveWorkflowSetting } from "./workflow-settings";
66
90
 
91
+ export {
92
+ categorizeComputerChangePath,
93
+ ciDevChangedPathRows,
94
+ computeCheckpointChangeSet,
95
+ computeUltragoalReviewSourceHash,
96
+ mergeChangeSetPaths,
97
+ normalizeChangeSetPath,
98
+ normalizeRepoPath,
99
+ parseGitNameStatus,
100
+ parseGitUntrackedPaths,
101
+ parseUnifiedDiffPaths,
102
+ resolveGitBase,
103
+ spawnText,
104
+ type UltragoalChangeCategory,
105
+ type UltragoalChangeSet,
106
+ type UltragoalChangeSetPath,
107
+ type UltragoalChangeStatus,
108
+ } from "./ultragoal-change-set";
109
+
67
110
  export {
68
111
  captureUltragoalRecoverySnapshot,
69
112
  parseStrictTerminalTranscript,
@@ -1526,33 +1569,6 @@ function formatExpectedKindWords(words: string[]): string {
1526
1569
 
1527
1570
  export type SurfaceFamily = "web" | "cli" | "native" | "api-package" | "algorithm-math" | "unknown";
1528
1571
 
1529
- export type UltragoalChangeStatus = "added" | "modified" | "deleted" | "renamed" | "copied" | "unknown";
1530
- export type UltragoalChangeCategory =
1531
- | "code"
1532
- | "generated-binding"
1533
- | "tool"
1534
- | "settings-registry"
1535
- | "prompt-doc-behavior"
1536
- | "docs-static"
1537
- | "other";
1538
- export interface UltragoalChangeSetPath extends JsonObject {
1539
- path: string;
1540
- status: UltragoalChangeStatus;
1541
- oldPath?: string;
1542
- category?: UltragoalChangeCategory;
1543
- }
1544
- export interface UltragoalChangeSet extends JsonObject {
1545
- source: "checkpoint-git" | "review-pr" | "review-branch" | "review-worktree" | "review-spec";
1546
- baseRef?: string;
1547
- headRef?: string;
1548
- mergeBase?: string;
1549
- paths: UltragoalChangeSetPath[];
1550
- rawDiffStat?: string;
1551
- rawDiff?: string;
1552
- captureIncomplete?: boolean;
1553
- trusted: true;
1554
- }
1555
-
1556
1572
  const MANDATORY_COMPUTER_CASE_IDS = [
1557
1573
  "kill-switch-bypass",
1558
1574
  "suspended-enforcement",
@@ -1562,40 +1578,6 @@ const MANDATORY_COMPUTER_CASE_IDS = [
1562
1578
  "runaway-loop-halt",
1563
1579
  "blast-radius",
1564
1580
  ] as const;
1565
- const TOOLS_INDEX_PATH = "packages/coding-agent/src/tools/index.ts";
1566
-
1567
- export function normalizeRepoPath(value: string): string {
1568
- return value.replaceAll("\\\\", "/").replace(/^\.\//, "");
1569
- }
1570
-
1571
- export function normalizeChangeSetPath(value: string): string {
1572
- return value.replace(/^\.\//, "");
1573
- }
1574
-
1575
- export function categorizeComputerChangePath(value: string): UltragoalChangeCategory {
1576
- const normalized = normalizeRepoPath(value);
1577
- if (normalized.startsWith("crates/pi-natives/src/computer/")) return "code";
1578
- if (/^packages\/natives\/native\/index\.(?:d\.ts|js)$/.test(normalized)) return "generated-binding";
1579
- if (
1580
- normalized === "packages/coding-agent/src/tools/computer.ts" ||
1581
- normalized.startsWith("packages/coding-agent/src/tools/computer/")
1582
- )
1583
- return "tool";
1584
- if (
1585
- normalized === TOOLS_INDEX_PATH ||
1586
- normalized === "packages/coding-agent/src/tools/renderers.ts" ||
1587
- normalized === "packages/coding-agent/src/config/settings-schema.ts"
1588
- )
1589
- return "settings-registry";
1590
- if (
1591
- normalized === "packages/coding-agent/src/prompts/tools/computer.md" ||
1592
- normalized === "packages/coding-agent/src/defaults/gjc/skills/ultragoal/SKILL.md" ||
1593
- normalized === "packages/coding-agent/src/prompts/agents/executor.md"
1594
- )
1595
- return "prompt-doc-behavior";
1596
- if (normalized === "docs/tools/computer.md" || normalized === "docs/computer-use/README.md") return "docs-static";
1597
- return "other";
1598
- }
1599
1581
 
1600
1582
  function isComputerControlSurfaceCategory(category: UltragoalChangeCategory): boolean {
1601
1583
  // Shared behavior registries are intentionally conservative: a path-only or
@@ -2500,6 +2482,98 @@ function validateDeferredCompletionQualityGate(
2500
2482
  "deferredToBatch.changeSet.changeSetHash does not match declared paths; omit changeSetHash and the runtime computes it",
2501
2483
  );
2502
2484
  }
2485
+ /** #4560: declared lane-selection proof shape on the quality gate. */
2486
+ type DeclaredValidationLaneSelection = UltragoalValidationLaneSelection;
2487
+
2488
+ function readDeclaredValidationLaneSelection(gate: JsonObject): DeclaredValidationLaneSelection | undefined {
2489
+ const declared = qualityGateObject(gate.validationLaneSelection);
2490
+ if (!declared) return undefined;
2491
+ const riskClass = nonEmptyString(declared.riskClass);
2492
+ const reasons = stringArray(declared.reasons);
2493
+ const omittedLanes = stringArray(declared.omittedLanes);
2494
+ if (!riskClass || !reasons || !omittedLanes) {
2495
+ throw new Error(
2496
+ "validationLaneSelection must carry riskClass, reasons, and omittedLanes string arrays mirroring the runtime selection",
2497
+ );
2498
+ }
2499
+ if (riskClass !== "low" && riskClass !== "high")
2500
+ throw new Error('validationLaneSelection.riskClass must be "low" or "high"');
2501
+ if (omittedLanes.some(lane => !["cleaner", "architect", "qa", "terminal-critic"].includes(lane)))
2502
+ throw new Error("validationLaneSelection.omittedLanes contains an unsupported lane");
2503
+ return { riskClass, reasons, omittedLanes: omittedLanes as UltragoalValidationLane[] };
2504
+ }
2505
+
2506
+ /**
2507
+ * #4560: validate a declared low-risk lane reduction against the
2508
+ * runtime-computed applicability. The runtime selection is authoritative: a
2509
+ * declaration that disagrees with the computed risk class, reasons, or
2510
+ * omitted-lane set fails closed and the full cohort stays mandatory.
2511
+ */
2512
+ function validateDeclaredValidationLaneSelection(
2513
+ declared: DeclaredValidationLaneSelection,
2514
+ applicability: UltragoalValidationApplicability,
2515
+ found: QualityGateDiagnostics,
2516
+ ): boolean {
2517
+ if (declared.riskClass !== applicability.riskClass) {
2518
+ found.add(
2519
+ "validationLaneSelection",
2520
+ "selection_mismatch",
2521
+ "declared validationLaneSelection riskClass does not match the runtime-computed risk class",
2522
+ );
2523
+ return false;
2524
+ }
2525
+ const expected = validationLaneSelectionFor(applicability);
2526
+ if (
2527
+ declared.reasons.length !== expected.reasons.length ||
2528
+ declared.reasons.some((reason, index) => reason !== expected.reasons[index])
2529
+ ) {
2530
+ found.add(
2531
+ "validationLaneSelection.reasons",
2532
+ "reasons_mismatch",
2533
+ "declared validationLaneSelection reasons must exactly mirror the runtime-computed selection basis",
2534
+ );
2535
+ return false;
2536
+ }
2537
+ const expectedOmitted = expected.omittedLanes;
2538
+ const declaredOmitted = new Set(declared.omittedLanes);
2539
+ if (declaredOmitted.has("qa")) {
2540
+ found.add(
2541
+ "validationLaneSelection",
2542
+ "qa_lane_mandatory",
2543
+ "validationLaneSelection can never omit the qa lane; verification is mandatory at every boundary",
2544
+ );
2545
+ return false;
2546
+ }
2547
+ if (declaredOmitted.size !== expectedOmitted.length || expectedOmitted.some(lane => !declaredOmitted.has(lane))) {
2548
+ found.add(
2549
+ "validationLaneSelection",
2550
+ "omitted_lanes_mismatch",
2551
+ "declared validationLaneSelection omittedLanes must exactly mirror the runtime-computed inapplicable lanes",
2552
+ );
2553
+ return false;
2554
+ }
2555
+ return applicability.riskClass === "low";
2556
+ }
2557
+
2558
+ /** #4560: newest joined cohort (generation + frozen sourceHash) in the ledger. */
2559
+ function latestJoinedCohortSourceHash(
2560
+ ledger: readonly UltragoalLedgerEvent[],
2561
+ ): { reviewGeneration: number; sourceHash: string } | undefined {
2562
+ let latest: { reviewGeneration: number; sourceHash: string } | undefined;
2563
+ for (const event of ledger) {
2564
+ if (event.event !== "goal_checkpointed" || event.status !== "complete") continue;
2565
+ const cohort = qualityGateObject(
2566
+ qualityGateObject(event.qualityGateJson)?.iteration as JsonObject | undefined,
2567
+ )?.reviewCohort;
2568
+ const record = qualityGateObject(cohort);
2569
+ if (!record) continue;
2570
+ const reviewGeneration = record.reviewGeneration;
2571
+ const sourceHash = nonEmptyString(record.sourceHash);
2572
+ if (typeof reviewGeneration !== "number" || !sourceHash) continue;
2573
+ if (!latest || reviewGeneration >= latest.reviewGeneration) latest = { reviewGeneration, sourceHash };
2574
+ }
2575
+ return latest;
2576
+ }
2503
2577
  const COHORT_LANE_KEYS = ["cleaner", "architect", "qa"] as const;
2504
2578
 
2505
2579
  /**
@@ -2509,7 +2583,11 @@ const COHORT_LANE_KEYS = ["cleaner", "architect", "qa"] as const;
2509
2583
  * generations are delta-only. Cohort state rides the existing `iteration` gate key so
2510
2584
  * no new top-level quality-gate key is introduced.
2511
2585
  */
2512
- function validateReviewCohort(gate: JsonObject, iteration: JsonObject): void {
2586
+ function validateReviewCohort(
2587
+ gate: JsonObject,
2588
+ iteration: JsonObject,
2589
+ options: { lowRiskReduced?: boolean } = {},
2590
+ ): void {
2513
2591
  const cohort = qualityGateObject(iteration.reviewCohort);
2514
2592
  if (!cohort) throw new Error("qualityGate iteration.reviewCohort is required at the review boundary");
2515
2593
  const generation = cohort.reviewGeneration;
@@ -2524,7 +2602,15 @@ function validateReviewCohort(gate: JsonObject, iteration: JsonObject): void {
2524
2602
  const unsupportedLanes = Object.keys(lanes).filter(key => !(COHORT_LANE_KEYS as readonly string[]).includes(key));
2525
2603
  if (unsupportedLanes.length > 0)
2526
2604
  throw new Error(`iteration.reviewCohort.lanes contains unsupported lanes: ${unsupportedLanes.join(", ")}`);
2527
- for (const lane of COHORT_LANE_KEYS) {
2605
+ // #4560: on a runtime-selected low-risk single-goal boundary, cleaner and
2606
+ // architect may be omitted only through the deterministic
2607
+ // validationLaneSelection proof validated by the caller; the QA lane and
2608
+ // the frozen source hash stay mandatory, and cohort parallelism is
2609
+ // untouched whenever lanes do run.
2610
+ const requiredLanes: readonly (typeof COHORT_LANE_KEYS)[number][] = options.lowRiskReduced
2611
+ ? COHORT_LANE_KEYS.filter(lane => lane === "qa")
2612
+ : [...COHORT_LANE_KEYS];
2613
+ for (const lane of requiredLanes) {
2528
2614
  if (Array.isArray(lanes[lane]))
2529
2615
  throw new Error(`iteration.reviewCohort.lanes.${lane} must be one lane per generation, not a list`);
2530
2616
  const record = qualityGateObject(lanes[lane]);
@@ -2618,6 +2704,7 @@ async function validateCompletionQualityGate(
2618
2704
  "executorQa",
2619
2705
  "iteration",
2620
2706
  "validationBatchClose",
2707
+ "validationLaneSelection",
2621
2708
  "criticReview",
2622
2709
  ]);
2623
2710
  const unsupportedKeys = Object.keys(gate).filter(key => !allowedKeys.has(key));
@@ -2643,8 +2730,15 @@ async function validateCompletionQualityGate(
2643
2730
  }
2644
2731
  const allowedKeys = new Set(
2645
2732
  batchMode
2646
- ? ["architectReview", "executorQa", "iteration", "validationBatchClose", "criticReview"]
2647
- : ["architectReview", "executorQa", "iteration", "criticReview"],
2733
+ ? [
2734
+ "architectReview",
2735
+ "executorQa",
2736
+ "iteration",
2737
+ "validationBatchClose",
2738
+ "criticReview",
2739
+ "validationLaneSelection",
2740
+ ]
2741
+ : ["architectReview", "executorQa", "iteration", "criticReview", "validationLaneSelection"],
2648
2742
  );
2649
2743
  const unsupportedKeys = Object.keys(gate).filter(key => !allowedKeys.has(key));
2650
2744
  if (unsupportedKeys.length > 0) {
@@ -2654,14 +2748,52 @@ async function validateCompletionQualityGate(
2654
2748
  `qualityGate contains unsupported keys: ${unsupportedKeys.join(", ")}`,
2655
2749
  );
2656
2750
  }
2751
+ // #4560: deterministic risk/applicability selection for expensive boundary
2752
+ // lanes. The runtime — never free-form model prose — decides whether a
2753
+ // low-risk single-goal boundary may omit redundant review ceremony
2754
+ // (cleaner/architect/terminal-critic). QA/targeted verification and the
2755
+ // hash/receipt/join guarantees always remain mandatory. Anything risky or
2756
+ // unprovable keeps today's full heavyweight cohort unchanged. The current
2757
+ // frozen source under review is the gate's own cohort sourceHash.
2758
+ const gateIteration = qualityGateObject(gate.iteration);
2759
+ const gateCohortSourceHash = nonEmptyString(qualityGateObject(gateIteration?.reviewCohort)?.sourceHash);
2760
+ const authoritativeSourceHash = computeUltragoalReviewSourceHash(options.changeSet);
2761
+ if (gateCohortSourceHash && authoritativeSourceHash && gateCohortSourceHash !== authoritativeSourceHash) {
2762
+ found.add(
2763
+ "iteration.reviewCohort.sourceHash",
2764
+ "source_hash_mismatch",
2765
+ "iteration.reviewCohort.sourceHash must equal the runtime-computed digest of the authoritative current source basis",
2766
+ );
2767
+ }
2768
+ const applicability = resolveUltragoalValidationApplicability({
2769
+ changeSet: options.changeSet,
2770
+ requiredGoals: options.plan?.goals.filter(goal => goal.status !== "superseded").length,
2771
+ hasOpenReviewBlockers: options.plan?.goals.some(goal => goal.status === "review_blocked") ?? false,
2772
+ latestCohortSourceHash: options.ledger ? latestJoinedCohortSourceHash(options.ledger)?.sourceHash : undefined,
2773
+ currentSourceHash: gateCohortSourceHash ?? undefined,
2774
+ authoritativeSourceHash,
2775
+ });
2776
+ let laneSelection: DeclaredValidationLaneSelection | undefined;
2777
+ found.check("validationLaneSelection", "selection_invalid", () => {
2778
+ laneSelection = readDeclaredValidationLaneSelection(gate);
2779
+ });
2780
+ const lowRiskReduced =
2781
+ laneSelection !== undefined && validateDeclaredValidationLaneSelection(laneSelection, applicability, found);
2657
2782
  const architectReview = qualityGateObject(gate.architectReview);
2658
2783
  const executorQa = qualityGateObject(gate.executorQa);
2659
2784
  const iteration = qualityGateObject(gate.iteration);
2660
- if (!architectReview || !executorQa || !iteration) {
2785
+ // #4560: when the runtime-verified selection omits the architect lane, the
2786
+ // boundary must not still demand architect evidence \u2014 otherwise the advertised
2787
+ // omission is unusable and the caller has to run or fabricate the lane anyway.
2788
+ // QA and iteration evidence stay mandatory in every case.
2789
+ const architectRequired = !lowRiskReduced || applicability.lanes.architect.applicable;
2790
+ if ((architectRequired && !architectReview) || !executorQa || !iteration) {
2661
2791
  found.add(
2662
2792
  "qualityGate",
2663
2793
  "missing_required_sections",
2664
- "qualityGate requires architectReview, executorQa, and iteration objects",
2794
+ architectRequired
2795
+ ? "qualityGate requires architectReview, executorQa, and iteration objects"
2796
+ : "qualityGate requires executorQa and iteration objects",
2665
2797
  );
2666
2798
  found.throwIfAny();
2667
2799
  return;
@@ -2678,8 +2810,16 @@ async function validateCompletionQualityGate(
2678
2810
  "checkpoint --status complete blocked: terminal-critic ceiling reached; requires human/leader gjc ultragoal record-critic-gate-override before completion",
2679
2811
  );
2680
2812
  }
2813
+ // #4560: terminal-critic proportionality. The critic verdict is
2814
+ // mandatory on multi-goal/boundary, high-risk, or evidence-uncertain
2815
+ // runs. A single-goal low-risk run whose joined cohort is clean and
2816
+ // whose immutable source basis is unchanged may satisfy the terminus
2817
+ // through the runtime-verified validationLaneSelection proof instead
2818
+ // of duplicating the already-joined review with another read pass.
2681
2819
  const criticReview = qualityGateObject(gate.criticReview);
2682
- if (criticReview?.verdict !== "OKAY") {
2820
+ const criticProportionallySatisfied =
2821
+ lowRiskReduced && applicability.basisUnchanged && !applicability.hasOpenReviewBlockers;
2822
+ if (criticReview?.verdict !== "OKAY" && !criticProportionallySatisfied) {
2683
2823
  found.add(
2684
2824
  "criticReview.verdict",
2685
2825
  "critic_verdict_not_okay",
@@ -2695,31 +2835,33 @@ async function validateCompletionQualityGate(
2695
2835
  );
2696
2836
  }
2697
2837
  }
2698
- if (
2699
- architectReview.architectureStatus !== CLEAN_ARCHITECT_STATUS ||
2700
- architectReview.productStatus !== CLEAN_ARCHITECT_STATUS ||
2701
- architectReview.codeStatus !== CLEAN_ARCHITECT_STATUS ||
2702
- architectReview.recommendation !== APPROVE_RECOMMENDATION
2703
- ) {
2704
- found.add(
2705
- "architectReview",
2706
- "architect_not_clear",
2707
- "checkpoint --status complete requires architect review approval: architectReview architecture/product/code must be CLEAR and recommendation must be APPROVE",
2838
+ if (architectReview) {
2839
+ if (
2840
+ architectReview.architectureStatus !== CLEAN_ARCHITECT_STATUS ||
2841
+ architectReview.productStatus !== CLEAN_ARCHITECT_STATUS ||
2842
+ architectReview.codeStatus !== CLEAN_ARCHITECT_STATUS ||
2843
+ architectReview.recommendation !== APPROVE_RECOMMENDATION
2844
+ ) {
2845
+ found.add(
2846
+ "architectReview",
2847
+ "architect_not_clear",
2848
+ "checkpoint --status complete requires architect review approval: architectReview architecture/product/code must be CLEAR and recommendation must be APPROVE",
2849
+ );
2850
+ }
2851
+ if (!nonEmptyStringArray(architectReview.commands)) {
2852
+ found.add(
2853
+ "architectReview.commands",
2854
+ "missing_command_array",
2855
+ "qualityGate architectReview.commands must be a non-empty string array",
2856
+ );
2857
+ }
2858
+ found.check("architectReview.evidence", "missing_evidence", () =>
2859
+ requireNonEmptyString(architectReview.evidence, "architectReview.evidence"),
2708
2860
  );
2709
- }
2710
- if (!nonEmptyStringArray(architectReview.commands)) {
2711
- found.add(
2712
- "architectReview.commands",
2713
- "missing_command_array",
2714
- "qualityGate architectReview.commands must be a non-empty string array",
2861
+ found.check("architectReview.blockers", "non_empty_blockers", () =>
2862
+ requireEmptyBlockers(architectReview.blockers, "architectReview.blockers"),
2715
2863
  );
2716
2864
  }
2717
- found.check("architectReview.evidence", "missing_evidence", () =>
2718
- requireNonEmptyString(architectReview.evidence, "architectReview.evidence"),
2719
- );
2720
- found.check("architectReview.blockers", "non_empty_blockers", () =>
2721
- requireEmptyBlockers(architectReview.blockers, "architectReview.blockers"),
2722
- );
2723
2865
  if (
2724
2866
  executorQa.status !== PASSED_STATUS ||
2725
2867
  executorQa.e2eStatus !== PASSED_STATUS ||
@@ -2763,7 +2905,9 @@ async function validateCompletionQualityGate(
2763
2905
  found.check("iteration.blockers", "non_empty_blockers", () =>
2764
2906
  requireEmptyBlockers(iteration.blockers, "iteration.blockers"),
2765
2907
  );
2766
- found.check("iteration.reviewCohort", "review_cohort_invalid", () => validateReviewCohort(gate, iteration));
2908
+ found.check("iteration.reviewCohort", "review_cohort_invalid", () =>
2909
+ validateReviewCohort(gate, iteration, { lowRiskReduced }),
2910
+ );
2767
2911
  if (batchMode && options.goal && options.plan && options.ledger) {
2768
2912
  found.check("validationBatchClose", "batch_close_invalid", () =>
2769
2913
  validateBatchCloseQualityGate(gate, options.plan!, batchMode, options.ledger!, options.changeSet),
@@ -3483,6 +3627,24 @@ export async function checkpointUltragoalGoal(input: {
3483
3627
  : input.qualityGateJson
3484
3628
  ? await readStructuredValue(input.cwd, input.qualityGateJson)
3485
3629
  : undefined;
3630
+ if (input.status === "complete" && !staleCompleteReceiptReplay) {
3631
+ // Validation consumes an asynchronous source snapshot. Re-capture directly
3632
+ // after it returns and require the authoritative digest to remain identical;
3633
+ // otherwise a concurrent edit could be committed against bytes that were not
3634
+ // the reviewed basis.
3635
+ const finalChangeSet = await computeCheckpointChangeSet(input.cwd);
3636
+ const initialSourceHash = computeUltragoalReviewSourceHash(changeSet);
3637
+ const finalSourceHash = computeUltragoalReviewSourceHash(finalChangeSet);
3638
+ if (
3639
+ ((initialSourceHash || finalSourceHash) && initialSourceHash !== finalSourceHash) ||
3640
+ changeSet?.captureIncomplete ||
3641
+ finalChangeSet?.captureIncomplete
3642
+ ) {
3643
+ throw new Error(
3644
+ "completion source changed or could not be captured consistently during validation; retry the checkpoint against a stable repository state",
3645
+ );
3646
+ }
3647
+ }
3486
3648
  const now = new Date().toISOString();
3487
3649
  const beforeStatus = goal.status;
3488
3650
  if (input.status === "complete") {
@@ -4212,28 +4374,6 @@ async function readOptionalExecutorQa(cwd: string, value: string | undefined): P
4212
4374
  return structured as JsonObject;
4213
4375
  }
4214
4376
 
4215
- import {
4216
- ciDevChangedPathRows,
4217
- computeCheckpointChangeSet,
4218
- mergeChangeSetPaths,
4219
- parseGitNameStatus,
4220
- parseGitUntrackedPaths,
4221
- parseUnifiedDiffPaths,
4222
- resolveGitBase,
4223
- spawnText,
4224
- } from "./ultragoal-change-set";
4225
-
4226
- export {
4227
- ciDevChangedPathRows,
4228
- computeCheckpointChangeSet,
4229
- mergeChangeSetPaths,
4230
- parseGitNameStatus,
4231
- parseGitUntrackedPaths,
4232
- parseUnifiedDiffPaths,
4233
- resolveGitBase,
4234
- spawnText,
4235
- };
4236
-
4237
4377
  function changeSetFromReviewSource(source: JsonObject): UltragoalChangeSet | undefined {
4238
4378
  const kind = nonEmptyString(source.kind);
4239
4379
  if (kind === "spec") {
@@ -4722,6 +4862,7 @@ function renderUltragoalHelp(args: readonly string[]): string | null {
4722
4862
  "",
4723
4863
  "USAGE",
4724
4864
  " $ gjc ultragoal quality-gate init [--surface <name> ...] --out <path>",
4865
+ " $ gjc ultragoal quality-gate source-hash [--json]",
4725
4866
  " $ gjc ultragoal quality-gate validate --quality-gate-json <json-or-path> [--goal-id <id>] [--json]",
4726
4867
  "",
4727
4868
  "FLAGS",
@@ -4733,6 +4874,7 @@ function renderUltragoalHelp(args: readonly string[]): string | null {
4733
4874
  "",
4734
4875
  "EXAMPLES",
4735
4876
  " $ gjc ultragoal quality-gate init --surface web --surface api --out ./quality-gate.json",
4877
+ " $ gjc ultragoal quality-gate source-hash --json",
4736
4878
  " $ gjc ultragoal quality-gate validate --quality-gate-json ./quality-gate.json --json",
4737
4879
  "",
4738
4880
  ].join("\n");
@@ -4756,6 +4898,8 @@ function renderUltragoalHelp(args: readonly string[]): string | null {
4756
4898
  " record-critic-verdict",
4757
4899
  " record-critic-gate-override",
4758
4900
  " quality-gate init",
4901
+ " quality-gate source-hash",
4902
+ " quality-gate lane-selection",
4759
4903
  " quality-gate validate",
4760
4904
 
4761
4905
  "",
@@ -5141,6 +5285,57 @@ async function dispatchUltragoalCommand(
5141
5285
  case "quality-gate": {
5142
5286
  const positional = args.filter(arg => !arg.startsWith("-"));
5143
5287
  const subcommand = positional[1];
5288
+ if (subcommand === "source-hash") {
5289
+ const changeSet = await computeCheckpointChangeSet(cwd);
5290
+ const sourceHash = computeUltragoalReviewSourceHash(changeSet);
5291
+ if (!sourceHash) {
5292
+ return {
5293
+ status: 1,
5294
+ stderr:
5295
+ "Unable to compute an authoritative source hash: change-set capture is incomplete, untrusted, or contains unknown-status paths.\n",
5296
+ };
5297
+ }
5298
+ const payload = {
5299
+ sourceHash,
5300
+ baseRef: changeSet?.baseRef,
5301
+ mergeBase: changeSet?.mergeBase,
5302
+ headRef: changeSet?.headRef,
5303
+ pathCount: changeSet?.paths.length ?? 0,
5304
+ };
5305
+ return json
5306
+ ? { status: 0, stdout: `${JSON.stringify(payload, null, 2)}\n` }
5307
+ : { status: 0, stdout: `${sourceHash}\n` };
5308
+ }
5309
+ if (subcommand === "lane-selection") {
5310
+ // #4560: expose the authoritative computed selection so a caller can
5311
+ // declare `validationLaneSelection` without running or fabricating the
5312
+ // very lanes the selection may omit.
5313
+ const sessionId = currentUltragoalSessionId(cwd);
5314
+ const changeSet = await computeCheckpointChangeSet(cwd);
5315
+ const plan = await readUltragoalPlan(cwd, sessionId).catch(() => null);
5316
+ const ledger = await readUltragoalLedger(cwd, sessionId).catch(() => null);
5317
+ const authoritativeSourceHash = computeUltragoalReviewSourceHash(changeSet);
5318
+ const applicability = resolveUltragoalValidationApplicability({
5319
+ changeSet,
5320
+ requiredGoals: plan?.goals.filter(goal => goal.status !== "superseded").length,
5321
+ hasOpenReviewBlockers: plan?.goals.some(goal => goal.status === "review_blocked") ?? false,
5322
+ latestCohortSourceHash: ledger ? latestJoinedCohortSourceHash(ledger)?.sourceHash : undefined,
5323
+ currentSourceHash: authoritativeSourceHash ?? undefined,
5324
+ authoritativeSourceHash,
5325
+ });
5326
+ const laneSelection = validationLaneSelectionFor(applicability);
5327
+ const payload = {
5328
+ validationLaneSelection: laneSelection,
5329
+ basisUnchanged: applicability.basisUnchanged,
5330
+ sourceHash: authoritativeSourceHash,
5331
+ };
5332
+ return json
5333
+ ? { status: 0, stdout: `${JSON.stringify(payload, null, 2)}\n` }
5334
+ : {
5335
+ status: 0,
5336
+ stdout: `riskClass=${applicability.riskClass} omittedLanes=${laneSelection.omittedLanes.join(",") || "(none)"}\n`,
5337
+ };
5338
+ }
5144
5339
  if (subcommand === "init") {
5145
5340
  const out = flagValue(args, "--out");
5146
5341
  if (!out?.trim()) {
@@ -5164,7 +5359,7 @@ async function dispatchUltragoalCommand(
5164
5359
  if (subcommand !== "validate") {
5165
5360
  return {
5166
5361
  status: 1,
5167
- stderr: `Unknown gjc ultragoal quality-gate subcommand: ${subcommand ?? "(missing)"}; supported: init, validate\n`,
5362
+ stderr: `Unknown gjc ultragoal quality-gate subcommand: ${subcommand ?? "(missing)"}; supported: init, source-hash, lane-selection, validate\n`,
5168
5363
  };
5169
5364
  }
5170
5365
  const qualityGateJson = flagValue(args, "--quality-gate-json");