@iowarp/clio-coder 0.4.3 → 0.4.4

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 (340) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +1 -1
  3. package/dist/{acp-H2NGRPWO.js → acp-WNAYYF4F.js} +4 -5
  4. package/dist/{agents-TL5LLUQP.js → agents-3OKXHLOI.js} +33 -31
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{auth-E5SW4HMS.js → auth-VKNNMGPU.js} +10 -11
  7. package/dist/{builtins-IA7V7FUC.js → builtins-WGALA46I.js} +4 -4
  8. package/dist/{chunk-F5JHEYZM.js → chunk-23L32XTI.js} +10 -7
  9. package/dist/{chunk-DWUOQKRU.js → chunk-25QBEXRS.js} +2 -2
  10. package/dist/{chunk-LLDJM5XK.js → chunk-26QSH3EJ.js} +2 -2
  11. package/dist/{chunk-N2Z7HLVY.js → chunk-2ASED4PZ.js} +10 -10
  12. package/dist/{chunk-MCEPRMZW.js → chunk-2CU2H6KE.js} +2 -2
  13. package/dist/chunk-2DSOYNFC.js +108 -0
  14. package/dist/{chunk-LDJG7DW3.js → chunk-2ZSONWVL.js} +4 -4
  15. package/dist/{chunk-GI7YYQ3F.js → chunk-36CT5VVL.js} +324 -41
  16. package/dist/{chunk-4UVU7BJ5.js → chunk-3GY4F45V.js} +2 -2
  17. package/dist/{chunk-PUVDKJ2Y.js → chunk-3ODX73FK.js} +3 -5
  18. package/dist/{chunk-IUE3Y34X.js → chunk-3UNOLWNZ.js} +2 -2
  19. package/dist/{chunk-6PTFB5VS.js → chunk-3UUXNFEX.js} +8 -4
  20. package/dist/{chunk-2APPQIER.js → chunk-4M6Z5QVF.js} +4 -4
  21. package/dist/{chunk-K6BSR66V.js → chunk-4NSRCOYP.js} +4 -1
  22. package/dist/{chunk-JGRC33J2.js → chunk-54X7T7DK.js} +12 -3
  23. package/dist/{chunk-YQWYVTMC.js → chunk-5636DCO5.js} +4 -4
  24. package/dist/{chunk-7E7I3WLS.js → chunk-57XXR6DR.js} +23 -22
  25. package/dist/{chunk-7ZYNNDKC.js → chunk-5TUB6SLS.js} +2 -2
  26. package/dist/{chunk-VW6DOEDG.js → chunk-6OSVSQL5.js} +11 -2
  27. package/dist/{chunk-L47TF46W.js → chunk-6PAZTBPA.js} +14 -2
  28. package/dist/{chunk-XIVNBFZS.js → chunk-6Q3CYFD3.js} +34 -16
  29. package/dist/{chunk-RSJ25QSL.js → chunk-6QOTUPRG.js} +155 -36
  30. package/dist/{chunk-JKKCYP3C.js → chunk-6UINWWS6.js} +10 -4
  31. package/dist/{chunk-WBKFA554.js → chunk-72YIHOZQ.js} +2 -2
  32. package/dist/{chunk-PAJQJ7BS.js → chunk-75W7L2E2.js} +246 -697
  33. package/dist/{chunk-RRNP2ANY.js → chunk-7UGL4MB5.js} +4 -4
  34. package/dist/{chunk-EKCHAPYA.js → chunk-AUPNRN7C.js} +2 -2
  35. package/dist/{chunk-ZDN3Y73Y.js → chunk-BJVFZO5U.js} +6 -48
  36. package/dist/{chunk-IWT4SF4R.js → chunk-CUSRQKPU.js} +64 -2
  37. package/dist/{chunk-SKHCAU7K.js → chunk-DQOVN6KV.js} +3 -2
  38. package/dist/{chunk-ZWPRK62N.js → chunk-DT3LWJOB.js} +4 -4
  39. package/dist/chunk-DXKJURES.js +671 -0
  40. package/dist/{chunk-BEPZRGGU.js → chunk-EL24TAU4.js} +2 -2
  41. package/dist/{chunk-OML5D5V5.js → chunk-ELWDPP3Y.js} +5 -5
  42. package/dist/{chunk-NDINPTJ4.js → chunk-ELZVTCGV.js} +5 -4
  43. package/dist/chunk-EXLD33WO.js +381 -0
  44. package/dist/{chunk-IJNZMHLA.js → chunk-FEAXX7B6.js} +2 -2
  45. package/dist/{chunk-TM6LQDI3.js → chunk-FFUPXJC4.js} +74 -323
  46. package/dist/chunk-GX5WYQO4.js +59 -0
  47. package/dist/chunk-I2DWJ4GM.js +390 -0
  48. package/dist/{chunk-TXOTCRLG.js → chunk-I5FWO7L5.js} +5 -5
  49. package/dist/chunk-IRXAATOX.js +539 -0
  50. package/dist/chunk-IXIY2H4R.js +44 -0
  51. package/dist/{chunk-KK4JZPBQ.js → chunk-IZXGRF7P.js} +76 -10
  52. package/dist/{chunk-5TSRNF4G.js → chunk-JCI2ROMZ.js} +164 -6
  53. package/dist/{chunk-INY6HTFL.js → chunk-JEIYHLOR.js} +2 -2
  54. package/dist/{chunk-MUW2BDDH.js → chunk-JQLNNIKT.js} +2 -2
  55. package/dist/{chunk-XPWWI35G.js → chunk-JSD46VO2.js} +19 -8
  56. package/dist/{chunk-VA5FNYMT.js → chunk-JT2RFCC5.js} +39 -2
  57. package/dist/{chunk-V2ANDPVT.js → chunk-K6T2ZAMZ.js} +168 -6
  58. package/dist/{chunk-B4OAX3SI.js → chunk-KFV5L5SK.js} +13 -6
  59. package/dist/{chunk-JEQ3XTHC.js → chunk-LLXSDWXS.js} +2 -2
  60. package/dist/{chunk-E3TPLWFX.js → chunk-LTIKRKFL.js} +3 -3
  61. package/dist/{chunk-NIQJ66N4.js → chunk-N56KALIC.js} +17 -17
  62. package/dist/{chunk-JDAY6FIL.js → chunk-NAI6ZFCY.js} +13 -9
  63. package/dist/{chunk-AF4YM7Z4.js → chunk-NRO2BJRH.js} +2490 -2182
  64. package/dist/{chunk-WRBAGUNF.js → chunk-NXIMQY5W.js} +2 -2
  65. package/dist/{chunk-I7ZPNEJM.js → chunk-NXYCB2VD.js} +5 -5
  66. package/dist/chunk-ODGTEFFI.js +50 -0
  67. package/dist/{chunk-2VIKGWFZ.js → chunk-OEJSLEPW.js} +2 -2
  68. package/dist/{chunk-KKOJXO6R.js → chunk-OMQNJVKW.js} +4 -2
  69. package/dist/{chunk-54CBCGIR.js → chunk-Q4WO54TA.js} +132 -77
  70. package/dist/{chunk-B4VEBZKF.js → chunk-QUFRYSWI.js} +12 -6
  71. package/dist/{chunk-42FMPA75.js → chunk-QZWQA4DE.js} +2 -2
  72. package/dist/{chunk-HEQY7ZFI.js → chunk-RAY4OVGZ.js} +2 -2
  73. package/dist/{chunk-G76U63X4.js → chunk-RQCKCSRL.js} +7 -7
  74. package/dist/{chunk-64I3JVYM.js → chunk-RXTN6AKH.js} +2 -2
  75. package/dist/{chunk-UPZU6GE4.js → chunk-RZDWV63N.js} +3 -3
  76. package/dist/{chunk-2UH2KFUP.js → chunk-S6PYF2XF.js} +2 -2
  77. package/dist/{chunk-JSC3U7TI.js → chunk-TOIVGRUX.js} +2 -2
  78. package/dist/{chunk-7DRAWPTZ.js → chunk-TQAHXW6Y.js} +1 -1
  79. package/dist/{chunk-JIEGK6UF.js → chunk-U6TMQNSI.js} +48 -4
  80. package/dist/{chunk-AX2THNSA.js → chunk-UEPWCCTY.js} +4 -4
  81. package/dist/{chunk-QWGDJJYJ.js → chunk-USR47QNF.js} +4 -4
  82. package/dist/{chunk-Y3CBHOR6.js → chunk-V6HJFQZE.js} +2 -2
  83. package/dist/chunk-V76WTFTW.js +318 -0
  84. package/dist/{chunk-NMJXSHBJ.js → chunk-W54I7H25.js} +2 -2
  85. package/dist/{chunk-2UG5F4C5.js → chunk-XULDXHTN.js} +24 -12
  86. package/dist/chunk-XXYSBZIQ.js +283 -0
  87. package/dist/{chunk-MWUZBSAQ.js → chunk-Y55JBDO5.js} +331 -51
  88. package/dist/{chunk-W6RRQCPQ.js → chunk-YD5GIKET.js} +4 -4
  89. package/dist/{chunk-CE5AX47J.js → chunk-YECAMM3D.js} +2 -2
  90. package/dist/{chunk-WCXUNS7U.js → chunk-YNFKXPEC.js} +6 -6
  91. package/dist/cli/index.js +36 -35
  92. package/dist/{clio-CMMK4KRR.js → clio-QLICPCF5.js} +2 -2
  93. package/dist/{code-nav-MDZNQS33.js → code-nav-IJR2DBPR.js} +6 -6
  94. package/dist/{components-UCUQ4QXW.js → components-2TGAI2RC.js} +3 -4
  95. package/dist/{config-SVM5P5YI.js → config-IUA6OYNS.js} +52 -47
  96. package/dist/{configure-LE3IK2TJ.js → configure-VEPX4NMX.js} +14 -15
  97. package/dist/{context-VNCR7KAG.js → context-2DKHWH2T.js} +23 -23
  98. package/dist/{context-2OHRKS42.js → context-4MPR7WKB.js} +48 -42
  99. package/dist/{context-E3VC7RX5.js → context-BOYF5EJM.js} +11 -11
  100. package/dist/{context-clear-BW4O37TG.js → context-clear-S4ZJCQUX.js} +48 -42
  101. package/dist/{context-working-set-VDS25HXZ.js → context-working-set-3I3FYX6Y.js} +8 -8
  102. package/dist/detail-A7JAVSIG.js +98 -0
  103. package/dist/{dispatch-runner-5AHT53RF.js → dispatch-runner-RJ5I2F2O.js} +71 -58
  104. package/dist/{docs-PD3EXDKU.js → docs-SPOV3BAN.js} +3 -5
  105. package/dist/{doctor-WNNVO6FY.js → doctor-DKICC2SN.js} +54 -31
  106. package/dist/{eval-7G7SGAYO.js → eval-OQOQUDHK.js} +47 -56
  107. package/dist/{evidence-VD6736FQ.js → evidence-4DQ25GUQ.js} +53 -150
  108. package/dist/evidence-4F5USFKH.js +208 -0
  109. package/dist/{evolve-AL3NGVRL.js → evolve-GSS52E5J.js} +44 -41
  110. package/dist/{extensions-MOVJ32NM.js → extensions-G7MFLYHT.js} +4 -5
  111. package/dist/{fleet-QZHUMAGI.js → fleet-Q37YHHAQ.js} +74 -68
  112. package/dist/{fleet-commands-BAYT5FJZ.js → fleet-commands-G7E4N7SM.js} +14 -11
  113. package/dist/{fleet-decisions-IREVMRU4.js → fleet-decisions-O7M6QBA2.js} +6 -6
  114. package/dist/{fleet-graph-YCTT3HTI.js → fleet-graph-TOUBW6OW.js} +13 -13
  115. package/dist/{fleet-inspect-QVJTDAVB.js → fleet-inspect-SW33JJNI.js} +43 -39
  116. package/dist/{fleet-preflight-25QAFPK4.js → fleet-preflight-CV2655TW.js} +2 -3
  117. package/dist/{fleet-validate-5O57AAJ7.js → fleet-validate-KESZX2YH.js} +16 -16
  118. package/dist/{fleet-verify-CPH2W2T6.js → fleet-verify-UQPMTVE3.js} +42 -38
  119. package/dist/{fleet-view-SWBR3VGQ.js → fleet-view-MN2VG4MR.js} +43 -39
  120. package/dist/{init-J477LKZH.js → init-PXEXQSBF.js} +60 -54
  121. package/dist/{interop-3FCM6XLG.js → interop-ZG5T62U3.js} +6 -7
  122. package/dist/inventory-C26CFDRR.js +101 -0
  123. package/dist/{library-QUQEIUG6.js → library-B2W4N74O.js} +16 -17
  124. package/dist/{memory-SGGSEP65.js → memory-YCANYS5A.js} +45 -42
  125. package/dist/{models-HEKUAXXK.js → models-GERTU3YI.js} +20 -22
  126. package/dist/{monitor-HKU57TYQ.js → monitor-CPNIUULB.js} +50 -44
  127. package/dist/{orchestrator-VDFAEFAI.js → orchestrator-J4BSH4WQ.js} +491 -1182
  128. package/dist/{panes-DN2SSFOH.js → panes-BOHAEGYC.js} +3 -3
  129. package/dist/{panes-TALGNPZT.js → panes-NXSLDQZ2.js} +5 -6
  130. package/dist/{paths-NBMFAIEZ.js → paths-VSUWNC22.js} +3 -4
  131. package/dist/{reset-EAJFFJVB.js → reset-TNWTB5LU.js} +5 -6
  132. package/dist/{resources-OVKSEFVE.js → resources-4PXNMD5G.js} +14 -14
  133. package/dist/{run-7DP7ZF2J.js → run-D6XJ34CN.js} +79 -83
  134. package/dist/{share-WML67FT3.js → share-2NWMJJEE.js} +14 -15
  135. package/dist/{skills-SG662R2K.js → skills-KR7WON5G.js} +18 -19
  136. package/dist/{skills-eval-VVZEUU46.js → skills-eval-O2ZNOLDS.js} +49 -46
  137. package/dist/{skills-inventory-I2E23GET.js → skills-inventory-ZZOUBK7O.js} +14 -14
  138. package/dist/{slash-commands-S7MBJDQK.js → slash-commands-ZXPJD64J.js} +32 -23
  139. package/dist/{steer-2LQOMCPB.js → steer-XA25PSCS.js} +3 -3
  140. package/dist/{support-CC2UJBJ6.js → support-7EMVWYG2.js} +2 -2
  141. package/dist/{targets-4QC3HIEW.js → targets-OMH2XCSN.js} +25 -27
  142. package/dist/tasks-IPAGMEIX.js +36 -0
  143. package/dist/{terminal-lease-TUHIJ6Y2.js → terminal-lease-C2J3JYRE.js} +4 -4
  144. package/dist/{tools-TFGJICCU.js → tools-EFFEAIDP.js} +5 -6
  145. package/dist/{uninstall-5PEVOE5B.js → uninstall-HALS6BLF.js} +7 -8
  146. package/dist/{upgrade-M4WXY6KN.js → upgrade-MS72RJEP.js} +14 -11
  147. package/dist/{usage-N7ZNVLEM.js → usage-NHG6MCJM.js} +60 -51
  148. package/dist/{verifiers-DJTP4XX6.js → verifiers-7AUNVXDY.js} +150 -17
  149. package/dist/{verify-RWE4PPEK.js → verify-FWYGPKMR.js} +12 -10
  150. package/dist/{web-fetch-MPARV2K7.js → web-fetch-V4FKSDAV.js} +4 -4
  151. package/dist/{wiki-generate-C7IQOXSP.js → wiki-generate-743CIGJW.js} +63 -56
  152. package/dist/{with-panes-4GCGSL7J.js → with-panes-BDQEWBRT.js} +2 -2
  153. package/dist/worker/entry.js +36 -33
  154. package/docs/README.md +3 -2
  155. package/docs/architecture/acp.md +17 -0
  156. package/docs/architecture/artifact-versions.md +1 -1
  157. package/docs/architecture/dispatch-typed-intent.md +1 -1
  158. package/docs/architecture/evidence-and-memory.md +1 -1
  159. package/docs/architecture/observability.md +7 -1
  160. package/docs/architecture/prompt-envelope-and-tools.md +15 -5
  161. package/docs/architecture/safety-model.md +10 -17
  162. package/docs/architecture/tui-design.md +1 -1
  163. package/docs/guide/built-in-agents.md +8 -8
  164. package/docs/guide/commands-and-modes.md +17 -2
  165. package/docs/guide/configuration-and-targets.md +3 -1
  166. package/docs/guide/configuration-reference.md +10 -5
  167. package/docs/guide/environment-variables.md +2 -2
  168. package/docs/guide/tool-usage.md +78 -3
  169. package/docs/history/config-knobs-audit.md +2 -2
  170. package/docs/process/development-pipeline.md +6 -1
  171. package/docs/process/git-commit-provenance.md +15 -0
  172. package/docs/process/release-cut-checklist.md +207 -0
  173. package/docs/process/scientific-validation.md +18 -17
  174. package/evals/behavioral-machinery-support.ts +1 -0
  175. package/evals/behavioral-machinery.yaml +1 -1
  176. package/package.json +1 -1
  177. package/skills/coding/coding-standards/SKILL.md +14 -4
  178. package/skills/context/context-handoff/SKILL.md +3 -3
  179. package/skills/context/context-prime/SKILL.md +3 -3
  180. package/skills/planning/architecture/SKILL.md +5 -5
  181. package/skills/planning/backlog/SKILL.md +4 -4
  182. package/skills/planning/prd/SKILL.md +4 -3
  183. package/skills/planning/product-intent/SKILL.md +8 -8
  184. package/skills/planning/tech-spec/SKILL.md +4 -4
  185. package/skills/registry.yaml +32 -32
  186. package/skills/research/arxiv-literature/SKILL.md +3 -3
  187. package/skills/research/experiment-protocol/SKILL.md +3 -3
  188. package/skills/research/scientific-debugging/SKILL.md +3 -2
  189. package/skills/research/scientific-modernization/SKILL.md +3 -3
  190. package/skills/skill-marketplace.json +16 -16
  191. package/skills/workflow/cut-it/SKILL.md +3 -4
  192. package/skills/workflow/design-council/SKILL.md +5 -10
  193. package/skills/workflow/grill-me/SKILL.md +14 -15
  194. package/skills/workflow/workflow-distiller/SKILL.md +4 -4
  195. package/src/cli/args.ts +0 -8
  196. package/src/cli/configure.ts +2 -1
  197. package/src/cli/doctor-state-size.ts +1 -12
  198. package/src/cli/doctor-validation-contract.ts +28 -0
  199. package/src/cli/doctor.ts +5 -0
  200. package/src/cli/evidence-detail.ts +1 -75
  201. package/src/cli/evidence-inventory.ts +1 -167
  202. package/src/cli/index.ts +2 -0
  203. package/src/cli/run.ts +0 -2
  204. package/src/cli/targets.ts +1 -1
  205. package/src/cli/tasks.ts +84 -0
  206. package/src/cli/upgrade.ts +10 -5
  207. package/src/cli/usage.ts +6 -0
  208. package/src/cli/verifiers.ts +147 -1
  209. package/src/cli/wiki-generate.ts +1 -0
  210. package/src/core/commit-attribution.ts +41 -1
  211. package/src/core/git-commit-attribution.ts +46 -3
  212. package/src/core/run-overrides.ts +0 -5
  213. package/src/core/skill-activation.ts +3 -0
  214. package/src/core/tool-names.ts +5 -2
  215. package/src/domains/agents/builtins/architect.md +1 -1
  216. package/src/domains/agents/builtins/coder.md +1 -1
  217. package/src/domains/agents/builtins/documenter.md +1 -1
  218. package/src/domains/agents/builtins/git-master.md +1 -1
  219. package/src/domains/agents/builtins/provenance.md +7 -7
  220. package/src/domains/agents/builtins/tester.md +1 -1
  221. package/src/domains/agents/builtins/verifier.md +2 -2
  222. package/src/domains/agents/builtins/wiki-writer.md +4 -3
  223. package/src/domains/context/extension.ts +31 -7
  224. package/src/domains/context/refresh.ts +3 -0
  225. package/src/domains/context/wiki/frontmatter.ts +5 -2
  226. package/src/domains/context/wiki/generate.ts +6 -0
  227. package/src/domains/context/wiki/prompts.ts +43 -0
  228. package/src/domains/dispatch/active-route-planner.ts +4 -0
  229. package/src/domains/dispatch/code-step.ts +11 -4
  230. package/src/domains/dispatch/contract.ts +31 -7
  231. package/src/domains/dispatch/execution-scheduler.ts +2 -0
  232. package/src/domains/dispatch/extension.ts +50 -27
  233. package/src/domains/dispatch/fleet-commit-attribution.ts +5 -0
  234. package/src/domains/dispatch/fleet-run.ts +1 -0
  235. package/src/domains/dispatch/host-verification.ts +114 -13
  236. package/src/domains/dispatch/intent.ts +28 -18
  237. package/src/domains/dispatch/orphan-recovery.ts +2 -0
  238. package/src/domains/dispatch/receipt-integrity.ts +4 -0
  239. package/src/domains/dispatch/reservation-store.ts +5 -3
  240. package/src/domains/dispatch/state.ts +15 -2
  241. package/src/domains/dispatch/types.ts +17 -2
  242. package/src/domains/eval/runners/clio-run.ts +12 -9
  243. package/src/domains/eval/runners/context-index.ts +2 -7
  244. package/src/domains/eval/runners/context-init.ts +3 -6
  245. package/src/domains/eval/runners/external-command.ts +29 -11
  246. package/src/domains/evidence/build.ts +102 -15
  247. package/src/domains/evidence/detail.ts +69 -0
  248. package/src/domains/evidence/eval.ts +13 -1
  249. package/src/domains/evidence/finish-contract-map.ts +5 -1
  250. package/src/domains/evidence/inventory.ts +167 -0
  251. package/src/domains/evidence/store.ts +16 -0
  252. package/src/domains/evidence/types.ts +12 -0
  253. package/src/domains/extensions/resources.ts +7 -0
  254. package/src/domains/middleware/marketplace-offer.ts +20 -1
  255. package/src/domains/middleware/runtime.ts +7 -3
  256. package/src/domains/mux/detect.ts +3 -6
  257. package/src/domains/observability/accountability.ts +15 -1
  258. package/src/domains/observability/contract.ts +52 -7
  259. package/src/domains/observability/evidence-index.ts +10 -0
  260. package/src/domains/observability/extension.ts +9 -5
  261. package/src/domains/observability/projection.ts +394 -45
  262. package/src/{interactive → domains/observability}/worker-progress.ts +3 -3
  263. package/src/domains/prompts/fragments/operating/contract.md +2 -0
  264. package/src/domains/prompts/fragments/wiki/page.md +8 -0
  265. package/src/domains/providers/model-discovery.ts +1 -4
  266. package/src/domains/providers/models/local-models/clio-coder-local-coding-targets.yaml +16 -14
  267. package/src/domains/providers/runtimes/claude/claude-code.ts +9 -0
  268. package/src/domains/providers/support.ts +1 -1
  269. package/src/domains/providers/types/runtime-descriptor.ts +1 -1
  270. package/src/domains/safety/action-classifier.ts +7 -0
  271. package/src/domains/safety/default-path-policy.ts +2 -0
  272. package/src/domains/safety/finish-contract-registration.ts +29 -14
  273. package/src/domains/safety/finish-contract.ts +252 -40
  274. package/src/domains/safety/index.ts +20 -1
  275. package/src/domains/safety/policy-engine.ts +26 -6
  276. package/src/domains/safety/rigor.ts +53 -39
  277. package/src/domains/safety/validation-contract.ts +388 -0
  278. package/src/domains/session/archive-readers.ts +10 -1
  279. package/src/domains/session/decision-board.ts +101 -2
  280. package/src/domains/session/entries.ts +44 -7
  281. package/src/domains/session/extension.ts +4 -4
  282. package/src/domains/session/handoff.ts +2 -1
  283. package/src/domains/session/manager.ts +2 -3
  284. package/src/domains/session/task-board.ts +14 -1
  285. package/src/domains/session/tree/fork.ts +1 -2
  286. package/src/domains/session/tree/navigator.ts +1 -1
  287. package/src/domains/user-tasks/acceptance.ts +56 -0
  288. package/src/domains/user-tasks/active-acceptance.ts +40 -0
  289. package/src/domains/user-tasks/store.ts +34 -3
  290. package/src/engine/acp/adapter.ts +24 -6
  291. package/src/engine/acp/server.ts +21 -4
  292. package/src/engine/acp/transport.ts +53 -8
  293. package/src/engine/acp/types.ts +4 -0
  294. package/src/engine/apis/ollama-native.ts +15 -0
  295. package/src/engine/claude/subprocess-runtime.ts +107 -60
  296. package/src/engine/external-subprocess.ts +12 -4
  297. package/src/entry/orchestrator.ts +11 -2
  298. package/src/interactive/dispatch-board.ts +46 -717
  299. package/src/interactive/fleet-run-preview.ts +2 -1
  300. package/src/interactive/interactive-application.ts +3 -2
  301. package/src/interactive/interactive-presentation.ts +55 -12
  302. package/src/interactive/interactive-slash-runtime.ts +2 -1
  303. package/src/interactive/oracle.ts +5 -2
  304. package/src/interactive/overlays/fleet-run-approval.ts +3 -2
  305. package/src/interactive/overlays/message-picker.ts +2 -2
  306. package/src/interactive/overlays/settings.ts +2 -2
  307. package/src/interactive/overlays/tree-selector.ts +2 -2
  308. package/src/interactive/renderers/branch-summary.ts +1 -1
  309. package/src/interactive/slash-autocomplete.ts +4 -6
  310. package/src/interactive/slash-commands.ts +25 -39
  311. package/src/interactive/slash-spec.ts +28 -0
  312. package/src/interactive/view/artifacts.ts +2 -0
  313. package/src/interactive/worker-stream.ts +7 -3
  314. package/src/tools/bootstrap.ts +4 -0
  315. package/src/tools/builtin-tool-catalog.ts +31 -0
  316. package/src/tools/compete-worktrees.ts +7 -1
  317. package/src/tools/core-bootstrap.ts +16 -0
  318. package/src/tools/decide.ts +136 -0
  319. package/src/tools/dispatch-admission.ts +21 -0
  320. package/src/tools/dispatch-plan.ts +5 -2
  321. package/src/tools/dispatch-runner.ts +15 -1
  322. package/src/tools/dispatch-types.ts +6 -0
  323. package/src/tools/evidence.ts +96 -0
  324. package/src/tools/limitation.ts +76 -0
  325. package/src/tools/policy.ts +9 -0
  326. package/src/tools/presentation.ts +3 -0
  327. package/src/tools/registry.ts +1 -1
  328. package/src/tools/result-shaping.ts +17 -5
  329. package/src/tools/task-worktree.ts +13 -3
  330. package/src/tools/tasks.ts +10 -1
  331. package/src/tools/verify/authoring.ts +170 -83
  332. package/src/tools/verify/catalog.ts +122 -5
  333. package/src/tools/verify/index.ts +2 -1
  334. package/src/tools/verify/numeric.ts +298 -0
  335. package/src/tools/verify/perf.ts +143 -0
  336. package/src/tools/verify/scripts.ts +229 -2
  337. package/dist/chunk-7DICMOS6.js +0 -313
  338. package/dist/chunk-RVG5JXAL.js +0 -41
  339. package/dist/chunk-T56WDKA5.js +0 -183
  340. package/dist/chunk-VPKWYKEY.js +0 -169
@@ -0,0 +1,388 @@
1
+ import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { parseDocument } from "yaml";
4
+ import { resolveSafeCwd } from "../../core/safe-exec.js";
5
+ import { byteLength } from "../../tools/truncate-utf8.js";
6
+ import { compareCodepoints } from "../evidence/ordering.js";
7
+
8
+ /**
9
+ * The one strict loader for the version-1 scientific validation contract.
10
+ * Rigor resolution, verifier authoring, doctor, and the startup hint all read
11
+ * the contract through this module, so a file either parses under one schema
12
+ * or is diagnosed under one vocabulary. Nothing here executes anything: a
13
+ * parsed contract is a requirement statement, and its `validators` stay prose
14
+ * until the project declares matching `.clio-coder/verifiers.yaml` entries.
15
+ */
16
+
17
+ export const VALIDATION_CONTRACT_VERSION = 1;
18
+
19
+ /** YAML contract paths in resolution order; the first that exists is the contract. */
20
+ export const VALIDATION_CONTRACT_YAML_PATHS: ReadonlyArray<string> = [
21
+ ".clio-coder/validation.yaml",
22
+ ".clio-coder/validation.yml",
23
+ "validation.yaml",
24
+ "validation.yml",
25
+ ];
26
+
27
+ /** Markdown is recognized as present but never parsed and never raises rigor. */
28
+ export const VALIDATION_CONTRACT_MARKDOWN_PATH = "VALIDATION.md";
29
+
30
+ /** Public schema limits. Diagnostics cite these values instead of hiding policy. */
31
+ export const VALIDATION_CONTRACT_CAPS = Object.freeze({
32
+ fileBytes: 256 * 1024,
33
+ textBytes: 4096,
34
+ notesBytes: 16 * 1024,
35
+ artifacts: 256,
36
+ validators: 128,
37
+ modules: 64,
38
+ mapEntries: 256,
39
+ mapKeyBytes: 256,
40
+ });
41
+
42
+ export type ValidationRuntimeKind = "local" | "slurm" | "mpi" | "other";
43
+
44
+ const RUNTIME_KINDS: ReadonlyArray<ValidationRuntimeKind> = ["local", "slurm", "mpi", "other"];
45
+
46
+ export interface ValidationContractRuntime {
47
+ kind: ValidationRuntimeKind;
48
+ nodes?: number;
49
+ ranks?: number;
50
+ walltime?: string;
51
+ modules?: string[];
52
+ }
53
+
54
+ export interface ValidationNumericalTolerances {
55
+ relative?: number;
56
+ absolute?: number;
57
+ ulp?: number;
58
+ }
59
+
60
+ export interface ValidationContractArtifact {
61
+ path: string;
62
+ format?: string;
63
+ expected_dimensions?: Record<string, number>;
64
+ expected_attributes?: Record<string, string>;
65
+ numerical_tolerances?: ValidationNumericalTolerances;
66
+ preserve?: boolean;
67
+ }
68
+
69
+ export interface ValidationContract {
70
+ version: typeof VALIDATION_CONTRACT_VERSION;
71
+ task?: string;
72
+ runtime?: ValidationContractRuntime;
73
+ artifacts?: ValidationContractArtifact[];
74
+ /** Advisory command prose. Never executed from here. */
75
+ validators?: string[];
76
+ notes?: string;
77
+ }
78
+
79
+ export type ValidationContractLoadResult =
80
+ /** A YAML contract parsed under the version-1 schema. */
81
+ | { ok: true; contract: ValidationContract; path: string }
82
+ /** No contract file at the workspace root. */
83
+ | { ok: true; contract: null }
84
+ /** Only `VALIDATION.md` exists: present, unparsed, advisory. */
85
+ | { ok: true; contract: null; path: string; advisory: true }
86
+ /** A YAML contract exists but does not parse; `reason` names the fault and cites the cap it crossed. */
87
+ | { ok: false; path: string; reason: string };
88
+
89
+ const ROOT_FIELDS = new Set(["version", "task", "runtime", "artifacts", "validators", "notes"]);
90
+ const RUNTIME_FIELDS = new Set(["kind", "nodes", "ranks", "walltime", "modules"]);
91
+ const ARTIFACT_FIELDS = new Set([
92
+ "path",
93
+ "format",
94
+ "expected_dimensions",
95
+ "expected_attributes",
96
+ "numerical_tolerances",
97
+ "preserve",
98
+ ]);
99
+ const TOLERANCE_FIELDS = new Set(["relative", "absolute", "ulp"]);
100
+
101
+ function isRecord(value: unknown): value is Record<string, unknown> {
102
+ return value !== null && typeof value === "object" && !Array.isArray(value);
103
+ }
104
+
105
+ function unknownFields(record: Record<string, unknown>, allowed: ReadonlySet<string>): string[] {
106
+ return Object.keys(record)
107
+ .filter((key) => !allowed.has(key))
108
+ .sort(compareCodepoints);
109
+ }
110
+
111
+ function boundedText(value: unknown, location: string, cap: number): string | Error {
112
+ if (typeof value !== "string" || value.length === 0) return new Error(`${location} must be a non-empty string`);
113
+ if (value.includes("\0")) return new Error(`${location} must not contain a NUL byte`);
114
+ if (byteLength(value) > cap) return new Error(`${location} exceeds the ${cap}-byte cap`);
115
+ return value;
116
+ }
117
+
118
+ function positiveInteger(value: unknown, location: string): number | Error {
119
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
120
+ return new Error(`${location} must be a positive integer`);
121
+ }
122
+ return value;
123
+ }
124
+
125
+ function nonNegativeNumber(value: unknown, location: string): number | Error {
126
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
127
+ return new Error(`${location} must be a finite non-negative number`);
128
+ }
129
+ return value;
130
+ }
131
+
132
+ function boundedStringList(value: unknown, location: string, cap: number): string[] | Error {
133
+ if (!Array.isArray(value)) return new Error(`${location} must be an array of strings`);
134
+ if (value.length > cap) return new Error(`${location} exceeds the ${cap}-entry cap`);
135
+ const items: string[] = [];
136
+ for (const [index, entry] of value.entries()) {
137
+ const text = boundedText(entry, `${location}[${index}]`, VALIDATION_CONTRACT_CAPS.textBytes);
138
+ if (text instanceof Error) return text;
139
+ items.push(text);
140
+ }
141
+ return items;
142
+ }
143
+
144
+ function boundedMap<T>(
145
+ value: unknown,
146
+ location: string,
147
+ validateEntry: (entry: unknown, entryLocation: string) => T | Error,
148
+ ): Record<string, T> | Error {
149
+ if (!isRecord(value)) return new Error(`${location} must be a map`);
150
+ const keys = Object.keys(value);
151
+ if (keys.length > VALIDATION_CONTRACT_CAPS.mapEntries) {
152
+ return new Error(`${location} exceeds the ${VALIDATION_CONTRACT_CAPS.mapEntries}-entry cap`);
153
+ }
154
+ const out: Record<string, T> = {};
155
+ for (const key of keys.sort(compareCodepoints)) {
156
+ const entryLocation = `${location}.${key}`;
157
+ if (key.length === 0) return new Error(`${location} has an empty key`);
158
+ if (byteLength(key) > VALIDATION_CONTRACT_CAPS.mapKeyBytes) {
159
+ return new Error(`${entryLocation} key exceeds the ${VALIDATION_CONTRACT_CAPS.mapKeyBytes}-byte cap`);
160
+ }
161
+ const entry = validateEntry(value[key], entryLocation);
162
+ if (entry instanceof Error) return entry;
163
+ out[key] = entry;
164
+ }
165
+ return out;
166
+ }
167
+
168
+ function validateRuntime(value: unknown, location: string): ValidationContractRuntime | Error {
169
+ if (!isRecord(value)) return new Error(`${location} must be an object with a kind field`);
170
+ const unknown = unknownFields(value, RUNTIME_FIELDS);
171
+ if (unknown.length > 0) return new Error(`${location} has unknown field(s): ${unknown.join(", ")}`);
172
+ if (!Object.hasOwn(value, "kind")) return new Error(`${location}.kind is required`);
173
+ const kind = value.kind;
174
+ if (typeof kind !== "string" || !(RUNTIME_KINDS as ReadonlyArray<string>).includes(kind)) {
175
+ return new Error(`${location}.kind must be one of ${RUNTIME_KINDS.join(", ")}`);
176
+ }
177
+ const runtime: ValidationContractRuntime = { kind: kind as ValidationRuntimeKind };
178
+ if (Object.hasOwn(value, "nodes")) {
179
+ const nodes = positiveInteger(value.nodes, `${location}.nodes`);
180
+ if (nodes instanceof Error) return nodes;
181
+ runtime.nodes = nodes;
182
+ }
183
+ if (Object.hasOwn(value, "ranks")) {
184
+ const ranks = positiveInteger(value.ranks, `${location}.ranks`);
185
+ if (ranks instanceof Error) return ranks;
186
+ runtime.ranks = ranks;
187
+ }
188
+ if (Object.hasOwn(value, "walltime")) {
189
+ const walltime = boundedText(value.walltime, `${location}.walltime`, VALIDATION_CONTRACT_CAPS.textBytes);
190
+ if (walltime instanceof Error) return walltime;
191
+ runtime.walltime = walltime;
192
+ }
193
+ if (Object.hasOwn(value, "modules")) {
194
+ const modules = boundedStringList(value.modules, `${location}.modules`, VALIDATION_CONTRACT_CAPS.modules);
195
+ if (modules instanceof Error) return modules;
196
+ runtime.modules = modules;
197
+ }
198
+ return runtime;
199
+ }
200
+
201
+ function validateTolerances(value: unknown, location: string): ValidationNumericalTolerances | Error {
202
+ if (!isRecord(value)) return new Error(`${location} must be an object`);
203
+ const unknown = unknownFields(value, TOLERANCE_FIELDS);
204
+ if (unknown.length > 0) return new Error(`${location} has unknown field(s): ${unknown.join(", ")}`);
205
+ const tolerances: ValidationNumericalTolerances = {};
206
+ if (Object.hasOwn(value, "relative")) {
207
+ const relative = nonNegativeNumber(value.relative, `${location}.relative`);
208
+ if (relative instanceof Error) return relative;
209
+ tolerances.relative = relative;
210
+ }
211
+ if (Object.hasOwn(value, "absolute")) {
212
+ const absolute = nonNegativeNumber(value.absolute, `${location}.absolute`);
213
+ if (absolute instanceof Error) return absolute;
214
+ tolerances.absolute = absolute;
215
+ }
216
+ if (Object.hasOwn(value, "ulp")) {
217
+ const ulp = value.ulp;
218
+ if (typeof ulp !== "number" || !Number.isInteger(ulp) || ulp < 0) {
219
+ return new Error(`${location}.ulp must be a non-negative integer`);
220
+ }
221
+ tolerances.ulp = ulp;
222
+ }
223
+ return tolerances;
224
+ }
225
+
226
+ function validateArtifact(value: unknown, location: string): ValidationContractArtifact | Error {
227
+ if (!isRecord(value)) return new Error(`${location} must be an object with a path field`);
228
+ const unknown = unknownFields(value, ARTIFACT_FIELDS);
229
+ if (unknown.length > 0) return new Error(`${location} has unknown field(s): ${unknown.join(", ")}`);
230
+ if (!Object.hasOwn(value, "path")) return new Error(`${location}.path is required`);
231
+ const artifactPath = boundedText(value.path, `${location}.path`, VALIDATION_CONTRACT_CAPS.textBytes);
232
+ if (artifactPath instanceof Error) return artifactPath;
233
+ const artifact: ValidationContractArtifact = { path: artifactPath };
234
+ if (Object.hasOwn(value, "format")) {
235
+ const format = boundedText(value.format, `${location}.format`, VALIDATION_CONTRACT_CAPS.textBytes);
236
+ if (format instanceof Error) return format;
237
+ artifact.format = format;
238
+ }
239
+ if (Object.hasOwn(value, "expected_dimensions")) {
240
+ const dimensions = boundedMap(value.expected_dimensions, `${location}.expected_dimensions`, (entry, entryLocation) =>
241
+ typeof entry === "number" && Number.isInteger(entry) && entry >= 0
242
+ ? entry
243
+ : new Error(`${entryLocation} must be a non-negative integer`),
244
+ );
245
+ if (dimensions instanceof Error) return dimensions;
246
+ artifact.expected_dimensions = dimensions;
247
+ }
248
+ if (Object.hasOwn(value, "expected_attributes")) {
249
+ const attributes = boundedMap(value.expected_attributes, `${location}.expected_attributes`, (entry, entryLocation) =>
250
+ typeof entry === "string"
251
+ ? boundedText(entry, entryLocation, VALIDATION_CONTRACT_CAPS.textBytes)
252
+ : new Error(`${entryLocation} must be a string`),
253
+ );
254
+ if (attributes instanceof Error) return attributes;
255
+ artifact.expected_attributes = attributes;
256
+ }
257
+ if (Object.hasOwn(value, "numerical_tolerances")) {
258
+ const tolerances = validateTolerances(value.numerical_tolerances, `${location}.numerical_tolerances`);
259
+ if (tolerances instanceof Error) return tolerances;
260
+ artifact.numerical_tolerances = tolerances;
261
+ }
262
+ if (Object.hasOwn(value, "preserve")) {
263
+ if (typeof value.preserve !== "boolean") return new Error(`${location}.preserve must be a boolean`);
264
+ artifact.preserve = value.preserve;
265
+ }
266
+ return artifact;
267
+ }
268
+
269
+ /** Parse contract text under the version-1 schema; `contractPath` only labels the result. */
270
+ export function parseValidationContractText(text: string, contractPath: string): ValidationContractLoadResult {
271
+ const invalid = (reason: string): ValidationContractLoadResult => ({ ok: false, path: contractPath, reason });
272
+ const document = parseDocument(text, { prettyErrors: false, strict: true, uniqueKeys: true });
273
+ if (document.errors.length > 0) {
274
+ return invalid(`invalid YAML: ${document.errors.map((error) => error.message).join("; ")}`);
275
+ }
276
+ let parsed: unknown;
277
+ try {
278
+ parsed = document.toJS({ maxAliasCount: 0 }) as unknown;
279
+ } catch (error) {
280
+ return invalid(`invalid YAML: ${error instanceof Error ? error.message : String(error)}`);
281
+ }
282
+ if (!isRecord(parsed)) return invalid("root must be an object with a version field");
283
+ const rootUnknown = unknownFields(parsed, ROOT_FIELDS);
284
+ if (rootUnknown.length > 0) return invalid(`root has unknown field(s): ${rootUnknown.join(", ")}`);
285
+ if (!Object.hasOwn(parsed, "version")) return invalid("root.version is required");
286
+ if (parsed.version !== VALIDATION_CONTRACT_VERSION) {
287
+ return invalid(
288
+ `unsupported version ${JSON.stringify(parsed.version)}; supported version is ${VALIDATION_CONTRACT_VERSION}`,
289
+ );
290
+ }
291
+ const contract: ValidationContract = { version: VALIDATION_CONTRACT_VERSION };
292
+ if (Object.hasOwn(parsed, "task")) {
293
+ const task = boundedText(parsed.task, "root.task", VALIDATION_CONTRACT_CAPS.textBytes);
294
+ if (task instanceof Error) return invalid(task.message);
295
+ contract.task = task;
296
+ }
297
+ if (Object.hasOwn(parsed, "runtime")) {
298
+ const runtime = validateRuntime(parsed.runtime, "root.runtime");
299
+ if (runtime instanceof Error) return invalid(runtime.message);
300
+ contract.runtime = runtime;
301
+ }
302
+ if (Object.hasOwn(parsed, "artifacts")) {
303
+ if (!Array.isArray(parsed.artifacts)) return invalid("root.artifacts must be an array");
304
+ if (parsed.artifacts.length > VALIDATION_CONTRACT_CAPS.artifacts) {
305
+ return invalid(`root.artifacts exceeds the ${VALIDATION_CONTRACT_CAPS.artifacts}-entry cap`);
306
+ }
307
+ const artifacts: ValidationContractArtifact[] = [];
308
+ for (const [index, value] of parsed.artifacts.entries()) {
309
+ const artifact = validateArtifact(value, `artifacts[${index}]`);
310
+ if (artifact instanceof Error) return invalid(artifact.message);
311
+ artifacts.push(artifact);
312
+ }
313
+ contract.artifacts = artifacts;
314
+ }
315
+ if (Object.hasOwn(parsed, "validators")) {
316
+ const validators = boundedStringList(parsed.validators, "root.validators", VALIDATION_CONTRACT_CAPS.validators);
317
+ if (validators instanceof Error) return invalid(validators.message);
318
+ contract.validators = validators;
319
+ }
320
+ if (Object.hasOwn(parsed, "notes")) {
321
+ const notes = boundedText(parsed.notes, "root.notes", VALIDATION_CONTRACT_CAPS.notesBytes);
322
+ if (notes instanceof Error) return invalid(notes.message);
323
+ contract.notes = notes;
324
+ }
325
+ return { ok: true, contract, path: contractPath };
326
+ }
327
+
328
+ function readContractText(absolutePath: string, workspaceRoot: string): string | Error {
329
+ let realPath: string;
330
+ try {
331
+ const realRoot = realpathSync(workspaceRoot);
332
+ realPath = realpathSync(absolutePath);
333
+ resolveSafeCwd(realPath, realRoot);
334
+ } catch (error) {
335
+ const message = error instanceof Error ? error.message : String(error);
336
+ return new Error(`contract file must resolve inside the workspace root (${message})`);
337
+ }
338
+ try {
339
+ const stats = statSync(realPath);
340
+ if (!stats.isFile()) return new Error("contract path must be a regular file");
341
+ if (stats.size > VALIDATION_CONTRACT_CAPS.fileBytes) {
342
+ return new Error(`file exceeds the ${VALIDATION_CONTRACT_CAPS.fileBytes}-byte cap`);
343
+ }
344
+ return readFileSync(realPath, "utf8");
345
+ } catch (error) {
346
+ return new Error(`cannot read contract (${error instanceof Error ? error.message : String(error)})`);
347
+ }
348
+ }
349
+
350
+ /**
351
+ * Load the workspace's validation contract. The first YAML path in
352
+ * {@link VALIDATION_CONTRACT_YAML_PATHS} that exists is the contract; a
353
+ * `VALIDATION.md` without any YAML is reported present and advisory. Every
354
+ * failure is a value, never a throw, so rigor resolution stays total.
355
+ */
356
+ export function loadValidationContract(workspaceRoot: string): ValidationContractLoadResult {
357
+ let contractPath: string | null = null;
358
+ try {
359
+ contractPath =
360
+ VALIDATION_CONTRACT_YAML_PATHS.find((relative) => existsSync(path.join(workspaceRoot, relative))) ?? null;
361
+ if (contractPath === null) {
362
+ if (existsSync(path.join(workspaceRoot, VALIDATION_CONTRACT_MARKDOWN_PATH))) {
363
+ return { ok: true, contract: null, path: VALIDATION_CONTRACT_MARKDOWN_PATH, advisory: true };
364
+ }
365
+ return { ok: true, contract: null };
366
+ }
367
+ } catch (error) {
368
+ return {
369
+ ok: false,
370
+ path: contractPath ?? VALIDATION_CONTRACT_YAML_PATHS[0] ?? "validation.yaml",
371
+ reason: `cannot inspect workspace root (${error instanceof Error ? error.message : String(error)})`,
372
+ };
373
+ }
374
+ const text = readContractText(path.join(workspaceRoot, contractPath), workspaceRoot);
375
+ if (text instanceof Error) return { ok: false, path: contractPath, reason: text.message };
376
+ return parseValidationContractText(text, contractPath);
377
+ }
378
+
379
+ /** One line a diagnostic surface can print for a load result, or null when there is nothing to say. */
380
+ export function describeValidationContract(result: ValidationContractLoadResult): string | null {
381
+ if (!result.ok) return `${result.path}: ${result.reason}`;
382
+ if (result.contract === null) {
383
+ return "advisory" in result
384
+ ? `${result.path} is advisory prose and does not raise rigor; add a version-1 validation.yaml to raise the default to high`
385
+ : null;
386
+ }
387
+ return null;
388
+ }
@@ -30,6 +30,8 @@ export interface AuditReadResult {
30
30
  }
31
31
 
32
32
  export interface SessionReadResult {
33
+ /** Persisted /tree selection, when the session has not appended past it. */
34
+ leafTurnId?: string;
33
35
  entries: SessionEntry[];
34
36
  missing: boolean;
35
37
  errors: string[];
@@ -142,7 +144,14 @@ export async function readSessionEntriesForId(stateDir: string, sessionId: strin
142
144
  return { entries: [], missing: false, errors: [`${currentPath}: ${err.message ?? String(err)}`] };
143
145
  }
144
146
  }
145
- return parseSessionEntries(raw, source);
147
+ const result = parseSessionEntries(raw, source);
148
+ try {
149
+ const meta: unknown = JSON.parse(await readFile(join(root, cwdHash, sessionId, "meta.json"), "utf8"));
150
+ if (isRecord(meta) && typeof meta.pinnedLeafTurnId === "string") result.leafTurnId = meta.pinnedLeafTurnId;
151
+ } catch {
152
+ // Legacy archives without metadata fall back to the newest message branch.
153
+ }
154
+ return result;
146
155
  }
147
156
  return { entries: [], missing: true, errors: [] };
148
157
  }
@@ -1,10 +1,12 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import type { AskUserToolPolicy } from "../../tools/registry.js";
2
3
  import type { AutonomyExposure } from "../safety/autonomy.js";
3
- import type { DecisionLedgerEntry, DecisionRecord } from "./entries.js";
4
+ import { type DecisionLedgerEntry, type DecisionRecord, decisionRef } from "./entries.js";
4
5
 
5
6
  export interface DecisionLedgerEntryFields {
6
7
  kind: "decisionLedger";
7
8
  parentTurnId: string;
9
+ origin?: "interview" | "agent";
8
10
  interviewId: string;
9
11
  interviewStatus: "complete" | "cancelled";
10
12
  startedAt: string;
@@ -28,11 +30,35 @@ export interface DecisionBoardStoreDeps {
28
30
  now?: () => Date;
29
31
  }
30
32
 
33
+ /** One design choice the model records itself through the `decide` tool. */
34
+ export interface AgentDecisionInput {
35
+ key: string;
36
+ value: string;
37
+ alternatives: ReadonlyArray<string>;
38
+ rationale: string;
39
+ label?: string;
40
+ }
41
+
42
+ export interface AgentDecisionOutcome {
43
+ /** The appended agent decision set; `decisionRef(entry.interviewId, key)` cites it. */
44
+ entry: DecisionLedgerEntryFields;
45
+ /** The earlier active record with the same key that this decision superseded, when there was one. */
46
+ superseded: { interviewId: string; key: string } | null;
47
+ }
48
+
31
49
  export interface DecisionBoardStore {
32
50
  /** Latest snapshot per interview, newest interview first. */
33
51
  snapshot(): ReadonlyArray<DecisionLedgerEntry>;
34
52
  /** Persist the one host-finalized snapshot for a settled interview. */
35
53
  recordFinalizedInterview(policy: AskUserToolPolicy): boolean;
54
+ /**
55
+ * Append one agent-recorded decision as its own complete set
56
+ * (`origin: "agent"`, `interviewId: "agent:<uuid>"`, `roundCount: 0`). An
57
+ * earlier active agent decision with the same key is superseded first with
58
+ * the new rationale as its correction. An active operator decision with the
59
+ * same key is never overwritten by the model; that throws.
60
+ */
61
+ recordAgentDecision(input: AgentDecisionInput): AgentDecisionOutcome;
36
62
  /** Append an operator revision snapshot anchored to the active branch leaf. */
37
63
  supersede(interviewId: string, key: string, correction?: string): DecisionLedgerEntryFields;
38
64
  /** Force the next read to refold, including after a same-session tree switch. */
@@ -90,6 +116,24 @@ function finalizedInterviewEntryFields(policy: AskUserToolPolicy): DecisionLedge
90
116
  };
91
117
  }
92
118
 
119
+ /** Most decision refs one dispatch request, envelope, or receipt carries. */
120
+ export const DECISION_REFS_CAP = 32;
121
+
122
+ /**
123
+ * Refs of every active decision on a board snapshot, sorted and capped at
124
+ * {@link DECISION_REFS_CAP}. Dispatch seals this onto each request it builds;
125
+ * an empty board yields an empty list, which callers leave off the request.
126
+ */
127
+ export function activeDecisionRefs(board: ReadonlyArray<DecisionLedgerEntry>): string[] {
128
+ const refs = new Set<string>();
129
+ for (const entry of board) {
130
+ for (const decision of entry.decisions) {
131
+ if (decision.status === "active") refs.add(decisionRef(entry.interviewId, decision.key));
132
+ }
133
+ }
134
+ return [...refs].sort().slice(0, DECISION_REFS_CAP);
135
+ }
136
+
93
137
  function isDecisionLedgerEntry(value: unknown): value is DecisionLedgerEntry {
94
138
  return !!value && typeof value === "object" && (value as { kind?: unknown }).kind === "decisionLedger";
95
139
  }
@@ -143,7 +187,7 @@ export function createDecisionBoardStore(deps: DecisionBoardStoreDeps = {}): Dec
143
187
  dirty = true;
144
188
  };
145
189
 
146
- return {
190
+ const store: DecisionBoardStore = {
147
191
  snapshot(): ReadonlyArray<DecisionLedgerEntry> {
148
192
  syncToSession();
149
193
  return interviews;
@@ -154,6 +198,51 @@ export function createDecisionBoardStore(deps: DecisionBoardStoreDeps = {}): Dec
154
198
  append(entry);
155
199
  return true;
156
200
  },
201
+ recordAgentDecision(input: AgentDecisionInput): AgentDecisionOutcome {
202
+ syncToSession();
203
+ let superseded: AgentDecisionOutcome["superseded"] = null;
204
+ for (const interview of interviews) {
205
+ const prior = interview.decisions.find((decision) => decision.key === input.key && decision.status === "active");
206
+ if (prior === undefined) continue;
207
+ if (interview.origin !== "agent" || prior.source !== "agent") {
208
+ throw new Error(
209
+ `decision board: '${input.key}' is an operator decision (${interview.interviewId}); it can only be revised through ask_user`,
210
+ );
211
+ }
212
+ superseded = { interviewId: interview.interviewId, key: prior.key };
213
+ break;
214
+ }
215
+ if (superseded !== null) store.supersede(superseded.interviewId, superseded.key, input.rationale);
216
+ const now = (deps.now?.() ?? new Date()).toISOString();
217
+ const entries = deps.readEntries?.() ?? [];
218
+ const parentTurnId = deps.getActiveLeafTurnId?.() ?? activeLeafFromEntries(entries);
219
+ if (!parentTurnId) throw new Error("decision board: no active branch leaf is available for the decision");
220
+ const entry: DecisionLedgerEntryFields = {
221
+ kind: "decisionLedger",
222
+ parentTurnId,
223
+ origin: "agent",
224
+ interviewId: `agent:${randomUUID()}`,
225
+ interviewStatus: "complete",
226
+ startedAt: now,
227
+ endedAt: now,
228
+ roundCount: 0,
229
+ exposure: "local",
230
+ decisions: [
231
+ {
232
+ key: input.key,
233
+ value: input.value,
234
+ ...(input.label ? { label: input.label } : {}),
235
+ status: "active",
236
+ decidedAt: now,
237
+ source: "agent",
238
+ alternatives: [...input.alternatives],
239
+ rationale: input.rationale,
240
+ },
241
+ ],
242
+ };
243
+ append(entry);
244
+ return { entry, superseded };
245
+ },
157
246
  supersede(interviewId: string, key: string, correction?: string): DecisionLedgerEntryFields {
158
247
  syncToSession();
159
248
  const interview = interviews.find((candidate) => candidate.interviewId === interviewId);
@@ -191,4 +280,14 @@ export function createDecisionBoardStore(deps: DecisionBoardStoreDeps = {}): Dec
191
280
  dirty = true;
192
281
  },
193
282
  };
283
+ return store;
284
+ }
285
+
286
+ /** Preserve the recorded argument wherever a decision is carried into another prompt. */
287
+ export function decisionRationale(decision: DecisionRecord): string | undefined {
288
+ const parts: string[] = [];
289
+ if (decision.alternatives?.length) parts.push(`Alternatives: ${decision.alternatives.join("; ")}`);
290
+ if (decision.rationale) parts.push(`Rationale: ${decision.rationale}`);
291
+ if (parts.length > 0) return parts.join(". ");
292
+ return decision.source_question ?? decision.correction;
194
293
  }
@@ -5,10 +5,13 @@
5
5
  * carries its `kind` discriminant, a `turnId` (uuid v7 where possible so
6
6
  * entries sort by creation), a parent pointer, and an ISO timestamp.
7
7
  *
8
- * Later slices extend this union:
9
- * - compactionSummary is produced by compaction/compact.ts (slice 12c).
10
- * - branchSummary is produced by the fork path (slice 12b).
11
- * - bashExecution / fileEntry become the wire shape for Phase 14 extensions.
8
+ * Non-message kinds and where they come from:
9
+ * - compactionSummary is produced by compaction/compact.ts.
10
+ * - branchSummary is produced by the fork path.
11
+ * - bashExecution is produced by the editor bash path
12
+ * (interactive/editor-bash.ts).
13
+ * - fileEntry is consumed by the renderer, compaction, handoff, and
14
+ * evidence builders but has no in-tree producer.
12
15
  */
13
16
 
14
17
  import { isSkillActivation, type SkillActivation } from "../../core/skill-activation.js";
@@ -213,6 +216,14 @@ export interface TaskLedgerEntry extends BaseSessionEntry {
213
216
 
214
217
  export type DecisionStatus = "active" | "superseded";
215
218
 
219
+ /**
220
+ * Who settled a decision. `operator` is an answer the operator gave in an
221
+ * `ask_user` interview. `agent` is a design choice the model recorded itself
222
+ * through the `decide` tool, with the alternatives it rejected and why. Absent
223
+ * on entries written before agent decisions existed, which were all operator.
224
+ */
225
+ export type DecisionSource = "operator" | "agent";
226
+
216
227
  export interface DecisionRecord {
217
228
  key: string;
218
229
  value: string;
@@ -222,11 +233,33 @@ export interface DecisionRecord {
222
233
  decidedAt: string;
223
234
  revisedAt?: string;
224
235
  correction?: string;
236
+ source?: DecisionSource;
237
+ /** Options considered and not taken. Only agent decisions carry these. */
238
+ alternatives?: string[];
239
+ /** Why `value` won over `alternatives`. Only agent decisions carry this. */
240
+ rationale?: string;
241
+ }
242
+
243
+ /**
244
+ * Stable reference to one decision: `<interviewId>/<key>`. Both halves are
245
+ * already unique on the active path, so the pair needs no third id. Run
246
+ * envelopes, receipts, and commit trailers cite decisions by this string.
247
+ */
248
+ export type DecisionRef = string;
249
+
250
+ export function decisionRef(interviewId: string, key: string): DecisionRef {
251
+ return `${interviewId}/${key}`;
225
252
  }
226
253
 
227
- /** One complete, branch-anchored snapshot of a settled operator interview. */
254
+ /**
255
+ * One complete, branch-anchored snapshot of a settled decision set. An
256
+ * `interview` origin is an operator `ask_user` round; an `agent` origin is a
257
+ * set the model recorded with the `decide` tool, where `interviewId` is the
258
+ * agent decision set id and `roundCount` is 0. Absent origin means interview.
259
+ */
228
260
  export interface DecisionLedgerEntry extends BaseSessionEntry {
229
261
  kind: "decisionLedger";
262
+ origin?: "interview" | "agent";
230
263
  interviewId: string;
231
264
  interviewStatus: "complete" | "cancelled";
232
265
  startedAt: string;
@@ -503,11 +536,14 @@ function isTaskLedgerEvidence(value: unknown): value is TaskLedgerValidationEvid
503
536
  );
504
537
  }
505
538
 
506
- function isDecisionRecord(value: unknown): value is DecisionRecord {
539
+ export function isDecisionRecord(value: unknown): value is DecisionRecord {
507
540
  if (!isRecord(value)) return false;
508
- if (!isString(value.key) || !/^[a-z0-9]+(?:_[a-z0-9]+)*$/.test(value.key)) return false;
541
+ if (!isString(value.key) || !/^[a-z0-9]+(?:[_-][a-z0-9]+)*$/.test(value.key)) return false;
509
542
  if (!isString(value.value) || !isOneOf(value.status, DECISION_STATUSES) || !isString(value.decidedAt)) return false;
510
543
  if (!isOptionalString(value.label) || !isOptionalString(value.source_question)) return false;
544
+ if (value.source !== undefined && !isOneOf(value.source, ["operator", "agent"])) return false;
545
+ if (value.alternatives !== undefined && !isStringArray(value.alternatives)) return false;
546
+ if (!isOptionalString(value.rationale)) return false;
511
547
  if (!isOptionalString(value.revisedAt) || !isOptionalString(value.correction)) return false;
512
548
  return value.status === "superseded"
513
549
  ? isString(value.revisedAt)
@@ -642,6 +678,7 @@ export function isSessionEntry(value: unknown): value is SessionEntry {
642
678
  );
643
679
  case "decisionLedger":
644
680
  return (
681
+ (v.origin === undefined || isOneOf(v.origin, ["interview", "agent"])) &&
645
682
  isString(v.interviewId) &&
646
683
  isOneOf(v.interviewStatus, DECISION_INTERVIEW_STATUSES) &&
647
684
  isString(v.startedAt) &&
@@ -282,10 +282,10 @@ export function createSessionBundle(context: DomainContext): DomainBundle<Sessio
282
282
  return snapshotFor(id);
283
283
  },
284
284
  switchBranch(sessionId) {
285
- // /tree-driven branch switch currently delegates to resume. Kept as a
286
- // distinct contract method so later slices can layer telemetry or
287
- // chat-loop rewiring without changing resume's semantics. Same
288
- // open-before-close ordering as resume() above and for the same reason.
285
+ // /tree-driven branch switch delegates to resume. Kept as a distinct
286
+ // contract method so telemetry or chat-loop rewiring can be layered on
287
+ // without changing resume's semantics. Same open-before-close ordering
288
+ // as resume() above and for the same reason.
289
289
  if (state?.meta.id === sessionId) return state.meta;
290
290
  const resumed = resumeSessionState(sessionId);
291
291
  if (state) {