@khoavu882/doflow 2.0.0-beta.4 → 2.0.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 (497) hide show
  1. package/README.md +12 -11
  2. package/bin/doflow.js +21 -11
  3. package/core/.antigravity-plugin/plugin.json +1 -3
  4. package/core/.claude-plugin/plugin.json +1 -1
  5. package/core/.codex-plugin/plugin.json +1 -1
  6. package/core/.plugin/plugin.json +1 -1
  7. package/core/harnesses/claude/hooks/lib.sh +43 -24
  8. package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
  9. package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
  10. package/core/harnesses/claude/hooks/post-compact.sh +35 -32
  11. package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
  12. package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
  13. package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
  14. package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
  15. package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
  16. package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
  17. package/core/harnesses/claude/hooks/session-end.sh +23 -10
  18. package/core/harnesses/claude/hooks/session-start.sh +5 -3
  19. package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
  20. package/core/harnesses/claude/hooks/stop-check.sh +5 -3
  21. package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
  22. package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
  23. package/core/harnesses/claude/settings/settings.json +18 -0
  24. package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
  25. package/core/harnesses/codex/agents/core-implementer.toml +1 -0
  26. package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
  27. package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
  28. package/core/harnesses/codex/agents/research-writer.toml +1 -1
  29. package/core/harnesses/codex/agents/reviewer.toml +10 -0
  30. package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
  31. package/core/harnesses/codex/agents/system-architect.toml +1 -1
  32. package/core/harnesses/codex/hooks/lib.sh +44 -25
  33. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
  34. package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
  35. package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
  36. package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
  37. package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
  38. package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
  39. package/core/harnesses/codex/hooks/session-start.sh +5 -3
  40. package/core/harnesses/codex/hooks/stop-check.sh +6 -5
  41. package/core/harnesses/codex/hooks/subagent-audit.sh +18 -6
  42. package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
  43. package/core/harnesses/copilot/hooks/doflow-lib.sh +93 -0
  44. package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
  45. package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +9 -0
  46. package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
  47. package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
  48. package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
  49. package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
  50. package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
  51. package/core/harnesses/copilot/hooks/doflow.json +57 -0
  52. package/core/harnesses/kiro/agents/doflow.json +1 -1
  53. package/core/harnesses/kiro/hooks/lib.sh +26 -6
  54. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +21 -5
  55. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +21 -5
  56. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +21 -5
  57. package/core/harnesses/kiro/hooks/session-start.sh +12 -5
  58. package/core/harnesses/kiro/hooks/stop-check.sh +22 -30
  59. package/core/harnesses/pi/extensions/doflow-guard.js +167 -0
  60. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -12
  61. package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
  62. package/core/harnesses/shared/hooks/bridge/bridge.sh +277 -0
  63. package/core/harnesses/shared/hooks/bridge/envelope.jq +129 -0
  64. package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
  65. package/core/harnesses/shared/hooks/bridge/respond.sh +70 -0
  66. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +28 -14
  67. package/core/harnesses/shared/hooks/policies/capture-failure.sh +51 -16
  68. package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
  69. package/core/harnesses/shared/hooks/policies/contract.sh +78 -0
  70. package/core/harnesses/shared/hooks/policies/deny-json.sh +35 -9
  71. package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
  72. package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
  73. package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
  74. package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
  75. package/core/harnesses/shared/hooks/policies/lib.sh +65 -40
  76. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +48 -35
  77. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +284 -33
  78. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +180 -110
  79. package/core/harnesses/shared/hooks/policies/session-context.sh +66 -7
  80. package/core/harnesses/shared/hooks/policies/stop-check.sh +61 -23
  81. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +39 -6
  82. package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
  83. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +97 -159
  84. package/core/harnesses/shared/hooks/stream-hook-runner.js +114 -187
  85. package/core/harnesses/shared/locator/doflow-run +2 -3
  86. package/core/registry/advice.json +3 -3
  87. package/core/registry/assets.json +72 -80
  88. package/core/registry/capabilities.json +12 -0
  89. package/core/registry/contracts.json +60 -33
  90. package/core/registry/contribution-points.json +32 -0
  91. package/core/registry/gates.json +32 -0
  92. package/core/registry/guidance-caps.json +40 -0
  93. package/core/registry/harnesses.json +1542 -277
  94. package/core/registry/lifecycle.json +2837 -37
  95. package/core/registry/mcp.json +5 -1
  96. package/core/registry/models.json +7 -1
  97. package/core/registry/package-templates/README.md +19 -0
  98. package/core/registry/package-templates/agent/files/agent.md +9 -0
  99. package/core/registry/package-templates/agent/template.json +4 -0
  100. package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
  101. package/core/registry/package-templates/capability-provider/template.json +11 -0
  102. package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
  103. package/core/registry/package-templates/debate-preset/template.json +4 -0
  104. package/core/registry/package-templates/review-preset/files/preset.json +30 -0
  105. package/core/registry/package-templates/review-preset/template.json +4 -0
  106. package/core/registry/package-templates/skill/files/SKILL.md +8 -0
  107. package/core/registry/package-templates/skill/template.json +4 -0
  108. package/core/registry/package-templates/worker/files/worker.json +92 -0
  109. package/core/registry/package-templates/worker/template.json +11 -0
  110. package/core/registry/packages/doflow/doflow-package.json +15 -0
  111. package/core/registry/packages/doflow/presets/debate-3.json +26 -0
  112. package/core/registry/protocols.json +61 -0
  113. package/core/registry/routes.json +17 -0
  114. package/core/registry/rules.json +431 -0
  115. package/core/registry/settings.json +276 -0
  116. package/core/registry/storage.json +701 -30
  117. package/core/registry/telemetry.json +236 -0
  118. package/core/registry/transcripts.json +142 -0
  119. package/core/registry/verification.json +76 -1
  120. package/core/registry/workflows.json +55 -0
  121. package/core/shared/agent-specs/core-implementer.md +3 -0
  122. package/core/shared/agent-specs/delivery-lead.md +68 -0
  123. package/core/shared/agent-specs/quality-guardian.md +5 -1
  124. package/core/shared/agent-specs/research-writer.md +10 -3
  125. package/core/shared/agent-specs/reviewer.md +53 -0
  126. package/core/shared/agent-specs/spec-analyst.md +5 -6
  127. package/core/shared/agent-specs/system-architect.md +5 -1
  128. package/core/shared/guidance/DOFLOW_CORE.md +1 -5
  129. package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
  130. package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
  131. package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
  132. package/core/shared/guidance/pointers/codex.md +1 -4
  133. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
  134. package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
  135. package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
  136. package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
  137. package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
  138. package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
  139. package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
  140. package/core/shared/guidance/references/ORCHESTRATION.md +1 -2
  141. package/core/shared/guidance/rules/RULES.md +45 -0
  142. package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
  143. package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
  144. package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
  145. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
  146. package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
  147. package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
  148. package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
  149. package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
  150. package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
  151. package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
  152. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
  153. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +48 -4
  154. package/core/shared/scripts/doflow/bin/doflow-run +154 -35
  155. package/core/shared/skills/do/SKILL.md +12 -10
  156. package/core/shared/skills/do/references/advice_calls.md +26 -0
  157. package/core/shared/skills/do/references/pm_routing.md +1 -2
  158. package/core/shared/skills/do/references/program_mode.md +31 -0
  159. package/core/shared/skills/do-brainstorm/SKILL.md +20 -74
  160. package/core/shared/skills/do-brainstorm/references/handoff_branches.md +35 -0
  161. package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
  162. package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
  163. package/core/shared/skills/do-code-review/SKILL.md +41 -144
  164. package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
  165. package/core/shared/skills/do-code-review/references/tools.md +90 -0
  166. package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
  167. package/core/shared/skills/do-design/SKILL.md +24 -23
  168. package/core/shared/skills/do-diagnose/SKILL.md +14 -4
  169. package/core/shared/skills/do-document/SKILL.md +11 -1
  170. package/core/shared/skills/do-document/references/deep_research.md +1 -2
  171. package/core/shared/skills/do-document/references/embedded-research.md +18 -6
  172. package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
  173. package/core/shared/skills/do-execute-plan/SKILL.md +17 -59
  174. package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
  175. package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
  176. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +28 -1
  177. package/core/shared/skills/do-execute-plan/references/run_position.md +13 -1
  178. package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
  179. package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
  180. package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
  181. package/core/shared/skills/do-flow/SKILL.md +8 -9
  182. package/core/shared/skills/do-flow/references/task_classes.md +2 -2
  183. package/core/shared/skills/do-git/SKILL.md +4 -0
  184. package/core/shared/skills/do-implement/SKILL.md +7 -9
  185. package/core/shared/skills/do-plan/SKILL.md +29 -19
  186. package/core/shared/skills/do-test/SKILL.md +7 -4
  187. package/core/shared/templates/doflow/constitution-template.md +9 -3
  188. package/core/shared/templates/doflow/data-model-template.md +1 -1
  189. package/core/shared/templates/doflow/design-template.md +113 -16
  190. package/core/shared/templates/doflow/plan-template.md +11 -10
  191. package/core/shared/templates/doflow/question-log-template.md +14 -2
  192. package/core/shared/templates/doflow/requirement-template.md +1 -1
  193. package/core/worker-evidence/antigravity/checks.json +14 -0
  194. package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
  195. package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
  196. package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
  197. package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
  198. package/core/worker-evidence/codex/checks.json +38 -0
  199. package/package.json +10 -6
  200. package/src/adapters/antigravity/index.js +226 -113
  201. package/src/adapters/claude/index.js +39 -5
  202. package/src/adapters/codex/agents.js +25 -15
  203. package/src/adapters/codex/hooks.js +40 -5
  204. package/src/adapters/codex/index.js +100 -22
  205. package/src/adapters/codex/native-projection.js +6 -2
  206. package/src/adapters/copilot/index.js +58 -23
  207. package/src/adapters/copy-tree.js +294 -48
  208. package/src/adapters/guidance-manifest.js +71 -0
  209. package/src/adapters/guidance-pointer.js +19 -0
  210. package/src/adapters/hook-commands.js +6 -10
  211. package/src/adapters/index.js +1 -0
  212. package/src/adapters/kiro/index.js +38 -13
  213. package/src/adapters/opencode/index.js +8 -8
  214. package/src/adapters/pi/hooks.js +120 -0
  215. package/src/adapters/pi/index.js +48 -16
  216. package/src/adapters/resolve-source.js +26 -2
  217. package/src/cli/commands/install.js +3 -2
  218. package/src/cli/commands/reconcile.js +53 -9
  219. package/src/cli/commands/remove.js +2 -2
  220. package/src/cli/commands/rollback.js +6 -3
  221. package/src/cli/commands/self-update.js +18 -9
  222. package/src/cli/commands/status.js +21 -2
  223. package/src/cli/commands/ui.js +99 -0
  224. package/src/cli/commands/update.js +3 -2
  225. package/src/cli/index.js +338 -33
  226. package/src/cli/runtime-commands.js +243 -52
  227. package/src/cli/shared.js +0 -2
  228. package/src/helper/git.js +1 -1
  229. package/src/helper/guardrails.js +1 -1
  230. package/src/helper/marker-merge.js +31 -13
  231. package/src/helper/package-ids.js +37 -0
  232. package/src/helper/semver.js +94 -0
  233. package/src/install/backup.js +105 -15
  234. package/src/install/context.js +1 -1
  235. package/src/install/targets.js +31 -16
  236. package/src/lifecycle/coverage.js +74 -0
  237. package/src/lifecycle/hook-step.js +61 -0
  238. package/src/lifecycle/hook-tier.js +160 -0
  239. package/src/lifecycle/index.js +434 -86
  240. package/src/lifecycle/retired-harnesses.js +318 -0
  241. package/src/lifecycle/view.js +35 -15
  242. package/src/registry/index.js +686 -32
  243. package/src/registry/worker-ids.js +45 -0
  244. package/src/release/build-package.js +0 -3
  245. package/src/release/docs-blocks.js +492 -0
  246. package/src/release/preflight.js +425 -0
  247. package/src/release/rehearse.js +401 -0
  248. package/src/release/release-workflow.js +733 -0
  249. package/src/release/sync-plugin-versions.js +43 -7
  250. package/src/runtime/advice/bench-gate.js +25 -0
  251. package/src/runtime/advice/cli.js +59 -16
  252. package/src/runtime/advice/client.js +32 -9
  253. package/src/runtime/advice/config.js +98 -133
  254. package/src/runtime/advice/doctor.js +1 -1
  255. package/src/runtime/advice/elevation.js +97 -0
  256. package/src/runtime/advice/endpoint-guard.js +9 -3
  257. package/src/runtime/advice/mode.js +37 -0
  258. package/src/runtime/advice/record.js +18 -5
  259. package/src/runtime/advice/registry.js +4 -1
  260. package/src/runtime/advice/shadow-config.js +229 -0
  261. package/src/runtime/advice/shadow-dispatcher.js +276 -0
  262. package/src/runtime/brief-rules.js +81 -0
  263. package/src/runtime/brief-tools.js +111 -34
  264. package/src/runtime/candidate.js +514 -0
  265. package/src/runtime/capabilities/executor.js +450 -0
  266. package/src/runtime/capabilities/loopback.js +41 -0
  267. package/src/runtime/capabilities/sanitizer.js +88 -0
  268. package/src/runtime/capability-router.js +135 -3
  269. package/src/runtime/checkouts.js +73 -11
  270. package/src/runtime/claims.js +27 -12
  271. package/src/runtime/cli-result.js +11 -4
  272. package/src/runtime/cli.js +221 -277
  273. package/src/runtime/config-home.js +42 -0
  274. package/src/runtime/context/arbitrate.js +767 -0
  275. package/src/runtime/context/budget.js +203 -0
  276. package/src/runtime/context/compose.js +1367 -0
  277. package/src/runtime/context/delivery-store.js +100 -0
  278. package/src/runtime/context/emission-rules.js +118 -0
  279. package/src/runtime/context/identity.js +227 -0
  280. package/src/runtime/context/mandatory.js +594 -0
  281. package/src/runtime/context/pointer.js +142 -0
  282. package/src/runtime/context/primary-command.js +117 -0
  283. package/src/runtime/context/primary.js +344 -0
  284. package/src/runtime/context/summary-store.js +655 -0
  285. package/src/runtime/context-pack.js +1106 -245
  286. package/src/runtime/decision-register.js +13 -4
  287. package/src/runtime/dispatch/admission.js +392 -0
  288. package/src/runtime/dispatch/attempt-end.js +274 -0
  289. package/src/runtime/dispatch/command.js +628 -0
  290. package/src/runtime/dispatch/controls.js +904 -0
  291. package/src/runtime/dispatch/envelope.js +276 -0
  292. package/src/runtime/dispatch/grants.js +310 -0
  293. package/src/runtime/dispatch/integrate.js +617 -0
  294. package/src/runtime/dispatch/loop.js +1835 -0
  295. package/src/runtime/dispatch/store.js +352 -0
  296. package/src/runtime/dispatch/verify-run.js +201 -0
  297. package/src/runtime/evidence-fields.js +39 -0
  298. package/src/runtime/evidence-ledger.js +41 -7
  299. package/src/runtime/failure/capture.js +3 -1
  300. package/src/runtime/failure/cli.js +25 -9
  301. package/src/runtime/failure/home.js +57 -30
  302. package/src/runtime/failure/store.js +7 -2
  303. package/src/runtime/feature-pointer.js +174 -0
  304. package/src/runtime/feature-resolve.js +13 -2
  305. package/src/runtime/fleet/command.js +77 -0
  306. package/src/runtime/fleet/projects.js +178 -0
  307. package/src/runtime/fleet/tree.js +383 -0
  308. package/src/runtime/freshness.js +77 -10
  309. package/src/runtime/git-memo.js +46 -0
  310. package/src/runtime/guidance/ablation.js +427 -0
  311. package/src/runtime/health.js +565 -17
  312. package/src/runtime/history-compactor.js +13 -3
  313. package/src/runtime/implementation-gate.js +277 -22
  314. package/src/runtime/inventory/coverage.js +1 -1
  315. package/src/runtime/inventory/index.js +1 -1
  316. package/src/runtime/inventory/precedence.js +2 -2
  317. package/src/runtime/inventory/siblings.js +6 -4
  318. package/src/runtime/knowledge/code-corpus.js +277 -0
  319. package/src/runtime/knowledge/code-lexical.js +130 -0
  320. package/src/runtime/knowledge/code-retrieval.js +307 -0
  321. package/src/runtime/knowledge/code-validation.js +106 -0
  322. package/src/runtime/knowledge/gp3-gate.js +74 -0
  323. package/src/runtime/knowledge/graph-adapter.js +288 -0
  324. package/src/runtime/knowledge/graph-fallback.js +394 -0
  325. package/src/runtime/knowledge/hybrid.js +136 -8
  326. package/src/runtime/knowledge/retrieval.js +34 -1
  327. package/src/runtime/knowledge/rtk-compress.js +77 -0
  328. package/src/runtime/knowledge/semble-bridge.py +106 -0
  329. package/src/runtime/knowledge/semble-lifecycle.js +114 -0
  330. package/src/runtime/knowledge/semble-provider.js +365 -0
  331. package/src/runtime/lifecycle/cli.js +16 -6
  332. package/src/runtime/lifecycle/event-store.js +8 -6
  333. package/src/runtime/lifecycle/followup.js +6 -1
  334. package/src/runtime/lifecycle/overview.js +24 -1
  335. package/src/runtime/lifecycle/report-store.js +8 -10
  336. package/src/runtime/lifecycle/retention.js +95 -31
  337. package/src/runtime/lifecycle/root.js +9 -1
  338. package/src/runtime/lifecycle/status.js +34 -17
  339. package/src/runtime/lifecycle/store-upkeep.js +56 -7
  340. package/src/runtime/mask.js +134 -7
  341. package/src/runtime/mcp-consultations.js +40 -6
  342. package/src/runtime/model-router.js +79 -7
  343. package/src/runtime/models/catalogue.js +191 -0
  344. package/src/runtime/models/command.js +153 -0
  345. package/src/runtime/off-words.js +12 -0
  346. package/src/runtime/outcome.js +3 -3
  347. package/src/runtime/packages/command.js +1009 -0
  348. package/src/runtime/packages/contributions.js +116 -0
  349. package/src/runtime/packages/digest.js +132 -0
  350. package/src/runtime/packages/fetch.js +182 -0
  351. package/src/runtime/packages/grants.js +192 -0
  352. package/src/runtime/packages/index-file.js +134 -0
  353. package/src/runtime/packages/instruction.js +90 -0
  354. package/src/runtime/packages/manifest.js +331 -0
  355. package/src/runtime/packages/points.js +477 -0
  356. package/src/runtime/packages/programs.js +216 -0
  357. package/src/runtime/packages/scaffold.js +277 -0
  358. package/src/runtime/packages/source.js +90 -0
  359. package/src/runtime/packages/state.js +164 -0
  360. package/src/runtime/packages/store.js +449 -0
  361. package/src/runtime/packages/tar.js +106 -0
  362. package/src/runtime/packages/view.js +406 -0
  363. package/src/runtime/plan-check.js +59 -0
  364. package/src/runtime/plan-content.js +324 -0
  365. package/src/runtime/policy-init.js +118 -0
  366. package/src/runtime/profiles/doctor.js +96 -0
  367. package/src/runtime/profiles/resolve.js +244 -0
  368. package/src/runtime/profiles/rollback.js +254 -0
  369. package/src/runtime/profiles/sampler.js +41 -0
  370. package/src/runtime/readiness-record.js +16 -11
  371. package/src/runtime/readiness.js +127 -16
  372. package/src/runtime/research-request.js +50 -25
  373. package/src/runtime/retrieval-plan.js +402 -73
  374. package/src/runtime/retrieval-record.js +293 -0
  375. package/src/runtime/review/command.js +246 -0
  376. package/src/runtime/review/dominance.js +68 -0
  377. package/src/runtime/review/findings.js +434 -0
  378. package/src/runtime/review/identity.js +255 -0
  379. package/src/runtime/review/protocol-command.js +109 -0
  380. package/src/runtime/review/protocols.js +405 -0
  381. package/src/runtime/review/resolve-protocol.js +302 -0
  382. package/src/runtime/review/start.js +530 -0
  383. package/src/runtime/roadmap/facts.js +479 -0
  384. package/src/runtime/roadmap/gates.js +270 -0
  385. package/src/runtime/roadmap/loop.js +1636 -0
  386. package/src/runtime/roadmap/merge.js +381 -0
  387. package/src/runtime/roadmap/rows.js +1113 -0
  388. package/src/runtime/roadmap/seed.js +951 -0
  389. package/src/runtime/rules-report.js +336 -0
  390. package/src/runtime/scaffold/artifacts.js +63 -9
  391. package/src/runtime/scaffold/generate.js +21 -9
  392. package/src/runtime/settings/autopilot.js +106 -0
  393. package/src/runtime/settings/cli.js +280 -0
  394. package/src/runtime/settings/config-write.js +314 -0
  395. package/src/runtime/settings/files.js +271 -0
  396. package/src/runtime/settings/legacy.js +154 -0
  397. package/src/runtime/settings/lexical.js +56 -0
  398. package/src/runtime/settings/prepared-run.js +269 -0
  399. package/src/runtime/settings/project-layers.js +118 -0
  400. package/src/runtime/settings/refs.js +288 -0
  401. package/src/runtime/settings/registry.js +553 -0
  402. package/src/runtime/settings/resolve.js +713 -0
  403. package/src/runtime/settings/shared.js +37 -0
  404. package/src/runtime/settings/worker-billing.js +107 -0
  405. package/src/runtime/settings/write.js +269 -0
  406. package/src/runtime/slot-command.js +78 -0
  407. package/src/runtime/slots.js +671 -0
  408. package/src/runtime/source-version.js +290 -0
  409. package/src/runtime/state-root.js +217 -0
  410. package/src/runtime/storage/cli.js +12 -5
  411. package/src/runtime/storage/engine.js +9 -2
  412. package/src/runtime/storage/registry.js +14 -1
  413. package/src/runtime/storage/roots.js +66 -16
  414. package/src/runtime/storage/rules.js +82 -10
  415. package/src/runtime/storage/upkeep.js +10 -2
  416. package/src/runtime/storage/work-status.js +245 -26
  417. package/src/runtime/task-attempts.js +589 -69
  418. package/src/runtime/task-classifier.js +47 -11
  419. package/src/runtime/task-graph.js +373 -0
  420. package/src/runtime/task-run.js +778 -0
  421. package/src/runtime/task-scope.js +202 -56
  422. package/src/runtime/task-sources.js +147 -0
  423. package/src/runtime/task-state.js +440 -65
  424. package/src/runtime/team/admission.js +301 -0
  425. package/src/runtime/team/envelope.js +324 -0
  426. package/src/runtime/team/review-guard.js +156 -0
  427. package/src/runtime/trace/analyze.js +427 -0
  428. package/src/runtime/trace/baseline.js +770 -0
  429. package/src/runtime/trace/ledger.js +63 -140
  430. package/src/runtime/trace/otlp-export.js +568 -0
  431. package/src/runtime/trace/otlp-map.js +481 -0
  432. package/src/runtime/trace/spans.js +745 -0
  433. package/src/runtime/trace/telemetry.js +225 -0
  434. package/src/runtime/trace/views.js +1 -0
  435. package/src/runtime/transcripts/command.js +98 -0
  436. package/src/runtime/transcripts/cursor.js +326 -0
  437. package/src/runtime/transcripts/read.js +126 -0
  438. package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
  439. package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
  440. package/src/runtime/transcripts/readers/copilot-events.js +90 -0
  441. package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
  442. package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
  443. package/src/runtime/transcripts/registry.js +161 -0
  444. package/src/runtime/transcripts/stats.js +161 -0
  445. package/src/runtime/verification/check-key.js +628 -0
  446. package/src/runtime/verification/contract-runner.js +546 -11
  447. package/src/runtime/verification/engine.js +499 -9
  448. package/src/runtime/verification/impact.js +617 -0
  449. package/src/runtime/verification/inflight.js +205 -0
  450. package/src/runtime/verification/registry.js +176 -0
  451. package/src/runtime/verification/reuse.js +184 -0
  452. package/src/runtime/verification/telemetry-emit.js +254 -0
  453. package/src/runtime/verification/test-selection.js +67 -7
  454. package/src/runtime/worker/active.js +90 -0
  455. package/src/runtime/worker/cancel.js +222 -0
  456. package/src/runtime/worker/catalogue.js +122 -0
  457. package/src/runtime/worker/command.js +677 -0
  458. package/src/runtime/worker/evidence.js +288 -0
  459. package/src/runtime/worker/launch.js +288 -0
  460. package/src/runtime/worker/models.js +137 -0
  461. package/src/runtime/worker/parsers.js +218 -0
  462. package/src/runtime/worker/probes/index.js +706 -0
  463. package/src/runtime/worker/process-tree.js +415 -0
  464. package/src/runtime/worker/quota.js +43 -0
  465. package/src/runtime/worker/readiness.js +713 -0
  466. package/src/runtime/worker/run-store.js +166 -0
  467. package/src/runtime/worker/supervisor.js +541 -0
  468. package/src/runtime/worker/worktree-check.js +252 -0
  469. package/src/runtime/workflow-engine.js +5 -1
  470. package/src/runtime/workflow-orchestrator.js +578 -63
  471. package/src/runtime/worktree.js +149 -22
  472. package/src/state/index.js +1 -1
  473. package/src/state/lockfile.js +64 -10
  474. package/src/state/run-lock.js +1 -1
  475. package/types/node-commonjs.d.ts +106 -7
  476. package/core/.antigravity-plugin/hooks.json +0 -39
  477. package/core/harnesses/gemini/hooks/hooks.json +0 -77
  478. package/core/harnesses/gemini/hooks/lib.sh +0 -302
  479. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -21
  480. package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
  481. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -21
  482. package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
  483. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -22
  484. package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
  485. package/core/harnesses/gemini/hooks/session-end.sh +0 -5
  486. package/core/harnesses/gemini/hooks/session-start.sh +0 -5
  487. package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
  488. package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
  489. package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
  490. package/core/harnesses/shared/hooks/hooks.json +0 -47
  491. package/core/shared/guidance/FLAGS.md +0 -25
  492. package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
  493. package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
  494. package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
  495. package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
  496. package/src/adapters/gemini/hooks.js +0 -210
  497. package/src/adapters/gemini/index.js +0 -390
@@ -0,0 +1,628 @@
1
+ 'use strict';
2
+
3
+ // Feature 082 C.5 (IC-001, IC-002; DEC-002, DEC-017, DEC-024, DEC-029, DEC-034, DEC-035, DEC-037,
4
+ // DEC-039, DEC-044, DEC-051): the `dispatch` verb's handler.
5
+ //
6
+ // `executeDispatchCommand(options)` prints nothing and resolves `{ exit, body }`, so tests and the
7
+ // loop's own callers use it in process (the pattern of 081's `executeWorkerCommand`).
8
+ // `handleDispatchCommand(options)` wraps it, prints the JSON with `json` or one summary line per
9
+ // action (inspect: IC-029's lines), and sets the process exit code. No text line carries envelope,
10
+ // output or verification argv content.
11
+ //
12
+ // Order for every action: `--action`, then IC-001's flag rules (a flag on an action that does not
13
+ // take it is exit 2 `usage`) and every value, before any read; then the slug; then, for every
14
+ // action but `spawn`, IC-001's identity checks before any read for writing, git call, spawn or
15
+ // signal (DEC-044): `identity-solo` and `worker-session` for all of them, and for every action but
16
+ // `start` the dispatch run's existence and its stored `runKey` (`no-dispatch-run`, `foreign-run`).
17
+ // The module each action calls repeats the check under its own lock, so it cannot be raced.
18
+ //
19
+ // `start` is IC-002's steps 0 to 8: steps 1 to 6 hold no lock, the integration worktree is made
20
+ // outside every lock, and steps 7 and 8 (the store check again and the in-process 080 `start`) run
21
+ // inside one dispatch-store write (lock order dispatch then ledger, IC-006). A refusal at step 7 or
22
+ // 8 removes the integration worktree by `removeVerified` and carries its `cleanup`.
23
+ //
24
+ // Exits (IC-001): 0 for the applied and read dispositions; 1 with the JSON for `refused`, `stale`
25
+ // and `ack-required`; 2 with `{ ok: false, error, message }` for `usage`, `store-unreadable`,
26
+ // `plan-unreadable`, `plan-malformed` and `registry-invalid`; `run` interrupted by SIGINT or SIGTERM
27
+ // exits 130 or 143 as the loop reports. `step` and `run` exit 0 whenever the pass ran, whatever its
28
+ // outcome.
29
+ //
30
+ // This module makes no fs write call: the store is written through `store.ts`, the ledger by 080's
31
+ // `start`, the worktree by `integrate.ts`.
32
+ //
33
+ // Feature 106 A.2 (IC-005): `start` takes `--fallback off|ask|auto` and `--ladder <id[,id...]>`
34
+ // (registry harness ids, each once, at most 8, never with `--harness`) and stores `run.fallback`;
35
+ // without `--fallback` the `dispatch.fallback` setting applies (fail-safe `off`). `resume` takes
36
+ // `--fallback`: once resumed, `off` or `ask` sets the run's mode in one write, and `auto` accepts the
37
+ // pending proposals once (review C-14): it clears the `quota` holds and records each task in
38
+ // `run.fallback.accepted`, the run's mode unchanged; run-wide `auto` comes only from `start`.
39
+ // Feature 120 C.4 (IC-010): start gates enabled rungs and stores the ladder and in-session permission.
40
+
41
+ const fs = require('node:fs');
42
+ const path = require('node:path');
43
+ const attempts = require('../task-attempts.js');
44
+ const { withGitMemo } = require('../git-memo.js');
45
+ const { isGraphPlan } = require('../task-graph.js');
46
+ const { runKeyFor, identityRefusal } = require('../task-run.js');
47
+ const { isSafeSlug } = require('../task-scope.js');
48
+ const { projectRoot } = require('../lifecycle/root.js');
49
+ const { loadRegistry } = require('../../registry/index.js');
50
+ const { knownWorkerIds, LADDER_MAX } = require('../../registry/worker-ids.js');
51
+ const { REPO_ROOT } = require('../../helper/repo-root.js');
52
+ const readiness = require('../worker/readiness.js');
53
+ const { activeWorkers, workerGate, inSessionGate } = require('../worker/active.js');
54
+ const { storeFileFor, newDispatchRunId, readDispatch, mutateDispatch, fallbackOf, DispatchStoreError, DISPATCH_ID } = require('./store.js');
55
+ const { concurrencyOf } = require('./admission.js');
56
+ const { validateGrant, requestSpawn, spawnStatus } = require('./grants.js');
57
+ const { createIntegrationWorktree, managerFor } = require('./integrate.js');
58
+ const { argvDigest } = require('./verify-run.js');
59
+ const loop = require('./loop.js');
60
+ const controls = require('./controls.js');
61
+
62
+ const ACTIONS = Object.freeze(['start', 'step', 'run', 'submit', 'spawn', 'pause', 'inspect', 'cancel', 'takeover', 'resume']);
63
+ /** IC-001: the dispositions that exit 0; every other disposition exits 1 with the JSON. */
64
+ const EXIT_ZERO = Object.freeze([
65
+ 'started', 'stepped', 'ran', 'submitted', 'spawn-requested', 'spawn-status', 'paused', 'inspected', 'cancelled', 'taken-over', 'resumed',
66
+ ]);
67
+ const ERROR_CODES = Object.freeze(['usage', 'store-unreadable', 'plan-unreadable', 'plan-malformed', 'registry-invalid']);
68
+ const MODES = Object.freeze(['auto', 'sequential', 'manual']);
69
+ const SAFE_NAME = /^[A-Za-z0-9][A-Za-z0-9._ -]*$/;
70
+ const TASK_ID = /^[A-Za-z]+\.\d+$/;
71
+ const ACK = /^sha256:[0-9a-f]{64}$/;
72
+ const DIGITS = /^[0-9]+$/;
73
+ /** IC-001's `--request-id`: a child of one dispatch, `c1` to `c4` (DEC-051). */
74
+ const REQUEST_ID_FLAG = /^d-[0-9a-f]{12}\.c[1-4]$/;
75
+ const VERIFY_ARGV_MAX = 64;
76
+ const VERIFY_ARG_CHARS = 4096;
77
+ const VERIFY_SECONDS_DEFAULT = 600;
78
+ const VERIFY_SECONDS_MAX = 14400;
79
+ /** DEC-035: 081's backstop of `n + 120` stays within 081's 14400. */
80
+ const ATTEMPT_SECONDS_MAX = 14400 - loop.BACKSTOP_SECONDS;
81
+ const RUN_MAX_SECONDS_MAX = 14400;
82
+ /** IC-005's submit bounds, the same values `loop.submit` checks. */
83
+ const SUMMARY_BYTES = 8192;
84
+ const BLOCKERS_MAX = 20;
85
+ const BLOCKER_CHARS = 200;
86
+ const TOKEN_BASES = Object.freeze(['measured', 'estimated']);
87
+
88
+ /** IC-001: the actions each flag may be given to; any other action is exit 2 `usage`. */
89
+ const FLAG_ACTIONS = Object.freeze({
90
+ by: { flag: '--by', actions: ['start', 'step', 'run', 'pause', 'cancel', 'takeover', 'resume'] },
91
+ reason: { flag: '--reason', actions: ['pause', 'cancel', 'takeover', 'resume'] },
92
+ concurrency: { flag: '--concurrency', actions: ['start'] },
93
+ mode: { flag: '--mode', actions: ['start'] },
94
+ billingCap: { flag: '--billing-cap', actions: ['start'] },
95
+ verifyArgv: { flag: '--verify-argv', actions: ['start'] },
96
+ verifySeconds: { flag: '--verify-seconds', actions: ['start'] },
97
+ attemptSeconds: { flag: '--attempt-seconds', actions: ['start'] },
98
+ reserveTokens: { flag: '--reserve-tokens', actions: ['start', 'spawn'] },
99
+ budgetAttempts: { flag: '--budget-attempts', actions: ['start'] },
100
+ budgetTokens: { flag: '--budget-tokens', actions: ['start'] },
101
+ spawnGrant: { flag: '--spawn-grant', actions: ['start'] },
102
+ harness: { flag: '--harness', actions: ['start'] },
103
+ need: { flag: '--need', actions: ['start'] },
104
+ maxSeconds: { flag: '--max-seconds', actions: ['run'] },
105
+ dispatch: { flag: '--dispatch', actions: ['submit'] },
106
+ status: { flag: '--status', actions: ['submit'] },
107
+ summary: { flag: '--summary', actions: ['submit'] },
108
+ blocker: { flag: '--blocker', actions: ['submit'] },
109
+ tokens: { flag: '--tokens', actions: ['submit'] },
110
+ tokensBasis: { flag: '--tokens-basis', actions: ['submit'] },
111
+ promptFile: { flag: '--prompt-file', actions: ['spawn'] },
112
+ requestId: { flag: '--request-id', actions: ['spawn'] },
113
+ task: { flag: '--task', actions: ['takeover'] },
114
+ ack: { flag: '--ack', actions: ['resume'] },
115
+ fallback: { flag: '--fallback', actions: ['start', 'resume'] },
116
+ ladder: { flag: '--ladder', actions: ['start'] },
117
+ });
118
+
119
+
120
+
121
+ const usage = (message ) => new DispatchStoreError('usage', message);
122
+ const given = (value ) => value !== undefined && value !== null && value !== false;
123
+ const iso = (at ) => at.toISOString();
124
+
125
+ function refused(reason , extra = {}) {
126
+ return { disposition: 'refused', reason, ...extra };
127
+ }
128
+
129
+ /** A whole number from digits only (or an integer), within `[min, max]`; undefined when not given. */
130
+ function digits(value , flag , min , max = Number.MAX_SAFE_INTEGER) {
131
+ if (!given(value)) return undefined;
132
+ const text = String(value);
133
+ if (!DIGITS.test(text) || Number(text) < min || Number(text) > max) throw usage(`${flag} expects a whole number from ${min} to ${max}, got '${value}'`);
134
+ return Number(text);
135
+ }
136
+
137
+ function requireBy(by , action ) {
138
+ if (!given(by) || !SAFE_NAME.test(String(by))) throw usage(`--by is required for --action ${action}: the name of the agent or person acting`);
139
+ return String(by);
140
+ }
141
+
142
+ function requireReason(reason , action ) {
143
+ const text = typeof reason === 'string' ? reason.trim() : '';
144
+ if (!text) throw usage(`--reason is required for --action ${action}`);
145
+ return text;
146
+ }
147
+
148
+ function refuseMisplacedFlags(action , flags ) {
149
+ for (const [key, { flag, actions }] of Object.entries(FLAG_ACTIONS)) {
150
+ if (given(flags[key]) && !actions.includes(action)) throw usage(`${flag} does not apply to --action ${action}`);
151
+ }
152
+ }
153
+
154
+ function needList(need ) {
155
+ if (!given(need)) return [];
156
+ const items = Array.isArray(need) ? need : [need];
157
+ return [...new Set(items.flatMap((item ) => String(item).split(',')).map((item ) => item.trim()).filter(Boolean))];
158
+ }
159
+
160
+ /** DEC-024: `--verify-argv` as 1 to 64 strings, each 1 to 4096 characters without NUL; null when absent. */
161
+ function verifyArgvOf(value ) {
162
+ if (!given(value)) return null;
163
+ let argv = value;
164
+ if (typeof value === 'string') {
165
+ try { argv = JSON.parse(value); } catch { throw usage('--verify-argv is not valid JSON: it expects a JSON array of strings'); }
166
+ }
167
+ if (!Array.isArray(argv) || argv.length < 1 || argv.length > VERIFY_ARGV_MAX) throw usage(`--verify-argv expects a JSON array of 1 to ${VERIFY_ARGV_MAX} strings`);
168
+ for (const arg of argv) {
169
+ if (typeof arg !== 'string' || arg.length < 1 || Array.from(arg).length > VERIFY_ARG_CHARS || arg.includes('\u0000')) {
170
+ throw usage(`--verify-argv elements are strings of 1 to ${VERIFY_ARG_CHARS} characters without NUL`);
171
+ }
172
+ }
173
+ return [...argv];
174
+ }
175
+
176
+ /** The registry, loaded once; a validation failure is `registry-invalid`. */
177
+ function registryOf(registry ) {
178
+ if (registry) return registry;
179
+ try {
180
+ return loadRegistry({ repoRoot: REPO_ROOT });
181
+ } catch (error ) {
182
+ throw new DispatchStoreError('registry-invalid', error?.message || String(error));
183
+ }
184
+ }
185
+
186
+ /** 106 IC-005: `--fallback` as a mode, or null when absent. */
187
+ function fallbackFlag(value ) {
188
+ if (!given(value)) return null;
189
+ if (!loop.FALLBACK_MODES.includes(String(value))) throw usage(`--fallback expects ${loop.FALLBACK_MODES.join(', ')}, got '${value}'`);
190
+ return String(value);
191
+ }
192
+
193
+ /** 106 IC-005: `--ladder` as 1 to 8 distinct registry harness ids in order, or null when absent. */
194
+ function ladderOf(o ) {
195
+ if (!given(o.ladder)) return null;
196
+ if (given(o.harness)) throw usage('--ladder does not go with --harness: a --harness run is a one-rung ladder');
197
+ const ids = String(o.ladder).split(',').map((id ) => id.trim()).filter(Boolean);
198
+ if (ids.length < 1 || ids.length > LADDER_MAX) throw usage(`--ladder expects 1 to ${LADDER_MAX} comma-separated harness ids, got ${ids.length}`);
199
+ const known = knownWorkerIds({ registry: registryOf(o.registry) }).ids;
200
+ const unknown = ids.find((id ) => !known.includes(id));
201
+ if (unknown !== undefined) throw usage(`--ladder names unknown harness '${unknown}'; known: ${known.join(', ')}`);
202
+ if (new Set(ids).size !== ids.length) throw usage('--ladder names a harness more than once');
203
+ return ids;
204
+ }
205
+
206
+ /**
207
+ * 106 IC-005: the `dispatch.fallback` setting for the project at `root`, read the way `verify.reuse`
208
+ * is (089 IC-005). Only a valid resolved `off` or `ask` applies; an invalid value, a load error or any
209
+ * fault is the key's fail-safe `off`.
210
+ */
211
+ function fallbackSetting(root , env ) {
212
+ try {
213
+ const { loadSettings } = require('../settings/resolve.js');
214
+ const resolution = loadSettings({ projectRoot: root, env: { ...env } }).get('dispatch.fallback', 'project');
215
+ return resolution?.state === 'ok' && ['off', 'ask'].includes(resolution.value) ? resolution.value : 'off';
216
+ } catch {
217
+ return 'off';
218
+ }
219
+ }
220
+
221
+ /** IC-010: gate start flags and resolve the run's enabled ladder and in-session permission. */
222
+ function enabledStart(o , s , root , env ) {
223
+ const active = activeWorkers({ registry: registryOf(o.registry), env, projectRoot: root });
224
+ if (s.harness !== null) {
225
+ const gate = workerGate(active, s.harness);
226
+ if (gate) return { refusal: refused(gate.reason, { harness: gate.harness, enabled: gate.enabled, origin: gate.origin }) };
227
+ }
228
+ if (s.ladder !== null) {
229
+ for (const id of s.ladder) {
230
+ const gate = workerGate(active, id);
231
+ if (gate) return { refusal: refused(gate.reason, { harness: gate.harness, enabled: gate.enabled, origin: gate.origin }) };
232
+ }
233
+ }
234
+ if (s.mode === 'sequential') {
235
+ const gate = inSessionGate(active);
236
+ if (gate) return { refusal: refused(gate.reason, { text: gate.text, enabled: [...active.workers], origin: active.origin }) };
237
+ }
238
+ const stored = active.state !== 'unset';
239
+ let ladder = s.ladder;
240
+ if (ladder === null) {
241
+ if (s.harness !== null) ladder = stored ? [s.harness] : null;
242
+ else ladder = stored ? [...active.workers] : null;
243
+ }
244
+ return { ladder, inSession: active.inSession, stored };
245
+ }
246
+
247
+ /** IC-002 step 0: every start flag, validated and normalized. */
248
+ function startSettings(o ) {
249
+ const by = requireBy(o.by, 'start');
250
+ const concurrency = concurrencyOf(given(o.concurrency) ? (typeof o.concurrency === 'number' ? o.concurrency : String(o.concurrency)) : null);
251
+ const mode = given(o.mode) ? String(o.mode) : 'auto';
252
+ if (!MODES.includes(mode)) throw usage(`--mode expects ${MODES.join(', ')}, got '${o.mode}'`);
253
+ const billingCap = given(o.billingCap) ? String(o.billingCap) : null;
254
+ if (billingCap !== null && billingCap !== 'free') throw usage(`--billing-cap expects free, got '${billingCap}'`);
255
+ const argv = verifyArgvOf(o.verifyArgv);
256
+ const verifySeconds = digits(o.verifySeconds, '--verify-seconds', 1, VERIFY_SECONDS_MAX) ?? VERIFY_SECONDS_DEFAULT;
257
+ const attemptSeconds = digits(o.attemptSeconds, '--attempt-seconds', 1, ATTEMPT_SECONDS_MAX) ?? loop.ATTEMPT_SECONDS_DEFAULT;
258
+ const reserveTokens = digits(o.reserveTokens, '--reserve-tokens', 0) ?? null;
259
+ const budgetAttempts = digits(o.budgetAttempts, '--budget-attempts', 1);
260
+ const budgetTokens = digits(o.budgetTokens, '--budget-tokens', 1);
261
+ if (budgetTokens !== undefined && (reserveTokens === null || reserveTokens < 1)) {
262
+ throw usage('--budget-tokens needs --reserve-tokens of at least 1: every claim reserves tokens against the budget');
263
+ }
264
+ const spawnGrant = given(o.spawnGrant) ? validateGrant(o.spawnGrant, reserveTokens) : null;
265
+ const needs = needList(o.need);
266
+ let harness = null;
267
+ if (given(o.harness) || needs.length) {
268
+ const reg = registryOf(o.registry);
269
+ if (given(o.harness)) {
270
+ harness = String(o.harness);
271
+ const ids = knownWorkerIds({ registry: reg }).ids;
272
+ if (!ids.includes(harness)) throw usage(`unknown harness '${harness}'; known: ${ids.join(', ')}`);
273
+ }
274
+ if (needs.length) readiness.validateNeeds(needs, o.coverage === undefined ? reg.coverage : o.coverage);
275
+ }
276
+ const fallback = fallbackFlag(o.fallback);
277
+ const ladder = ladderOf(o);
278
+ return { by, concurrency, mode, billingCap, argv, verifySeconds, attemptSeconds, reserveTokens, budgetAttempts, budgetTokens, spawnGrant, needs, harness, fallback, ladder };
279
+ }
280
+
281
+ /**
282
+ * IC-005 step 0's `--summary`, `--blocker` and `--tokens`/`--tokens-basis` values, checked here so
283
+ * a malformed submit is `usage` before any store read (IC-001). `loop.submit` repeats them for
284
+ * in-process callers.
285
+ */
286
+ function submitValues(o ) {
287
+ const summary = o.summary;
288
+ if (typeof summary !== 'string' || !summary.length || Buffer.byteLength(summary, 'utf8') > SUMMARY_BYTES) throw usage(`--summary expects 1 to ${SUMMARY_BYTES} bytes`);
289
+ const blockers = given(o.blocker) ? [].concat(o.blocker).map(String) : [];
290
+ if (blockers.length > BLOCKERS_MAX || blockers.some((b) => !b.length || Array.from(b).length > BLOCKER_CHARS)) throw usage(`--blocker is repeatable up to ${BLOCKERS_MAX}, each 1 to ${BLOCKER_CHARS} characters`);
291
+ if (given(o.tokens) !== given(o.tokensBasis)) throw usage('--tokens and --tokens-basis go together or not at all');
292
+ if (given(o.tokens) && (!DIGITS.test(String(o.tokens)) || !TOKEN_BASES.includes(String(o.tokensBasis)))) throw usage('--tokens expects a whole number and --tokens-basis measured or estimated');
293
+ }
294
+
295
+ /** IC-001 step 0 for every action but start: required flags and their values. */
296
+ function actionFlags(action , o ) {
297
+ switch (action) {
298
+ case 'step': if (given(o.by)) requireBy(o.by, action); break;
299
+ case 'run':
300
+ if (given(o.by)) requireBy(o.by, action);
301
+ digits(o.maxSeconds, '--max-seconds', 1, RUN_MAX_SECONDS_MAX);
302
+ break;
303
+ case 'submit':
304
+ if (!given(o.dispatch) || !DISPATCH_ID.test(String(o.dispatch))) throw usage(`--dispatch expects 'd-' plus 12 hex characters, got '${o.dispatch ?? ''}'`);
305
+ if (o.status !== 'success' && o.status !== 'failure') throw usage('--status expects success or failure');
306
+ submitValues(o);
307
+ break;
308
+ case 'spawn': {
309
+ const request = given(o.promptFile) || given(o.reserveTokens);
310
+ if (request && given(o.requestId)) throw usage('--request-id reads one request; it does not go with --prompt-file or --reserve-tokens');
311
+ if (given(o.requestId)) {
312
+ if (!REQUEST_ID_FLAG.test(String(o.requestId))) throw usage(`--request-id expects '<d-id>.c<n>', got '${o.requestId}'`);
313
+ } else {
314
+ if (!given(o.promptFile)) throw usage('--action spawn needs --prompt-file and --reserve-tokens, or --request-id');
315
+ if (digits(o.reserveTokens, '--reserve-tokens', 0) === undefined) throw usage('--action spawn needs --reserve-tokens with --prompt-file');
316
+ }
317
+ break;
318
+ }
319
+ case 'pause': case 'cancel':
320
+ requireBy(o.by, action);
321
+ requireReason(o.reason, action);
322
+ break;
323
+ case 'takeover':
324
+ requireBy(o.by, action);
325
+ requireReason(o.reason, action);
326
+ if (!given(o.task) || !TASK_ID.test(String(o.task))) throw usage(`--task expects a plan task id such as A.1, got '${o.task ?? ''}'`);
327
+ break;
328
+ case 'resume':
329
+ if (given(o.ack) && !ACK.test(String(o.ack))) throw usage(`--ack expects sha256:<64 hex>, got '${o.ack}'`);
330
+ if (given(o.ack)) requireBy(o.by, action);
331
+ else if (given(o.by)) requireBy(o.by, action);
332
+ if (given(o.reason)) requireReason(o.reason, action);
333
+ fallbackFlag(o.fallback);
334
+ break;
335
+ default: break;
336
+ }
337
+ }
338
+
339
+ function featureOf(slug , resolveSlug , cwd ) {
340
+ const feature = given(slug) ? String(slug) : (resolveSlug ? resolveSlug(cwd) : null);
341
+ if (!feature) throw usage('--slug is required: no feature could be resolved from the current branch');
342
+ if (!isSafeSlug(feature)) throw usage(`'${feature}' is not a valid feature slug`);
343
+ return feature;
344
+ }
345
+
346
+ /** One bounded git call through the integration manager's helper; `{ ok, stdout, code }`. */
347
+ function gitAt(root , slug , env , cwd , args ) {
348
+ return managerFor(root, slug, 'integration', env).git(args, cwd, { allowFailure: true });
349
+ }
350
+
351
+ /** IC-002: steps 1 to 8. `s` is startSettings' result. */
352
+ function start(o , s ) {
353
+ const { env, cwd, slug, clock } = o;
354
+ // Step 1.
355
+ const key = runKeyFor(env, cwd);
356
+ const who = identityRefusal(key.identity);
357
+ if (who) return refused(who.reason, { runKey: key.runKey });
358
+ const root = projectRoot(cwd);
359
+ const enabled = enabledStart(o, s, root, env);
360
+ if (enabled.refusal) return enabled.refusal;
361
+ // Step 2.
362
+ const roots = [...new Set([cwd, root])];
363
+ const plan = attempts.loadPlan({ roots, slug, fsImpl: fs });
364
+ // Step 3 (DEC-034).
365
+ if (!isGraphPlan(plan.graph)) return refused('not-graph-plan', { slug });
366
+ // Step 4.
367
+ const top = key.top;
368
+ const inside = fs.existsSync(top) ? gitAt(root, slug, env, top, ['rev-parse', '--is-inside-work-tree']) : { ok: false, stdout: '' };
369
+ if (!inside.ok || inside.stdout.trim() !== 'true') return refused('not-a-git-worktree', { top });
370
+ const head = gitAt(root, slug, env, top, ['rev-parse', '--verify', '-q', 'HEAD^{commit}']);
371
+ if (!head.ok || !head.stdout.trim()) return refused('no-head', { top });
372
+ const base = head.stdout.trim();
373
+ const status = gitAt(root, slug, env, top, ['status', '--porcelain=v1', '-z', '--untracked-files=all']);
374
+ const baseDirty = !status.ok || status.stdout.length > 0;
375
+ // Step 5.
376
+ const file = storeFileFor(root, slug);
377
+ const seen = readDispatch(file);
378
+ if (seen.run) return refused('dispatch-run-exists', { dispatchRunId: seen.run.dispatchRunId, state: seen.run.state });
379
+ // The cap names the user's billing record it judges by, so the owner sees what counts as free.
380
+ const billingRecord = s.billingCap ? billingRecordOf(env, registryOf(o.registry)) : null;
381
+ // Step 6: outside every lock.
382
+ const dispatchRunId = newDispatchRunId();
383
+ let integration ;
384
+ try {
385
+ integration = createIntegrationWorktree({ root, slug, dispatchRunId, base, env });
386
+ } catch (error ) {
387
+ const code = String(error?.exitCode ?? error?.status ?? error?.signal ?? error?.code ?? 'unknown');
388
+ return refused('integration-worktree-failed', { code });
389
+ }
390
+ const verify = s.argv
391
+ ? { argv: s.argv, argvSha256: argvDigest(s.argv), argvCount: s.argv.length, timeoutSeconds: s.verifySeconds }
392
+ : { argv: null, argvSha256: null, argvCount: 0, timeoutSeconds: s.verifySeconds };
393
+ const remove = () => managerFor(root, slug, 'integration', env).removeVerified(dispatchRunId);
394
+ // 106 IC-005: `--fallback` wins for the run, else the setting.
395
+ const { ladder, inSession, stored } = enabled;
396
+ const fallback = { mode: s.fallback ?? fallbackSetting(root, env), ladder, ...(stored ? { inSession } : {}), exhausted: [], holds: {}, events: [], eventsDropped: 0 };
397
+ // Steps 7 and 8: one dispatch-store write, the 080 start inside it.
398
+ let outcome ;
399
+ try {
400
+ outcome = mutateDispatch(file, (store ) => {
401
+ if (store.run) return { write: false, result: refused('dispatch-run-exists', { dispatchRunId: store.run.dispatchRunId, state: store.run.state }) };
402
+ const now = clock();
403
+ const res = attempts.start({
404
+ file: attempts.storeFile(root, slug), fsImpl: fs, roots, slug, by: s.by,
405
+ budgetAttempts: s.budgetAttempts, budgetTokens: s.budgetTokens, env, cwd, now,
406
+ });
407
+ if (res.disposition !== 'started') return { write: false, result: res };
408
+ store.run = {
409
+ dispatchRunId, slug, state: 'running', runKey: key.runKey,
410
+ run: key.identity.run, lineage: key.identity.lineage, lineageKind: key.identity.lineageKind,
411
+ epoch: res.epoch, by: s.by, concurrency: s.concurrency, mode: s.mode,
412
+ ...(s.billingCap ? { billingCap: s.billingCap } : {}),
413
+ reserveTokens: s.reserveTokens, attemptSeconds: s.attemptSeconds, harness: s.harness, needs: s.needs,
414
+ spawnGrant: s.spawnGrant, baseDirty, createdAt: iso(now), endedAt: null,
415
+ integration: { worktree: integration.worktree, branch: integration.branch, base: integration.base, head: integration.head },
416
+ verify, fallback,
417
+ };
418
+ return { write: true, result: { started: true, epoch: res.epoch, runKey: res.runKey } };
419
+ });
420
+ } catch (error) {
421
+ remove();
422
+ throw error;
423
+ }
424
+ if (!outcome.started) return { ...outcome, cleanup: remove() };
425
+ return {
426
+ disposition: 'started', dispatchRunId, slug, runKey: key.runKey, epoch: outcome.epoch,
427
+ integration: { worktree: integration.worktree, branch: integration.branch, base: integration.base },
428
+ baseDirty, concurrency: s.concurrency, mode: s.mode,
429
+ verification: { declared: verify.argv !== null, argc: verify.argvCount, sha256: verify.argvSha256, seconds: verify.timeoutSeconds },
430
+ reserveTokens: s.reserveTokens, attemptSeconds: s.attemptSeconds, workerTimeoutSeconds: s.attemptSeconds + loop.BACKSTOP_SECONDS,
431
+ spawnGrant: s.spawnGrant,
432
+ ...(billingRecord ? { billingRecord } : {}),
433
+ };
434
+ }
435
+
436
+ /** The `start` body's `billingRecord`: the record's state and reason, and one row per recorded worker. */
437
+ function billingRecordOf(env , reg ) {
438
+ const { readWorkerBilling } = require('../settings/worker-billing.js'); // lazy: see loop.ts evaluateHarnesses
439
+ const record = readWorkerBilling(env, { workerIds: knownWorkerIds({ registry: reg }).ids });
440
+ return {
441
+ state: record.state, reason: record.reason,
442
+ workers: Object.entries(record.workers).map(([harness, w] ) => ({
443
+ harness, billing: w.billing, asserted: w.asserted, ...(w.basis !== undefined ? { basis: w.basis } : {}),
444
+ })),
445
+ };
446
+ }
447
+
448
+ /** The loop's and controls' shared options: identity inputs, clock and the in-process test seams. */
449
+ function passOptions(o ) {
450
+ const out = { slug: o.slug, env: o.env, cwd: o.cwd, now: o.clock };
451
+ for (const seam of ['platform', 'descriptors', 'registry', 'coverage', 'evaluateImpl', 'verify', 'sleep', 'fallbackDefault']) {
452
+ if (o[seam] !== undefined) out[seam] = o[seam];
453
+ }
454
+ return out;
455
+ }
456
+
457
+ /**
458
+ * 106 IC-005: after an applied resume with `--fallback`, one locked write (creating `run.fallback`
459
+ * for a run started before 106, with the setting's mode). `off` or `ask` sets `run.fallback.mode`
460
+ * and clears `run.fallback.accepted` (review R-2).
461
+ * `auto` accepts once (review C-14): every hold whose reason is `quota` is deleted and its task
462
+ * recorded in `run.fallback.accepted` with its proposal; the mode is unchanged, and the task's next
463
+ * selection steps once. `frontier-no-step` and `ladder-exhausted` holds stay. Returns
464
+ * `{ mode, cleared }`, `mode` the run's mode after the write.
465
+ */
466
+ function setFallbackMode(o , mode ) {
467
+ const initial = mode === 'auto' ? fallbackSetting(projectRoot(o.cwd), o.env) : mode;
468
+ return mutateDispatch(storeFileFor(projectRoot(o.cwd), o.slug), (s ) => {
469
+ if (!s.run) return { write: false, result: { mode: initial, cleared: [] } };
470
+ const f = fallbackOf(s.run, initial);
471
+ const cleared = [];
472
+ if (mode !== 'auto') {
473
+ f.mode = mode;
474
+ // Review R-2: `off` or `ask` revokes every pending acceptance.
475
+ if (f.accepted && typeof f.accepted === 'object') f.accepted = {};
476
+ }
477
+ else if (f.holds && typeof f.holds === 'object') {
478
+ if (!f.accepted || typeof f.accepted !== 'object') f.accepted = {};
479
+ for (const [task, hold] of Object.entries(f.holds) ) {
480
+ if (!hold || hold.reason !== 'quota') continue;
481
+ f.accepted[task] = hold.proposed ?? null;
482
+ delete f.holds[task];
483
+ cleared.push(task);
484
+ }
485
+ }
486
+ return { write: true, result: { mode: f.mode, cleared } };
487
+ });
488
+ }
489
+
490
+ async function perform(action , o ) {
491
+ const base = passOptions(o);
492
+ switch (action) {
493
+ case 'step': return loop.step({ ...base, by: given(o.by) ? String(o.by) : undefined });
494
+ case 'run': return loop.run({ ...base, by: given(o.by) ? String(o.by) : undefined, maxSeconds: digits(o.maxSeconds, '--max-seconds', 1, RUN_MAX_SECONDS_MAX) });
495
+ case 'submit': return loop.submit({
496
+ ...base, dispatchId: String(o.dispatch), status: o.status, summary: o.summary,
497
+ blockers: given(o.blocker) ? [].concat(o.blocker) : undefined,
498
+ tokens: given(o.tokens) ? String(o.tokens) : undefined, tokensBasis: given(o.tokensBasis) ? String(o.tokensBasis) : undefined,
499
+ });
500
+ case 'pause': return controls.pause({ ...base, by: o.by, reason: o.reason });
501
+ case 'inspect': {
502
+ const body = controls.inspect(base);
503
+ // 106 IC-005: the run's fallback state, as stored.
504
+ if (body && body.disposition === 'inspected') body.fallback = readDispatch(storeFileFor(projectRoot(o.cwd), o.slug)).run?.fallback ?? null;
505
+ return body;
506
+ }
507
+ case 'cancel': return controls.cancel({ ...base, by: o.by, reason: o.reason });
508
+ case 'takeover': return controls.takeOver({ ...base, by: o.by, reason: o.reason, task: String(o.task) });
509
+ case 'resume': {
510
+ const body = await controls.resume({ ...base, by: given(o.by) ? o.by : undefined, reason: given(o.reason) ? o.reason : undefined, ack: given(o.ack) ? String(o.ack) : undefined });
511
+ const mode = fallbackFlag(o.fallback);
512
+ if (mode !== null && body && body.disposition === 'resumed') body.fallback = setFallbackMode(o, mode);
513
+ return body;
514
+ }
515
+ default: throw usage(`unknown action '${action}'`);
516
+ }
517
+ }
518
+
519
+ /**
520
+ * IC-001: the `dispatch` verb. Options mirror the flags (camelCase: `action`, `slug`, `by`,
521
+ * `reason`, `concurrency`, `mode`, `billingCap`, `verifyArgv`, `verifySeconds`, `attemptSeconds`, `reserveTokens`,
522
+ * `budgetAttempts`, `budgetTokens`, `spawnGrant`, `harness`, `need`, `maxSeconds`, `dispatch`,
523
+ * `status`, `summary`, `blocker`, `tokens`, `tokensBasis`, `promptFile`, `requestId`, `task`,
524
+ * `ack`, `fallback`, `ladder`), plus `env`, `cwd`, `resolveSlug(cwd)`, `now`, and the in-process seams `platform`,
525
+ * `descriptors`, `registry`, `coverage`, `evaluateImpl`, `verify`, `sleep`. Prints nothing.
526
+ */
527
+ async function executeDispatchCommand(options = {}) {
528
+ // One call, one git memo scope (git-memo.ts): the project root and common directory are asked once.
529
+ return withGitMemo(() => runDispatchCommand(options));
530
+ }
531
+
532
+ async function runDispatchCommand(options ) {
533
+ const env = options.env || process.env;
534
+ const cwd = path.resolve(options.cwd || process.cwd());
535
+ const now = options.now;
536
+ const clock = typeof now === 'function'
537
+ ? () => { const at = now(); return at instanceof Date ? at : new Date(at); }
538
+ : (now !== undefined && now !== null ? () => new Date(now) : () => new Date());
539
+ try {
540
+ const { action } = options;
541
+ if (!given(action)) throw usage(`--action is required; valid: ${ACTIONS.join(' | ')}`);
542
+ if (!ACTIONS.includes(action)) throw usage(`unknown action '${action}'; valid: ${ACTIONS.join(' | ')}`);
543
+ refuseMisplacedFlags(action, options);
544
+ const settings = action === 'start' ? startSettings(options) : (actionFlags(action, options), null);
545
+ const slug = featureOf(options.slug, options.resolveSlug, cwd);
546
+ const o = { ...options, env, cwd, slug, clock };
547
+
548
+ let body ;
549
+ if (action === 'start') body = start(o, settings);
550
+ else if (action === 'spawn') {
551
+ body = given(options.requestId)
552
+ ? spawnStatus({ slug, requestId: String(options.requestId), env, cwd })
553
+ : requestSpawn({ slug, promptFile: String(options.promptFile), reserveTokens: String(options.reserveTokens), env, cwd, now: clock() });
554
+ } else {
555
+ // IC-001 identity checks (DEC-044), before anything the action reads for writing, runs or signals.
556
+ const caller = loop.checkCaller({ env, cwd, file: storeFileFor(projectRoot(cwd), slug) });
557
+ // 106 IC-004 (D3): a run started before 106 has no `run.fallback`; the loop reads it with the setting.
558
+ if (!caller.refusal && (action === 'step' || action === 'run') && !caller.store.run.fallback && o.fallbackDefault === undefined) {
559
+ o.fallbackDefault = fallbackSetting(projectRoot(cwd), env);
560
+ }
561
+ body = caller.refusal ? caller.refusal : await perform(action, o);
562
+ }
563
+ if (body && body.disposition === 'ran' && Number.isInteger(body.exit)) return { exit: body.exit, body };
564
+ return { exit: body && EXIT_ZERO.includes(body.disposition) ? 0 : 1, body };
565
+ } catch (error ) {
566
+ const code = typeof error?.code === 'string' ? error.code : null;
567
+ if (code && ERROR_CODES.includes(code)) return { exit: 2, body: { ok: false, error: code, message: error.message } };
568
+ throw error;
569
+ }
570
+ }
571
+
572
+ /** 106 IC-004 / FR-005: one `fallback <kind> <task> <from> -> <to|none> (...)` line per fallback StepAction. */
573
+ function fallbackLines(actions ) {
574
+ return (Array.isArray(actions) ? actions : []).filter((a ) => a && a.action === 'fallback')
575
+ .map((a ) => `fallback ${String(a.reason).replace(/^([a-z]+):/, '$1 ')}`);
576
+ }
577
+
578
+ /** One summary line per result (plus one line per fallback event for step and run); inspect uses
579
+ * IC-029's lines. No envelope, output or argv content. */
580
+ function summaryLines(body ) {
581
+ const d = body.disposition;
582
+ switch (d) {
583
+ case 'started': {
584
+ const v = body.verification;
585
+ const record = body.billingRecord;
586
+ const recorded = !record ? [] : record.state === 'invalid'
587
+ ? [`billing: user record invalid (${record.reason}): no worker counts as free`]
588
+ : record.workers.map((w ) => `billing: ${w.harness} ${w.billing} (user record, asserted ${w.asserted})`);
589
+ return [`started ${body.dispatchRunId} slug ${body.slug} branch ${body.integration.branch} base ${body.integration.base}${body.baseDirty ? ' (caller checkout dirty)' : ''} concurrency ${body.concurrency} mode ${body.mode} verification ${v.declared ? `argc ${v.argc} sha256 ${v.sha256}` : 'undeclared'}`, ...recorded];
590
+ }
591
+ case 'stepped': {
592
+ const extra = body.perform ? ` perform ${body.perform.dispatchId} ${body.perform.task} token ${body.perform.token}` : '';
593
+ return [`stepped ${body.dispatchRunId} step ${body.step ?? '-'} outcome ${body.outcome} actions ${body.actions.length}${extra}`, ...fallbackLines(body.actions)];
594
+ }
595
+ case 'ran': return [`ran ${body.dispatchRunId} steps ${body.steps} outcome ${body.outcome}${body.timeUp ? ' time-up' : ''}${body.signal ? ` signal ${body.signal}` : ''}`, ...fallbackLines(body.fallback)];
596
+ case 'submitted': return [`submitted ${body.dispatchId} task ${body.task} token ${body.token} phase ${body.phase} changes ${body.changes.length}`];
597
+ case 'spawn-requested': return [`spawn-requested ${body.requestId} ${body.state}`];
598
+ case 'spawn-status': return [`spawn-status ${body.requestId} ${body.state}${body.childRunId ? ` child ${body.childRunId}` : ''}`];
599
+ case 'paused': return [`paused ${body.dispatchRunId} active ${Array.isArray(body.active) ? body.active.length : body.active ?? 0}`];
600
+ case 'inspected': {
601
+ // Review C-10: one line per fallback hold after IC-029's lines.
602
+ const holds = body.fallback && body.fallback.holds && typeof body.fallback.holds === 'object' ? body.fallback.holds : {};
603
+ return [...controls.inspectLines(body), ...Object.entries(holds).map(([task, h] ) => `hold ${task} ${h.reason} from ${h.from} proposed ${h.proposed ?? 'none'}`)];
604
+ }
605
+ case 'cancelled': return [`cancelled ${body.dispatchRunId} cleanupVerified ${body.cleanupVerified}`];
606
+ case 'taken-over': return [`taken-over ${body.task} token ${body.token} dispatch ${body.dispatchId}`];
607
+ case 'resumed': return [`resumed epoch ${body.epoch} settled ${body.settled.length} orphans ${body.orphans.length}${body.fallback ? ` fallback ${body.fallback.mode} cleared ${body.fallback.cleared.length}` : ''}`];
608
+ case 'ack-required': return [`ack-required ${body.digest} settled ${Array.isArray(body.settled) ? body.settled.length : 0}`];
609
+ default: return [`${d ?? 'refused'} ${body.reason ?? ''}`.trim()];
610
+ }
611
+ }
612
+
613
+ /**
614
+ * `doflow-run dispatch --action <action> ...` (IC-001). Takes `executeDispatchCommand`'s options
615
+ * plus `json`. Prints the JSON with `json`, else one summary line (inspect: one per dispatch, then
616
+ * the run lines); a usage or environment error goes to stderr. Sets `process.exitCode` and resolves
617
+ * the exit code.
618
+ */
619
+ async function handleDispatchCommand(options = {}) {
620
+ const { finishRuntime } = require('../cli-result.js');
621
+ const { exit, body } = await executeDispatchCommand(options);
622
+ if (options.json) console.log(JSON.stringify(body, null, 2));
623
+ else if (exit === 2) console.error(`doflow dispatch: ${body.message}`);
624
+ else for (const line of summaryLines(body)) (exit === 0 ? console.log : console.error)(line);
625
+ return finishRuntime(exit);
626
+ }
627
+
628
+ module.exports = { handleDispatchCommand, executeDispatchCommand, summaryLines, fallbackSetting, ACTIONS, FLAG_ACTIONS, EXIT_ZERO, ERROR_CODES };