@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
@@ -15,15 +15,20 @@ import { resolveSafeCwd, SAFE_EXEC_DEFAULT_TIMEOUT_MS } from "../../core/safe-ex
15
15
  import { parseTomlDocument, tomlTableAt } from "../../core/toml.js";
16
16
  import { isVerificationScriptName } from "../../core/verification-scripts.js";
17
17
  import { compareCodepoints } from "../../domains/evidence/ordering.js";
18
+ import { loadValidationContract, VALIDATION_CONTRACT_MARKDOWN_PATH } from "../../domains/safety/validation-contract.js";
18
19
  import type { ToolResult } from "../registry.js";
19
20
  import {
20
21
  type DeclaredCheck,
22
+ type DeclaredCheckKind,
23
+ type DeclaredNumericCompare,
24
+ type DeclaredPerfBudget,
21
25
  PROJECT_VERIFIER_CATALOG_CAPS,
22
26
  PROJECT_VERIFIER_CATALOG_RELATIVE_PATH,
23
27
  PROJECT_VERIFIER_CATALOG_VERSION,
24
28
  parseProjectVerifierCatalogText,
25
29
  } from "./catalog.js";
26
30
  import { verifyTool } from "./index.js";
31
+ import { type NumericTolerance, normalizeNumericTolerance } from "./numeric.js";
27
32
  import { discoverDeclaredChecksAtRoot, discoverDeclaredProjectEntriesAtRoot } from "./scripts.js";
28
33
 
29
34
  export type VerifierProposalAuthority = "project-declared" | "toolchain-defined";
@@ -54,12 +59,31 @@ export interface AuthoringCheck {
54
59
  cwd: string;
55
60
  timeoutMs: number;
56
61
  tags: string[];
62
+ /** Absent means `command`. Written to the catalog only when it is not the default. */
63
+ kind?: DeclaredCheckKind;
64
+ numeric?: DeclaredNumericCompare;
65
+ perf?: DeclaredPerfBudget;
57
66
  provenance: VerifierProvenance;
58
67
  state: "active" | "existing" | "proposed" | "manual";
59
68
  /** The id this check has in the on-disk catalog, so a rename still edits its node in place. */
60
69
  catalogId?: string;
61
70
  }
62
71
 
72
+ /**
73
+ * A numeric-compare check the validation contract implies but cannot make
74
+ * executable: the artifact declared tolerances, so the reference and the
75
+ * tolerance are known, and the command is the operator's to supply. It never
76
+ * enters the draft, so nothing about it runs until an explicit `add`.
77
+ */
78
+ export interface NumericProposal {
79
+ id: string;
80
+ artifactPath: string;
81
+ reference: string;
82
+ tolerance: NumericTolerance;
83
+ /** The exact `clio-coder verifiers add` invocation with `--command` left to fill. */
84
+ addCommand: string;
85
+ }
86
+
63
87
  export interface VerifierAuthoringDiscovery {
64
88
  ok: true;
65
89
  workspaceRoot: string;
@@ -67,6 +91,8 @@ export interface VerifierAuthoringDiscovery {
67
91
  activeChecks: AuthoringCheck[];
68
92
  existingChecks: AuthoringCheck[];
69
93
  proposals: AuthoringCheck[];
94
+ /** Tolerance-bearing contract artifacts, each rendered as an incomplete numeric-compare check. */
95
+ numericProposals: NumericProposal[];
70
96
  diagnostics: string[];
71
97
  manualEntry: string;
72
98
  /** The catalog file as found, when one exists; revisions are written into it in place. */
@@ -86,6 +112,8 @@ export interface VerifierDraft {
86
112
  catalogPath: string;
87
113
  activeChecks: AuthoringCheck[];
88
114
  checks: AuthoringCheck[];
115
+ /** Rendered in the preview beneath the executable checks; never serialized. */
116
+ numericProposals: NumericProposal[];
89
117
  diagnostics: string[];
90
118
  manualEntry: string;
91
119
  catalogText?: string;
@@ -96,7 +124,9 @@ export type VerifierRevision =
96
124
  | {
97
125
  kind: "edit";
98
126
  id: string;
99
- changes: Partial<Pick<AuthoringCheck, "description" | "command" | "cwd" | "timeoutMs" | "tags">>;
127
+ changes: Partial<
128
+ Pick<AuthoringCheck, "description" | "command" | "cwd" | "timeoutMs" | "tags" | "kind" | "numeric" | "perf">
129
+ >;
100
130
  }
101
131
  | { kind: "rename"; id: string; newId: string }
102
132
  | { kind: "remove"; id: string };
@@ -168,12 +198,6 @@ interface RawProposal {
168
198
  }
169
199
 
170
200
  const DECLARED_FILE_CAP_BYTES = 1024 * 1024;
171
- const VALIDATION_CONTRACT_PATHS = [
172
- ".clio-coder/validation.yaml",
173
- ".clio-coder/validation.yml",
174
- "validation.yaml",
175
- "validation.yml",
176
- ] as const;
177
201
 
178
202
  function manualEntryInstruction(): string {
179
203
  return (
@@ -187,10 +211,25 @@ function cloneCheck(check: AuthoringCheck): AuthoringCheck {
187
211
  ...check,
188
212
  command: [...check.command],
189
213
  tags: [...check.tags],
214
+ ...(check.numeric !== undefined ? { numeric: structuredClone(check.numeric) } : {}),
215
+ ...(check.perf !== undefined ? { perf: structuredClone(check.perf) } : {}),
190
216
  provenance: { ...check.provenance },
191
217
  };
192
218
  }
193
219
 
220
+ /** The kind fields of a declared or authored check, absent for a plain command check. */
221
+ function kindFields(
222
+ check: Pick<DeclaredCheck, "kind" | "numeric" | "perf"> | Pick<AuthoringCheck, "kind" | "numeric" | "perf">,
223
+ ): Pick<AuthoringCheck, "kind" | "numeric" | "perf"> {
224
+ const kind = check.kind ?? "command";
225
+ if (kind === "command") return {};
226
+ return {
227
+ kind,
228
+ ...(check.numeric !== undefined ? { numeric: structuredClone(check.numeric) } : {}),
229
+ ...(check.perf !== undefined ? { perf: structuredClone(check.perf) } : {}),
230
+ };
231
+ }
232
+
194
233
  function relativePath(workspaceRoot: string, filePath: string): string {
195
234
  const relative = path.relative(workspaceRoot, filePath);
196
235
  return relative.length === 0 ? "." : relative.split(path.sep).join("/");
@@ -283,6 +322,7 @@ function projectedCheck(workspaceRoot: string, check: DeclaredCheck): AuthoringC
283
322
  cwd: check.cwd,
284
323
  timeoutMs: check.timeoutMs,
285
324
  tags: [...check.tags],
325
+ ...kindFields(check),
286
326
  provenance: sourceProvenance(workspaceRoot, check),
287
327
  state: check.source.kind === "package.json" ? "active" : "existing",
288
328
  ...(check.source.kind === "package.json" ? {} : { catalogId: check.id }),
@@ -626,18 +666,6 @@ function shellLikeArgv(command: string): string[] | Error {
626
666
  return argv;
627
667
  }
628
668
 
629
- function validatorArgv(value: unknown): string[] | Error {
630
- if (typeof value === "string") return shellLikeArgv(value);
631
- if (
632
- !Array.isArray(value) ||
633
- value.length === 0 ||
634
- value.some((entry) => typeof entry !== "string" || entry.length === 0)
635
- ) {
636
- return new Error("validator must be a command string or a non-empty argv string array");
637
- }
638
- return [...(value as string[])];
639
- }
640
-
641
669
  function validationPreferredId(argv: ReadonlyArray<string>): string {
642
670
  const executable = path.basename(argv[0] ?? "check").replace(/\.[^.]+$/u, "");
643
671
  const script = argv.find((entry, index) => index > 0 && /\.(?:py|js|mjs|sh)$/u.test(entry));
@@ -647,87 +675,76 @@ function validationPreferredId(argv: ReadonlyArray<string>): string {
647
675
 
648
676
  function validationContractProposals(workspaceRoot: string, diagnostics: string[]): RawProposal[] {
649
677
  const proposals: RawProposal[] = [];
650
- for (const relative of VALIDATION_CONTRACT_PATHS) {
651
- const text = regularFileText(path.join(workspaceRoot, relative), workspaceRoot);
652
- if (text === null) continue;
653
- if (text instanceof Error) {
654
- diagnostics.push(`${relative}: ${text.message}; validation command discovery skipped.`);
655
- continue;
656
- }
657
- const document = parseDocument(text, { prettyErrors: false, strict: true, uniqueKeys: true });
658
- if (document.errors.length > 0) {
659
- diagnostics.push(`${relative}: invalid YAML; validation command discovery skipped.`);
660
- continue;
661
- }
662
- let parsed: unknown;
663
- try {
664
- parsed = document.toJS({ maxAliasCount: 0 }) as unknown;
665
- } catch (error) {
666
- diagnostics.push(`${relative}: invalid YAML (${error instanceof Error ? error.message : String(error)}).`);
667
- continue;
668
- }
669
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) continue;
670
- const validators = (parsed as Record<string, unknown>).validators;
671
- if (!Array.isArray(validators)) continue;
672
- for (const [index, validator] of validators.entries()) {
673
- const record =
674
- validator !== null && typeof validator === "object" && !Array.isArray(validator)
675
- ? (validator as Record<string, unknown>)
676
- : null;
677
- if (record !== null) {
678
- const typedFields: ReadonlyArray<readonly [string, (value: unknown) => boolean]> = [
679
- ["id", (value: unknown) => typeof value === "string"],
680
- ["description", (value: unknown) => typeof value === "string"],
681
- ["cwd", (value: unknown) => typeof value === "string"],
682
- ["timeoutMs", (value: unknown) => typeof value === "number"],
683
- ["tags", (value: unknown) => Array.isArray(value) && value.every((tag) => typeof tag === "string")],
684
- ];
685
- const invalidField = typedFields.find(([field, valid]) => Object.hasOwn(record, field) && !valid(record[field]));
686
- if (invalidField !== undefined) {
687
- diagnostics.push(
688
- `${relative}: validators[${index}].${String(invalidField[0])} has an ambiguous type; use manual argv entry.`,
689
- );
690
- continue;
691
- }
692
- }
693
- const commandValue = record === null ? validator : record.command;
694
- const argv = validatorArgv(commandValue);
678
+ const loaded = loadValidationContract(workspaceRoot);
679
+ if (!loaded.ok) {
680
+ diagnostics.push(`${loaded.path}: ${loaded.reason}; validation command discovery skipped.`);
681
+ } else if (loaded.contract !== null) {
682
+ for (const [index, validator] of (loaded.contract.validators ?? []).entries()) {
683
+ const argv = shellLikeArgv(validator);
695
684
  if (argv instanceof Error) {
696
- diagnostics.push(`${relative}: validators[${index}] is ambiguous (${argv.message}); use manual argv entry.`);
685
+ diagnostics.push(`${loaded.path}: validators[${index}] is ambiguous (${argv.message}); use manual argv entry.`);
697
686
  continue;
698
687
  }
699
- const declaredId = typeof record?.id === "string" ? record.id : validationPreferredId(argv);
700
- const description =
701
- typeof record?.description === "string"
702
- ? record.description
703
- : `Run validation contract command ${JSON.stringify(argv)}`;
704
- const cwd = typeof record?.cwd === "string" ? record.cwd : ".";
705
- const timeoutMs = typeof record?.timeoutMs === "number" ? record.timeoutMs : SAFE_EXEC_DEFAULT_TIMEOUT_MS;
706
- const tags = Array.isArray(record?.tags) ? record.tags.filter((tag): tag is string => typeof tag === "string") : [];
707
688
  proposals.push({
708
- preferredId: declaredId,
709
- description,
689
+ preferredId: validationPreferredId(argv),
690
+ description: `Run validation contract command ${JSON.stringify(argv)}`,
710
691
  command: argv,
711
- cwd,
712
- timeoutMs,
713
- tags: tags.length > 0 ? tags : ["scientific", "validation"],
692
+ cwd: ".",
693
+ timeoutMs: SAFE_EXEC_DEFAULT_TIMEOUT_MS,
694
+ tags: ["scientific", "validation"],
714
695
  provenance: {
715
696
  kind: "validation-contract",
716
- path: relative,
697
+ path: loaded.path,
717
698
  detail: `validators[${index}]`,
718
699
  authority: "project-declared",
719
700
  },
720
701
  });
721
702
  }
722
703
  }
723
- if (existsSync(path.join(workspaceRoot, "VALIDATION.md"))) {
704
+ if (existsSync(path.join(workspaceRoot, VALIDATION_CONTRACT_MARKDOWN_PATH))) {
724
705
  diagnostics.push(
725
- "VALIDATION.md is advisory prose; enter an exact argv vector manually instead of inferring a command.",
706
+ `${VALIDATION_CONTRACT_MARKDOWN_PATH} is advisory prose; enter an exact argv vector manually instead of inferring a command.`,
726
707
  );
727
708
  }
728
709
  return proposals;
729
710
  }
730
711
 
712
+ /**
713
+ * One incomplete numeric-compare check per contract artifact that declares
714
+ * `numerical_tolerances`. The reference path is the artifact's path with a
715
+ * `.reference.json` suffix, a suggestion the operator may change; the
716
+ * command is deliberately absent, so the proposal cannot become executable
717
+ * until the operator confirms an exact argv through `verifiers add`.
718
+ */
719
+ function numericContractProposals(workspaceRoot: string): NumericProposal[] {
720
+ const loaded = loadValidationContract(workspaceRoot);
721
+ if (!loaded.ok || loaded.contract === null) return [];
722
+ const proposals: NumericProposal[] = [];
723
+ const seen = new Set<string>();
724
+ for (const artifact of loaded.contract.artifacts ?? []) {
725
+ if (artifact.numerical_tolerances === undefined) continue;
726
+ const tolerance = normalizeNumericTolerance(artifact.numerical_tolerances);
727
+ if (tolerance instanceof Error) continue;
728
+ const base = slug(path.basename(artifact.path).replace(/\.[^.]+$/u, ""), "artifact");
729
+ const preferred = boundedId(`numeric-${base}`);
730
+ let id = preferred;
731
+ for (let suffix = 2; seen.has(id); suffix += 1) id = `${boundedId(preferred, `-${suffix}`)}-${suffix}`;
732
+ seen.add(id);
733
+ const reference = `${artifact.path}.reference.json`;
734
+ proposals.push({
735
+ id,
736
+ artifactPath: artifact.path,
737
+ reference,
738
+ tolerance,
739
+ addCommand:
740
+ `clio-coder verifiers add --id ${id} --kind numeric-compare --reference ${JSON.stringify(reference)} ` +
741
+ `--tolerance '${JSON.stringify(tolerance)}' --description ${JSON.stringify(`Compare ${artifact.path} against its reference within tolerance`)} ` +
742
+ `--command '["<executable>","<arg>"]'`,
743
+ });
744
+ }
745
+ return proposals;
746
+ }
747
+
731
748
  function deduplicatedRawProposals(proposals: RawProposal[], diagnostics: string[]): RawProposal[] {
732
749
  const seen = new Map<string, RawProposal>();
733
750
  for (const proposal of proposals.sort(rawProposalSort)) {
@@ -825,6 +842,7 @@ export function discoverVerifierAuthoring(workspaceRoot = process.cwd()): Verifi
825
842
  activeChecks,
826
843
  existingChecks,
827
844
  proposals,
845
+ numericProposals: numericContractProposals(workspaceRoot),
828
846
  diagnostics,
829
847
  manualEntry,
830
848
  ...(typeof catalogText === "string" ? { catalogText } : {}),
@@ -842,14 +860,30 @@ export function createVerifierDraft(
842
860
  checks: [...discovery.existingChecks, ...(options.includeProposals === false ? [] : discovery.proposals)]
843
861
  .map(cloneCheck)
844
862
  .sort((left, right) => compareCodepoints(left.id, right.id)),
863
+ numericProposals:
864
+ options.includeProposals === false ? [] : discovery.numericProposals.map((proposal) => ({ ...proposal })),
845
865
  diagnostics: [...discovery.diagnostics],
846
866
  manualEntry: discovery.manualEntry,
847
867
  ...(discovery.catalogText === undefined ? {} : { catalogText: discovery.catalogText }),
848
868
  };
849
869
  }
850
870
 
851
- function catalogEntry({ id, description, command, cwd, timeoutMs, tags }: AuthoringCheck): Record<string, unknown> {
852
- return { id, description, command: [...command], cwd, timeoutMs, tags: [...tags] };
871
+ function catalogEntry(check: AuthoringCheck): Record<string, unknown> {
872
+ const { id, description, command, cwd, timeoutMs, tags } = check;
873
+ const entry: Record<string, unknown> = { id, description, command: [...command], cwd, timeoutMs, tags: [...tags] };
874
+ const kind = check.kind ?? "command";
875
+ if (kind === "command") return entry;
876
+ entry.kind = kind;
877
+ if (check.numeric !== undefined) {
878
+ entry.reference = check.numeric.reference;
879
+ entry.tolerance = { ...check.numeric.tolerance };
880
+ }
881
+ if (check.perf !== undefined) {
882
+ if (check.perf.budget !== undefined) entry.budget = structuredClone(check.perf.budget);
883
+ if (check.perf.baseline !== undefined) entry.baseline = check.perf.baseline;
884
+ if (check.perf.tolerance !== undefined) entry.tolerance = { ...check.perf.tolerance };
885
+ }
886
+ return entry;
853
887
  }
854
888
 
855
889
  /**
@@ -885,8 +919,20 @@ function serializeVerifierDraft(draft: VerifierDraft): string {
885
919
  node.set(field, value);
886
920
  changed = true;
887
921
  }
922
+ // A check that returned to a plain command drops the kind fields it no longer carries.
923
+ for (const field of ["kind", "reference", "tolerance", "budget", "baseline"]) {
924
+ if (Object.hasOwn(current, field) && !Object.hasOwn(entry, field)) {
925
+ node.delete(field);
926
+ changed = true;
927
+ }
928
+ }
888
929
  }
889
930
  if (!changed && appended.length === 0 && kept.size === sequence.items.length) return draft.catalogText as string;
931
+ // The edited file speaks this build's schema version: a version-1 file that
932
+ // gains a judged check would otherwise be rejected by its own version line.
933
+ if (document.get("version") !== PROJECT_VERIFIER_CATALOG_VERSION) {
934
+ document.set("version", PROJECT_VERIFIER_CATALOG_VERSION);
935
+ }
890
936
  sequence.items = [...sequence.items.filter((node) => isMap(node) && kept.has(node)), ...appended];
891
937
  return document.toString({ lineWidth: 0 });
892
938
  }
@@ -919,6 +965,7 @@ function validateVerifierDraft(draft: VerifierDraft): VerifierDraftValidation {
919
965
  cwd: check.cwd,
920
966
  timeoutMs: check.timeoutMs,
921
967
  tags: [...check.tags],
968
+ ...kindFields(check),
922
969
  provenance:
923
970
  original?.provenance ??
924
971
  ({
@@ -939,6 +986,7 @@ function cloneDraft(draft: VerifierDraft): VerifierDraft {
939
986
  ...draft,
940
987
  activeChecks: draft.activeChecks.map(cloneCheck),
941
988
  checks: draft.checks.map(cloneCheck),
989
+ numericProposals: draft.numericProposals.map((proposal) => ({ ...proposal })),
942
990
  diagnostics: [...draft.diagnostics],
943
991
  };
944
992
  }
@@ -1005,6 +1053,17 @@ function reviseVerifierDraft(draft: VerifierDraft, revisions: ReadonlyArray<Veri
1005
1053
  if (revision.changes.cwd !== undefined) check.cwd = revision.changes.cwd;
1006
1054
  if (revision.changes.timeoutMs !== undefined) check.timeoutMs = revision.changes.timeoutMs;
1007
1055
  if (revision.changes.tags !== undefined) check.tags = [...revision.changes.tags];
1056
+ if (revision.changes.kind !== undefined) {
1057
+ const next = kindFields({
1058
+ kind: revision.changes.kind,
1059
+ ...(revision.changes.numeric !== undefined ? { numeric: revision.changes.numeric } : {}),
1060
+ ...(revision.changes.perf !== undefined ? { perf: revision.changes.perf } : {}),
1061
+ });
1062
+ delete check.kind;
1063
+ delete check.numeric;
1064
+ delete check.perf;
1065
+ Object.assign(check, next);
1066
+ }
1008
1067
  diagnostics.push(`Edited check '${revision.id}' without changing its deterministic ID.`);
1009
1068
  }
1010
1069
  next.checks.sort((left, right) => compareCodepoints(left.id, right.id));
@@ -1034,10 +1093,26 @@ function renderCheck(check: AuthoringCheck, destinationPath: string): string[] {
1034
1093
  ` cwd: ${check.cwd}`,
1035
1094
  ` timeoutMs: ${check.timeoutMs}`,
1036
1095
  ` tags: ${JSON.stringify(check.tags)}`,
1096
+ ...renderKind(check),
1037
1097
  ` effective execution authority: ${authorityDescription(check)}`,
1038
1098
  ];
1039
1099
  }
1040
1100
 
1101
+ function renderKind(check: AuthoringCheck): string[] {
1102
+ const kind = check.kind ?? "command";
1103
+ if (kind === "command") return [];
1104
+ const lines = [` kind: ${kind}`];
1105
+ if (check.numeric !== undefined) {
1106
+ lines.push(` reference: ${check.numeric.reference}`, ` tolerance: ${JSON.stringify(check.numeric.tolerance)}`);
1107
+ }
1108
+ if (check.perf !== undefined) {
1109
+ if (check.perf.budget !== undefined) lines.push(` budget: ${JSON.stringify(check.perf.budget)}`);
1110
+ if (check.perf.baseline !== undefined) lines.push(` baseline: ${check.perf.baseline}`);
1111
+ if (check.perf.tolerance !== undefined) lines.push(` tolerance: ${JSON.stringify(check.perf.tolerance)}`);
1112
+ }
1113
+ return lines;
1114
+ }
1115
+
1041
1116
  export interface VerifierPreviewOptions {
1042
1117
  /** Suppress the unconfirmed-preview footer once the write is authorized. */
1043
1118
  confirmed?: boolean;
@@ -1058,6 +1133,18 @@ export function previewVerifierDraft(draft: VerifierDraft, options: VerifierPrev
1058
1133
  lines.push("", "Catalog checks after confirmation:");
1059
1134
  if (draft.checks.length === 0) lines.push("(none)");
1060
1135
  else for (const check of draft.checks) lines.push(...renderCheck(check, draft.catalogPath));
1136
+ if (draft.numericProposals.length > 0) {
1137
+ lines.push("", "Numeric-compare checks the validation contract implies (command required before they can be added):");
1138
+ for (const proposal of draft.numericProposals) {
1139
+ lines.push(
1140
+ `- ${proposal.id}`,
1141
+ ` artifact: ${proposal.artifactPath}`,
1142
+ ` reference: ${proposal.reference}`,
1143
+ ` tolerance: ${JSON.stringify(proposal.tolerance)}`,
1144
+ ` add with: ${proposal.addCommand}`,
1145
+ );
1146
+ }
1147
+ }
1061
1148
  if (draft.diagnostics.length > 0) {
1062
1149
  lines.push("", "Diagnostics:");
1063
1150
  for (const diagnostic of draft.diagnostics) lines.push(`- ${diagnostic}`);
@@ -4,9 +4,13 @@ import { parseDocument } from "yaml";
4
4
  import { resolveSafeCwd, SAFE_EXEC_DEFAULT_TIMEOUT_MS } from "../../core/safe-exec.js";
5
5
  import { isProjectVerifierCheckId } from "../../core/verification-scripts.js";
6
6
  import { compareCodepoints } from "../../domains/evidence/ordering.js";
7
+ import { type NumericTolerance, normalizeNumericTolerance } from "./numeric.js";
8
+ import { normalizePerfBudget, normalizePerfTolerance, type PerfBudgetSpec } from "./perf.js";
7
9
 
8
10
  export const PROJECT_VERIFIER_CATALOG_RELATIVE_PATH = ".clio-coder/verifiers.yaml";
9
- export const PROJECT_VERIFIER_CATALOG_VERSION = 1;
11
+ /** The version this build writes. Version 1 files still load; every check there is `kind: command`. */
12
+ export const PROJECT_VERIFIER_CATALOG_VERSION = 2;
13
+ const SUPPORTED_CATALOG_VERSIONS: ReadonlyArray<number> = [1, PROJECT_VERIFIER_CATALOG_VERSION];
10
14
 
11
15
  /** Public schema limits. Diagnostics cite these values instead of hiding policy. */
12
16
  export const PROJECT_VERIFIER_CATALOG_CAPS = Object.freeze({
@@ -22,6 +26,31 @@ export const PROJECT_VERIFIER_CATALOG_CAPS = Object.freeze({
22
26
  tagBytes: 32,
23
27
  });
24
28
 
29
+ /**
30
+ * What a check proves. `command` reads the exit code. `numeric-compare` runs
31
+ * the command, parses its stdout as `string -> number | number[]`, and judges
32
+ * it against a reference file under a tolerance. `perf-budget` runs the
33
+ * command and judges its wall time against a budget or a recorded baseline.
34
+ */
35
+ export type DeclaredCheckKind = "command" | "numeric-compare" | "perf-budget";
36
+
37
+ export const DECLARED_CHECK_KINDS: ReadonlyArray<DeclaredCheckKind> = ["command", "numeric-compare", "perf-budget"];
38
+
39
+ export interface DeclaredNumericCompare {
40
+ /** Repository-relative JSON file of the same shape as the command's stdout. */
41
+ reference: string;
42
+ tolerance: NumericTolerance;
43
+ }
44
+
45
+ export interface DeclaredPerfBudget {
46
+ /** Declared bound; exactly one of `budget` and `baseline` is present. */
47
+ budget?: PerfBudgetSpec;
48
+ /** Repository-relative JSON file written by `clio-coder verifiers baseline <id>`. */
49
+ baseline?: string;
50
+ /** Headroom over a recorded baseline; only with `baseline`. */
51
+ tolerance?: { relative?: number };
52
+ }
53
+
25
54
  export type DeclaredCheckSourceKind = "package.json" | "project-catalog";
26
55
 
27
56
  export interface DeclaredCheckSourceRef {
@@ -38,6 +67,9 @@ export interface DeclaredCheck {
38
67
  timeoutMs: number;
39
68
  tags: string[];
40
69
  source: DeclaredCheckSourceRef;
70
+ kind: DeclaredCheckKind;
71
+ numeric?: DeclaredNumericCompare;
72
+ perf?: DeclaredPerfBudget;
41
73
  }
42
74
 
43
75
  export interface DeclaredCheckSource extends DeclaredCheckSourceRef {
@@ -48,6 +80,8 @@ export type ProjectCatalogLoadResult = { ok: true; source: DeclaredCheckSource |
48
80
 
49
81
  const ROOT_FIELDS = new Set(["version", "checks"]);
50
82
  const CHECK_FIELDS = new Set(["id", "description", "command", "cwd", "timeoutMs", "tags"]);
83
+ /** Version 2 adds the kind and its parameters; every one is optional and kind-gated. */
84
+ const CHECK_FIELDS_V2 = new Set([...CHECK_FIELDS, "kind", "reference", "tolerance", "budget", "baseline"]);
51
85
  const TAG_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
52
86
  const SHELL_EXECUTABLES = new Set([
53
87
  "bash",
@@ -130,6 +164,85 @@ function safelyResolveCatalogCwd(
130
164
  return { declared: relativeCwd(workspaceRoot, resolved), execution: realCwd };
131
165
  }
132
166
 
167
+ /**
168
+ * A repository-relative file the check reads or writes at run time. It need
169
+ * not exist when the catalog loads (a baseline is written later), so only the
170
+ * shape is checked: relative, NUL-free, bounded, and unable to escape the root.
171
+ */
172
+ function validateRepositoryFile(value: unknown, location: string): string | Error {
173
+ if (typeof value !== "string" || value.length === 0) {
174
+ return new Error(`${location} must be a non-empty repository-relative path`);
175
+ }
176
+ if (value.includes("\0")) return new Error(`${location} must not contain a NUL byte`);
177
+ if (utf8Bytes(value) > PROJECT_VERIFIER_CATALOG_CAPS.cwdBytes) {
178
+ return new Error(`${location} exceeds the ${PROJECT_VERIFIER_CATALOG_CAPS.cwdBytes}-byte cap`);
179
+ }
180
+ if (path.isAbsolute(value) || path.win32.isAbsolute(value)) {
181
+ return new Error(`${location} must be repository-relative; absolute path '${value}' is not allowed`);
182
+ }
183
+ const normalized = path.posix.normalize(value.split(path.sep).join("/"));
184
+ if (normalized === ".." || normalized.startsWith("../")) {
185
+ return new Error(`${location} escapes the workspace root: '${value}'`);
186
+ }
187
+ return normalized;
188
+ }
189
+
190
+ /**
191
+ * The kind-specific fields of one check. Version 1 admits no such field, so a
192
+ * version-1 check is always `command`. Under version 2 the kind gates which
193
+ * parameters may appear, and each kind requires its own.
194
+ */
195
+ function validateCheckKind(
196
+ value: Record<string, unknown>,
197
+ location: string,
198
+ version: number,
199
+ ): Pick<DeclaredCheck, "kind" | "numeric" | "perf"> | Error {
200
+ const kindValue = Object.hasOwn(value, "kind") ? value.kind : "command";
201
+ if (typeof kindValue !== "string" || !(DECLARED_CHECK_KINDS as ReadonlyArray<string>).includes(kindValue)) {
202
+ return new Error(`${location}.kind must be one of ${DECLARED_CHECK_KINDS.join(", ")}`);
203
+ }
204
+ const kind = kindValue as DeclaredCheckKind;
205
+ const present = (field: string): boolean => Object.hasOwn(value, field);
206
+ const accepted: Record<DeclaredCheckKind, ReadonlyArray<string>> = {
207
+ command: [],
208
+ "numeric-compare": ["reference", "tolerance"],
209
+ "perf-budget": ["budget", "baseline", "tolerance"],
210
+ };
211
+ const stray = ["reference", "tolerance", "budget", "baseline"].filter(
212
+ (field) => present(field) && !accepted[kind].includes(field),
213
+ );
214
+ if (stray.length > 0) return new Error(`${location} kind '${kind}' does not accept ${stray.join(", ")}`);
215
+ if (version === 1 && kind !== "command") return new Error(`${location}.kind requires catalog version 2`);
216
+ if (kind === "command") return { kind };
217
+ if (kind === "numeric-compare") {
218
+ if (!present("reference")) return new Error(`${location}.reference is required for kind numeric-compare`);
219
+ if (!present("tolerance")) return new Error(`${location}.tolerance is required for kind numeric-compare`);
220
+ const reference = validateRepositoryFile(value.reference, `${location}.reference`);
221
+ if (reference instanceof Error) return reference;
222
+ const tolerance = normalizeNumericTolerance(value.tolerance);
223
+ if (tolerance instanceof Error) return new Error(`${location}.${tolerance.message}`);
224
+ return { kind, numeric: { reference, tolerance } };
225
+ }
226
+ if (present("budget") === present("baseline")) {
227
+ return new Error(`${location} kind 'perf-budget' requires exactly one of budget or baseline`);
228
+ }
229
+ if (present("budget")) {
230
+ if (present("tolerance")) return new Error(`${location}.tolerance belongs inside budget when budget is given`);
231
+ const budget = normalizePerfBudget(value.budget);
232
+ if (budget instanceof Error) return new Error(`${location}.${budget.message}`);
233
+ return { kind, perf: { budget } };
234
+ }
235
+ const baseline = validateRepositoryFile(value.baseline, `${location}.baseline`);
236
+ if (baseline instanceof Error) return baseline;
237
+ const perf: DeclaredPerfBudget = { baseline };
238
+ if (present("tolerance")) {
239
+ const tolerance = normalizePerfTolerance(value.tolerance, `${location}.tolerance`);
240
+ if (tolerance instanceof Error) return tolerance;
241
+ perf.tolerance = tolerance;
242
+ }
243
+ return { kind, perf };
244
+ }
245
+
133
246
  /** Revalidate a stored catalog cwd and return its canonical in-workspace execution path. */
134
247
  export function resolveProjectVerifierExecutionCwd(rawCwd: string, workspaceRoot: string): string | Error {
135
248
  const resolution = safelyResolveCatalogCwd(rawCwd, workspaceRoot, "project check cwd");
@@ -276,11 +389,12 @@ export function parseProjectVerifierCatalogText(
276
389
  const rootUnknown = unknownFields(parsed, ROOT_FIELDS);
277
390
  if (rootUnknown.length > 0) return catalogDiagnostic(`root has unknown field(s): ${rootUnknown.join(", ")}`);
278
391
  if (!Object.hasOwn(parsed, "version")) return catalogDiagnostic("root.version is required");
279
- if (parsed.version !== PROJECT_VERIFIER_CATALOG_VERSION) {
392
+ if (typeof parsed.version !== "number" || !SUPPORTED_CATALOG_VERSIONS.includes(parsed.version)) {
280
393
  return catalogDiagnostic(
281
- `unsupported version ${JSON.stringify(parsed.version)}; supported version is ${PROJECT_VERIFIER_CATALOG_VERSION}`,
394
+ `unsupported version ${JSON.stringify(parsed.version)}; supported versions are ${SUPPORTED_CATALOG_VERSIONS.join(" and ")}`,
282
395
  );
283
396
  }
397
+ const version = parsed.version;
284
398
  if (!Object.hasOwn(parsed, "checks")) return catalogDiagnostic("root.checks is required");
285
399
  if (!Array.isArray(parsed.checks)) return catalogDiagnostic("root.checks must be an array");
286
400
  if (parsed.checks.length > PROJECT_VERIFIER_CATALOG_CAPS.checks) {
@@ -293,7 +407,7 @@ export function parseProjectVerifierCatalogText(
293
407
  for (const [index, value] of parsed.checks.entries()) {
294
408
  const location = `checks[${index}]`;
295
409
  if (!isRecord(value)) return catalogDiagnostic(`${location} must be an object`);
296
- const checkUnknown = unknownFields(value, CHECK_FIELDS);
410
+ const checkUnknown = unknownFields(value, version === 1 ? CHECK_FIELDS : CHECK_FIELDS_V2);
297
411
  if (checkUnknown.length > 0) {
298
412
  return catalogDiagnostic(`${location} has unknown field(s): ${checkUnknown.join(", ")}`);
299
413
  }
@@ -325,9 +439,11 @@ export function parseProjectVerifierCatalogText(
325
439
  if (timeoutMs instanceof Error) return catalogDiagnostic(timeoutMs.message);
326
440
  const tags = validateTags(value.tags, `${location}.tags`);
327
441
  if (tags instanceof Error) return catalogDiagnostic(tags.message);
442
+ const kindFields = validateCheckKind(value, location, version);
443
+ if (kindFields instanceof Error) return catalogDiagnostic(kindFields.message);
328
444
 
329
445
  ids.set(id, index);
330
- checks.push({ id, description, command, cwd, timeoutMs, tags, source: { ...sourceRef } });
446
+ checks.push({ id, description, command, cwd, timeoutMs, tags, source: { ...sourceRef }, ...kindFields });
331
447
  }
332
448
  checks.sort((left, right) => compareCodepoints(left.id, right.id));
333
449
  return { ok: true, source: { ...sourceRef, checks } };
@@ -342,5 +458,6 @@ export function packageDeclaredCheck(id: string, packagePath: string, cwd: strin
342
458
  timeoutMs: SAFE_EXEC_DEFAULT_TIMEOUT_MS,
343
459
  tags,
344
460
  source: { kind: "package.json", path: packagePath },
461
+ kind: "command",
345
462
  };
346
463
  }
@@ -36,6 +36,7 @@ export const verifyTool: ToolSpec = {
36
36
  message: `verify: '${check}' is not a verification check (${VERIFICATION_SCRIPT_FAMILY_HINT} or "frontend"); run it through bash.`,
37
37
  };
38
38
  }
39
- return runScriptCheck(check, args, options);
39
+ const result = await runScriptCheck(check, args, options);
40
+ return declared ? { ...result, details: { ...result.details, check, source: { ...declared.source } } } : result;
40
41
  },
41
42
  };