@gajae-code/coding-agent 0.11.6 → 0.11.8

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 (259) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/dist/types/capability/index.d.ts +10 -20
  3. package/dist/types/capability/types.d.ts +3 -0
  4. package/dist/types/cli/args.d.ts +0 -2
  5. package/dist/types/commands/daemon.d.ts +2 -2
  6. package/dist/types/commands/sdk.d.ts +20 -2
  7. package/dist/types/config/keybindings.d.ts +34 -4
  8. package/dist/types/config/model-profile-activation.d.ts +4 -3
  9. package/dist/types/config/model-profile-contract.d.ts +38 -0
  10. package/dist/types/config/settings-schema.d.ts +70 -7
  11. package/dist/types/edit/modes/patch.d.ts +13 -0
  12. package/dist/types/edit/path-mutation-lock.d.ts +15 -0
  13. package/dist/types/eval/py/executor.d.ts +1 -1
  14. package/dist/types/eval/py/runner-artifact.d.ts +8 -0
  15. package/dist/types/eval/py/tool-bridge.d.ts +1 -2
  16. package/dist/types/gjc-runtime/repository-binding.d.ts +63 -0
  17. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +6 -5
  18. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +20 -0
  19. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +43 -5
  20. package/dist/types/main.d.ts +32 -1
  21. package/dist/types/modes/components/btw-panel.d.ts +1 -0
  22. package/dist/types/modes/components/model-selector.d.ts +2 -0
  23. package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +2 -1
  24. package/dist/types/modes/components/tool-status-header.d.ts +4 -2
  25. package/dist/types/modes/components/welcome.d.ts +2 -0
  26. package/dist/types/modes/controllers/btw-controller.d.ts +5 -0
  27. package/dist/types/modes/controllers/command-controller.d.ts +2 -0
  28. package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
  29. package/dist/types/modes/interactive-mode.d.ts +7 -2
  30. package/dist/types/modes/prompt-action-autocomplete.d.ts +6 -1
  31. package/dist/types/modes/prompt-suggestion-controller.d.ts +35 -0
  32. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +2 -2
  33. package/dist/types/modes/types.d.ts +4 -0
  34. package/dist/types/modes/utils/hotkeys-markdown.d.ts +2 -1
  35. package/dist/types/runtime-mcp/content-limits.d.ts +7 -0
  36. package/dist/types/runtime-mcp/manager.d.ts +1 -1
  37. package/dist/types/runtime-mcp/oauth-flow.d.ts +0 -5
  38. package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +14 -0
  39. package/dist/types/sdk/broker/broker.d.ts +2 -0
  40. package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
  41. package/dist/types/sdk/bus/chat-daemon-control.d.ts +6 -1
  42. package/dist/types/sdk/bus/config-commands.d.ts +2 -2
  43. package/dist/types/sdk/bus/config.d.ts +2 -0
  44. package/dist/types/sdk/bus/conversation-store.d.ts +1 -0
  45. package/dist/types/sdk/bus/index.d.ts +26 -1
  46. package/dist/types/sdk/bus/lifecycle-commands.d.ts +5 -4
  47. package/dist/types/sdk/bus/notification-orchestration.d.ts +1 -0
  48. package/dist/types/sdk/bus/notification-service.d.ts +34 -0
  49. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +105 -0
  50. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +18 -3
  51. package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -1
  52. package/dist/types/sdk/bus/telegram-daemon.d.ts +189 -4
  53. package/dist/types/sdk/bus/telegram-reference.d.ts +2 -0
  54. package/dist/types/sdk/bus/topic-registry.d.ts +88 -2
  55. package/dist/types/sdk/client/discovery.d.ts +2 -0
  56. package/dist/types/sdk/client/liveness.d.ts +7 -0
  57. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  58. package/dist/types/sdk/host/host.d.ts +1 -1
  59. package/dist/types/sdk/host/query/handlers.d.ts +9 -0
  60. package/dist/types/sdk/index.d.ts +2 -0
  61. package/dist/types/sdk/prompt-status.d.ts +88 -0
  62. package/dist/types/sdk/session-directory.d.ts +1 -1
  63. package/dist/types/sdk/startup-capability.d.ts +3 -0
  64. package/dist/types/sdk/transport/auth-preface.d.ts +9 -0
  65. package/dist/types/sdk/transport/index.d.ts +12 -0
  66. package/dist/types/sdk/transport/relay.d.ts +43 -0
  67. package/dist/types/sdk/transport/serve-cli.d.ts +4 -0
  68. package/dist/types/sdk/transport/socket.d.ts +7 -0
  69. package/dist/types/sdk/transport/stdio.d.ts +3 -0
  70. package/dist/types/session/agent-session.d.ts +30 -5
  71. package/dist/types/session/btw-contract.d.ts +16 -0
  72. package/dist/types/session/internal/managed-session-scope.d.ts +32 -9
  73. package/dist/types/session/internal/managed-session-storage.d.ts +27 -10
  74. package/dist/types/session/internal/native-publish-outcome.d.ts +48 -0
  75. package/dist/types/session/session-manager.d.ts +25 -4
  76. package/dist/types/session/session-storage.d.ts +18 -0
  77. package/dist/types/setup/credential-import.d.ts +5 -1
  78. package/dist/types/setup/provider-onboarding.d.ts +2 -0
  79. package/dist/types/ssh/utils.d.ts +1 -0
  80. package/dist/types/task/discovery.d.ts +3 -1
  81. package/dist/types/task/index.d.ts +3 -1
  82. package/dist/types/task/receipt.d.ts +2 -0
  83. package/dist/types/task/types.d.ts +94 -0
  84. package/dist/types/tools/ask.d.ts +454 -174
  85. package/dist/types/tools/fetch.d.ts +5 -8
  86. package/dist/types/tools/image-gen.d.ts +63 -1
  87. package/dist/types/tools/index.d.ts +4 -0
  88. package/dist/types/tools/skill-discovery.d.ts +7 -0
  89. package/dist/types/tools/sqlite-reader.d.ts +7 -0
  90. package/dist/types/utils/prompt-suggestion.d.ts +33 -0
  91. package/dist/types/utils/shell-snapshot.d.ts +12 -0
  92. package/dist/types/web/insane/bridge.d.ts +1 -0
  93. package/dist/types/web/insane/url-guard.d.ts +17 -0
  94. package/package.json +8 -8
  95. package/scripts/capture-platform-shortcut-labels-showcase.ts +342 -0
  96. package/scripts/dogfood-repository-binding.ts +209 -0
  97. package/scripts/generate-sdk-operation-inventory.ts +4 -0
  98. package/scripts/run-test-manifest.ts +4 -4
  99. package/src/capability/index.ts +50 -42
  100. package/src/capability/types.ts +3 -0
  101. package/src/cli/args.ts +2 -92
  102. package/src/cli/notify-cli.ts +5 -38
  103. package/src/cli/setup-cli.ts +10 -4
  104. package/src/cli.ts +52 -9
  105. package/src/commands/daemon.ts +22 -5
  106. package/src/commands/sdk.ts +44 -11
  107. package/src/config/keybindings.ts +158 -25
  108. package/src/config/model-profile-activation.ts +7 -18
  109. package/src/config/model-profile-contract.ts +161 -0
  110. package/src/config/model-profiles.ts +18 -0
  111. package/src/config/model-registry.ts +11 -1
  112. package/src/config/settings-schema.ts +73 -7
  113. package/src/coordinator-mcp/model-preset.ts +21 -74
  114. package/src/defaults/gjc/skills/deep-interview/SKILL.md +2 -2
  115. package/src/defaults/gjc/skills/ralplan/SKILL.md +3 -1
  116. package/src/defaults/gjc/skills/ultragoal/SKILL.md +42 -1
  117. package/src/discovery/ssh.ts +12 -1
  118. package/src/edit/index.ts +3 -6
  119. package/src/edit/modes/patch.ts +38 -1
  120. package/src/edit/modes/replace.ts +42 -0
  121. package/src/edit/path-mutation-lock.ts +112 -0
  122. package/src/eval/py/executor.ts +30 -11
  123. package/src/eval/py/kernel.ts +1 -21
  124. package/src/eval/py/prelude.py +2 -2
  125. package/src/eval/py/runner-artifact.ts +84 -0
  126. package/src/eval/py/tool-bridge.ts +34 -18
  127. package/src/extensibility/gjc-plugins/runtime-adapters.ts +2 -1
  128. package/src/extensibility/plugins/marketplace/fetcher.ts +191 -7
  129. package/src/gjc-runtime/launch-tmux.ts +1 -1
  130. package/src/gjc-runtime/managed-owner-supervisor.ts +83 -4
  131. package/src/gjc-runtime/ralplan-runtime.ts +75 -3
  132. package/src/gjc-runtime/repository-binding.ts +267 -0
  133. package/src/gjc-runtime/session-state-sidecar.ts +25 -9
  134. package/src/gjc-runtime/tmux-owner-isolation.ts +2 -1
  135. package/src/gjc-runtime/tmux-sessions.ts +45 -6
  136. package/src/gjc-runtime/ultragoal-guard.ts +93 -15
  137. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +103 -0
  138. package/src/gjc-runtime/ultragoal-runtime.ts +456 -31
  139. package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
  140. package/src/gjc-runtime/workflow-manifest.ts +23 -0
  141. package/src/internal-urls/docs-index.generated.ts +12 -10
  142. package/src/internal-urls/local-protocol.ts +64 -21
  143. package/src/internal-urls/mcp-protocol.ts +73 -11
  144. package/src/main.ts +111 -37
  145. package/src/modes/DESIGN.md +5 -0
  146. package/src/modes/acp/acp-agent.ts +50 -16
  147. package/src/modes/action-registry.ts +1 -1
  148. package/src/modes/components/btw-panel.ts +82 -16
  149. package/src/modes/components/model-selector.ts +73 -11
  150. package/src/modes/components/plan-preview-overlay.ts +1 -1
  151. package/src/modes/components/runtime-mcp-add-wizard.ts +5 -0
  152. package/src/modes/components/tool-status-header.ts +25 -4
  153. package/src/modes/components/welcome.ts +22 -18
  154. package/src/modes/controllers/btw-controller.ts +119 -36
  155. package/src/modes/controllers/command-controller.ts +97 -54
  156. package/src/modes/controllers/event-controller.ts +10 -0
  157. package/src/modes/controllers/input-controller.ts +38 -2
  158. package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -6
  159. package/src/modes/controllers/selector-controller.ts +149 -63
  160. package/src/modes/interactive-mode.ts +57 -34
  161. package/src/modes/prompt-action-autocomplete.ts +32 -13
  162. package/src/modes/prompt-suggestion-controller.ts +96 -0
  163. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +2 -2
  164. package/src/modes/types.ts +4 -0
  165. package/src/modes/utils/hotkeys-markdown.ts +64 -36
  166. package/src/modes/utils/ui-helpers.ts +5 -3
  167. package/src/prompts/agents/architect.md +0 -1
  168. package/src/prompts/agents/critic.md +0 -1
  169. package/src/prompts/system/btw-user.md +3 -8
  170. package/src/prompts/system/prompt-suggestion-system.md +32 -0
  171. package/src/prompts/system/system-prompt.md +6 -4
  172. package/src/prompts/tools/cron.md +3 -1
  173. package/src/prompts/tools/skill-discovery.md +1 -0
  174. package/src/runtime-mcp/client.ts +41 -55
  175. package/src/runtime-mcp/content-limits.ts +71 -0
  176. package/src/runtime-mcp/json-rpc.ts +23 -9
  177. package/src/runtime-mcp/manager.ts +51 -53
  178. package/src/runtime-mcp/oauth-flow.ts +0 -72
  179. package/src/runtime-mcp/plugin-network-boundary.ts +101 -0
  180. package/src/runtime-mcp/transports/http.ts +63 -22
  181. package/src/sdk/broker/broker.ts +34 -20
  182. package/src/sdk/broker/lifecycle.ts +185 -36
  183. package/src/sdk/bus/chat-daemon-control.ts +19 -5
  184. package/src/sdk/bus/config-commands.ts +2 -2
  185. package/src/sdk/bus/config.ts +9 -1
  186. package/src/sdk/bus/conversation-store.ts +40 -6
  187. package/src/sdk/bus/index.ts +244 -70
  188. package/src/sdk/bus/lifecycle-commands.ts +63 -10
  189. package/src/sdk/bus/lifecycle-control-runtime.ts +11 -2
  190. package/src/sdk/bus/lifecycle-orchestrator.ts +32 -1
  191. package/src/sdk/bus/notification-orchestration.ts +12 -0
  192. package/src/sdk/bus/notification-service.ts +68 -20
  193. package/src/sdk/bus/prompt-reconciliation.ts +239 -0
  194. package/src/sdk/bus/recent-activity.ts +90 -26
  195. package/src/sdk/bus/telegram-daemon-contract.ts +18 -3
  196. package/src/sdk/bus/telegram-daemon-control.ts +147 -35
  197. package/src/sdk/bus/telegram-daemon.ts +2463 -450
  198. package/src/sdk/bus/telegram-reference.ts +20 -2
  199. package/src/sdk/bus/topic-registry.ts +451 -12
  200. package/src/sdk/cli/session-cli.ts +15 -2
  201. package/src/sdk/client/discovery.ts +19 -3
  202. package/src/sdk/client/liveness.ts +49 -0
  203. package/src/sdk/host/control/dispatch.ts +1 -1
  204. package/src/sdk/host/control/operations.ts +1 -1
  205. package/src/sdk/host/host.ts +2 -6
  206. package/src/sdk/host/query/handlers.ts +70 -5
  207. package/src/sdk/index.ts +12 -0
  208. package/src/sdk/prompt-status.ts +97 -0
  209. package/src/sdk/protocol/operation-inventory.generated.json +58 -0
  210. package/src/sdk/protocol/operation-registry.ts +18 -4
  211. package/src/sdk/session-directory.ts +5 -1
  212. package/src/sdk/session.ts +31 -5
  213. package/src/sdk/startup-capability.ts +9 -1
  214. package/src/sdk/transport/auth-preface.ts +82 -0
  215. package/src/sdk/transport/index.ts +18 -0
  216. package/src/sdk/transport/relay.ts +252 -0
  217. package/src/sdk/transport/serve-cli.ts +105 -0
  218. package/src/sdk/transport/socket.ts +197 -0
  219. package/src/sdk/transport/stdio.ts +17 -0
  220. package/src/session/agent-session.ts +292 -154
  221. package/src/session/blob-store.ts +25 -7
  222. package/src/session/btw-contract.ts +59 -0
  223. package/src/session/internal/managed-session-scope.ts +1163 -137
  224. package/src/session/internal/managed-session-storage.ts +397 -234
  225. package/src/session/internal/native-publish-outcome.ts +266 -0
  226. package/src/session/session-manager.ts +620 -403
  227. package/src/session/session-storage.ts +128 -11
  228. package/src/setup/credential-import.ts +28 -3
  229. package/src/setup/provider-onboarding.ts +30 -1
  230. package/src/setup/provider-presets.json +2 -1
  231. package/src/slash-commands/builtin-registry.ts +1 -1
  232. package/src/ssh/utils.ts +14 -0
  233. package/src/task/discovery.ts +8 -2
  234. package/src/task/gjc-command.ts +2 -2
  235. package/src/task/index.ts +121 -15
  236. package/src/task/receipt.ts +3 -0
  237. package/src/task/types.ts +34 -3
  238. package/src/tools/ask.ts +199 -89
  239. package/src/tools/ast-edit.ts +2 -2
  240. package/src/tools/bash-pty-selection.ts +2 -2
  241. package/src/tools/browser/screenshot-format.ts +6 -1
  242. package/src/tools/cron.ts +1 -1
  243. package/src/tools/fetch.ts +13 -131
  244. package/src/tools/image-gen.ts +476 -23
  245. package/src/tools/index.ts +4 -0
  246. package/src/tools/read.ts +2 -0
  247. package/src/tools/render-utils.ts +7 -1
  248. package/src/tools/skill-discovery.ts +39 -3
  249. package/src/tools/sqlite-reader.ts +63 -5
  250. package/src/utils/edit-mode.ts +2 -2
  251. package/src/utils/pasted-image-path.ts +15 -6
  252. package/src/utils/prompt-suggestion.ts +265 -0
  253. package/src/utils/shell-snapshot.ts +173 -63
  254. package/src/utils/sixel.ts +3 -3
  255. package/src/web/insane/bridge.ts +5 -1
  256. package/src/web/insane/url-guard.ts +140 -27
  257. package/src/web/scrapers/docs-rs.ts +30 -5
  258. package/src/web/scrapers/types.ts +19 -49
  259. package/src/web/scrapers/utils.ts +12 -26
@@ -6,15 +6,43 @@ import { buildUltragoalHudSummary as buildWorkflowUltragoalHudSummary } from "..
6
6
  import { renderCliWriteReceipt } from "./cli-write-receipt";
7
7
  import { DEFAULT_ULTRAGOAL_OBJECTIVE } from "./goal-mode-request";
8
8
  import {
9
+ assertCwdMatchesRepositoryBinding,
10
+ captureRepositoryBinding,
11
+ parseRepositoryBinding,
12
+ type RepositoryBinding,
13
+ } from "./repository-binding";
14
+ import {
15
+ CRITIC_GATE_HARD_STOP_EVENT,
16
+ CRITIC_GATE_OVERRIDE_EVENT,
17
+ CRITIC_VERDICT_EVENT,
18
+ type CriticVerdict,
19
+ computeCriticVerdictPlanGeneration,
9
20
  computeUltragoalPlanGeneration,
21
+ countNonOkayTerminalCriticVerdicts,
10
22
  findFreshBatchCloseReceipt,
11
23
  findLedgerReceiptEvent,
24
+ isCleanPauseCriticVerdictShape,
12
25
  requiredUltragoalGoals,
26
+ TERMINAL_CRITIC_CEILING,
27
+ terminalCriticCeilingReached,
28
+ terminalCriticGateOverridden,
29
+ terminalCriticHardStopReached,
13
30
  validateDeferredMemberReceiptFresh,
14
31
  validateReceiptFreshBase,
15
32
  } from "./ultragoal-receipt-freshness";
16
33
 
17
- export { computeUltragoalPlanGeneration, receiptRelevantGoals } from "./ultragoal-receipt-freshness";
34
+ export {
35
+ CRITIC_GATE_HARD_STOP_EVENT,
36
+ CRITIC_GATE_OVERRIDE_EVENT,
37
+ CRITIC_VERDICT_EVENT,
38
+ type CriticVerdict,
39
+ computeUltragoalPlanGeneration,
40
+ countTerminalCriticVerdicts,
41
+ receiptRelevantGoals,
42
+ TERMINAL_CRITIC_CEILING,
43
+ terminalCriticCeilingReached,
44
+ terminalCriticGateOverridden,
45
+ } from "./ultragoal-receipt-freshness";
18
46
 
19
47
  import { gjcRoot, sessionUltragoalDir } from "./session-layout";
20
48
  import {
@@ -170,6 +198,8 @@ export interface UltragoalPlan {
170
198
  gjcObjective: string;
171
199
  gjcObjectiveAliases?: string[];
172
200
  goals: UltragoalGoal[];
201
+ /** Authoritative repository identity for multi-repo fail-closed spawn (#2901). */
202
+ repositoryBinding?: RepositoryBinding;
173
203
  createdAt: string;
174
204
  updatedAt: string;
175
205
  [key: string]: unknown;
@@ -427,7 +457,6 @@ export const DEFAULT_ULTRAGOAL_NUDGE_BUDGET = 10;
427
457
  export function countUltragoalNudges(ledger: readonly UltragoalLedgerEvent[], goalId: string): number {
428
458
  return ledger.filter(event => event.event === "nudge" && event.goalId === goalId).length;
429
459
  }
430
-
431
460
  function parseNudgeBudgetValue(value: unknown): number | null {
432
461
  return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value >= 0 ? value : null;
433
462
  }
@@ -1328,6 +1357,10 @@ function normalizePlan(raw: unknown): UltragoalPlan {
1328
1357
  (value): value is string => typeof value === "string" && value.trim().length > 0,
1329
1358
  )
1330
1359
  : undefined;
1360
+ let repositoryBinding: RepositoryBinding | undefined;
1361
+ if (record.repositoryBinding !== undefined) {
1362
+ repositoryBinding = parseRepositoryBinding(record.repositoryBinding);
1363
+ }
1331
1364
  return {
1332
1365
  version: 1,
1333
1366
  brief,
@@ -1337,6 +1370,7 @@ function normalizePlan(raw: unknown): UltragoalPlan {
1337
1370
  goals,
1338
1371
  createdAt,
1339
1372
  updatedAt,
1373
+ ...(repositoryBinding ? { repositoryBinding } : {}),
1340
1374
  ...(typeof record.state_revision === "number" && Number.isFinite(record.state_revision)
1341
1375
  ? { state_revision: record.state_revision }
1342
1376
  : {}),
@@ -1537,12 +1571,16 @@ export async function createUltragoalPlan(input: {
1537
1571
  goal.validationBatch = validationBatchByGoalId.get(goal.id);
1538
1572
  validateValidationBatchPipelineExclusion(goal);
1539
1573
  }
1574
+ const repositoryBinding = await captureRepositoryBinding(input.cwd, {
1575
+ displayPath: input.cwd,
1576
+ });
1540
1577
  const plan: UltragoalPlan = {
1541
1578
  version: 1,
1542
1579
  brief,
1543
1580
  gjcGoalMode: input.gjcGoalMode ?? "aggregate",
1544
1581
  gjcObjective: DEFAULT_ULTRAGOAL_OBJECTIVE,
1545
1582
  goals,
1583
+ repositoryBinding,
1546
1584
  createdAt: now,
1547
1585
  updatedAt: now,
1548
1586
  };
@@ -1618,6 +1656,50 @@ export function getUltragoalRunCompletionState(
1618
1656
  };
1619
1657
  }
1620
1658
 
1659
+ /**
1660
+ * Discriminated next-action for `complete-goals` handoff (#2903).
1661
+ * `none` is reserved for genuine completion; `execute-goal` always carries a goal.
1662
+ */
1663
+ export type UltragoalCompleteNextActionKind =
1664
+ | "none"
1665
+ | "execute-goal"
1666
+ | "retry-failed"
1667
+ | "resolve-blockers"
1668
+ | "final-aggregate-receipt";
1669
+
1670
+ export type UltragoalCompleteNextAction = {
1671
+ kind: UltragoalCompleteNextActionKind;
1672
+ goal?: UltragoalGoal;
1673
+ blockedGoals?: UltragoalGoal[];
1674
+ failedGoals?: UltragoalGoal[];
1675
+ };
1676
+
1677
+ /**
1678
+ * Resolve the actionable next step after scheduling / complete-goals.
1679
+ * Blocked and review_blocked goals remain unschedulable; they surface as
1680
+ * `resolve-blockers` instead of a contradictory `execute-goal` without goal_id.
1681
+ */
1682
+ export function resolveUltragoalCompleteNextAction(
1683
+ plan: UltragoalPlan,
1684
+ options: { retryFailed?: boolean; selectedGoal?: UltragoalGoal } = {},
1685
+ ): UltragoalCompleteNextAction {
1686
+ const state = getUltragoalRunCompletionState(plan, { retryFailed: options.retryFailed });
1687
+ // Genuine completion keeps next_action=`none` (historical complete-goals contract).
1688
+ // final-aggregate-receipt is reserved for a future dedicated handoff; do not remap
1689
+ // allComplete here so aggregate runs still finish with `none` / complete text.
1690
+ if (state.allComplete) return { kind: "none" };
1691
+ const goal = options.selectedGoal ?? state.nextGoal;
1692
+ if (goal) return { kind: "execute-goal", goal };
1693
+ const blockedGoals = state.incompleteGoals.filter(
1694
+ item => item.status === "blocked" || item.status === "review_blocked",
1695
+ );
1696
+ if (blockedGoals.length > 0) return { kind: "resolve-blockers", blockedGoals };
1697
+ const failedGoals = state.incompleteGoals.filter(item => item.status === "failed");
1698
+ if (failedGoals.length > 0) return { kind: "retry-failed", failedGoals };
1699
+ // Incomplete but not schedulable (unexpected statuses): still actionable, not "none".
1700
+ return { kind: "resolve-blockers", blockedGoals: state.incompleteGoals };
1701
+ }
1702
+
1621
1703
  export async function startNextUltragoalGoal(input: {
1622
1704
  cwd: string;
1623
1705
  retryFailed?: boolean;
@@ -1626,11 +1708,27 @@ export async function startNextUltragoalGoal(input: {
1626
1708
  plan: UltragoalPlan;
1627
1709
  goal?: UltragoalGoal;
1628
1710
  allComplete: boolean;
1711
+ nextAction: UltragoalCompleteNextAction;
1629
1712
  }> {
1630
1713
  const plan = await readUltragoalPlan(input.cwd, input.sessionId);
1631
1714
  if (!plan) throw new Error("No ultragoal plan found. Run `gjc ultragoal create-goals --brief ...` first.");
1632
- const goal = chooseNextGoal(plan, input.retryFailed === true);
1633
- if (!goal) return { plan, allComplete: getUltragoalRunCompletionState(plan).allComplete };
1715
+ // Fail closed: delegated execution requires stamped repository authority (#2901).
1716
+ if (!plan.repositoryBinding) {
1717
+ throw new Error(
1718
+ "Ultragoal plan is missing repositoryBinding; recreate goals so the plan is bound to an authoritative repository identity.",
1719
+ );
1720
+ }
1721
+ await assertCwdMatchesRepositoryBinding(input.cwd, plan.repositoryBinding);
1722
+ const retryFailed = input.retryFailed === true;
1723
+ const goal = chooseNextGoal(plan, retryFailed);
1724
+ if (!goal) {
1725
+ const state = getUltragoalRunCompletionState(plan, { retryFailed });
1726
+ return {
1727
+ plan,
1728
+ allComplete: state.allComplete,
1729
+ nextAction: resolveUltragoalCompleteNextAction(plan, { retryFailed }),
1730
+ };
1731
+ }
1634
1732
  if (goal.status !== "active") {
1635
1733
  const now = new Date().toISOString();
1636
1734
  goal.status = "active";
@@ -1640,7 +1738,12 @@ export async function startNextUltragoalGoal(input: {
1640
1738
  await writePlan(input.cwd, plan, input.sessionId);
1641
1739
  await appendLedger(input.cwd, { event: "goal_started", goalId: goal.id }, input.sessionId);
1642
1740
  }
1643
- return { plan, goal, allComplete: false };
1741
+ return {
1742
+ plan,
1743
+ goal,
1744
+ allComplete: false,
1745
+ nextAction: { kind: "execute-goal", goal },
1746
+ };
1644
1747
  }
1645
1748
 
1646
1749
  async function readStructuredValue(cwd: string, value: string): Promise<unknown> {
@@ -2514,12 +2617,23 @@ async function validateCompletionQualityGate(
2514
2617
  } = {},
2515
2618
  ): Promise<void> {
2516
2619
  const batchMode = options.goal?.validationBatch;
2620
+ const receiptKind =
2621
+ options.plan && options.goal && options.ledger
2622
+ ? chooseReceiptKind(options.plan, options.ledger, options.goal, "complete")
2623
+ : undefined;
2624
+ const isFinalAggregate = receiptKind === "final-aggregate";
2517
2625
  if (batchMode && options.goal && options.goal.id !== batchMode.finalGoalId) {
2518
2626
  validateDeferredCompletionQualityGate(gate, options.goal, batchMode, options.changeSet);
2519
2627
  return;
2520
2628
  }
2521
2629
  if (batchMode && options.goal && options.goal.id === batchMode.finalGoalId) {
2522
- const allowedKeys = new Set(["architectReview", "executorQa", "iteration", "validationBatchClose"]);
2630
+ const allowedKeys = new Set([
2631
+ "architectReview",
2632
+ "executorQa",
2633
+ "iteration",
2634
+ "validationBatchClose",
2635
+ "criticReview",
2636
+ ]);
2523
2637
  const unsupportedKeys = Object.keys(gate).filter(key => !allowedKeys.has(key));
2524
2638
  if (unsupportedKeys.length > 0)
2525
2639
  throw new Error(`qualityGate contains unsupported keys: ${unsupportedKeys.join(", ")}`);
@@ -2534,8 +2648,8 @@ async function validateCompletionQualityGate(
2534
2648
  }
2535
2649
  const allowedKeys = new Set(
2536
2650
  batchMode
2537
- ? ["architectReview", "executorQa", "iteration", "validationBatchClose"]
2538
- : ["architectReview", "executorQa", "iteration"],
2651
+ ? ["architectReview", "executorQa", "iteration", "validationBatchClose", "criticReview"]
2652
+ : ["architectReview", "executorQa", "iteration", "criticReview"],
2539
2653
  );
2540
2654
  const unsupportedKeys = Object.keys(gate).filter(key => !allowedKeys.has(key));
2541
2655
  if (unsupportedKeys.length > 0) {
@@ -2547,6 +2661,25 @@ async function validateCompletionQualityGate(
2547
2661
  if (!architectReview || !executorQa || !iteration) {
2548
2662
  throw new Error("qualityGate requires architectReview, executorQa, and iteration objects");
2549
2663
  }
2664
+ if (isFinalAggregate) {
2665
+ if (
2666
+ options.ledger &&
2667
+ terminalCriticCeilingReached(options.ledger) &&
2668
+ !terminalCriticGateOverridden(options.ledger)
2669
+ ) {
2670
+ throw new Error(
2671
+ "checkpoint --status complete blocked: terminal-critic ceiling reached; requires human/leader gjc ultragoal record-critic-gate-override before completion",
2672
+ );
2673
+ }
2674
+ const criticReview = qualityGateObject(gate.criticReview);
2675
+ if (criticReview?.verdict !== "OKAY") {
2676
+ throw new Error(
2677
+ "checkpoint --status complete (final aggregate) requires criticReview with verdict OKAY, non-empty evidence, and empty blockers",
2678
+ );
2679
+ }
2680
+ requireNonEmptyString(criticReview.evidence, "criticReview.evidence");
2681
+ requireEmptyBlockers(criticReview.blockers, "criticReview.blockers");
2682
+ }
2550
2683
  if (
2551
2684
  architectReview.architectureStatus !== CLEAN_ARCHITECT_STATUS ||
2552
2685
  architectReview.productStatus !== CLEAN_ARCHITECT_STATUS ||
@@ -3550,10 +3683,10 @@ export async function recordUltragoalReviewBlockers(input: {
3550
3683
  export type UltragoalBlockerClassification = "human_blocked" | "resolvable";
3551
3684
 
3552
3685
  /**
3553
- * Record an audited blocker triage classification in the durable ledger. A
3554
- * `human_blocked` classification is the only thing that authorizes
3555
- * `goal({"op":"pause"})` while an Ultragoal run is active; `resolvable` is an
3556
- * audit note and never unblocks pause.
3686
+ * Record an audited blocker triage classification in the durable ledger. Pause
3687
+ * requires the latest `blocker_classified` event to be `human_blocked` and a
3688
+ * later clean pause terminal critic verdict bound to that classification; `resolvable`
3689
+ * is an audit note and never unblocks pause.
3557
3690
  */
3558
3691
  export async function recordUltragoalBlockerClassification(input: {
3559
3692
  cwd: string;
@@ -3574,6 +3707,130 @@ export async function recordUltragoalBlockerClassification(input: {
3574
3707
  });
3575
3708
  }
3576
3709
 
3710
+ export async function recordUltragoalCriticVerdict(input: {
3711
+ cwd: string;
3712
+ terminus: "completion" | "pause";
3713
+ verdict: CriticVerdict;
3714
+ evidence: string;
3715
+ blockers?: string[];
3716
+ goalId?: string;
3717
+ classificationEventId?: string;
3718
+ }): Promise<UltragoalLedgerEvent> {
3719
+ const evidence = input.evidence.trim();
3720
+ if (!evidence) throw new Error("record-critic-verdict --evidence is required");
3721
+ if (input.terminus !== "completion" && input.terminus !== "pause") {
3722
+ throw new Error('record-critic-verdict --terminus must be "completion" or "pause"');
3723
+ }
3724
+ if (input.verdict !== "OKAY" && input.verdict !== "ITERATE" && input.verdict !== "REJECT") {
3725
+ throw new Error("record-critic-verdict --verdict must be OKAY, ITERATE, or REJECT");
3726
+ }
3727
+ const blockers = stringArray(input.blockers ?? []);
3728
+ if (!blockers) throw new Error("record-critic-verdict --blockers-json must be a JSON string array");
3729
+ if (input.terminus === "completion" && input.verdict === "OKAY" && blockers.length > 0) {
3730
+ throw new Error("OKAY critic verdict must have empty blockers");
3731
+ }
3732
+ const classificationEventId = input.classificationEventId?.trim();
3733
+ if (input.terminus === "pause" && !classificationEventId) {
3734
+ throw new Error("record-critic-verdict --classification-event-id is required for pause verdicts");
3735
+ }
3736
+ const resolvedSessionId = resolveGjcSessionForWrite(input.cwd, {
3737
+ envSessionId: process.env.GJC_SESSION_ID,
3738
+ }).gjcSessionId;
3739
+ const paths = getUltragoalPaths(input.cwd, resolvedSessionId);
3740
+ return withWorkflowStateLock(
3741
+ paths.ledgerPath,
3742
+ async () => {
3743
+ const plan = await readUltragoalPlan(input.cwd, resolvedSessionId);
3744
+ if (!plan) throw new Error("record-critic-verdict requires an active ultragoal plan");
3745
+ const ledger = await readUltragoalLedger(input.cwd, resolvedSessionId);
3746
+ if (input.terminus === "pause") {
3747
+ const latestClassification = [...ledger].reverse().find(event => event.event === "blocker_classified");
3748
+ if (
3749
+ latestClassification?.classification !== "human_blocked" ||
3750
+ latestClassification.eventId !== classificationEventId
3751
+ ) {
3752
+ throw new Error(
3753
+ "record-critic-verdict pause requires --classification-event-id to name the latest human_blocked classification",
3754
+ );
3755
+ }
3756
+ }
3757
+ const planGeneration = computeCriticVerdictPlanGeneration(plan);
3758
+ if (
3759
+ input.terminus === "pause" &&
3760
+ input.verdict === "OKAY" &&
3761
+ !isCleanPauseCriticVerdictShape(
3762
+ {
3763
+ event: CRITIC_VERDICT_EVENT,
3764
+ terminus: input.terminus,
3765
+ verdict: input.verdict,
3766
+ evidence,
3767
+ blockers,
3768
+ planGeneration,
3769
+ classificationEventId,
3770
+ },
3771
+ planGeneration,
3772
+ classificationEventId!,
3773
+ )
3774
+ ) {
3775
+ throw new Error("OKAY critic verdict must have empty blockers");
3776
+ }
3777
+ const criticVerdict = await appendLedger(
3778
+ input.cwd,
3779
+ {
3780
+ event: CRITIC_VERDICT_EVENT,
3781
+ terminus: input.terminus,
3782
+ verdict: input.verdict,
3783
+ evidence,
3784
+ blockers,
3785
+ planGeneration,
3786
+ ...(classificationEventId ? { classificationEventId } : {}),
3787
+ ...(input.goalId?.trim() ? { goalId: input.goalId.trim() } : {}),
3788
+ },
3789
+ resolvedSessionId,
3790
+ );
3791
+ const updatedLedger = [...ledger, criticVerdict];
3792
+ const count = countNonOkayTerminalCriticVerdicts(updatedLedger);
3793
+ if (count >= TERMINAL_CRITIC_CEILING && !terminalCriticHardStopReached(updatedLedger)) {
3794
+ await appendLedger(
3795
+ input.cwd,
3796
+ {
3797
+ event: CRITIC_GATE_HARD_STOP_EVENT,
3798
+ planGeneration,
3799
+ reason: "Terminal critic verdict ceiling reached.",
3800
+ count,
3801
+ },
3802
+ resolvedSessionId,
3803
+ );
3804
+ }
3805
+ return criticVerdict;
3806
+ },
3807
+ { cwd: input.cwd },
3808
+ );
3809
+ }
3810
+
3811
+ export async function recordUltragoalCriticGateOverride(input: {
3812
+ cwd: string;
3813
+ evidence: string;
3814
+ }): Promise<UltragoalLedgerEvent> {
3815
+ const evidence = input.evidence.trim();
3816
+ if (!evidence) throw new Error("record-critic-gate-override --evidence is required");
3817
+ const resolvedSessionId = resolveGjcSessionForWrite(input.cwd, {
3818
+ envSessionId: process.env.GJC_SESSION_ID,
3819
+ }).gjcSessionId;
3820
+ const paths = getUltragoalPaths(input.cwd, resolvedSessionId);
3821
+ return withWorkflowStateLock(
3822
+ paths.ledgerPath,
3823
+ async () => {
3824
+ const ledger = await readUltragoalLedger(input.cwd, resolvedSessionId);
3825
+ if (!terminalCriticHardStopReached(ledger)) {
3826
+ throw new Error("record-critic-gate-override requires a durably recorded terminal critic hard stop");
3827
+ }
3828
+ return appendLedger(input.cwd, { event: CRITIC_GATE_OVERRIDE_EVENT, evidence }, resolvedSessionId);
3829
+ },
3830
+ { cwd: input.cwd },
3831
+ );
3832
+ }
3833
+
3577
3834
  type UltragoalReviewMode = "review-only" | "review-start";
3578
3835
  type UltragoalReviewContractStrength = "strong" | "thin-derived";
3579
3836
 
@@ -3972,7 +4229,7 @@ function renderUltragoalHelp(args: readonly string[]): string | null {
3972
4229
  " $ gjc ultragoal classify-blocker --classification <human_blocked|resolvable> --evidence <text> [FLAGS]",
3973
4230
  "",
3974
4231
  "FLAGS",
3975
- " --classification=<value> Required. human_blocked authorizes pause only as the latest ledger event; resolvable never authorizes pause",
4232
+ " --classification=<value> Required. human_blocked must be the latest blocker_classified event; pause also requires a later bound clean pause terminal critic OKAY verdict; resolvable never authorizes pause",
3976
4233
  " --evidence=<value> Required. Specific blocker evidence; must name the human-only dependency for human_blocked",
3977
4234
  " --goal-id=<value> Optional durable .gjc/ultragoal goal id, e.g. G001",
3978
4235
  " --json Output a machine-readable receipt",
@@ -3983,6 +4240,44 @@ function renderUltragoalHelp(args: readonly string[]): string | null {
3983
4240
  "",
3984
4241
  ].join("\n");
3985
4242
  }
4243
+ if (subject === "record-critic-verdict") {
4244
+ return [
4245
+ "Run native GJC Ultragoal workflow commands",
4246
+ "",
4247
+ "USAGE",
4248
+ " $ gjc ultragoal record-critic-verdict --terminus <completion|pause> --verdict <OKAY|ITERATE|REJECT> --evidence <text> [--blockers-json <json>] [--goal-id <id>] [--classification-event-id <id>]",
4249
+ "",
4250
+ "FLAGS",
4251
+ " --terminus=<value> Required. completion or pause",
4252
+ " --verdict=<value> Required. OKAY, ITERATE, or REJECT",
4253
+ " --evidence=<value> Required. Specific evidence supporting the verdict",
4254
+ " --blockers-json=<value> Optional JSON string array of blockers",
4255
+ " --goal-id=<value> Optional durable .gjc/ultragoal goal id, e.g. G001",
4256
+ " --classification-event-id=<id> Required for pause verdicts; binds the human_blocked classification",
4257
+ " --json Output a machine-readable receipt",
4258
+ "",
4259
+ "EXAMPLES",
4260
+ ' $ gjc ultragoal record-critic-verdict --terminus completion --verdict OKAY --evidence "all final-aggregate checkpoint evidence is current"',
4261
+ "",
4262
+ ].join("\n");
4263
+ }
4264
+ if (subject === "record-critic-gate-override") {
4265
+ return [
4266
+ "Run native GJC Ultragoal workflow commands",
4267
+ "",
4268
+ "USAGE",
4269
+ " $ gjc ultragoal record-critic-gate-override --evidence <text> [--json]",
4270
+ "",
4271
+ "FLAGS",
4272
+ " --evidence=<value> Required. Human/leader authorization evidence for the terminal-critic ceiling override",
4273
+ " --json Output a machine-readable receipt",
4274
+ "",
4275
+ "EXAMPLES",
4276
+ ' $ gjc ultragoal record-critic-gate-override --evidence "leader approved another terminal attempt after reviewing all five findings"',
4277
+ "",
4278
+ ].join("\n");
4279
+ }
4280
+
3986
4281
  return [
3987
4282
  "Run native GJC Ultragoal workflow commands",
3988
4283
  "",
@@ -3998,11 +4293,14 @@ function renderUltragoalHelp(args: readonly string[]): string | null {
3998
4293
  " steer",
3999
4294
  " record-review-blockers",
4000
4295
  " classify-blocker",
4296
+ " record-critic-verdict",
4297
+ " record-critic-gate-override",
4298
+
4001
4299
  " start-pipeline-overlap",
4002
4300
  " join-pipeline-overlap",
4003
4301
  " rebaseline-pipeline-overlap",
4004
4302
  "",
4005
- "Run `gjc ultragoal checkpoint --help`, `gjc ultragoal review --help`, or `gjc ultragoal classify-blocker --help` for command-specific requirements.",
4303
+ "Run `gjc ultragoal checkpoint --help`, `gjc ultragoal review --help`, `gjc ultragoal classify-blocker --help`, `gjc ultragoal record-critic-verdict --help`, or `gjc ultragoal record-critic-gate-override --help` for command-specific requirements.",
4006
4304
  "",
4007
4305
  ].join("\n");
4008
4306
  }
@@ -4021,31 +4319,111 @@ function renderStatus(summary: UltragoalStatusSummary, json: boolean): string {
4021
4319
  return renderUltragoalStatusMarkdown(summary);
4022
4320
  }
4023
4321
 
4322
+ function summarizeBlockedGoalForHandoff(goal: UltragoalGoal): {
4323
+ id: string;
4324
+ status: UltragoalGoalStatus;
4325
+ evidence?: string;
4326
+ } {
4327
+ const evidence = typeof goal.evidence === "string" && goal.evidence.trim() ? goal.evidence.trim() : undefined;
4328
+ return {
4329
+ id: goal.id,
4330
+ status: goal.status,
4331
+ ...(evidence ? { evidence } : {}),
4332
+ };
4333
+ }
4334
+
4024
4335
  function renderCompleteHandoff(
4025
- result: { plan: UltragoalPlan; goal?: UltragoalGoal; allComplete: boolean },
4336
+ result: {
4337
+ plan: UltragoalPlan;
4338
+ goal?: UltragoalGoal;
4339
+ allComplete: boolean;
4340
+ nextAction?: UltragoalCompleteNextAction;
4341
+ },
4026
4342
  json: boolean,
4027
4343
  cwd: string,
4028
4344
  ): string {
4345
+ const nextAction =
4346
+ result.nextAction ??
4347
+ resolveUltragoalCompleteNextAction(result.plan, {
4348
+ selectedGoal: result.goal,
4349
+ });
4350
+ const goalsPath = getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).goalsPath;
4351
+
4029
4352
  if (json) {
4030
- return renderCliWriteReceipt({
4353
+ const receipt: Record<string, unknown> = {
4031
4354
  ok: true,
4032
4355
  all_complete: result.allComplete,
4033
- next_action: result.allComplete ? "none" : "execute-goal",
4034
- goal_id: result.goal?.id,
4035
- goal_status: result.goal?.status,
4356
+ next_action: nextAction.kind,
4036
4357
  gjc_objective: result.plan.gjcObjective,
4037
- goals_path: getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).goalsPath,
4038
- });
4358
+ goals_path: goalsPath,
4359
+ };
4360
+ if (nextAction.kind === "execute-goal" && nextAction.goal) {
4361
+ receipt.goal_id = nextAction.goal.id;
4362
+ receipt.goal_status = nextAction.goal.status;
4363
+ }
4364
+ if (nextAction.kind === "resolve-blockers" && nextAction.blockedGoals) {
4365
+ receipt.blocked_goals = nextAction.blockedGoals.map(summarizeBlockedGoalForHandoff);
4366
+ receipt.blocked_goal_ids = nextAction.blockedGoals.map(goal => goal.id);
4367
+ receipt.recovery_hints = [
4368
+ "gjc ultragoal classify-blocker --help",
4369
+ "gjc ultragoal record-review-blockers --help",
4370
+ "gjc ultragoal steer --kind add_subgoal --help",
4371
+ "gjc ultragoal steer --kind mark_blocked_superseded --help",
4372
+ ];
4373
+ }
4374
+ if (nextAction.kind === "retry-failed" && nextAction.failedGoals) {
4375
+ receipt.failed_goal_ids = nextAction.failedGoals.map(goal => goal.id);
4376
+ receipt.recovery_hints = ["gjc ultragoal complete-goals --retry-failed"];
4377
+ }
4378
+ if (nextAction.kind === "final-aggregate-receipt") {
4379
+ receipt.recovery_hints = [
4380
+ "Finalize the aggregate completion receipt before treating the ultragoal run as closed.",
4381
+ ];
4382
+ }
4383
+ return renderCliWriteReceipt(receipt);
4039
4384
  }
4040
- if (result.allComplete) return "ultragoal complete all=true\n";
4041
- if (!result.goal) return "ultragoal next-action=none\n";
4042
- return [
4043
- `ultragoal next-action=execute-goal goal-id=${result.goal.id}`,
4044
- `objective=${result.goal.objective}`,
4045
- `gjc-objective=${result.plan.gjcObjective}`,
4046
- "checkpoint requires=architectReview:CLEAR+APPROVE,executorQa:passed",
4047
- "",
4048
- ].join("\n");
4385
+
4386
+ if (nextAction.kind === "none" || (result.allComplete && nextAction.kind !== "final-aggregate-receipt")) {
4387
+ return "ultragoal complete all=true\n";
4388
+ }
4389
+ if (nextAction.kind === "final-aggregate-receipt") {
4390
+ return [
4391
+ "ultragoal next-action=final-aggregate-receipt",
4392
+ "hint=finalize the aggregate completion receipt before treating the run as closed",
4393
+ "",
4394
+ ].join("\n");
4395
+ }
4396
+ if (nextAction.kind === "execute-goal" && nextAction.goal) {
4397
+ return [
4398
+ `ultragoal next-action=execute-goal goal-id=${nextAction.goal.id}`,
4399
+ `objective=${nextAction.goal.objective}`,
4400
+ `gjc-objective=${result.plan.gjcObjective}`,
4401
+ "checkpoint requires=architectReview:CLEAR+APPROVE,executorQa:passed",
4402
+ "",
4403
+ ].join("\n");
4404
+ }
4405
+ if (nextAction.kind === "resolve-blockers" && nextAction.blockedGoals && nextAction.blockedGoals.length > 0) {
4406
+ const ids = nextAction.blockedGoals.map(goal => goal.id).join(",");
4407
+ const statuses = nextAction.blockedGoals.map(goal => `${goal.id}:${goal.status}`).join(",");
4408
+ return [
4409
+ "ultragoal next-action=resolve-blockers",
4410
+ `blocked-goal-ids=${ids}`,
4411
+ `blocked-statuses=${statuses}`,
4412
+ "hint=resolve blockers via classify-blocker / record-review-blockers / steer --kind add_subgoal (or audited mark_blocked_superseded); blocked goals stay unschedulable",
4413
+ "",
4414
+ ].join("\n");
4415
+ }
4416
+ if (nextAction.kind === "retry-failed" && nextAction.failedGoals && nextAction.failedGoals.length > 0) {
4417
+ const ids = nextAction.failedGoals.map(goal => goal.id).join(",");
4418
+ return [
4419
+ "ultragoal next-action=retry-failed",
4420
+ `failed-goal-ids=${ids}`,
4421
+ "hint=run `gjc ultragoal complete-goals --retry-failed` after the failure is addressed",
4422
+ "",
4423
+ ].join("\n");
4424
+ }
4425
+ // Fail closed: never claim complete or execute-goal without a goal id.
4426
+ return "ultragoal next-action=resolve-blockers\nhint=no schedulable goal; inspect goals.json and ledger\n";
4049
4427
  }
4050
4428
  function renderCheckpointContinuation(
4051
4429
  result: UltragoalCheckpointContinuation,
@@ -4354,8 +4732,53 @@ async function dispatchUltragoalCommand(args: string[], cwd: string): Promise<Ul
4354
4732
  ok: true,
4355
4733
  event: "blocker_classified",
4356
4734
  classification: event.classification,
4735
+ event_id: event.eventId,
4736
+ })
4737
+ : `Recorded blocker classification: ${String(event.classification)} event-id=${String(event.eventId)}.\n`,
4738
+ };
4739
+ }
4740
+ case "record-critic-verdict": {
4741
+ const blockersJson = flagValue(args, "--blockers-json");
4742
+ const blockers =
4743
+ blockersJson === undefined ? undefined : stringArray(await readStructuredValue(cwd, blockersJson));
4744
+ if (blockersJson !== undefined && !blockers) {
4745
+ throw new Error("record-critic-verdict --blockers-json must be a JSON string array");
4746
+ }
4747
+ const event = await recordUltragoalCriticVerdict({
4748
+ cwd,
4749
+ terminus: (flagValue(args, "--terminus") ?? "") as "completion" | "pause",
4750
+ verdict: (flagValue(args, "--verdict") ?? "") as CriticVerdict,
4751
+ evidence: flagValue(args, "--evidence") ?? "",
4752
+ blockers: blockers ?? undefined,
4753
+ goalId: flagValue(args, "--goal-id"),
4754
+ classificationEventId: flagValue(args, "--classification-event-id"),
4755
+ });
4756
+ return {
4757
+ status: 0,
4758
+ stdout: json
4759
+ ? renderCliWriteReceipt({
4760
+ ok: true,
4761
+ event: CRITIC_VERDICT_EVENT,
4762
+ terminus: event.terminus,
4763
+ verdict: event.verdict,
4764
+ })
4765
+ : `Recorded critic verdict: ${String(event.verdict)} (${String(event.terminus)}).\n`,
4766
+ };
4767
+ }
4768
+ case "record-critic-gate-override": {
4769
+ const event = await recordUltragoalCriticGateOverride({
4770
+ cwd,
4771
+ evidence: flagValue(args, "--evidence") ?? "",
4772
+ });
4773
+ return {
4774
+ status: 0,
4775
+ stdout: json
4776
+ ? renderCliWriteReceipt({
4777
+ ok: true,
4778
+ event: CRITIC_GATE_OVERRIDE_EVENT,
4779
+ event_id: event.eventId,
4357
4780
  })
4358
- : `Recorded blocker classification: ${String(event.classification)}.\n`,
4781
+ : `Recorded terminal critic gate override event-id=${String(event.eventId)}.\n`,
4359
4782
  };
4360
4783
  }
4361
4784
  case "start-pipeline-overlap": {
@@ -4435,6 +4858,8 @@ const RECONCILE_COMMANDS = new Set([
4435
4858
  "record-review-blockers",
4436
4859
  "review",
4437
4860
  "classify-blocker",
4861
+ "record-critic-verdict",
4862
+ "record-critic-gate-override",
4438
4863
  "start-pipeline-overlap",
4439
4864
  "join-pipeline-overlap",
4440
4865
  "rebaseline-pipeline-overlap",