@iowarp/clio-coder 0.3.6 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +22 -6
  3. package/dist/{acp-2BEHC4DL.js → acp-U67UHUK2.js} +14 -14
  4. package/dist/{agents-LNNFTM53.js → agents-YU6SGALZ.js} +42 -35
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{auth-KXXFI2VS.js → auth-5ZPJOIVG.js} +32 -24
  7. package/dist/builtins-C6JMZVV6.js +17 -0
  8. package/dist/{chunk-E25LMLRW.js → chunk-26LEYJZH.js} +2 -2
  9. package/dist/{chunk-TSHXZTOQ.js → chunk-2HEJ2F35.js} +5 -5
  10. package/dist/{chunk-PBTHKCPN.js → chunk-2HFZQUHL.js} +7 -7
  11. package/dist/{chunk-FO5ZOVUY.js → chunk-3BINW3FP.js} +5 -5
  12. package/dist/{chunk-4OC57DA6.js → chunk-4DGYLA73.js} +53 -2
  13. package/dist/{chunk-CKXWIANG.js → chunk-4SPRNWDE.js} +18 -16
  14. package/dist/{chunk-E2ER4LJF.js → chunk-5C3AQNDW.js} +25 -1
  15. package/dist/{chunk-XF5N4U5A.js → chunk-5DHKRSMQ.js} +9 -8
  16. package/dist/{chunk-43AOLP7E.js → chunk-5FR74PWO.js} +2 -1
  17. package/dist/{chunk-EKY57CSP.js → chunk-5H3GB5BO.js} +68 -772
  18. package/dist/{chunk-6XXKFVSN.js → chunk-5Q2VVUKB.js} +4 -4
  19. package/dist/{chunk-DJVECN66.js → chunk-7RFXX52T.js} +295 -3705
  20. package/dist/{chunk-ZXF4XRKW.js → chunk-7RGZWPB6.js} +158 -8
  21. package/dist/{chunk-FYYLNIL5.js → chunk-A2NJGIB3.js} +2 -2
  22. package/dist/{chunk-XXQNGV4M.js → chunk-A3WNZD3P.js} +548 -72
  23. package/dist/{chunk-VEZEGCGW.js → chunk-B5CSFE7B.js} +23 -21
  24. package/dist/{chunk-2SFS6XQE.js → chunk-DGSYXYMX.js} +3 -2
  25. package/dist/chunk-DR52UMZW.js +21 -0
  26. package/dist/{chunk-ZWLZP4ZT.js → chunk-DYIM5TJT.js} +98 -12
  27. package/dist/{chunk-QKMUKYO7.js → chunk-E77JEWSD.js} +270 -125
  28. package/dist/{chunk-LYF7OHWH.js → chunk-EMYUUSFG.js} +12 -467
  29. package/dist/{chunk-4VP4KH3K.js → chunk-EQ63NRB7.js} +8 -8
  30. package/dist/chunk-FBVTI2TJ.js +518 -0
  31. package/dist/{chunk-R46L2BIR.js → chunk-FHJEP5SW.js} +19 -25
  32. package/dist/{chunk-WR67VIZY.js → chunk-GN57SG4G.js} +66 -8
  33. package/dist/{chunk-RD5U66HV.js → chunk-GPIEI3LY.js} +9 -9
  34. package/dist/{chunk-PCZJO5TI.js → chunk-GU2UIAFZ.js} +13 -178
  35. package/dist/chunk-GWS3VEIW.js +195 -0
  36. package/dist/chunk-H7IXIC72.js +103 -0
  37. package/dist/{chunk-3BPUFZDL.js → chunk-HLE42MG7.js} +3 -3
  38. package/dist/{chunk-24I7BN55.js → chunk-IFBNV6H6.js} +3 -3
  39. package/dist/{chunk-QNQHSOLF.js → chunk-IGWKHNIQ.js} +114 -55
  40. package/dist/chunk-IIZWH4XA.js +172 -0
  41. package/dist/{chunk-AD2SYQYC.js → chunk-IJ7RPIYJ.js} +124 -6
  42. package/dist/chunk-J3YUBZWY.js +382 -0
  43. package/dist/{chunk-5JGRAMKL.js → chunk-JOZYP4GM.js} +8 -6
  44. package/dist/{chunk-OH3TOQTB.js → chunk-K4XHGFR5.js} +751 -15
  45. package/dist/{chunk-EYPA3EGJ.js → chunk-KTYTFRMB.js} +178 -14
  46. package/dist/chunk-LU7P4LHA.js +33 -0
  47. package/dist/chunk-ME6CCNFO.js +108 -0
  48. package/dist/chunk-MXKJU4JB.js +1100 -0
  49. package/dist/{chunk-RY3LY4J5.js → chunk-N22QMJKY.js} +21 -18
  50. package/dist/chunk-NMPKI6XL.js +3006 -0
  51. package/dist/chunk-NUGM5KR6.js +165 -0
  52. package/dist/{chunk-22NAGB7X.js → chunk-P43ETTHK.js} +5 -94
  53. package/dist/{chunk-NILBFAPG.js → chunk-PMDBGQSJ.js} +2 -2
  54. package/dist/chunk-PT7HYKEM.js +165 -0
  55. package/dist/chunk-RVG5JXAL.js +41 -0
  56. package/dist/{chunk-GEYXPTRF.js → chunk-RWSI4YD7.js} +2 -2
  57. package/dist/{chunk-4BPJXDWC.js → chunk-TANS5ZJS.js} +35 -19
  58. package/dist/{verifiers-NCBTHHN2.js → chunk-TB5666IT.js} +67 -324
  59. package/dist/{chunk-QM3F2GKX.js → chunk-TLQJPP24.js} +7927 -8088
  60. package/dist/{chunk-G7MUEIGA.js → chunk-TT36MB5S.js} +2 -1
  61. package/dist/chunk-TTHACPOM.js +961 -0
  62. package/dist/{chunk-6US73PDB.js → chunk-TYPGUK6W.js} +7 -7
  63. package/dist/{chunk-MFFY33HR.js → chunk-U6MBIEMB.js} +554 -209
  64. package/dist/chunk-VAWNZU7Z.js +242 -0
  65. package/dist/{chunk-IR4CFBFN.js → chunk-VCBR6CU7.js} +12 -12
  66. package/dist/{chunk-WHJYKASB.js → chunk-VHN4MY6O.js} +2 -2
  67. package/dist/{chunk-XYDYPRZI.js → chunk-VWZOAB7K.js} +10 -10
  68. package/dist/{chunk-ZRGEBJ4T.js → chunk-WLFILSD5.js} +48 -48
  69. package/dist/{chunk-K7T3E2SR.js → chunk-WNIJTQQK.js} +12 -11
  70. package/dist/{chunk-CYQKWTG3.js → chunk-WSB3FPX7.js} +68 -20
  71. package/dist/{chunk-KHSFENX2.js → chunk-WWCZ5F23.js} +116 -14
  72. package/dist/{chunk-XE2VEJHX.js → chunk-WXY7KU3G.js} +2 -2
  73. package/dist/{chunk-PPAMZ32Z.js → chunk-XK56QHLX.js} +6 -1
  74. package/dist/{chunk-KOHPCX4K.js → chunk-XWSF374K.js} +5 -5
  75. package/dist/{chunk-CJUB2JJ2.js → chunk-YS5VLNH5.js} +10 -10
  76. package/dist/{chunk-ZZMN5OM4.js → chunk-ZNLWCMVZ.js} +2 -2
  77. package/dist/{chunk-WHGPSPT5.js → chunk-ZVJ5BLO2.js} +2 -2
  78. package/dist/cli/index.js +33 -31
  79. package/dist/{clio-M2KGYUFZ.js → clio-QVTYJ57A.js} +10 -10
  80. package/dist/{code-nav-GQNL7XA6.js → code-nav-FGGFIE7L.js} +5 -5
  81. package/dist/codewiki/build-worker.js +4 -4
  82. package/dist/{components-5TTYYX6G.js → components-ZFA3SAER.js} +9 -9
  83. package/dist/{config-XUUYQIWO.js → config-LW5IJFQN.js} +65 -55
  84. package/dist/{configure-IHJ7YOMV.js → configure-7XIZCOU4.js} +29 -24
  85. package/dist/{context-75MIWW3U.js → context-L3WL3X7K.js} +54 -43
  86. package/dist/{context-ZQ7SIFJV.js → context-N52ZA626.js} +30 -14
  87. package/dist/{context-74JLXAWD.js → context-Y6Y7QPR6.js} +12 -12
  88. package/dist/{context-clear-GYKWNUML.js → context-clear-MBQRLSDQ.js} +54 -43
  89. package/dist/{context-index-SSR5ECNE.js → context-index-HVMFQHK3.js} +5 -5
  90. package/dist/{context-working-set-UX5KEP4J.js → context-working-set-GS6DSO7F.js} +18 -18
  91. package/dist/{dispatch-runner-GIJBHNFL.js → dispatch-runner-22ZCNOM3.js} +367 -79
  92. package/dist/{docs-6FZSCG5B.js → docs-7LQ23DLM.js} +9 -9
  93. package/dist/{doctor-SVJ5BZCW.js → doctor-M7YEDGAE.js} +27 -23
  94. package/dist/{eval-CG6LLBLD.js → eval-BEC2WHDA.js} +73 -20
  95. package/dist/{evidence-ZYFIEN42.js → evidence-REJUMSKM.js} +70 -59
  96. package/dist/{evolve-QGEXEMDW.js → evolve-PY5ZBA5K.js} +50 -39
  97. package/dist/{extensions-ADGNCJJD.js → extensions-HVKU65YU.js} +7 -7
  98. package/dist/{fleet-S5R4ZOQY.js → fleet-7WZEWRFA.js} +236 -377
  99. package/dist/fleet-commands-UVHWM76J.js +70 -0
  100. package/dist/fleet-graph-6ULH7PES.js +125 -0
  101. package/dist/fleet-new-RDVJLHHH.js +48 -0
  102. package/dist/{fleet-preflight-BHSNPBMH.js → fleet-preflight-J53T6CCE.js} +5 -5
  103. package/dist/fleet-validate-72PC4SLA.js +79 -0
  104. package/dist/{init-5DRU55YR.js → init-OG3TPGQG.js} +69 -57
  105. package/dist/library-CNTMPLRF.js +217 -0
  106. package/dist/{memory-7YKKR6UC.js → memory-6IS7F275.js} +52 -41
  107. package/dist/{models-ZPOLRU2C.js → models-ENRJDA5W.js} +37 -31
  108. package/dist/{monitor-US5F5YGZ.js → monitor-XLDVO7TN.js} +79 -42
  109. package/dist/{orchestrator-E2AL4T5N.js → orchestrator-6KSPYRHA.js} +4126 -762
  110. package/dist/{paths-E7KYAQWE.js → paths-DBXMZMDU.js} +6 -6
  111. package/dist/registry-LG64LTF4.js +11 -0
  112. package/dist/{reset-KZ652EK6.js → reset-RZ4ER727.js} +12 -12
  113. package/dist/{run-SRNBKDWD.js → run-Y2CNK5RU.js} +89 -76
  114. package/dist/{share-CGZE33UP.js → share-A55GYP6Z.js} +37 -13
  115. package/dist/{skills-S2X4DLY5.js → skills-ALC5J6AT.js} +29 -13
  116. package/dist/{skills-eval-W2GGIC4R.js → skills-eval-JPBEBYQU.js} +60 -48
  117. package/dist/support-MIETYA5E.js +38 -0
  118. package/dist/{targets-54SWINWB.js → targets-VGNXIR3S.js} +46 -36
  119. package/dist/{terminal-lease-SAIF2OGY.js → terminal-lease-WOBR64YA.js} +4 -4
  120. package/dist/{uninstall-BVLWXKBT.js → uninstall-ZJF5H5ZN.js} +9 -9
  121. package/dist/{upgrade-JKAR27XC.js → upgrade-FUSUAGHR.js} +31 -28
  122. package/dist/{usage-MSAWCLX4.js → usage-N4MKVHKD.js} +140 -68
  123. package/dist/verifiers-YAWOJ3H2.js +336 -0
  124. package/dist/{verify-X5HDROLA.js → verify-LTDHYBGY.js} +10 -9
  125. package/dist/{wiki-generate-GUSOQ6ZP.js → wiki-generate-6M7GHTBJ.js} +75 -62
  126. package/dist/worker/entry.js +67 -57
  127. package/docs/README.md +3 -2
  128. package/docs/acp.md +1 -1
  129. package/docs/alcf-provider.md +1 -1
  130. package/docs/architecture.md +2 -2
  131. package/docs/artifact-versions.md +11 -6
  132. package/docs/built-in-agents.md +26 -2
  133. package/docs/capacity-and-scheduling.md +1 -1
  134. package/docs/commands-and-modes.md +83 -3
  135. package/docs/configuration-and-targets.md +86 -4
  136. package/docs/context-engine.md +1 -1
  137. package/docs/development-pipeline.md +1 -1
  138. package/docs/dispatch-architecture-rationale.md +1 -1
  139. package/docs/documentation-coverage.md +3 -3
  140. package/docs/documentation-guide.md +3 -3
  141. package/docs/eval-runner.md +1 -1
  142. package/docs/evals-internal.md +1 -1
  143. package/docs/evidence-and-memory.md +74 -10
  144. package/docs/evolution.md +1 -1
  145. package/docs/exit-codes-and-output.md +4 -1
  146. package/docs/extensions-and-sharing.md +6 -2
  147. package/docs/fleet-demo-runbook.md +2 -2
  148. package/docs/fleet-dispatch.md +228 -16
  149. package/docs/git-commit-provenance.md +2 -2
  150. package/docs/glossary.md +22 -2
  151. package/docs/installation-and-lifecycle.md +2 -2
  152. package/docs/middleware-and-components.md +2 -1
  153. package/docs/model-catalog.md +1 -1
  154. package/docs/observability.md +56 -9
  155. package/docs/proactive-memory.md +1 -1
  156. package/docs/prompt-envelope-and-tools.md +4 -2
  157. package/docs/provider-adapter-cookbook.md +1 -1
  158. package/docs/release-cut-checklist.md +83 -65
  159. package/docs/resource-library.md +59 -0
  160. package/docs/safety-model.md +2 -2
  161. package/docs/scientific-validation.md +3 -3
  162. package/docs/session-lifecycle.md +37 -1
  163. package/docs/skills-marketplace.md +16 -3
  164. package/docs/tool-usage.md +14 -7
  165. package/docs/trace-store.md +1 -1
  166. package/docs/troubleshooting.md +1 -1
  167. package/docs/tui-design.md +1 -1
  168. package/docs/worker-dispatch-mechanics.md +3 -3
  169. package/package.json +1 -2
  170. package/src/cli/argv.ts +5 -0
  171. package/src/cli/configure.ts +107 -23
  172. package/src/cli/doctor.ts +5 -1
  173. package/src/cli/evidence.ts +30 -25
  174. package/src/cli/fleet-commands.ts +37 -0
  175. package/src/cli/fleet-graph.ts +102 -0
  176. package/src/cli/fleet-new.ts +36 -0
  177. package/src/cli/fleet-preflight.ts +111 -0
  178. package/src/cli/fleet-validate.ts +30 -0
  179. package/src/cli/fleet.ts +173 -335
  180. package/src/cli/index.ts +3 -1
  181. package/src/cli/library.ts +190 -0
  182. package/src/cli/share.ts +13 -1
  183. package/src/cli/shared.ts +1 -0
  184. package/src/cli/targets.ts +4 -1
  185. package/src/cli/usage.ts +111 -19
  186. package/src/cli/validate-model.ts +60 -5
  187. package/src/core/bus-events.ts +29 -0
  188. package/src/core/commit-attribution.ts +4 -4
  189. package/src/core/config.ts +130 -0
  190. package/src/core/defaults.ts +81 -0
  191. package/src/core/path-boundary.ts +100 -0
  192. package/src/domains/agents/builtins/architect.md +1 -0
  193. package/src/domains/agents/builtins/oracle.md +33 -0
  194. package/src/domains/agents/catalog.ts +13 -1
  195. package/src/domains/agents/extension.ts +2 -11
  196. package/src/domains/agents/fleet-contract.ts +304 -24
  197. package/src/domains/agents/index.ts +14 -0
  198. package/src/domains/agents/recipe.ts +7 -1
  199. package/src/domains/agents/registry.ts +73 -5
  200. package/src/domains/agents/result-contract.ts +360 -15
  201. package/src/domains/agents/write-boundary.ts +15 -50
  202. package/src/domains/config/classify.ts +4 -0
  203. package/src/domains/context/project-rules.ts +51 -1
  204. package/src/domains/dispatch/active-route-planner.ts +14 -0
  205. package/src/domains/dispatch/assignment-reconcile.ts +22 -5
  206. package/src/domains/dispatch/assignment-store.ts +151 -14
  207. package/src/domains/dispatch/backoff.ts +2 -1
  208. package/src/domains/dispatch/capability-match.ts +1 -0
  209. package/src/domains/dispatch/checkout-writer-lease.ts +175 -0
  210. package/src/domains/dispatch/contract.ts +48 -0
  211. package/src/domains/dispatch/delegation-plan.ts +164 -0
  212. package/src/domains/dispatch/execution-plan.ts +76 -5
  213. package/src/domains/dispatch/execution-role.ts +12 -2
  214. package/src/domains/dispatch/execution-scheduler.ts +183 -67
  215. package/src/domains/dispatch/extension.ts +412 -74
  216. package/src/domains/dispatch/fleet-gate.ts +14 -0
  217. package/src/domains/dispatch/fleet-plan.ts +63 -3
  218. package/src/domains/dispatch/fleet-run.ts +791 -0
  219. package/src/domains/dispatch/gate-role-prompts.ts +47 -0
  220. package/src/domains/dispatch/host-verification.ts +178 -0
  221. package/src/domains/dispatch/index.ts +41 -1
  222. package/src/domains/dispatch/intent-requirements.ts +40 -0
  223. package/src/domains/dispatch/intent.ts +235 -0
  224. package/src/domains/dispatch/path-scope.ts +370 -0
  225. package/src/domains/dispatch/receipt-integrity.ts +9 -4
  226. package/src/domains/dispatch/state.ts +36 -3
  227. package/src/domains/dispatch/types.ts +64 -12
  228. package/src/domains/dispatch/validation.ts +69 -6
  229. package/src/domains/dispatch/write-boundary-enforcer.ts +45 -0
  230. package/src/domains/dispatch/write-boundary.ts +201 -22
  231. package/src/domains/eval/metrics/evidence.ts +79 -2
  232. package/src/domains/eval/runners/clio-run.ts +12 -2
  233. package/src/domains/evidence/build.ts +69 -11
  234. package/src/domains/evidence/index.ts +21 -0
  235. package/src/domains/evidence/provenance.ts +46 -11
  236. package/src/domains/evidence/trust-projection.ts +274 -0
  237. package/src/domains/evidence/trust-status.ts +155 -18
  238. package/src/domains/evidence/types.ts +4 -0
  239. package/src/domains/extensions/discovery.ts +88 -1
  240. package/src/domains/extensions/resources.ts +20 -8
  241. package/src/domains/extensions/state.ts +6 -2
  242. package/src/domains/extensions/types.ts +4 -1
  243. package/src/domains/lifecycle/doctor.ts +140 -1
  244. package/src/domains/middleware/index.ts +15 -0
  245. package/src/domains/middleware/watchdog.ts +281 -0
  246. package/src/domains/observability/contract.ts +3 -1
  247. package/src/domains/observability/cost.ts +12 -1
  248. package/src/domains/observability/extension.ts +2 -2
  249. package/src/domains/observability/index.ts +10 -0
  250. package/src/domains/observability/out-of-turn-usage.ts +223 -0
  251. package/src/domains/prompts/contract.ts +3 -5
  252. package/src/domains/providers/extension.ts +30 -2
  253. package/src/domains/resources/common-loader.ts +3 -0
  254. package/src/domains/resources/index.ts +20 -0
  255. package/src/domains/resources/library.ts +326 -0
  256. package/src/domains/resources/prompts/loader.ts +119 -14
  257. package/src/domains/resources/skills/marketplace.ts +37 -12
  258. package/src/domains/safety/policy-engine.ts +5 -5
  259. package/src/domains/safety/run-effects.ts +64 -1
  260. package/src/domains/safety/scope.ts +7 -12
  261. package/src/domains/session/handoff.ts +629 -0
  262. package/src/domains/share/archive.ts +67 -2
  263. package/src/engine/acp/server.ts +4 -1
  264. package/src/engine/prompt-templates.ts +18 -1
  265. package/src/engine/worker-runtime.ts +6 -3
  266. package/src/entry/orchestrator.ts +37 -0
  267. package/src/interactive/bus-notices.ts +26 -0
  268. package/src/interactive/chat-loop.ts +235 -1
  269. package/src/interactive/chat-renderer.ts +22 -0
  270. package/src/interactive/cost-overlay.ts +31 -3
  271. package/src/interactive/council-dispatch.ts +30 -0
  272. package/src/interactive/council-grid.ts +213 -0
  273. package/src/interactive/council.ts +99 -0
  274. package/src/interactive/dispatch-board.ts +311 -17
  275. package/src/interactive/fleet-run-preview.ts +307 -0
  276. package/src/interactive/footer/notifications.ts +219 -0
  277. package/src/interactive/handoff-round.ts +56 -0
  278. package/src/interactive/interactive-application.ts +43 -1
  279. package/src/interactive/interactive-event-projection.ts +23 -1
  280. package/src/interactive/interactive-slash-runtime.ts +52 -2
  281. package/src/interactive/interactive-subscriptions.ts +14 -2
  282. package/src/interactive/oracle.ts +179 -0
  283. package/src/interactive/overlay-ask-user-lifecycle.ts +6 -0
  284. package/src/interactive/overlay-general-openers.ts +190 -1
  285. package/src/interactive/overlay-key-routing.ts +17 -1
  286. package/src/interactive/overlay-lifecycle.ts +41 -1
  287. package/src/interactive/overlay-permission-lifecycle.ts +10 -0
  288. package/src/interactive/overlay-resource-openers.ts +11 -3
  289. package/src/interactive/overlay-session-lifecycle.ts +234 -2
  290. package/src/interactive/overlays/fleet-run-approval.ts +208 -0
  291. package/src/interactive/overlays/handoff-review.ts +185 -0
  292. package/src/interactive/overlays/library-install-confirm.ts +151 -0
  293. package/src/interactive/overlays/list-overlay.ts +168 -2
  294. package/src/interactive/overlays/settings.ts +221 -30
  295. package/src/interactive/overlays/side-question.ts +139 -0
  296. package/src/interactive/overlays/skills-hub.ts +401 -15
  297. package/src/interactive/side-question.ts +171 -0
  298. package/src/interactive/slash-commands.ts +439 -7
  299. package/src/interactive/slash-spec.ts +19 -6
  300. package/src/interactive/theme/tokens.ts +30 -0
  301. package/src/interactive/turn-middleware.ts +15 -1
  302. package/src/interactive/view/artifacts.ts +42 -9
  303. package/src/interactive/view/view-overlay.ts +15 -3
  304. package/src/interactive/watchdog-run.ts +75 -0
  305. package/src/interactive/worker-receipts.ts +14 -2
  306. package/src/interactive/worker-share.ts +56 -1
  307. package/src/interactive/worker-stream.ts +15 -0
  308. package/src/tools/bootstrap.ts +3 -0
  309. package/src/tools/compete-worktrees.ts +13 -79
  310. package/src/tools/dispatch-admission.ts +251 -18
  311. package/src/tools/dispatch-arguments.ts +84 -1
  312. package/src/tools/dispatch-plan.ts +165 -6
  313. package/src/tools/dispatch-runner.ts +364 -23
  314. package/src/tools/dispatch-types.ts +20 -1
  315. package/src/tools/dispatch.ts +72 -2
  316. package/src/tools/monitor.ts +29 -0
  317. package/src/tools/profiles.ts +18 -4
  318. package/src/tools/task-worktree.ts +238 -0
  319. package/src/tools/verify/authoring.ts +61 -1
  320. package/src/tools/verify/scripts.ts +62 -0
  321. package/src/tools/worker-evidence.ts +21 -14
  322. package/src/worker/spec-contract.ts +3 -1
  323. package/dist/chunk-HC4CLZ2Y.js +0 -68
@@ -27,7 +27,12 @@ import { fingerprintNativeRuntime } from "../domains/providers/probe/fingerprint
27
27
  import { getRuntimeRegistry } from "../domains/providers/registry.js";
28
28
  import { registerBuiltinRuntimes } from "../domains/providers/runtimes/builtins.js";
29
29
  import { greetLmStudio } from "../domains/providers/runtimes/common/lmstudio-http.js";
30
- import type { ProbeContext, ProbeResult, RuntimeDescriptor } from "../domains/providers/types/runtime-descriptor.js";
30
+ import type {
31
+ ProbeContext,
32
+ ProbeModelStatus,
33
+ ProbeResult,
34
+ RuntimeDescriptor,
35
+ } from "../domains/providers/types/runtime-descriptor.js";
31
36
  import type { TargetDescriptor } from "../domains/providers/types/target-descriptor.js";
32
37
  import { registerClioOAuthProviders } from "../engine/oauth.js";
33
38
  import { ask, askYesNo } from "./ask.js";
@@ -44,7 +49,7 @@ import { createDelayedManualCodeInput } from "./oauth-manual-input.js";
44
49
  import { promptOAuthSelection } from "./oauth-select.js";
45
50
  import { credentialWriteFailed, printError, printOk, printPlaintextCredentialWarning } from "./shared.js";
46
51
  import { terminalColumns, wrapPlain } from "./text-layout.js";
47
- import { validateModelChoice } from "./validate-model.js";
52
+ import { type LiveModelInventory, validateModelChoice } from "./validate-model.js";
48
53
 
49
54
  const HELP = `clio-coder configure
50
55
 
@@ -70,7 +75,8 @@ Non-interactive flags:
70
75
  --agent-profile-model <id> model to use for --agent-profile
71
76
  --api-key-env <VAR> read API key from this env var at call time
72
77
  --api-key <literal> store API key in credentials.yaml
73
- --force allow a model outside the runtime catalog
78
+ --force save a model outside the runtime catalog, or one the
79
+ target does not advertise, without refusing
74
80
  --gateway mark the target as a gateway
75
81
  --lifecycle <user-managed|clio-managed>
76
82
  resident model lifecycle policy
@@ -378,19 +384,35 @@ function catalogContextWindowFor(runtime: RuntimeDescriptor, modelId: string): n
378
384
  return null;
379
385
  }
380
386
 
381
- function validateResolvedModel(runtimeId: string, modelId: string | undefined, force: boolean): boolean {
387
+ /**
388
+ * Check the resolved model against the catalog where there is one and against
389
+ * the target's own list where there is not. A runtime with no catalog used to
390
+ * pass any string here while the same command wrote the live list that ruled
391
+ * it out into `wireModels`, so the target saved as `ok` and failed on its first
392
+ * turn. Without a catalog or a live list there is nothing to check against, and
393
+ * the note says so rather than implying the id was verified.
394
+ */
395
+ function validateResolvedModel(
396
+ runtime: RuntimeDescriptor,
397
+ target: Pick<TargetDescriptor, "id" | "url">,
398
+ modelId: string | undefined,
399
+ force: boolean,
400
+ inventory: WireModelInventory,
401
+ ): boolean {
382
402
  if (!modelId) return true;
383
- const validation = validateModelChoice({
384
- runtimeId,
385
- modelId,
386
- knownModels: runtimeKnownModelsFor(runtimeId),
387
- force,
388
- });
403
+ const knownModels = runtimeKnownModelsFor(runtime.id);
404
+ const live = inventory.source === "probe" ? liveInventoryFor(target, inventory) : undefined;
405
+ const validation = validateModelChoice({ runtimeId: runtime.id, modelId, knownModels, live, force });
389
406
  if (!validation.ok) {
390
407
  process.stderr.write(`error: ${validation.reason}\n`);
391
408
  return false;
392
409
  }
393
410
  if (validation.warning) process.stderr.write(`warning: ${validation.warning}\n`);
411
+ if (knownModels.length === 0 && live === undefined && runtime.kind === "http") {
412
+ process.stderr.write(
413
+ `warning: could not verify model '${modelId}': ${target.url ?? `target '${target.id}'`} returned no model list; it is saved as written\n`,
414
+ );
415
+ }
394
416
  return true;
395
417
  }
396
418
 
@@ -412,10 +434,15 @@ function validateContextWindowOverride(
412
434
  return true;
413
435
  }
414
436
 
415
- async function runtimeProbe(runtime: RuntimeDescriptor, target: TargetDescriptor): Promise<ProbeResult | null> {
437
+ async function runtimeProbe(
438
+ runtime: RuntimeDescriptor,
439
+ target: TargetDescriptor,
440
+ authToken?: string,
441
+ ): Promise<ProbeResult | null> {
416
442
  if (typeof runtime.probe !== "function") return null;
417
443
  try {
418
- return await runtime.probe(target, await buildProbeContext(runtime, target));
444
+ const context = await buildProbeContext(runtime, target);
445
+ return await runtime.probe(target, authToken === undefined ? context : { ...context, authToken });
419
446
  } catch (err) {
420
447
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
421
448
  }
@@ -679,17 +706,69 @@ async function loginOAuthRuntime(rl: ReturnType<typeof createInterface>, runtime
679
706
  }
680
707
  }
681
708
 
709
+ /**
710
+ * The model ids a target can be configured with, and where they came from.
711
+ * `probe` is the only source that says anything about the server in front of
712
+ * us right now; `catalog` is static provider knowledge, and `existing` is the
713
+ * list a previous configure recorded, which the server may no longer match.
714
+ */
715
+ interface WireModelInventory {
716
+ models: string[];
717
+ source: "catalog" | "probe" | "existing" | "none";
718
+ /** Per-model load state when the probe reported it. */
719
+ modelStates?: Readonly<Record<string, ProbeModelStatus>> | undefined;
720
+ }
721
+
722
+ /**
723
+ * Every id the runtime would resolve. LM Studio lists a loaded model under its
724
+ * instance id and keeps the model key only in the state map, and the request
725
+ * path accepts either, so both count as advertised.
726
+ */
727
+ function advertisedModelIds(inventory: Pick<WireModelInventory, "models" | "modelStates">): string[] {
728
+ const ids = [...inventory.models];
729
+ for (const id of Object.keys(inventory.modelStates ?? {})) if (!ids.includes(id)) ids.push(id);
730
+ return ids;
731
+ }
732
+
733
+ function residentModelIds(modelStates: WireModelInventory["modelStates"]): string[] {
734
+ return Object.entries(modelStates ?? {})
735
+ .filter(([, status]) => status.state === "loaded" || status.state === "loading")
736
+ .map(([id]) => id);
737
+ }
738
+
739
+ function liveInventoryFor(
740
+ target: Pick<TargetDescriptor, "id" | "url">,
741
+ inventory: WireModelInventory,
742
+ ): LiveModelInventory {
743
+ return {
744
+ targetId: target.id,
745
+ url: target.url,
746
+ models: advertisedModelIds(inventory),
747
+ resident: residentModelIds(inventory.modelStates),
748
+ };
749
+ }
750
+
682
751
  async function resolveSupportedWireModels(
683
752
  runtime: RuntimeDescriptor,
684
753
  target: TargetDescriptor,
685
754
  existing?: TargetDescriptor,
686
755
  authToken?: string,
687
- ): Promise<string[]> {
756
+ ): Promise<WireModelInventory> {
688
757
  const known = listKnownModelsForRuntime(runtime.id);
689
- if (known.length > 0) return known;
690
- const discovered = runtime.kind === "http" ? await runtimeProbeModels(runtime, target, authToken) : [];
691
- if (discovered.length > 0) return discovered;
692
- return existing?.wireModels ? [...existing.wireModels] : [];
758
+ if (known.length > 0) return { models: known, source: "catalog" };
759
+ if (runtime.kind === "http") {
760
+ // The full probe carries load state alongside the ids; a runtime that
761
+ // lists models only through probeModels still gets its ids checked.
762
+ const probe = await runtimeProbe(runtime, target, authToken);
763
+ if (probe?.ok && probe.models && probe.models.length > 0) {
764
+ return { models: [...probe.models], source: "probe", modelStates: probe.modelStates };
765
+ }
766
+ const discovered = await runtimeProbeModels(runtime, target, authToken);
767
+ if (discovered.length > 0) return { models: discovered, source: "probe" };
768
+ }
769
+ if (existing?.wireModels && existing.wireModels.length > 0)
770
+ return { models: [...existing.wireModels], source: "existing" };
771
+ return { models: [], source: "none" };
693
772
  }
694
773
 
695
774
  function resolveModelChoice(
@@ -810,7 +889,8 @@ async function runNonInteractive(runtime: RuntimeDescriptor, args: ParsedArgs):
810
889
  ...(args.reasoning !== undefined ? { reasoning: args.reasoning } : {}),
811
890
  ...(existing?.lmstudio ? { lmstudio: existing.lmstudio } : {}),
812
891
  });
813
- const wireModels = await resolveSupportedWireModels(runtime, seed, existing, args.apiKey);
892
+ const inventory = await resolveSupportedWireModels(runtime, seed, existing, args.apiKey);
893
+ const wireModels = inventory.models;
814
894
  const model =
815
895
  args.model ??
816
896
  existing?.defaultModel ??
@@ -820,7 +900,7 @@ async function runNonInteractive(runtime: RuntimeDescriptor, args: ParsedArgs):
820
900
  refuseCatalogSeededModel(runtime, support);
821
901
  return 2;
822
902
  }
823
- if (!validateResolvedModel(runtime.id, model, args.force)) return 2;
903
+ if (!validateResolvedModel(runtime, seed, model, args.force, inventory)) return 2;
824
904
  if (!validateContextWindowOverride(runtime, model, args.contextWindow, args.force)) return 2;
825
905
  const descriptor = buildDescriptor(runtime, args.id, {
826
906
  ...(url !== undefined ? { url } : {}),
@@ -1192,7 +1272,10 @@ async function runInteractive(
1192
1272
  }
1193
1273
 
1194
1274
  let model: string | undefined = defaults.model;
1195
- let wireModels: string[] = existing?.wireModels ? [...existing.wireModels] : [];
1275
+ let inventory: WireModelInventory =
1276
+ existing?.wireModels && existing.wireModels.length > 0
1277
+ ? { models: [...existing.wireModels], source: "existing" }
1278
+ : { models: [], source: "none" };
1196
1279
  const tentative = buildDescriptor(runtime, targetId, {
1197
1280
  ...(url !== undefined ? { url } : {}),
1198
1281
  ...(apiKeyEnv !== undefined ? { apiKeyEnv } : {}),
@@ -1211,8 +1294,9 @@ async function runInteractive(
1211
1294
  });
1212
1295
 
1213
1296
  if (runtime.kind === "http") {
1214
- wireModels = await resolveSupportedWireModels(runtime, tentative, existing ?? undefined);
1297
+ inventory = await resolveSupportedWireModels(runtime, tentative, existing ?? undefined);
1215
1298
  }
1299
+ const wireModels = inventory.models;
1216
1300
  // The wizard shows the whole list, so a catalog-ordered runtime does not need
1217
1301
  // --model here the way the non-interactive path does. It does need to stop
1218
1302
  // offering the alphabetically first id as though it were the recommended one.
@@ -1234,11 +1318,11 @@ async function runInteractive(
1234
1318
  process.stdout.write(" a model is required: enter a number from the list or a model id.\n");
1235
1319
  continue;
1236
1320
  }
1237
- if (validateResolvedModel(runtime.id, model, defaults.force)) break;
1321
+ if (validateResolvedModel(runtime, tentative, model, defaults.force, inventory)) break;
1238
1322
  }
1239
1323
  } else {
1240
1324
  for (;;) {
1241
- if (model && validateResolvedModel(runtime.id, model, defaults.force)) break;
1325
+ if (model && validateResolvedModel(runtime, tentative, model, defaults.force, inventory)) break;
1242
1326
  const manual = await ask(rl, "Default model id (blank to leave empty)", model ?? "");
1243
1327
  if (manual === null) return 0;
1244
1328
  model = manual.length > 0 ? manual : undefined;
package/src/cli/doctor.ts CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  runDoctor,
5
5
  runDoctorFleetChecks,
6
6
  runDoctorInteropChecks,
7
+ runDoctorModelChecks,
7
8
  runDoctorRuntimeChecks,
8
9
  } from "../domains/lifecycle/doctor.js";
9
10
  import { printError } from "./shared.js";
@@ -35,6 +36,9 @@ export async function runDoctorCommand(args: ReadonlyArray<string> = []): Promis
35
36
  const untouched = !fix && isUninitializedHome();
36
37
  const findings = runDoctor({ fix });
37
38
  const runtimeChecks = await runDoctorRuntimeChecks();
39
+ // Every model pointer is checked against what its target advertises, so a
40
+ // placeholder id saved by configure is reported here and not on the first turn.
41
+ const modelChecks = await runDoctorModelChecks();
38
42
  // The interop and fleet sweeps read the state and config roots through the
39
43
  // ensuring accessors, which create them, and there is no fleet or interop
40
44
  // state to inspect before Clio has ever written anything. On a home Clio has
@@ -43,7 +47,7 @@ export async function runDoctorCommand(args: ReadonlyArray<string> = []): Promis
43
47
  // Fleet preflight probes each configured node over SSH and persists the
44
48
  // per-node eligibility verdicts dispatch placement enforces.
45
49
  const fleetChecks = untouched ? [] : await runDoctorFleetChecks();
46
- const all = [...findings, ...runtimeChecks, ...interopChecks, ...fleetChecks];
50
+ const all = [...findings, ...runtimeChecks, ...modelChecks, ...interopChecks, ...fleetChecks];
47
51
  const ok = all.every((f) => f.ok);
48
52
  if (json) {
49
53
  process.stdout.write(`${JSON.stringify({ ok, fix, findings: all }, null, 2)}\n`);
@@ -1,16 +1,18 @@
1
1
  import { InvalidIdError } from "../core/safe-id.js";
2
2
  import { clioDataDir, clioStateDir } from "../core/xdg.js";
3
- import type { CanonicalTrustStatus, EvidenceOverview, EvidenceRunProvenance } from "../domains/evidence/index.js";
3
+ import type { EvidenceOverview, EvidenceRunProvenance } from "../domains/evidence/index.js";
4
4
  import {
5
5
  buildEvalEvidence,
6
6
  buildEvidence,
7
7
  EvidenceNotFoundError,
8
+ formatTrustAxes,
9
+ formatTrustSummary,
8
10
  inspectEvidence,
9
11
  listEvidenceOverviews,
10
12
  loadEvidenceRunProvenance,
11
13
  provenanceTranscriptLines,
12
14
  } from "../domains/evidence/index.js";
13
- import { printError, printOk } from "./shared.js";
15
+ import { printError, printNote, printOk } from "./shared.js";
14
16
 
15
17
  const HELP = `clio-coder evidence build --run <runId>
16
18
  clio-coder evidence build --session <sessionId>
@@ -137,15 +139,19 @@ export async function runEvidenceCommand(args: ReadonlyArray<string>): Promise<n
137
139
  // but the verdict line and exit code must not say ok over a receipt
138
140
  // that failed integrity verification.
139
141
  const integrityFailures = result.findings.filter((entry) => entry.tag === "receipt-integrity");
140
- if (integrityFailures.length > 0) {
141
- process.stdout.write(`wrote ${result.evidenceId} ${result.directory}\n`);
142
- for (const failure of integrityFailures) {
143
- printError(`${failure.runId !== null ? `run ${failure.runId}: ` : ""}${failure.message}`);
144
- }
145
- return 1;
142
+ // A retired seal is the expected state of every run that predates an
143
+ // integrity bump: the receipt is set aside unread, as a missing one
144
+ // is, so it is a note beside the verdict and leaves the exit code alone.
145
+ const retiredSeals = result.findings.filter((entry) => entry.tag === "receipt-retired");
146
+ if (integrityFailures.length > 0) process.stdout.write(`wrote ${result.evidenceId} ${result.directory}\n`);
147
+ else printOk(`wrote ${result.evidenceId} ${result.directory}`);
148
+ for (const entry of retiredSeals) {
149
+ printNote(`${entry.runId !== null ? `run ${entry.runId}: ` : ""}${entry.message}`);
146
150
  }
147
- printOk(`wrote ${result.evidenceId} ${result.directory}`);
148
- return 0;
151
+ for (const failure of integrityFailures) {
152
+ printError(`${failure.runId !== null ? `run ${failure.runId}: ` : ""}${failure.message}`);
153
+ }
154
+ return integrityFailures.length > 0 ? 1 : 0;
149
155
  }
150
156
  if (parsed.command === "inspect") {
151
157
  const evidenceId = parsed.evidenceId;
@@ -191,30 +197,29 @@ function renderEvidence(
191
197
  if (trustStatus.projection === "historical_format") {
192
198
  process.stdout.write("trust status: unavailable (historical bundle without canonical projection)\n");
193
199
  } else {
200
+ // The compact projection answers the trust questions; the canonical axis
201
+ // line beneath it is the drill-down, in the same form the dispatch tool
202
+ // and monitor print.
194
203
  for (const run of trustStatus.runs) {
195
- process.stdout.write(`trust ${run.runId}: ${formatTrustStatus(run.status)}\n`);
204
+ process.stdout.write(`trust ${run.runId}: ${formatTrustSummary(run.status)}\n`);
205
+ process.stdout.write(` ${formatTrustAxes(run.status)}\n`);
196
206
  }
197
207
  }
198
- // Provenance is printed only for runs whose receipts carry it, so a legacy
199
- // bundle prints nothing new here.
208
+ // Provenance is the detail behind the projection, never a second reading
209
+ // of it: each run's block is gated on that run's canonical status, so a
210
+ // receipt the projection withheld (a broken or retired seal) prints no
211
+ // autonomy value here, and a run whose block would be empty prints no
212
+ // header. A historical bundle has no projection and admits no axis detail.
213
+ const statusByRun = new Map(trustStatus.runs.map((entry) => [entry.runId, entry.status]));
200
214
  for (const { runId, view } of provenance) {
215
+ const lines = provenanceTranscriptLines(view, statusByRun.get(runId));
216
+ if (lines.length === 0) continue;
201
217
  process.stdout.write(`provenance ${runId}:\n`);
202
- for (const line of provenanceTranscriptLines(view)) process.stdout.write(` ${line}\n`);
218
+ for (const line of lines) process.stdout.write(` ${line}\n`);
203
219
  }
204
220
  process.stdout.write(`files: ${formatList(overview.files)}\n`);
205
221
  }
206
222
 
207
- function formatTrustStatus(status: CanonicalTrustStatus): string {
208
- return [
209
- `artifactIntegrity=${status.artifactIntegrity.state}`,
210
- `validationGrounding=${status.validationGrounding.state}`,
211
- `independentReview=${status.independentReview.state}`,
212
- `contextProvenance=${status.contextProvenance.state}`,
213
- `autonomyEnforcement=${status.autonomyEnforcement.state}`,
214
- `completionEvidence=${status.completionEvidence.state}`,
215
- ].join(" ");
216
- }
217
-
218
223
  function renderEvidenceList(overviews: ReadonlyArray<EvidenceOverview>): void {
219
224
  process.stdout.write(`${overviews.length} evidence artifacts\n`);
220
225
  if (overviews.length === 0) return;
@@ -0,0 +1,37 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { discoverDeclaredProjectCommands } from "../tools/verify/authoring.js";
4
+
5
+ function quoted(value: string): string {
6
+ return JSON.stringify(value);
7
+ }
8
+
9
+ export function runFleetCommands(args: ReadonlyArray<string>): number {
10
+ if (args.length !== 1 || args[0] !== "init") {
11
+ process.stderr.write("clio-coder fleet: commands: usage: clio-coder fleet commands init\n");
12
+ return 2;
13
+ }
14
+ const destination = join(process.cwd(), ".clio-coder", "fleets", "commands.yaml");
15
+ if (existsSync(destination)) {
16
+ process.stderr.write(`clio-coder fleet: commands init: destination already exists: ${destination}\n`);
17
+ return 2;
18
+ }
19
+ const entries = discoverDeclaredProjectCommands(process.cwd());
20
+ const lines = [
21
+ "# Draft fleet command registry.",
22
+ "# Every entry was discovered from a project declaration. Uncommenting an entry confirms its exact invocation.",
23
+ "# version: 1",
24
+ "# commands:",
25
+ ];
26
+ for (const entry of entries) {
27
+ lines.push(
28
+ `# ${entry.id}:`,
29
+ `# argv: [${entry.command.map(quoted).join(", ")}]`,
30
+ `# description: ${quoted(`Discovered from ${entry.provenance.path}: ${entry.provenance.detail}`)}`,
31
+ );
32
+ }
33
+ mkdirSync(dirname(destination), { recursive: true });
34
+ writeFileSync(destination, `${lines.join("\n")}\n`, { flag: "wx" });
35
+ process.stdout.write(`${destination}\n`);
36
+ return 0;
37
+ }
@@ -0,0 +1,102 @@
1
+ import type { ExecutionPlanStep } from "../domains/dispatch/execution-plan.js";
2
+ import { inspectFleet } from "./fleet-preflight.js";
3
+
4
+ /**
5
+ * The contract-level kind of a compiled step. The execution plan knows only
6
+ * `agent` and `code`; a version 5 `gate` or `plan` step compiles to an agent
7
+ * step carrying `gate` or `plan` facts, and the graph names it as the author
8
+ * wrote it.
9
+ */
10
+ function contractKind(step: ExecutionPlanStep): "agent" | "code" | "gate" | "plan" {
11
+ if (step.kind === "code") return "code";
12
+ if (step.gate !== undefined) return "gate";
13
+ if (step.plan !== undefined) return "plan";
14
+ return "agent";
15
+ }
16
+
17
+ function projectedStep(step: ExecutionPlanStep): Record<string, unknown> {
18
+ return {
19
+ id: step.id,
20
+ kind: contractKind(step),
21
+ ...(step.kind === "agent" ? { agent: step.agentId } : { command: step.commandId }),
22
+ ...(step.kind === "agent" && step.gate !== undefined
23
+ ? { gate: { path: step.gate.path, command: step.gate.commandId } }
24
+ : {}),
25
+ ...(step.kind === "agent" && step.plan !== undefined
26
+ ? { plan: { roster: [...step.plan.roster], maxTasks: step.plan.maxTasks, proposals: step.plan.proposals } }
27
+ : {}),
28
+ ...(step.kind === "agent" && step.target !== undefined ? { target: step.target } : {}),
29
+ ...(step.kind === "agent" && step.profile !== undefined ? { profile: step.profile } : {}),
30
+ scope: step.scope,
31
+ writes: [...(step.writes ?? [])],
32
+ dependencies: [...step.dependencies],
33
+ };
34
+ }
35
+
36
+ export function runFleetGraph(args: ReadonlyArray<string>): number {
37
+ const json = args.includes("--json");
38
+ const name = args.find((arg) => !arg.startsWith("-"));
39
+ const unknown = args.find((arg, index) => (arg.startsWith("-") && arg !== "--json") || index > 1);
40
+ if (name === undefined || unknown !== undefined) {
41
+ process.stderr.write("clio-coder fleet: graph: usage: clio-coder fleet graph <name> [--json]\n");
42
+ return 2;
43
+ }
44
+ try {
45
+ const { plan } = inspectFleet(name);
46
+ const byId = new Map(plan.steps.map((step) => [step.id, step]));
47
+ const waves = plan.waves.map((ids, index) => ({
48
+ wave: index + 1,
49
+ steps: ids.flatMap((id) => {
50
+ const step = byId.get(id);
51
+ return step === undefined ? [] : [projectedStep(step)];
52
+ }),
53
+ }));
54
+ const loops = plan.loops.map((loop) => ({
55
+ id: loop.id,
56
+ check: loop.checkStepIds.map((id) => projectedStep(byId.get(id) as ExecutionPlanStep)),
57
+ repair: loop.repairStepIds.map((id) => projectedStep(byId.get(id) as ExecutionPlanStep)),
58
+ }));
59
+ if (json) {
60
+ process.stdout.write(`${JSON.stringify({ fleet: name, planHash: plan.hash, waves, loops }, null, 2)}\n`);
61
+ return 0;
62
+ }
63
+ for (const wave of waves) {
64
+ process.stdout.write(`wave ${wave.wave}\n`);
65
+ for (const step of wave.steps) {
66
+ const subject = step.kind === "code" ? `command=${step.command}` : `agent=${step.agent}`;
67
+ const gate = step.gate as { path: string; command: string } | undefined;
68
+ const plan = step.plan as { roster: string[]; maxTasks: number; proposals: boolean } | undefined;
69
+ const detail =
70
+ gate !== undefined
71
+ ? ` gate=${gate.path} run=${gate.command}`
72
+ : plan !== undefined
73
+ ? ` roster=${JSON.stringify(plan.roster)} maxTasks=${plan.maxTasks}${plan.proposals ? " proposals" : ""}`
74
+ : "";
75
+ const route =
76
+ step.target !== undefined
77
+ ? ` target=${step.target}`
78
+ : step.profile !== undefined
79
+ ? ` profile=${step.profile}`
80
+ : "";
81
+ process.stdout.write(
82
+ ` ${step.id} kind=${step.kind} ${subject}${route} scope=${step.scope} writes=${JSON.stringify(step.writes)}${detail}\n`,
83
+ );
84
+ }
85
+ }
86
+ for (const loop of loops) {
87
+ process.stdout.write(`loop ${loop.id}\n`);
88
+ for (const step of loop.check)
89
+ process.stdout.write(
90
+ ` check ${step.id} kind=${step.kind} scope=${step.scope} writes=${JSON.stringify(step.writes)}\n`,
91
+ );
92
+ for (const step of loop.repair)
93
+ process.stdout.write(
94
+ ` repair ${step.id} kind=${step.kind} scope=${step.scope} writes=${JSON.stringify(step.writes)}\n`,
95
+ );
96
+ }
97
+ return 0;
98
+ } catch (error) {
99
+ process.stderr.write(`clio-coder fleet: ${error instanceof Error ? error.message : String(error)}\n`);
100
+ return 1;
101
+ }
102
+ }
@@ -0,0 +1,36 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { resolvePackageRoot } from "../core/package-root.js";
4
+
5
+ const BUILTINS = ["build-review", "build-test", "sdlc"] as const;
6
+ const SAFE_STEM = /^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/u;
7
+
8
+ function usage(message: string): number {
9
+ process.stderr.write(`clio-coder fleet: new: ${message}\n`);
10
+ return 2;
11
+ }
12
+
13
+ export function runFleetNew(args: ReadonlyArray<string>): number {
14
+ const name = args[0];
15
+ const fromIndex = args.indexOf("--from");
16
+ const builtin = fromIndex === -1 ? undefined : args[fromIndex + 1];
17
+ const known = BUILTINS.join(", ");
18
+ if (name === undefined) return usage("usage: clio-coder fleet new <name> --from <builtin>");
19
+ if (!SAFE_STEM.test(name)) return usage(`'${name}' is not a safe file stem`);
20
+ if (fromIndex === -1 || builtin === undefined) return usage("--from <builtin> is required");
21
+ const expected = new Set([name, "--from", builtin]);
22
+ const unknown = args.find((arg) => !expected.has(arg));
23
+ if (unknown !== undefined) return usage(`unknown argument: ${unknown}`);
24
+ if (!(BUILTINS as ReadonlyArray<string>).includes(builtin)) {
25
+ return usage(`unknown builtin '${builtin}'. Known builtins: ${known}`);
26
+ }
27
+ const destination = join(process.cwd(), ".clio-coder", "fleets", `${name}.md`);
28
+ if (existsSync(destination)) return usage(`destination already exists: ${destination}`);
29
+ const source = join(resolvePackageRoot(), "src", "domains", "agents", "fleets", `${builtin}.md`);
30
+ mkdirSync(dirname(destination), { recursive: true });
31
+ const sourceText = readFileSync(source, "utf8");
32
+ const renamed = sourceText.replace(/^name:\s*[^\n]+$/mu, `name: ${name}`);
33
+ writeFileSync(destination, renamed, { flag: "wx" });
34
+ process.stdout.write(`${destination}\n`);
35
+ return 0;
36
+ }
@@ -0,0 +1,111 @@
1
+ import { readSettings } from "../core/config.js";
2
+ import {
3
+ type FleetCommandRegistry,
4
+ type FleetContract,
5
+ loadFleetCommands,
6
+ loadFleetContract,
7
+ renderFleetPrompt,
8
+ } from "../domains/agents/index.js";
9
+ import { discoverAgentRecipes } from "../domains/agents/registry.js";
10
+ import { type AgentSpec, normalizeAgentSpec } from "../domains/agents/spec.js";
11
+ import type { ExecutionPlan } from "../domains/dispatch/execution-plan.js";
12
+ import { requestExecutionRole, withAttemptRole } from "../domains/dispatch/execution-role.js";
13
+ import { compileFleetExecutionPlan } from "../domains/dispatch/fleet-plan.js";
14
+
15
+ export interface FleetPreflightResult {
16
+ contract: FleetContract;
17
+ commands: FleetCommandRegistry | null;
18
+ plan: ExecutionPlan;
19
+ checks: ReadonlyArray<{ check: "parse" | "graph" | "commands" | "agents" | "plan"; summary: string }>;
20
+ }
21
+
22
+ function discoverSpecs(cwd: string): AgentSpec[] {
23
+ return discoverAgentRecipes(cwd).map(normalizeAgentSpec);
24
+ }
25
+
26
+ export function inspectFleet(name: string, vars?: Readonly<Record<string, string>>): FleetPreflightResult {
27
+ const cwd = process.cwd();
28
+ const contract = loadFleetContract(cwd, name);
29
+ const commands = loadFleetCommands(cwd);
30
+ const prompt = vars === undefined ? contract.body : renderFleetPrompt(contract.body, vars);
31
+ const specs = discoverSpecs(cwd);
32
+ const byId = new Map(specs.map((spec) => [spec.id, spec]));
33
+ const resolved = new Set<string>();
34
+ const settings = readSettings();
35
+ const targetIds = new Set(settings.targets.map((target) => target.id));
36
+ const profileIds = new Set(Object.keys(settings.workers?.profiles ?? {}));
37
+ for (const step of contract.steps) {
38
+ const positions =
39
+ step.kind === "loop"
40
+ ? [
41
+ ...(step.check.kind === "agent" ? [{ id: `${step.id}.check`, route: step.check }] : []),
42
+ { id: `${step.id}.repair`, route: step.repair },
43
+ ]
44
+ : step.kind === "agent" || step.kind === "gate" || step.kind === "plan"
45
+ ? [{ id: step.id, route: step }]
46
+ : [];
47
+ for (const position of positions) {
48
+ if (position.route.target !== undefined && !targetIds.has(position.route.target)) {
49
+ throw new Error(`unknown target '${position.route.target}' at step '${position.id}'`);
50
+ }
51
+ if (position.route.profile !== undefined && !profileIds.has(position.route.profile)) {
52
+ throw new Error(`unknown profile '${position.route.profile}' at step '${position.id}'`);
53
+ }
54
+ }
55
+ }
56
+ const plan = compileFleetExecutionPlan({
57
+ contract,
58
+ task: prompt,
59
+ resolveAgent(context) {
60
+ const spec = byId.get(context.agentId);
61
+ if (spec === undefined) {
62
+ throw new Error(
63
+ `unknown agent '${context.agentId}' (step '${context.stepId}' must name a recipe from 'clio-coder agents')`,
64
+ );
65
+ }
66
+ if (spec.capabilityClass === "orchestration" || spec.capabilityClass === "internal") {
67
+ throw new Error(`fleet step '${context.stepId}' has no automatable agent authority`);
68
+ }
69
+ resolved.add(spec.id);
70
+ const role = requestExecutionRole({
71
+ agentId: spec.id,
72
+ resolveFacts: (id) => {
73
+ const found = byId.get(id);
74
+ return found === undefined
75
+ ? null
76
+ : {
77
+ category: found.category,
78
+ capabilityClass: found.capabilityClass,
79
+ resultContractKind: found.resultContract.kind,
80
+ };
81
+ },
82
+ ...(context.gateRole === undefined ? {} : { gateRole: context.gateRole }),
83
+ });
84
+ return {
85
+ requestedAuthority: spec.capabilityClass,
86
+ approvedAuthority: spec.capabilityClass,
87
+ expectedResultContract:
88
+ context.planRole === true
89
+ ? "delegation-plan"
90
+ : context.gateAuthorRole === true
91
+ ? "artifact-report"
92
+ : context.gateRole === "reviewer"
93
+ ? "verifier-report"
94
+ : spec.resultContract.kind,
95
+ executionRole: withAttemptRole(role, context.attempt),
96
+ };
97
+ },
98
+ });
99
+ return {
100
+ contract,
101
+ commands,
102
+ plan,
103
+ checks: [
104
+ { check: "parse", summary: `parsed contract version ${contract.version}` },
105
+ { check: "graph", summary: `validated ${contract.steps.length} declared steps` },
106
+ { check: "commands", summary: `validated ${plan.steps.filter((step) => step.kind === "code").length} code steps` },
107
+ { check: "agents", summary: `resolved ${resolved.size} agents` },
108
+ { check: "plan", summary: `compiled ${plan.steps.length} plan steps with hash ${plan.hash}` },
109
+ ],
110
+ };
111
+ }
@@ -0,0 +1,30 @@
1
+ import { FLEET_COMMANDS_REMEDY, FleetCommandRegistryMissingError } from "../domains/agents/index.js";
2
+ import { preflightWriteBoundaries } from "../domains/dispatch/write-boundary-enforcer.js";
3
+ import { inspectFleet } from "./fleet-preflight.js";
4
+
5
+ export function runFleetValidate(args: ReadonlyArray<string>): number {
6
+ const json = args.includes("--json");
7
+ const unknown = args.find((arg, index) => (arg.startsWith("-") && arg !== "--json") || index > 1);
8
+ const name = args.find((arg) => !arg.startsWith("-"));
9
+ if (name === undefined || unknown !== undefined) {
10
+ process.stderr.write("clio-coder fleet: validate: usage: clio-coder fleet validate <name> [--json]\n");
11
+ return 2;
12
+ }
13
+ try {
14
+ const result = inspectFleet(name);
15
+ preflightWriteBoundaries(result.plan, process.cwd());
16
+ if (json)
17
+ process.stdout.write(
18
+ `${JSON.stringify({ valid: true, fleet: name, checks: result.checks, planHash: result.plan.hash }, null, 2)}\n`,
19
+ );
20
+ else for (const check of result.checks) process.stdout.write(`${check.check}: ${check.summary}\n`);
21
+ return 0;
22
+ } catch (error) {
23
+ const raw = error instanceof Error ? error.message : String(error);
24
+ const message = raw.startsWith("unknown agent '") ? `preflight failed: ${raw}` : raw;
25
+ const diagnostics = [message, ...(error instanceof FleetCommandRegistryMissingError ? [FLEET_COMMANDS_REMEDY] : [])];
26
+ if (json) process.stdout.write(`${JSON.stringify({ valid: false, fleet: name, diagnostics }, null, 2)}\n`);
27
+ else process.stderr.write(`clio-coder fleet: ${diagnostics.join("\n ")}\n`);
28
+ return 1;
29
+ }
30
+ }