@iowarp/clio-coder 0.3.6 → 0.3.7

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 (258) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +16 -5
  3. package/dist/{acp-2BEHC4DL.js → acp-SK4MD6MM.js} +10 -10
  4. package/dist/{agents-LNNFTM53.js → agents-2FN2K6ME.js} +30 -25
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{auth-KXXFI2VS.js → auth-QIYZWM5I.js} +13 -13
  7. package/dist/{chunk-KHSFENX2.js → chunk-3HAPLH5M.js} +10 -10
  8. package/dist/{chunk-24I7BN55.js → chunk-465YSENW.js} +2 -2
  9. package/dist/{chunk-4OC57DA6.js → chunk-4DGYLA73.js} +53 -2
  10. package/dist/{chunk-CYQKWTG3.js → chunk-4DWFMQDR.js} +4 -4
  11. package/dist/{chunk-E2ER4LJF.js → chunk-5C3AQNDW.js} +25 -1
  12. package/dist/{chunk-22NAGB7X.js → chunk-5C77SEEY.js} +5 -94
  13. package/dist/{chunk-43AOLP7E.js → chunk-5FR74PWO.js} +2 -1
  14. package/dist/{chunk-K7T3E2SR.js → chunk-5UJ6ECTS.js} +10 -9
  15. package/dist/{chunk-6US73PDB.js → chunk-6M7VS3J3.js} +5 -5
  16. package/dist/{chunk-CJUB2JJ2.js → chunk-6TUKSZVF.js} +5 -5
  17. package/dist/{chunk-5JGRAMKL.js → chunk-AB4XIIVB.js} +8 -6
  18. package/dist/{chunk-R46L2BIR.js → chunk-BMWK7ZIZ.js} +14 -20
  19. package/dist/{chunk-4BPJXDWC.js → chunk-C4JBQ5SR.js} +30 -14
  20. package/dist/{chunk-XXQNGV4M.js → chunk-CEYBNUGC.js} +243 -63
  21. package/dist/{chunk-VEZEGCGW.js → chunk-D4MDIG46.js} +20 -18
  22. package/dist/chunk-DJNLUABN.js +843 -0
  23. package/dist/{chunk-RY3LY4J5.js → chunk-DMD2AGVS.js} +5 -4
  24. package/dist/{chunk-KOHPCX4K.js → chunk-DOOEX22V.js} +2 -2
  25. package/dist/chunk-DQA7QLMD.js +123 -0
  26. package/dist/chunk-DR52UMZW.js +21 -0
  27. package/dist/{chunk-XF5N4U5A.js → chunk-EBEFWSGL.js} +6 -5
  28. package/dist/{chunk-EYPA3EGJ.js → chunk-EELBMBT6.js} +120 -13
  29. package/dist/{chunk-CKXWIANG.js → chunk-EOOQZZDE.js} +16 -14
  30. package/dist/{chunk-WR67VIZY.js → chunk-FOT2FX5J.js} +63 -5
  31. package/dist/{chunk-FYYLNIL5.js → chunk-GH5622CP.js} +2 -2
  32. package/dist/chunk-GWS3VEIW.js +195 -0
  33. package/dist/{chunk-LYF7OHWH.js → chunk-J7PIKKWC.js} +8 -463
  34. package/dist/{chunk-NILBFAPG.js → chunk-JNXPYBB4.js} +2 -2
  35. package/dist/{chunk-4VP4KH3K.js → chunk-JRIO5UD2.js} +4 -4
  36. package/dist/{chunk-6XXKFVSN.js → chunk-JTSEDYVQ.js} +7 -7
  37. package/dist/{chunk-QKMUKYO7.js → chunk-KCMKRQX4.js} +236 -84
  38. package/dist/chunk-KZ2H5X4G.js +1026 -0
  39. package/dist/{chunk-QNQHSOLF.js → chunk-LADCF22A.js} +12 -12
  40. package/dist/chunk-M4AKACEO.js +382 -0
  41. package/dist/{chunk-XYDYPRZI.js → chunk-MXI6J5JF.js} +7 -7
  42. package/dist/{chunk-G7MUEIGA.js → chunk-OB5HIGJY.js} +1 -1
  43. package/dist/{chunk-EKY57CSP.js → chunk-OBMAI2DP.js} +61 -767
  44. package/dist/chunk-PD3MESLB.js +242 -0
  45. package/dist/{chunk-ZRGEBJ4T.js → chunk-QCTRSGHQ.js} +21 -21
  46. package/dist/chunk-RVG5JXAL.js +41 -0
  47. package/dist/{chunk-RD5U66HV.js → chunk-SROCI7ZU.js} +7 -7
  48. package/dist/{chunk-MFFY33HR.js → chunk-THKY7CD7.js} +466 -205
  49. package/dist/{chunk-PCZJO5TI.js → chunk-UFQ3F4FW.js} +13 -178
  50. package/dist/{chunk-AD2SYQYC.js → chunk-UHXRNZ2J.js} +121 -3
  51. package/dist/chunk-UND3GU2L.js +103 -0
  52. package/dist/{chunk-QM3F2GKX.js → chunk-UUANF5CR.js} +2247 -2096
  53. package/dist/chunk-UVDSQ6LW.js +472 -0
  54. package/dist/{chunk-DJVECN66.js → chunk-VQNODYQ4.js} +14 -14
  55. package/dist/{chunk-3BPUFZDL.js → chunk-VREKEFLL.js} +3 -3
  56. package/dist/{chunk-PBTHKCPN.js → chunk-WJHBC77E.js} +6 -6
  57. package/dist/{chunk-XE2VEJHX.js → chunk-X2KV5FXT.js} +2 -2
  58. package/dist/{chunk-ZXF4XRKW.js → chunk-XEGB6BCN.js} +157 -7
  59. package/dist/{chunk-E25LMLRW.js → chunk-YD734TPH.js} +2 -2
  60. package/dist/{verifiers-NCBTHHN2.js → chunk-YTYFXUI3.js} +65 -322
  61. package/dist/{chunk-OH3TOQTB.js → chunk-ZGH7FGS5.js} +13 -7
  62. package/dist/cli/index.js +32 -30
  63. package/dist/{clio-M2KGYUFZ.js → clio-WBVQEBKO.js} +7 -7
  64. package/dist/{code-nav-GQNL7XA6.js → code-nav-FGGFIE7L.js} +3 -3
  65. package/dist/{components-5TTYYX6G.js → components-F7OEATSO.js} +4 -4
  66. package/dist/{config-XUUYQIWO.js → config-TRBL3RCF.js} +34 -29
  67. package/dist/{configure-IHJ7YOMV.js → configure-OLCVPHNM.js} +15 -15
  68. package/dist/{context-74JLXAWD.js → context-MJIJ6GOX.js} +11 -11
  69. package/dist/{context-ZQ7SIFJV.js → context-WFPKQSM6.js} +19 -3
  70. package/dist/{context-75MIWW3U.js → context-XEWE3MOJ.js} +31 -26
  71. package/dist/{context-clear-GYKWNUML.js → context-clear-KNOS2JPB.js} +31 -26
  72. package/dist/{context-working-set-UX5KEP4J.js → context-working-set-EUXAZI6N.js} +8 -8
  73. package/dist/{dispatch-runner-GIJBHNFL.js → dispatch-runner-B7MTOVKL.js} +313 -53
  74. package/dist/{docs-6FZSCG5B.js → docs-FLJTIDSE.js} +4 -4
  75. package/dist/{doctor-SVJ5BZCW.js → doctor-RN4YKO2X.js} +14 -14
  76. package/dist/{eval-CG6LLBLD.js → eval-RUBJVSNQ.js} +8 -7
  77. package/dist/{evidence-ZYFIEN42.js → evidence-JZNBUOQZ.js} +30 -25
  78. package/dist/{evolve-QGEXEMDW.js → evolve-FJVC4KKI.js} +30 -25
  79. package/dist/{extensions-ADGNCJJD.js → extensions-IQL36S7K.js} +4 -4
  80. package/dist/{fleet-S5R4ZOQY.js → fleet-BDKYJFCP.js} +214 -360
  81. package/dist/fleet-commands-ZFIWZSB3.js +70 -0
  82. package/dist/fleet-graph-Y6HPXIVF.js +125 -0
  83. package/dist/fleet-new-RDVJLHHH.js +48 -0
  84. package/dist/fleet-validate-BIYREGIK.js +79 -0
  85. package/dist/{init-5DRU55YR.js → init-LQUB5COQ.js} +44 -37
  86. package/dist/library-NJAHIGG4.js +217 -0
  87. package/dist/{memory-7YKKR6UC.js → memory-OG6HOYKM.js} +31 -26
  88. package/dist/{models-ZPOLRU2C.js → models-5ZG5XY7J.js} +21 -20
  89. package/dist/{monitor-US5F5YGZ.js → monitor-TJ7AMTGB.js} +49 -30
  90. package/dist/{orchestrator-E2AL4T5N.js → orchestrator-WZYB54DM.js} +3827 -581
  91. package/dist/{paths-E7KYAQWE.js → paths-XUC7GS6E.js} +4 -4
  92. package/dist/{reset-KZ652EK6.js → reset-PXQT45IY.js} +7 -7
  93. package/dist/{run-SRNBKDWD.js → run-FQ74YF62.js} +53 -45
  94. package/dist/{share-CGZE33UP.js → share-FW7SVCL3.js} +33 -9
  95. package/dist/{skills-S2X4DLY5.js → skills-7E7IRB3R.js} +24 -8
  96. package/dist/{skills-eval-W2GGIC4R.js → skills-eval-LI75W6OK.js} +34 -27
  97. package/dist/{targets-54SWINWB.js → targets-4CIFKCTW.js} +23 -22
  98. package/dist/{terminal-lease-SAIF2OGY.js → terminal-lease-WUZY7ZV5.js} +4 -4
  99. package/dist/{uninstall-BVLWXKBT.js → uninstall-7FV7IP4E.js} +4 -4
  100. package/dist/{upgrade-JKAR27XC.js → upgrade-K2HVIVMQ.js} +20 -19
  101. package/dist/{usage-MSAWCLX4.js → usage-GTZELZQX.js} +116 -49
  102. package/dist/verifiers-RLAHT27O.js +336 -0
  103. package/dist/{verify-X5HDROLA.js → verify-BX3BRKH5.js} +7 -6
  104. package/dist/{wiki-generate-GUSOQ6ZP.js → wiki-generate-ASIFASCN.js} +45 -37
  105. package/dist/worker/entry.js +38 -35
  106. package/docs/README.md +3 -2
  107. package/docs/acp.md +1 -1
  108. package/docs/alcf-provider.md +1 -1
  109. package/docs/architecture.md +2 -2
  110. package/docs/artifact-versions.md +10 -6
  111. package/docs/built-in-agents.md +26 -2
  112. package/docs/capacity-and-scheduling.md +1 -1
  113. package/docs/commands-and-modes.md +82 -2
  114. package/docs/configuration-and-targets.md +79 -2
  115. package/docs/context-engine.md +1 -1
  116. package/docs/development-pipeline.md +1 -1
  117. package/docs/dispatch-architecture-rationale.md +1 -1
  118. package/docs/documentation-coverage.md +3 -3
  119. package/docs/documentation-guide.md +3 -3
  120. package/docs/eval-runner.md +1 -1
  121. package/docs/evals-internal.md +1 -1
  122. package/docs/evidence-and-memory.md +5 -5
  123. package/docs/evolution.md +1 -1
  124. package/docs/exit-codes-and-output.md +4 -1
  125. package/docs/extensions-and-sharing.md +6 -2
  126. package/docs/fleet-demo-runbook.md +2 -2
  127. package/docs/fleet-dispatch.md +197 -10
  128. package/docs/git-commit-provenance.md +2 -2
  129. package/docs/glossary.md +1 -1
  130. package/docs/installation-and-lifecycle.md +2 -2
  131. package/docs/middleware-and-components.md +2 -1
  132. package/docs/model-catalog.md +1 -1
  133. package/docs/observability.md +55 -8
  134. package/docs/proactive-memory.md +1 -1
  135. package/docs/prompt-envelope-and-tools.md +1 -1
  136. package/docs/provider-adapter-cookbook.md +1 -1
  137. package/docs/release-cut-checklist.md +79 -64
  138. package/docs/resource-library.md +59 -0
  139. package/docs/safety-model.md +2 -2
  140. package/docs/scientific-validation.md +3 -3
  141. package/docs/session-lifecycle.md +37 -1
  142. package/docs/skills-marketplace.md +16 -3
  143. package/docs/tool-usage.md +14 -7
  144. package/docs/trace-store.md +1 -1
  145. package/docs/troubleshooting.md +1 -1
  146. package/docs/tui-design.md +1 -1
  147. package/docs/worker-dispatch-mechanics.md +3 -3
  148. package/package.json +1 -1
  149. package/src/cli/fleet-commands.ts +37 -0
  150. package/src/cli/fleet-graph.ts +102 -0
  151. package/src/cli/fleet-new.ts +36 -0
  152. package/src/cli/fleet-preflight.ts +121 -0
  153. package/src/cli/fleet-validate.ts +30 -0
  154. package/src/cli/fleet.ts +173 -335
  155. package/src/cli/index.ts +3 -1
  156. package/src/cli/library.ts +190 -0
  157. package/src/cli/share.ts +13 -1
  158. package/src/cli/usage.ts +111 -19
  159. package/src/core/bus-events.ts +4 -0
  160. package/src/core/commit-attribution.ts +4 -4
  161. package/src/core/config.ts +130 -0
  162. package/src/core/defaults.ts +81 -0
  163. package/src/domains/agents/builtins/architect.md +1 -0
  164. package/src/domains/agents/builtins/oracle.md +33 -0
  165. package/src/domains/agents/catalog.ts +13 -1
  166. package/src/domains/agents/fleet-contract.ts +278 -16
  167. package/src/domains/agents/index.ts +14 -0
  168. package/src/domains/agents/result-contract.ts +235 -1
  169. package/src/domains/config/classify.ts +4 -0
  170. package/src/domains/dispatch/active-route-planner.ts +14 -0
  171. package/src/domains/dispatch/backoff.ts +2 -1
  172. package/src/domains/dispatch/capability-match.ts +1 -0
  173. package/src/domains/dispatch/checkout-writer-lease.ts +175 -0
  174. package/src/domains/dispatch/contract.ts +34 -0
  175. package/src/domains/dispatch/delegation-plan.ts +167 -0
  176. package/src/domains/dispatch/execution-plan.ts +76 -5
  177. package/src/domains/dispatch/execution-role.ts +3 -1
  178. package/src/domains/dispatch/execution-scheduler.ts +183 -67
  179. package/src/domains/dispatch/extension.ts +258 -9
  180. package/src/domains/dispatch/fleet-gate.ts +14 -0
  181. package/src/domains/dispatch/fleet-plan.ts +63 -3
  182. package/src/domains/dispatch/fleet-run.ts +737 -0
  183. package/src/domains/dispatch/gate-role-prompts.ts +9 -0
  184. package/src/domains/dispatch/host-verification.ts +178 -0
  185. package/src/domains/dispatch/index.ts +38 -0
  186. package/src/domains/dispatch/intent.ts +159 -0
  187. package/src/domains/dispatch/receipt-integrity.ts +8 -4
  188. package/src/domains/dispatch/state.ts +36 -3
  189. package/src/domains/dispatch/types.ts +51 -6
  190. package/src/domains/dispatch/validation.ts +66 -6
  191. package/src/domains/evidence/trust-status.ts +10 -1
  192. package/src/domains/middleware/index.ts +15 -0
  193. package/src/domains/middleware/watchdog.ts +281 -0
  194. package/src/domains/observability/contract.ts +3 -1
  195. package/src/domains/observability/cost.ts +12 -1
  196. package/src/domains/observability/extension.ts +2 -2
  197. package/src/domains/observability/index.ts +10 -0
  198. package/src/domains/observability/out-of-turn-usage.ts +223 -0
  199. package/src/domains/resources/index.ts +20 -0
  200. package/src/domains/resources/library.ts +326 -0
  201. package/src/domains/resources/skills/marketplace.ts +37 -12
  202. package/src/domains/session/handoff.ts +629 -0
  203. package/src/domains/share/archive.ts +67 -2
  204. package/src/entry/orchestrator.ts +37 -0
  205. package/src/interactive/bus-notices.ts +26 -0
  206. package/src/interactive/chat-loop.ts +235 -1
  207. package/src/interactive/chat-renderer.ts +22 -0
  208. package/src/interactive/cost-overlay.ts +31 -3
  209. package/src/interactive/council-dispatch.ts +30 -0
  210. package/src/interactive/council-grid.ts +213 -0
  211. package/src/interactive/council.ts +99 -0
  212. package/src/interactive/dispatch-board.ts +260 -16
  213. package/src/interactive/fleet-run-preview.ts +307 -0
  214. package/src/interactive/footer/notifications.ts +219 -0
  215. package/src/interactive/handoff-round.ts +56 -0
  216. package/src/interactive/interactive-application.ts +43 -1
  217. package/src/interactive/interactive-event-projection.ts +9 -1
  218. package/src/interactive/interactive-slash-runtime.ts +52 -2
  219. package/src/interactive/interactive-subscriptions.ts +14 -2
  220. package/src/interactive/oracle.ts +179 -0
  221. package/src/interactive/overlay-ask-user-lifecycle.ts +6 -0
  222. package/src/interactive/overlay-general-openers.ts +190 -1
  223. package/src/interactive/overlay-key-routing.ts +17 -1
  224. package/src/interactive/overlay-lifecycle.ts +41 -1
  225. package/src/interactive/overlay-permission-lifecycle.ts +10 -0
  226. package/src/interactive/overlay-resource-openers.ts +11 -3
  227. package/src/interactive/overlay-session-lifecycle.ts +234 -2
  228. package/src/interactive/overlays/fleet-run-approval.ts +208 -0
  229. package/src/interactive/overlays/handoff-review.ts +185 -0
  230. package/src/interactive/overlays/library-install-confirm.ts +151 -0
  231. package/src/interactive/overlays/list-overlay.ts +168 -2
  232. package/src/interactive/overlays/settings.ts +101 -4
  233. package/src/interactive/overlays/side-question.ts +139 -0
  234. package/src/interactive/overlays/skills-hub.ts +401 -15
  235. package/src/interactive/side-question.ts +171 -0
  236. package/src/interactive/slash-commands.ts +432 -5
  237. package/src/interactive/slash-spec.ts +19 -6
  238. package/src/interactive/theme/tokens.ts +30 -0
  239. package/src/interactive/turn-middleware.ts +15 -1
  240. package/src/interactive/watchdog-run.ts +75 -0
  241. package/src/interactive/worker-share.ts +56 -1
  242. package/src/interactive/worker-stream.ts +7 -0
  243. package/src/tools/bootstrap.ts +3 -0
  244. package/src/tools/compete-worktrees.ts +13 -79
  245. package/src/tools/dispatch-admission.ts +242 -8
  246. package/src/tools/dispatch-arguments.ts +57 -1
  247. package/src/tools/dispatch-plan.ts +136 -6
  248. package/src/tools/dispatch-runner.ts +319 -13
  249. package/src/tools/dispatch-types.ts +20 -1
  250. package/src/tools/dispatch.ts +72 -2
  251. package/src/tools/monitor.ts +16 -0
  252. package/src/tools/profiles.ts +18 -4
  253. package/src/tools/task-worktree.ts +238 -0
  254. package/src/tools/verify/authoring.ts +61 -1
  255. package/src/tools/verify/scripts.ts +62 -0
  256. package/src/tools/worker-evidence.ts +2 -1
  257. package/src/worker/spec-contract.ts +1 -0
  258. package/dist/chunk-HC4CLZ2Y.js +0 -68
package/src/cli/fleet.ts CHANGED
@@ -2,6 +2,9 @@
2
2
  * `clio-coder fleet` operator surface.
3
3
  *
4
4
  * clio-coder fleet list enumerate .clio-coder/fleets/*.md with validity
5
+ * clio-coder fleet new <name> --from <name> copy a shipped contract into the project
6
+ * clio-coder fleet validate|graph <name> inspect a contract without executing it
7
+ * clio-coder fleet commands init draft a repository command registry
5
8
  * clio-coder fleet run <name> --var k=v ... preflight + execute a fleet contract
6
9
  * clio-coder fleet status [--json] runtime snapshot from the durable ledger
7
10
  * clio-coder fleet drain|resume [--json] close or reopen durable dispatch admission
@@ -15,7 +18,7 @@
15
18
  import { randomBytes } from "node:crypto";
16
19
  import { readFileSync } from "node:fs";
17
20
  import { join } from "node:path";
18
- import { attributeCommitMessage } from "../core/commit-attribution.js";
21
+ import { type ClioSettings, readSettings } from "../core/config.js";
19
22
  import { loadDomains } from "../core/domain-loader.js";
20
23
  import { clioStateDir } from "../core/xdg.js";
21
24
  import type { AgentsContract } from "../domains/agents/contract.js";
@@ -32,7 +35,6 @@ import {
32
35
  loadFleetCommands,
33
36
  loadFleetContract,
34
37
  renderFleetPrompt,
35
- resultContractAuthorship,
36
38
  } from "../domains/agents/index.js";
37
39
  import type { ConfigContract } from "../domains/config/contract.js";
38
40
  import { ConfigDomainModule } from "../domains/config/index.js";
@@ -45,35 +47,22 @@ import {
45
47
  type RunToolBudgetEnvelope,
46
48
  } from "../domains/dispatch/budget-envelope.js";
47
49
  import { type CapacityDrain, capacityDrain, setCapacityDraining } from "../domains/dispatch/capacity-lease.js";
48
- import { runCodeStep } from "../domains/dispatch/code-step.js";
49
- import { codeStepDir, writeCodeStepRecord } from "../domains/dispatch/code-step-store.js";
50
- import type { DispatchContract, DispatchRequest } from "../domains/dispatch/contract.js";
51
- import {
52
- type ExecutionPlan,
53
- type ExecutionPlanCodeStep,
54
- executionPlanAncestors,
55
- } from "../domains/dispatch/execution-plan.js";
56
- import {
57
- agentRoleFactsResolver,
58
- gateRouteCorrelation,
59
- requestExecutionRole,
60
- withAttemptRole,
61
- } from "../domains/dispatch/execution-role.js";
62
- import { type ExecutionPlanResult, executePlan } from "../domains/dispatch/execution-scheduler.js";
63
- import { deriveFleetCommitAttribution } from "../domains/dispatch/fleet-commit-attribution.js";
50
+ import type { DispatchContract } from "../domains/dispatch/contract.js";
51
+ import type { ExecutionPlan } from "../domains/dispatch/execution-plan.js";
52
+ import { agentRoleFactsResolver, requestExecutionRole, withAttemptRole } from "../domains/dispatch/execution-role.js";
64
53
  import { compileFleetExecutionPlan } from "../domains/dispatch/fleet-plan.js";
65
54
  import {
66
- decideReviewGate,
67
- type GateDecisionCorrelation,
68
- materializePendingGateDecision,
69
- stagePendingGateDecision,
70
- } from "../domains/dispatch/gate-decisions.js";
71
- import { DispatchDomainModule } from "../domains/dispatch/index.js";
72
- import { verifyReceiptIntegrity } from "../domains/dispatch/receipt-integrity.js";
55
+ DispatchDomainModule,
56
+ type ExecuteFleetRunInput,
57
+ executeFleetRun,
58
+ type FleetRunOutcome,
59
+ planFleetResume,
60
+ readFleetRun,
61
+ } from "../domains/dispatch/index.js";
73
62
  import { openLedger } from "../domains/dispatch/state.js";
74
63
  import type { RunEnvelope, RunReceipt } from "../domains/dispatch/types.js";
75
64
  import { WRITE_BOUNDARY_VIOLATION_REASON } from "../domains/dispatch/write-boundary.js";
76
- import { createWriteBoundaryEnforcer, preflightWriteBoundaries } from "../domains/dispatch/write-boundary-enforcer.js";
65
+ import { preflightWriteBoundaries } from "../domains/dispatch/write-boundary-enforcer.js";
77
66
  import { ensureClioState, LifecycleDomainModule } from "../domains/lifecycle/index.js";
78
67
  import { MiddlewareDomainModule } from "../domains/middleware/index.js";
79
68
  import { ObservabilityDomainModule } from "../domains/observability/index.js";
@@ -92,7 +81,12 @@ Repo-owned fleet contracts and the dispatch status surface.
92
81
 
93
82
  Subcommands:
94
83
  list list .clio-coder/fleets/*.md contracts with validation status
84
+ new <name> --from <builtin> copy build-review, build-test, or sdlc into this repository
85
+ validate <name> [--json] run the fleet execution preflight without side effects
86
+ graph <name> [--json] print compiled waves, loops, scopes, and write boundaries
87
+ commands init draft a commented command registry from declared project entries
95
88
  run <name> [--var k=v ...] preflight and execute a fleet contract
89
+ [--resume <runId>] replay a completed prefix from a prior run of the same plan
96
90
  [--json] emit step receipts as JSON
97
91
  status [--json] show running, retrying, and total dispatch state
98
92
  drain [--json] deny new execution starts for up to one hour
@@ -111,6 +105,11 @@ function fail(message: string): number {
111
105
  return 2;
112
106
  }
113
107
 
108
+ function refuse(message: string): number {
109
+ process.stderr.write(`clio-coder fleet: ${message}\n`);
110
+ return 1;
111
+ }
112
+
114
113
  function parseVars(args: ReadonlyArray<string>): { vars: Record<string, string>; rest: string[]; error?: string } {
115
114
  const vars: Record<string, string> = {};
116
115
  const rest: string[] = [];
@@ -134,25 +133,6 @@ function parseVars(args: ReadonlyArray<string>): { vars: Record<string, string>;
134
133
  return { vars, rest };
135
134
  }
136
135
 
137
- /** Sealed independence facts between the work under review and its reviewer. */
138
- function fleetGateCorrelation(subject: RunReceipt, decider: RunReceipt): GateDecisionCorrelation {
139
- const facts = (receipt: RunReceipt) => ({
140
- agentId: receipt.agentId,
141
- targetId: receipt.targetId,
142
- wireModelId: receipt.wireModelId,
143
- runtimeId: receipt.runtimeId,
144
- nodeId: receipt.node?.id ?? "local",
145
- });
146
- const { agent, target, modelFamily, runtime, node, independent } = gateRouteCorrelation(
147
- facts(subject),
148
- facts(decider),
149
- );
150
- return { agent, target, modelFamily, runtime, node, independent };
151
- }
152
-
153
- /** Owner id for a plan with no model runs; it holds nothing to release. */
154
- const NO_WORKER_RESERVATION = "fleet-no-worker-reservation";
155
-
156
136
  function newFleetRootId(): string {
157
137
  return `fleet-${randomBytes(6).toString("hex")}`;
158
138
  }
@@ -164,7 +144,14 @@ function newFleetRootId(): string {
164
144
  function renderStep(step: FleetContractStep): string {
165
145
  if (step.kind === "code") return `code:${step.command}[${step.scope}]`;
166
146
  if (step.kind === "agent") return `${step.agent}[${step.scope}]`;
167
- const check = step.check.kind === "code" ? `code:${step.check.command}` : step.check.agent;
147
+ if (step.kind === "gate") return `gate:${step.path}[${step.run}]`;
148
+ if (step.kind === "plan") return `plan:${step.agent}[${step.roster.join(",")}; max ${step.maxTasks}]`;
149
+ const check =
150
+ step.check.kind === "code"
151
+ ? `code:${step.check.command}`
152
+ : step.check.kind === "gate"
153
+ ? `gate:${step.check.gate}`
154
+ : step.check.agent;
168
155
  return `loop:${step.id}(${check} -> ${step.repair.agent} x${step.maxAttempts})`;
169
156
  }
170
157
 
@@ -216,6 +203,7 @@ interface FleetPreflightDeps {
216
203
  agents: AgentsContract;
217
204
  safety: SafetyContract;
218
205
  scheduling: SchedulingContract | undefined;
206
+ settings: Readonly<ClioSettings>;
219
207
  }
220
208
 
221
209
  /** Every agent a contract dispatches, including both halves of every loop. */
@@ -225,7 +213,11 @@ function contractAgents(
225
213
  const agents: Array<{ id: string; agent: string; scope: "readonly" | "workspace" }> = [];
226
214
  for (const step of contract.steps) {
227
215
  if (step.kind === "agent") agents.push({ id: step.id, agent: step.agent, scope: step.scope });
228
- else if (step.kind === "loop") {
216
+ else if (step.kind === "gate") agents.push({ id: step.id, agent: step.agent, scope: "workspace" });
217
+ else if (step.kind === "plan") {
218
+ agents.push({ id: step.id, agent: step.agent, scope: step.scope });
219
+ for (const agent of step.roster) agents.push({ id: `${step.id}.roster`, agent, scope: "readonly" });
220
+ } else if (step.kind === "loop") {
229
221
  if (step.check.kind === "agent") {
230
222
  agents.push({ id: `${step.id}.check`, agent: step.check.agent, scope: step.check.scope });
231
223
  }
@@ -236,6 +228,28 @@ function contractAgents(
236
228
  }
237
229
 
238
230
  function preflightFleet(contract: FleetContract, deps: FleetPreflightDeps): string | null {
231
+ const targets = new Set(deps.settings.targets.map((target) => target.id));
232
+ const profiles = new Set(Object.keys(deps.settings.workers?.profiles ?? {}));
233
+ const checkRoute = (id: string, route: { target?: string; profile?: string }): string | null => {
234
+ if (route.target !== undefined && !targets.has(route.target))
235
+ return `unknown target '${route.target}' at step '${id}'`;
236
+ if (route.profile !== undefined && !profiles.has(route.profile))
237
+ return `unknown profile '${route.profile}' at step '${id}'`;
238
+ return null;
239
+ };
240
+ for (const step of contract.steps) {
241
+ if (step.kind === "agent" || step.kind === "gate" || step.kind === "plan") {
242
+ const error = checkRoute(step.id, step);
243
+ if (error !== null) return error;
244
+ }
245
+ if (step.kind !== "loop") continue;
246
+ if (step.check.kind === "agent") {
247
+ const error = checkRoute(`${step.id}.check`, step.check);
248
+ if (error !== null) return error;
249
+ }
250
+ const error = checkRoute(`${step.id}.repair`, step.repair);
251
+ if (error !== null) return error;
252
+ }
239
253
  for (const step of contractAgents(contract)) {
240
254
  if (!deps.agents.get(step.agent)) {
241
255
  return `unknown agent '${step.agent}' (step '${step.id}' must name a recipe from 'clio-coder agents')`;
@@ -264,23 +278,21 @@ async function runFleet(args: ReadonlyArray<string>): Promise<number> {
264
278
  const { vars, rest, error } = parseVars(args);
265
279
  if (error !== undefined) return fail(error);
266
280
  const json = rest.includes("--json");
281
+ const resumeIndex = rest.indexOf("--resume");
282
+ const resumeId = resumeIndex === -1 ? undefined : rest[resumeIndex + 1];
283
+ if (resumeIndex !== -1 && (resumeId === undefined || resumeId.startsWith("-")))
284
+ return fail("--resume requires a run id");
267
285
  const name = rest.find((arg) => !arg.startsWith("-"));
268
- if (!name) return fail("usage: clio-coder fleet run <name> [--var key=value ...] [--json]");
286
+ if (!name) return fail("usage: clio-coder fleet run <name> [--var key=value ...] [--resume <runId>] [--json]");
269
287
 
270
- // Phase 1: zero-side-effect validation. Parse and render strictly before
271
- // any domain boots or any process spawns.
272
288
  let contract: FleetContract;
273
289
  let prompt: string;
274
290
  let commands: FleetCommandRegistry | null;
275
291
  try {
276
292
  contract = loadFleetContract(process.cwd(), name);
277
293
  prompt = renderFleetPrompt(contract.body, vars);
278
- // loadFleetContract already refused any unregistered command id; this
279
- // read is the binding the runner executes.
280
294
  commands = loadFleetCommands(process.cwd());
281
295
  } catch (err) {
282
- // The listing calls this state `setup` and says how to leave it; the run
283
- // path is where a user meets it with intent, so it says the same thing.
284
296
  if (err instanceof FleetCommandRegistryMissingError) {
285
297
  process.stderr.write(`clio-coder fleet: ${err.message}\n ${FLEET_COMMANDS_REMEDY}\n`);
286
298
  return 2;
@@ -313,16 +325,19 @@ async function runFleet(args: ReadonlyArray<string>): Promise<number> {
313
325
  await loaded.stop();
314
326
  return fail("required domains unavailable (dispatch/agents/safety)");
315
327
  }
316
-
317
328
  const roleFacts = agentRoleFactsResolver((id) => agents.getSpec(id));
318
- const preflightError = preflightFleet(contract, { agents, safety, scheduling });
329
+ const preflightError = preflightFleet(contract, {
330
+ agents,
331
+ safety,
332
+ scheduling,
333
+ settings: config?.get() ?? readSettings(),
334
+ });
319
335
  if (preflightError !== null) {
320
336
  await loaded.stop();
321
337
  return fail(`preflight failed: ${preflightError}`);
322
338
  }
323
339
 
324
340
  const fleetRootId = newFleetRootId();
325
- let planOutcome: ExecutionPlanResult;
326
341
  let plan: ExecutionPlan;
327
342
  try {
328
343
  plan = compileFleetExecutionPlan({
@@ -341,9 +356,14 @@ async function runFleet(args: ReadonlyArray<string>): Promise<number> {
341
356
  return {
342
357
  requestedAuthority: spec.capabilityClass,
343
358
  approvedAuthority: spec.capabilityClass,
344
- // A gate decider answers the coordinator's question, so its
345
- // postcondition is the gate result contract, not its recipe's.
346
- expectedResultContract: context.gateRole === "reviewer" ? "verifier-report" : spec.resultContract.kind,
359
+ expectedResultContract:
360
+ context.planRole === true
361
+ ? "delegation-plan"
362
+ : context.gateAuthorRole === true
363
+ ? "artifact-report"
364
+ : context.gateRole === "reviewer"
365
+ ? "verifier-report"
366
+ : spec.resultContract.kind,
347
367
  executionRole: withAttemptRole(requestRole, context.attempt),
348
368
  };
349
369
  },
@@ -352,268 +372,91 @@ async function runFleet(args: ReadonlyArray<string>): Promise<number> {
352
372
  await loaded.stop();
353
373
  return fail(err instanceof Error ? err.message : String(err));
354
374
  }
355
- // A declared boundary is verified against the checkout, so the checkout has
356
- // to be one this can read. Refused here, before any spawn, rather than
357
- // discovered as an unenforceable claim halfway through the run.
358
375
  try {
359
376
  preflightWriteBoundaries(plan, process.cwd());
360
377
  } catch (err) {
361
378
  await loaded.stop();
362
379
  return fail(`preflight failed: ${err instanceof Error ? err.message : String(err)}`);
363
380
  }
364
- const boundaryByStep = new Map(plan.steps.map((step) => [step.id, step.writes]));
365
- const boundaryEnforcer = createWriteBoundaryEnforcer({
366
- root: process.cwd(),
367
- rootId: fleetRootId,
368
- boundaryFor: (stepId) => boundaryByStep.get(stepId),
369
- onVerdict(verdict, path) {
370
- if (verdict.reason === null) return;
371
- process.stderr.write(`write boundary ${verdict.window}: ${verdict.detail ?? verdict.reason} record=${path}\n`);
372
- },
373
- });
381
+
382
+ let resume: ExecuteFleetRunInput["resume"];
383
+ if (resumeId !== undefined) {
384
+ const record = readFleetRun(resumeId);
385
+ if (record === null) {
386
+ await loaded.stop();
387
+ return refuse(`resume run '${resumeId}' was not found in the durable fleet ledger`);
388
+ }
389
+ const planned = planFleetResume(record, plan, contract, vars);
390
+ if (!planned.ok) {
391
+ if (planned.reason === "fleet-name") {
392
+ await loaded.stop();
393
+ return refuse(`resume run '${resumeId}' belongs to fleet '${planned.priorFleet}', not '${planned.currentFleet}'`);
394
+ }
395
+ if (planned.reason === "vars") {
396
+ await loaded.stop();
397
+ return refuse(`resume run '${resumeId}' used different --var values`);
398
+ }
399
+ process.stderr.write(
400
+ `clio-coder fleet: resume plan hash differs: prior=${planned.priorHash} current=${planned.currentHash}\n`,
401
+ );
402
+ for (const entry of planned.diff) {
403
+ process.stderr.write(` step ${entry.index + 1}: ${entry.priorId} -> ${entry.currentId}\n`);
404
+ process.stderr.write(` prior: ${JSON.stringify(entry.prior)}\n`);
405
+ process.stderr.write(` current: ${JSON.stringify(entry.current)}\n`);
406
+ }
407
+ await loaded.stop();
408
+ return 1;
409
+ }
410
+ resume = { record, replayed: planned.replayed };
411
+ }
412
+
374
413
  process.stderr.write(
375
414
  `fleet ${contract.name}: root=${fleetRootId} plan=${plan.hash} steps=${plan.steps.length} loops=${plan.loops.length}\n`,
376
415
  );
377
- const receipts: RunReceipt[] = [];
378
- const receiptsByStep = new Map<string, RunReceipt>();
379
- const planStep = (stepId: string) => plan.steps.find((entry) => entry.id === stepId);
380
- const attributionEnabled = config?.get().attribution.gitCommits ?? true;
381
- // Freshness is coordinator-owned execution state. Worker prose cannot set
382
- // either flag: a successful deterministic verification sets the first, and
383
- // an integrity-valid independent gate decision sets the second. Any later
384
- // workspace agent invalidates both before a commit may read them.
385
- let validationFresh = false;
386
- let independentReviewFresh = false;
387
-
388
- /**
389
- * The commit message is the words of the agent that produced the work, and
390
- * only that agent's. Candidates are consulted newest first; when none of
391
- * them authored one, code writes a deterministic line rather than inventing
392
- * a sentence or reusing somebody else's.
393
- */
394
- const commitMessageFor = (
395
- step: ExecutionPlanCodeStep,
396
- priorResults: ReadonlyMap<string, { output: string }>,
397
- ): string => {
398
- for (const candidate of step.commitFrom ?? []) {
399
- const source = planStep(candidate);
400
- const result = priorResults.get(candidate);
401
- if (source === undefined || source.kind !== "agent" || result === undefined) continue;
402
- const resultContract = agents.getSpec(source.agentId)?.resultContract ?? null;
403
- if (resultContract === null) continue;
404
- const authored = resultContractAuthorship(resultContract, result.output);
405
- if (authored.commitMessage !== null) return authored.commitMessage;
406
- if (authored.summary !== null) return `clio(${fleetRootId}): ${authored.summary}`;
407
- }
408
- return `clio(${fleetRootId}): ${contract.name} ${step.id}`;
409
- };
416
+ let outcome: FleetRunOutcome;
410
417
  try {
411
- planOutcome = await executePlan(plan, {
412
- preflight(step) {
413
- const request: DispatchRequest = {
414
- agentId: step.agentId,
415
- executionRole: step.executionRole,
416
- task: step.task,
417
- requestOrigin: "user",
418
- lineage: { parentRunId: fleetRootId, rootRunId: fleetRootId, attempt: 0, depth: 1 },
419
- ...(step.scope === "readonly" ? { autonomy: "read-only" as const } : {}),
420
- };
421
- const resolution = dispatch.preview?.(request);
422
- if (!resolution) throw new Error(`fleet preflight cannot resolve step '${step.id}'`);
423
- return { step, costUpperBoundUsd: resolution.costUpperBoundUsd, nodeId: resolution.node.id };
424
- },
425
- reserve(_plan, admissions) {
426
- // A fleet of deterministic steps admits no worker and holds no
427
- // capacity. The reservation authority refuses an empty reservation,
428
- // correctly: there is nothing to reserve, so nothing is asked of it.
429
- if (admissions.length === 0) return { ownerId: NO_WORKER_RESERVATION };
430
- const reservation = dispatch.reservations?.prepare({
431
- topology: "parallel",
432
- tasks: admissions.map((admission) => ({
433
- memberId: admission.step.id,
434
- wave: plan.waves.findIndex((wave) => wave.includes(admission.step.id)),
435
- resolution: dispatch.preview?.({
436
- agentId: admission.step.agentId,
437
- executionRole: admission.step.executionRole,
438
- task: admission.step.task,
439
- }) as NonNullable<ReturnType<NonNullable<DispatchContract["preview"]>>>,
440
- })),
441
- });
442
- if (!reservation) throw new Error("fleet whole-plan reservation is unavailable");
443
- return { ownerId: reservation.ownerId };
444
- },
445
- async run(step, handoffs, reservation, ledger) {
446
- // A loop repair is attempt n of the same logical work, so it enters
447
- // the run ledger as one: recovery evidence, not a fresh observation.
448
- const attempt = step.loop?.role === "repair" ? step.loop.attempt : 0;
449
- const request: DispatchRequest = {
450
- agentId: step.agentId,
451
- executionRole: step.executionRole,
452
- task: step.task,
453
- predecessorHandoffs: handoffs,
454
- requestOrigin: "user",
455
- lineage: { parentRunId: fleetRootId, rootRunId: fleetRootId, attempt, depth: 1 },
456
- reservation,
457
- ...(ledger !== undefined ? { ledger } : {}),
458
- ...(step.loop?.role === "check"
459
- ? {
460
- gate: {
461
- role: "reviewer" as const,
462
- group: `${fleetRootId}:${step.loop.loopId}`,
463
- cycle: step.loop.attempt,
464
- subjects: handoffs.map((handoff) => ({
465
- runId: handoff.terminalRunId,
466
- digest: handoff.receiptDigest,
467
- })),
468
- },
469
- }
470
- : {}),
471
- ...(step.scope === "readonly" ? { autonomy: "read-only" as const } : {}),
472
- };
473
- const handle = await dispatch.dispatch(request);
474
- void (async () => {
475
- for await (const _event of handle.events) {
476
- /* background display drain */
477
- }
478
- })().catch(() => {});
479
- return {
480
- assignmentId: handle.runId,
481
- result: handle.finalPromise.then((receipt) => {
482
- if (step.scope === "workspace") {
483
- validationFresh = false;
484
- independentReviewFresh = false;
485
- }
486
- receipts.push(receipt);
487
- receiptsByStep.set(step.id, receipt);
488
- const envelope = dispatch.getRun(receipt.runId);
489
- const integrityValid = envelope !== null && verifyReceiptIntegrity(receipt, envelope).ok;
490
- const succeeded = receipt.exitCode === 0 && (receipt.outcome === undefined || receipt.outcome === "succeeded");
491
- if (json) process.stdout.write(`${JSON.stringify(receipt)}\n`);
492
- else
493
- process.stdout.write(
494
- `step ${step.id} ${step.agentId}: ${succeeded ? "succeeded" : "failed"} assignment=${handle.runId} terminal-run=${receipt.runId} cost=$${receipt.costUsd.toFixed(4)}\n`,
495
- );
496
- return {
497
- stepId: step.id,
498
- assignmentId: handle.runId,
499
- terminalRunId: receipt.runId,
500
- receiptDigest: receipt.integrity.digest,
501
- output: receipt.output?.state === "final" ? receipt.output.text : "",
502
- succeeded,
503
- integrityValid,
504
- };
505
- }),
506
- };
507
- },
508
- async runCode(step, _handoffs, signal, priorResults) {
509
- const command = commands?.commands.get(step.commandId);
510
- if (command === undefined) throw new Error(`fleet code step '${step.id}' has no registered command`);
511
- const isCommit = step.commitFrom !== undefined;
512
- const evidence = isCommit
513
- ? deriveFleetCommitAttribution({
514
- plan,
515
- step,
516
- priorResults,
517
- validationFresh,
518
- independentReviewFresh,
519
- })
520
- : null;
521
- if (!isCommit && step.scope === "workspace" && step.verification !== true) {
522
- validationFresh = false;
523
- independentReviewFresh = false;
418
+ outcome = await executeFleetRun({
419
+ plan,
420
+ contractName: contract.name,
421
+ commands,
422
+ workspaceRoot: process.cwd(),
423
+ fleetRootId,
424
+ dispatch,
425
+ agents: { getSpec: (agentId) => agents.getSpec(agentId) },
426
+ attributionEnabled: config?.get().attribution.gitCommits ?? true,
427
+ vars,
428
+ ...(resume !== undefined ? { resume } : {}),
429
+ onStepSettled(step) {
430
+ if (step.replayed === true && step.result !== undefined) {
431
+ if (json)
432
+ process.stdout.write(
433
+ `${JSON.stringify({ stepId: step.stepId, status: "replayed", receipt: { runId: step.result.terminalRunId, digest: step.result.receiptDigest } })}\n`,
434
+ );
435
+ else
436
+ process.stdout.write(
437
+ `step ${step.stepId}: replayed terminal-run=${step.result.terminalRunId} receipt=${step.result.receiptDigest}\n`,
438
+ );
439
+ return;
524
440
  }
525
- const originalMessage = isCommit ? commitMessageFor(step, priorResults) : null;
526
- const outcome = await runCodeStep({
527
- stepId: step.id,
528
- command,
529
- workspaceRoot: process.cwd(),
530
- artifactDir: codeStepDir(fleetRootId),
531
- signal,
532
- ...(isCommit && evidence !== null && originalMessage !== null
533
- ? {
534
- substitutions: {
535
- commitMessage: attributeCommitMessage(originalMessage, evidence, attributionEnabled),
536
- },
537
- requireWorkspaceChanges: true,
538
- commitAttribution: { enabled: attributionEnabled, evidence },
539
- }
540
- : {}),
541
- });
542
- if (step.verification === true) validationFresh = outcome.report.passed;
543
- const recordPath = await writeCodeStepRecord(fleetRootId, outcome.record);
544
- if (json) process.stdout.write(`${JSON.stringify({ codeStep: outcome.record })}\n`);
545
- else
546
- process.stdout.write(
547
- `step ${step.id} code:${command.id}: ${outcome.report.passed ? "passed" : "failed"} exit=${outcome.report.exitCode} ${outcome.record.durationMs}ms record=${recordPath}\n`,
548
- );
549
- return {
550
- stepId: step.id,
551
- assignmentId: outcome.record.runId,
552
- terminalRunId: outcome.record.runId,
553
- receiptDigest: outcome.record.reportDigest,
554
- output: outcome.output,
555
- succeeded: outcome.report.passed,
556
- // The runner authored this report itself, so its provenance is
557
- // valid by construction; only the command's verdict can be red.
558
- integrityValid: true,
559
- };
560
- },
561
- async decideLoop({ loop, step, attempt, terminalAttempt, result }) {
562
- // Reuse the coordinator's existing review policy verbatim: pass ends
563
- // the loop, a failure with attempts left earns one repair, and the
564
- // same failure at the bound is simply the terminal answer. `revise`
565
- // is never a verdict the reviewed model authors.
566
- const decider = receiptsByStep.get(step.id);
567
- // The subject is the work under review: the most recent agent run
568
- // upstream of this verification. A direct dependency is not enough,
569
- // because a loop's declared dependency is another loop's
570
- // deterministic check, which produced no receipt at all.
571
- const ancestors = executionPlanAncestors(plan, step.id);
572
- let subject: RunReceipt | undefined;
573
- for (const [stepId, receipt] of receiptsByStep) {
574
- if (ancestors.has(stepId)) subject = receipt;
575
- }
576
- if (decider === undefined || subject === undefined) {
577
- return {
578
- resolved: false,
579
- findings: null,
580
- needsDecision: `loop '${loop.id}' cycle ${attempt} has no sealed reviewer or subject receipt`,
581
- };
441
+ if (step.receipt !== undefined) {
442
+ if (json) process.stdout.write(`${JSON.stringify(step.receipt)}\n`);
443
+ else
444
+ process.stdout.write(
445
+ `step ${step.stepId} ${step.agentId}: ${step.succeeded ? "succeeded" : "failed"}${step.failureReason !== undefined ? ` reason=${step.failureReason}` : ""} assignment=${step.assignmentId} terminal-run=${step.terminalRunId} cost=$${step.costUsd.toFixed(4)}\n`,
446
+ );
447
+ return;
582
448
  }
583
- const correlation = fleetGateCorrelation(subject, decider);
584
- const decided = decideReviewGate({
585
- group: `${fleetRootId}:${loop.id}`,
586
- cycle: attempt,
587
- terminalCycle: terminalAttempt,
588
- subjects: [{ runId: subject.runId, digest: subject.integrity.digest }],
589
- decider: { runId: decider.runId, digest: decider.integrity.digest },
590
- correlation,
591
- output: result.output,
592
- });
593
- independentReviewFresh = decided.verdict === "pass" && correlation.independent;
594
- const staged = stagePendingGateDecision(decided.draft);
595
- const { path: decisionPath } = materializePendingGateDecision(staged);
596
- if (!json) {
597
- process.stdout.write(
598
- `loop ${loop.id} cycle ${attempt}: ${decided.draft.outcome}${decided.draft.detail === undefined ? "" : ` (${decided.draft.detail})`} decision=${decisionPath}\n`,
599
- );
449
+ if (step.codeStep !== undefined) {
450
+ if (json) process.stdout.write(`${JSON.stringify({ codeStep: step.codeStep })}\n`);
451
+ else
452
+ process.stdout.write(
453
+ `step ${step.stepId} code:${step.commandId}: ${step.succeeded ? "passed" : "failed"} exit=${step.codeStep.exitCode} ${step.codeStep.durationMs}ms record=${step.recordPath}\n`,
454
+ );
600
455
  }
601
- return {
602
- resolved: decided.verdict === "pass",
603
- findings: decided.findings,
604
- needsDecision: decided.needsDecision,
605
- };
606
- },
607
- beginWriteBoundary: (window, stepIds) => boundaryEnforcer.begin(window, stepIds),
608
- verifyWriteBoundary: (window, stepIds) => boundaryEnforcer.verify(window, stepIds),
609
- cancel: (assignmentId) => dispatch.abort(assignmentId),
610
- release: (ownerId) => {
611
- if (ownerId === NO_WORKER_RESERVATION) return;
612
- dispatch.reservations?.rollbackUnconsumed(ownerId);
613
456
  },
614
- releaseUnconsumed: (ownerId) => {
615
- if (ownerId === NO_WORKER_RESERVATION) return;
616
- dispatch.reservations?.rollbackUnconsumed(ownerId);
457
+ onNotice(text, kind) {
458
+ if (kind === "write-boundary") process.stderr.write(`${text}\n`);
459
+ else if (kind === "gate" && !json) process.stdout.write(`${text}\n`);
617
460
  },
618
461
  });
619
462
  } catch (err) {
@@ -623,53 +466,40 @@ async function runFleet(args: ReadonlyArray<string>): Promise<number> {
623
466
  }
624
467
  await dispatch.drain();
625
468
  await loaded.stop();
626
- // A loop is judged by whether it converged, not attempt by attempt: a red
627
- // verification that a later attempt fixed is the loop working as declared,
628
- // and a node a resolved loop made unnecessary never had to run at all.
629
- const required = plan.steps.filter((step) => step.loop === undefined && !planOutcome.unneeded.includes(step.id));
630
- const failedLoops = planOutcome.loops.filter((loop) => !loop.resolved);
631
- const succeededSteps = required.filter((step) => planOutcome.results.get(step.id)?.succeeded === true).length;
632
- const resolvedLoops = planOutcome.loops.length - failedLoops.length;
469
+
633
470
  if (json) {
634
471
  process.stdout.write(
635
472
  `${JSON.stringify({
636
473
  fleet: contract.name,
637
474
  rootId: fleetRootId,
638
475
  planHash: plan.hash,
639
- loops: planOutcome.loops,
640
- revalidated: planOutcome.revalidated,
641
- unneeded: planOutcome.unneeded,
642
- skipped: planOutcome.skipped,
643
- needsDecision: planOutcome.needsDecision,
644
- writeBoundaries: planOutcome.writeBoundaries,
476
+ loops: outcome.result.loops,
477
+ revalidated: outcome.result.revalidated,
478
+ unneeded: outcome.result.unneeded,
479
+ skipped: outcome.result.skipped,
480
+ needsDecision: outcome.result.needsDecision,
481
+ writeBoundaries: outcome.result.writeBoundaries,
645
482
  })}\n`,
646
483
  );
647
484
  } else {
648
- const spentUsd = receipts.reduce((sum, receipt) => sum + receipt.costUsd, 0);
649
485
  process.stdout.write(
650
- `fleet ${contract.name}: ${succeededSteps}/${required.length} steps succeeded, ${resolvedLoops}/${planOutcome.loops.length} loops resolved, total cost $${spentUsd.toFixed(4)}\n`,
486
+ `fleet ${contract.name}: ${outcome.succeededStepCount}/${outcome.requiredStepCount} steps succeeded, ${outcome.resolvedLoopCount}/${outcome.result.loops.length} loops resolved, total cost $${outcome.totalCostUsd.toFixed(4)}\n`,
651
487
  );
652
- for (const loop of planOutcome.loops) {
488
+ for (const loop of outcome.result.loops)
653
489
  process.stdout.write(
654
490
  ` loop ${loop.loopId}: ${loop.reason} after ${loop.attempts} verification(s) and ${loop.repairs} repair(s)\n`,
655
491
  );
656
- }
657
- if (planOutcome.revalidated.length > 0) {
492
+ if (outcome.result.revalidated.length > 0)
658
493
  process.stdout.write(
659
- ` staleness: re-ran ${planOutcome.revalidated.join(", ")} because a workspace step landed after the last green\n`,
494
+ ` staleness: re-ran ${outcome.result.revalidated.join(", ")} because a workspace step landed after the last green\n`,
660
495
  );
661
- }
662
- for (const message of planOutcome.needsDecision) process.stdout.write(` needs operator decision: ${message}\n`);
663
- for (const boundary of planOutcome.writeBoundaries) {
496
+ for (const message of outcome.result.needsDecision) process.stdout.write(` needs operator decision: ${message}\n`);
497
+ for (const boundary of outcome.result.writeBoundaries) {
664
498
  if (!boundary.violated) continue;
665
499
  process.stdout.write(` write boundary ${boundary.window}: ${boundary.detail ?? WRITE_BOUNDARY_VIOLATION_REASON}\n`);
666
500
  }
667
501
  }
668
- const cleanRun =
669
- failedLoops.length === 0 &&
670
- planOutcome.skipped.length === 0 &&
671
- required.every((step) => planOutcome.results.get(step.id)?.succeeded === true);
672
- return cleanRun ? 0 : 1;
502
+ return outcome.cleanRun ? 0 : 1;
673
503
  }
674
504
 
675
505
  // ---------------------------------------------------------------------------
@@ -867,6 +697,14 @@ export async function runFleetCommand(args: ReadonlyArray<string>): Promise<numb
867
697
  switch (sub) {
868
698
  case "list":
869
699
  return runList(args.slice(1));
700
+ case "new":
701
+ return (await import("./fleet-new.js")).runFleetNew(args.slice(1));
702
+ case "validate":
703
+ return (await import("./fleet-validate.js")).runFleetValidate(args.slice(1));
704
+ case "graph":
705
+ return (await import("./fleet-graph.js")).runFleetGraph(args.slice(1));
706
+ case "commands":
707
+ return (await import("./fleet-commands.js")).runFleetCommands(args.slice(1));
870
708
  case "run":
871
709
  return runFleet(args.slice(1));
872
710
  case "status":