@iowarp/clio-coder 0.3.1 → 0.3.2

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 (595) hide show
  1. package/CHANGELOG.md +90 -2
  2. package/CONTRIBUTING.md +23 -23
  3. package/README.md +284 -613
  4. package/dist/{acp-FPR54DGL.js → acp-BIYHVZIM.js} +43 -53
  5. package/dist/{agents-OGPIHPJH.js → agents-YT6SSRIT.js} +43 -26
  6. package/dist/assets/codewiki.json +1 -1
  7. package/dist/{auth-IC3K6NIZ.js → auth-5TWEIYDN.js} +20 -12
  8. package/dist/{chunk-PV4JUBVJ.js → chunk-2EHAIA3X.js} +40 -21
  9. package/dist/{chunk-IS3ONKU3.js → chunk-2IR2NMPA.js} +6 -4
  10. package/dist/chunk-2SFS6XQE.js +122 -0
  11. package/dist/chunk-2VTFPG5O.js +48 -0
  12. package/dist/{chunk-MAR7Y6HW.js → chunk-3ZXDFGR5.js} +23 -16
  13. package/dist/chunk-4BJ5BYCE.js +61 -0
  14. package/dist/{chunk-474KN5II.js → chunk-4BPJXDWC.js} +111 -181
  15. package/dist/chunk-4KLWL3UC.js +18 -0
  16. package/dist/chunk-4VP4KH3K.js +962 -0
  17. package/dist/chunk-4ZG3XFUR.js +77 -0
  18. package/dist/chunk-5B2AEOW5.js +5407 -0
  19. package/dist/{chunk-K2ITRMHZ.js → chunk-5TSRNF4G.js} +6 -138
  20. package/dist/{chunk-OLBBMFRD.js → chunk-5UUP6MWO.js} +24 -62
  21. package/dist/chunk-65DEGPJ6.js +52 -0
  22. package/dist/chunk-6EJMN2Y3.js +17 -0
  23. package/dist/chunk-6EJV5X2W.js +16405 -0
  24. package/dist/chunk-6N5PTWMY.js +136 -0
  25. package/dist/chunk-6XLNIQDB.js +27 -0
  26. package/dist/{chunk-TEKV33Q5.js → chunk-77VKQEHF.js} +65 -33
  27. package/dist/chunk-7CR24IG7.js +242 -0
  28. package/dist/{chunk-M5T5VO65.js → chunk-7EYHLWU7.js} +837 -635
  29. package/dist/chunk-7MNJORFF.js +22 -0
  30. package/dist/{chunk-KY56HMHH.js → chunk-A3CYT5EX.js} +125 -31
  31. package/dist/chunk-AGYYIBLL.js +1069 -0
  32. package/dist/chunk-AO4RKG4M.js +277 -0
  33. package/dist/{chunk-GB6QRBXN.js → chunk-APJ265NV.js} +54 -1187
  34. package/dist/chunk-ARBGF5F7.js +174 -0
  35. package/dist/{chunk-673JJUWJ.js → chunk-BMEMKKIT.js} +2 -2
  36. package/dist/chunk-CBCAPZAA.js +229 -0
  37. package/dist/chunk-CMZWFGD2.js +352 -0
  38. package/dist/chunk-ECH6PKUQ.js +39 -0
  39. package/dist/chunk-ED4KHGC3.js +143 -0
  40. package/dist/chunk-EKMEHE4H.js +340 -0
  41. package/dist/{chunk-RPTR2H26.js → chunk-EPVUXGXG.js} +21 -15
  42. package/dist/chunk-FCSXB6T2.js +338 -0
  43. package/dist/chunk-FJ3H4MN5.js +48 -0
  44. package/dist/chunk-FQ4SKYE4.js +29 -0
  45. package/dist/chunk-G2DE3C7R.js +644 -0
  46. package/dist/chunk-G4BMMOKF.js +182 -0
  47. package/dist/{chunk-ZPY3JZ5E.js → chunk-GGXXDWE4.js} +183 -1233
  48. package/dist/chunk-HC4CLZ2Y.js +68 -0
  49. package/dist/{chunk-LU4TK2PR.js → chunk-HFSBBKSQ.js} +5 -56
  50. package/dist/{chunk-PIUMUEMV.js → chunk-HKIYEGME.js} +10 -6
  51. package/dist/chunk-I4HZDVNP.js +73 -0
  52. package/dist/{chunk-4QKXUHSR.js → chunk-IGLFWIYI.js} +70 -20
  53. package/dist/chunk-IKCO5N3L.js +162 -0
  54. package/dist/chunk-IR4CFBFN.js +56 -0
  55. package/dist/{chunk-PFEFKVGL.js → chunk-J5HN4RYU.js} +13 -11
  56. package/dist/{chunk-R5KLMSBV.js → chunk-J5Q24KAG.js} +2 -2
  57. package/dist/{chunk-K5XEMXTI.js → chunk-JVCV3ICN.js} +1 -1
  58. package/dist/chunk-KJ5LWLOE.js +1077 -0
  59. package/dist/chunk-LBMZMYH2.js +285 -0
  60. package/dist/{chunk-G34LV2PF.js → chunk-LBNRH5WM.js} +84 -170
  61. package/dist/{chunk-H6F6BYOH.js → chunk-LZSJBIVT.js} +7003 -7434
  62. package/dist/{chunk-HQQID6OA.js → chunk-M6SHUN7Q.js} +5 -5
  63. package/dist/chunk-MAW544W2.js +1882 -0
  64. package/dist/chunk-MBS4V7ZP.js +217 -0
  65. package/dist/{chunk-FST4FYJB.js → chunk-MFFY33HR.js} +99 -140
  66. package/dist/chunk-MNA4JGU4.js +255 -0
  67. package/dist/chunk-MQSRRFWA.js +3428 -0
  68. package/dist/{chunk-BSU2YIWB.js → chunk-MVVUPGPW.js} +131 -136
  69. package/dist/chunk-OAO4GE4M.js +619 -0
  70. package/dist/chunk-OHHN2SO4.js +5135 -0
  71. package/dist/chunk-OKGUZO2U.js +34 -0
  72. package/dist/{chunk-GAEBEQVI.js → chunk-OOJYHWRB.js} +32 -346
  73. package/dist/{chunk-Q5WJOSJ7.js → chunk-OQ33BKR3.js} +2 -1
  74. package/dist/chunk-OQE5J4C6.js +73 -0
  75. package/dist/{chunk-KKNLWXI6.js → chunk-ORBHGJC5.js} +8 -8
  76. package/dist/chunk-POHLU5DW.js +1186 -0
  77. package/dist/chunk-QKMUKYO7.js +4961 -0
  78. package/dist/{chunk-ASND7OZK.js → chunk-QTYWRVRA.js} +13 -13
  79. package/dist/{chunk-EYOKLTMF.js → chunk-SRF2PJNW.js} +17 -3
  80. package/dist/chunk-SST6Z5JA.js +80 -0
  81. package/dist/chunk-STBPMHSX.js +2456 -0
  82. package/dist/chunk-T6YILFSB.js +80 -0
  83. package/dist/chunk-TZTZS7QK.js +227 -0
  84. package/dist/chunk-UOV2BYIW.js +107 -0
  85. package/dist/{chunk-Q3RUPKEJ.js → chunk-V4RXGQ5Q.js} +58 -189
  86. package/dist/chunk-VAKQQHWR.js +434 -0
  87. package/dist/chunk-VG7TBQIY.js +128 -0
  88. package/dist/chunk-VJWL6YS5.js +244 -0
  89. package/dist/chunk-WEH5XRJQ.js +32 -0
  90. package/dist/chunk-WMSVI4G2.js +2095 -0
  91. package/dist/chunk-WVO7V2QY.js +797 -0
  92. package/dist/chunk-X4RCMKVQ.js +641 -0
  93. package/dist/chunk-X75S7HFS.js +374 -0
  94. package/dist/chunk-XN3L4EYL.js +46 -0
  95. package/dist/{chunk-RDLVBZEO.js → chunk-YCWGATWI.js} +6 -4
  96. package/dist/chunk-YHZX5GEU.js +193 -0
  97. package/dist/chunk-YXLYO42X.js +91 -0
  98. package/dist/{chunk-NMOX6HFD.js → chunk-ZDOOVTXZ.js} +29 -77
  99. package/dist/chunk-ZI647VB5.js +37 -0
  100. package/dist/{chunk-C4PTHK7P.js → chunk-ZWLZP4ZT.js} +5 -5
  101. package/dist/cli/index.js +62 -54
  102. package/dist/clio-4LY5K2AC.js +25 -0
  103. package/dist/code-nav-7AX6FYE6.js +600 -0
  104. package/dist/codewiki/build-worker.js +66 -0
  105. package/dist/compile-cache-CVJMMODC.js +18 -0
  106. package/dist/{components-DMAOEKFB.js → components-KELWS457.js} +11 -6
  107. package/dist/{config-IRUQ7SE4.js → config-GTLUW2PR.js} +92 -55
  108. package/dist/configure-R6A64DHX.js +42 -0
  109. package/dist/context-5VKGUVJJ.js +866 -0
  110. package/dist/{context-3KWFLHJG.js → context-JFZEJ7W5.js} +15 -13
  111. package/dist/{context-5RADCKTR.js → context-RW5HC47S.js} +71 -35
  112. package/dist/{context-clear-7TSNPAAI.js → context-clear-6ZHBAZZT.js} +54 -28
  113. package/dist/{context-index-W4RLWOQH.js → context-index-BZ4UYMTC.js} +30 -24
  114. package/dist/dispatch-runner-VKBRCWQC.js +1997 -0
  115. package/dist/{docs-5AWSPS37.js → docs-2C2LTVT2.js} +23 -10
  116. package/dist/{doctor-UC5NAJYQ.js → doctor-KI767GSN.js} +27 -17
  117. package/dist/{eval-U6TJHRLX.js → eval-XSSNATB4.js} +29 -16
  118. package/dist/{evidence-YEGUW4L3.js → evidence-UA6AWDQQ.js} +46 -26
  119. package/dist/{evolve-TXARCTPG.js → evolve-QNTFGV6Z.js} +45 -25
  120. package/dist/{extensions-OZFJ3A3G.js → extensions-QVDOHDGJ.js} +16 -7
  121. package/dist/{fleet-6G3DHNYE.js → fleet-Q7UOMUSG.js} +163 -54
  122. package/dist/{fleet-preflight-DSNT37JK.js → fleet-preflight-DDN536IT.js} +7 -4
  123. package/dist/{init-KZ5QTF6M.js → init-WBB65ZHQ.js} +69 -32
  124. package/dist/{memory-73ESV5YC.js → memory-MD3O64RI.js} +48 -27
  125. package/dist/{models-A4PVNWJK.js → models-BZU34YWD.js} +39 -25
  126. package/dist/monitor-MEQA5C3I.js +661 -0
  127. package/dist/{chunk-FCIH3BIZ.js → orchestrator-CGFKEP27.js} +11832 -8687
  128. package/dist/{paths-C4H6IV77.js → paths-UXLN5YYZ.js} +10 -5
  129. package/dist/{preload-6WVMHX3A.js → preload-P6DGH2PZ.js} +2 -2
  130. package/dist/{reset-BGW6OGMV.js → reset-L2FQEE3E.js} +16 -10
  131. package/dist/{run-YTPEYQOH.js → run-IV4Q6RLN.js} +101 -61
  132. package/dist/{share-YIFFV4NQ.js → share-S5BZQC5I.js} +15 -8
  133. package/dist/{skills-2V6RA3OQ.js → skills-LQEKRDTN.js} +34 -14
  134. package/dist/{skills-eval-S2TVJO4F.js → skills-eval-3DC4HEWS.js} +70 -34
  135. package/dist/steer-GGWFUJUD.js +77 -0
  136. package/dist/{targets-TYXLPB23.js → targets-C4SSGQOB.js} +43 -27
  137. package/dist/terminal-lease-IT5JW2NR.js +395 -0
  138. package/dist/{trace-GGOJ6Q6Z.js → trace-PNCASAXC.js} +41 -16
  139. package/dist/{chunk-N6F52NLF.js → tree-sitter-HGKH6LG4.js} +28 -2306
  140. package/dist/{uninstall-LLLT4F4W.js → uninstall-FZCQCDKC.js} +10 -5
  141. package/dist/{upgrade-33G2LMM5.js → upgrade-7TT7SQ3G.js} +45 -25
  142. package/dist/{usage-ZAFSXKKG.js → usage-GV4PKT3M.js} +62 -31
  143. package/dist/verify-G6V4D2G7.js +716 -0
  144. package/dist/web-fetch-2YHJ3KTG.js +638 -0
  145. package/dist/{wiki-generate-NUQCVOQ3.js → wiki-generate-DQF6Z66B.js} +74 -34
  146. package/dist/worker/entry.js +221 -36
  147. package/dist/workspace-G4ZWUIPR.js +22 -0
  148. package/docs/README.md +22 -17
  149. package/docs/acp.md +168 -16
  150. package/docs/alcf-provider.md +1 -1
  151. package/docs/architecture.md +136 -7
  152. package/docs/artifact-versions.md +1 -1
  153. package/docs/built-in-agents.md +1 -1
  154. package/docs/capacity-and-scheduling.md +1 -1
  155. package/docs/commands-and-modes.md +114 -71
  156. package/docs/config-knobs-audit.md +1 -3
  157. package/docs/configuration-and-targets.md +174 -46
  158. package/docs/context-engine.md +29 -6
  159. package/docs/development-pipeline.md +26 -1
  160. package/docs/dispatch-architecture-rationale.md +1 -1
  161. package/docs/documentation-coverage.md +2 -2
  162. package/docs/documentation-guide.md +1 -1
  163. package/docs/environment-variables.md +13 -5
  164. package/docs/eval-runner.md +1 -1
  165. package/docs/evals-internal.md +1 -1
  166. package/docs/evidence-and-memory.md +6 -2
  167. package/docs/evolution.md +2 -2
  168. package/docs/exit-codes-and-output.md +15 -9
  169. package/docs/extensions-and-sharing.md +9 -9
  170. package/docs/fleet-dispatch.md +7 -5
  171. package/docs/git-commit-provenance.md +120 -0
  172. package/docs/glossary.md +1 -1
  173. package/docs/installation-and-lifecycle.md +34 -27
  174. package/docs/middleware-and-components.md +1 -1
  175. package/docs/model-catalog.md +45 -14
  176. package/docs/observability.md +8 -5
  177. package/docs/performance-methodology.md +491 -0
  178. package/docs/pi-boundary.md +72 -0
  179. package/docs/proactive-memory.md +3 -3
  180. package/docs/prompt-envelope-and-tools.md +24 -3
  181. package/docs/provider-adapter-cookbook.md +57 -4
  182. package/docs/release-cut-checklist.md +129 -115
  183. package/docs/safety-model.md +9 -5
  184. package/docs/scientific-validation.md +3 -3
  185. package/docs/session-lifecycle.md +55 -12
  186. package/docs/skills-marketplace.md +12 -8
  187. package/docs/time-conventions.md +1 -1
  188. package/docs/tool-usage.md +3 -3
  189. package/docs/trace-store.md +1 -1
  190. package/docs/troubleshooting.md +10 -7
  191. package/docs/tui-design.md +47 -10
  192. package/docs/worker-dispatch-mechanics.md +1 -1
  193. package/package.json +19 -22
  194. package/skills/coding/ast-grep/SKILL.md +136 -0
  195. package/skills/coding/ast-grep/evals.md +56 -0
  196. package/skills/coding/ast-grep/references/rule_reference.md +297 -0
  197. package/skills/coding/coding-standards/SKILL.md +113 -0
  198. package/skills/coding/coding-standards/evals.md +34 -0
  199. package/skills/coding/prototype/SKILL.md +86 -0
  200. package/skills/coding/prototype/evals.md +42 -0
  201. package/skills/coding/prototype/references/LOGIC.md +67 -0
  202. package/skills/coding/prototype/references/UI.md +112 -0
  203. package/skills/coding/tdd/SKILL.md +101 -0
  204. package/skills/coding/tdd/evals.md +41 -0
  205. package/skills/coding/tdd/references/mocking.md +59 -0
  206. package/skills/coding/tdd/references/tests.md +77 -0
  207. package/skills/context/context-handoff/SKILL.md +126 -0
  208. package/skills/context/context-handoff/evals.md +57 -0
  209. package/skills/context/context-handoff/scripts/new-handoff.sh +26 -0
  210. package/skills/context/context-prime/SKILL.md +95 -0
  211. package/skills/context/context-prime/evals.md +54 -0
  212. package/skills/meta/clio-dev/SKILL.md +91 -0
  213. package/skills/meta/clio-dev/evals.md +45 -0
  214. package/skills/meta/clio-test/SKILL.md +130 -0
  215. package/skills/meta/clio-test/evals.md +43 -0
  216. package/skills/meta/clio-test/references/harness.md +97 -0
  217. package/skills/meta/clio-test/references/test-map.md +59 -0
  218. package/skills/meta/credentials/SKILL.md +125 -0
  219. package/skills/meta/credentials/evals.md +104 -0
  220. package/skills/meta/find-skills/SKILL.md +72 -0
  221. package/skills/meta/find-skills/evals.md +47 -0
  222. package/skills/meta/herdr/SKILL.md +127 -0
  223. package/skills/meta/herdr/evals.md +38 -0
  224. package/skills/meta/skill-craft/SKILL.md +102 -0
  225. package/skills/meta/skill-craft/evals.md +41 -0
  226. package/skills/planning/architecture/SKILL.md +129 -0
  227. package/skills/planning/architecture/evals.md +36 -0
  228. package/skills/planning/backlog/SKILL.md +90 -0
  229. package/skills/planning/backlog/evals.md +43 -0
  230. package/skills/planning/prd/SKILL.md +82 -0
  231. package/skills/planning/prd/evals.md +49 -0
  232. package/skills/planning/product-intent/SKILL.md +112 -0
  233. package/skills/planning/product-intent/evals.md +36 -0
  234. package/skills/planning/tech-spec/SKILL.md +115 -0
  235. package/skills/planning/tech-spec/evals.md +47 -0
  236. package/skills/registry.yaml +136 -0
  237. package/skills/research/arxiv-literature/SKILL.md +104 -0
  238. package/skills/research/arxiv-literature/evals.md +58 -0
  239. package/skills/research/experiment-protocol/SKILL.md +122 -0
  240. package/skills/research/experiment-protocol/evals.md +91 -0
  241. package/skills/research/scientific-debugging/SKILL.md +119 -0
  242. package/skills/research/scientific-debugging/evals.md +138 -0
  243. package/skills/research/scientific-modernization/SKILL.md +138 -0
  244. package/skills/research/scientific-modernization/evals.md +84 -0
  245. package/skills/workflow/design-council/SKILL.md +139 -0
  246. package/skills/workflow/design-council/evals.md +97 -0
  247. package/skills/workflow/grill-me/SKILL.md +186 -0
  248. package/skills/workflow/grill-me/evals.md +78 -0
  249. package/skills/workflow/workflow-distiller/SKILL.md +136 -0
  250. package/skills/workflow/workflow-distiller/evals.md +107 -0
  251. package/src/cli/acp.ts +31 -4
  252. package/src/cli/clio.ts +68 -6
  253. package/src/cli/config-inspect.ts +28 -22
  254. package/src/cli/configure.ts +47 -9
  255. package/src/cli/context-clear.ts +2 -2
  256. package/src/cli/context-index.ts +21 -23
  257. package/src/cli/context.ts +13 -8
  258. package/src/cli/default-target.ts +9 -17
  259. package/src/cli/docs.ts +11 -5
  260. package/src/cli/evidence.ts +4 -1
  261. package/src/cli/extensions.ts +10 -1
  262. package/src/cli/fleet.ts +47 -6
  263. package/src/cli/index.ts +55 -26
  264. package/src/cli/memory.ts +3 -1
  265. package/src/cli/models.ts +1 -1
  266. package/src/cli/modes/json-stream.ts +37 -1
  267. package/src/cli/modes/print.ts +24 -9
  268. package/src/cli/run.ts +2 -2
  269. package/src/cli/skills-eval.ts +23 -8
  270. package/src/cli/skills.ts +19 -4
  271. package/src/cli/targets.ts +4 -0
  272. package/src/cli/text-layout.ts +15 -5
  273. package/src/cli/trace.ts +62 -14
  274. package/src/cli/upgrade.ts +18 -2
  275. package/src/cli/usage.ts +10 -3
  276. package/src/cli/wiki-generate.ts +2 -1
  277. package/src/core/agent-environment.ts +7 -0
  278. package/src/core/bash-exec.ts +72 -1
  279. package/src/core/boot-trace.ts +9 -4
  280. package/src/core/bus-events.ts +20 -4
  281. package/src/core/commit-attribution.ts +157 -0
  282. package/src/core/compile-cache.ts +159 -0
  283. package/src/core/config.ts +131 -2
  284. package/src/core/defaults.ts +39 -5
  285. package/src/core/domain-loader.ts +12 -5
  286. package/src/core/git-commit-attribution.ts +362 -0
  287. package/src/core/incomplete-installation.ts +45 -0
  288. package/src/core/response-schema.ts +1 -1
  289. package/src/core/safe-exec.ts +13 -1
  290. package/src/core/settings-layers.ts +155 -21
  291. package/src/core/skill-activation.ts +1 -1
  292. package/src/core/startup-timer.ts +3 -3
  293. package/src/core/state-file-lock.ts +13 -1
  294. package/src/core/termination.ts +78 -5
  295. package/src/domains/config/classify.ts +15 -3
  296. package/src/domains/config/extension.ts +19 -13
  297. package/src/domains/config/index.ts +10 -0
  298. package/src/domains/config/keybindings.ts +42 -6
  299. package/src/domains/context/bootstrap-prompt.ts +1 -1
  300. package/src/domains/context/bootstrap.ts +111 -18
  301. package/src/domains/context/clear.ts +16 -11
  302. package/src/domains/context/clio-md.ts +111 -9
  303. package/src/domains/context/codewiki/artifact.ts +400 -0
  304. package/src/domains/context/codewiki/build-worker-protocol.ts +24 -0
  305. package/src/domains/context/codewiki/build-worker.ts +54 -0
  306. package/src/domains/context/codewiki/coordinator.ts +182 -0
  307. package/src/domains/context/codewiki/indexer.ts +59 -144
  308. package/src/domains/context/codewiki/paths.ts +67 -0
  309. package/src/domains/context/codewiki/schema.ts +80 -0
  310. package/src/domains/context/codewiki/tree-sitter.ts +1 -1
  311. package/src/domains/context/contract.ts +11 -5
  312. package/src/domains/context/extension.ts +94 -143
  313. package/src/domains/context/fingerprint.ts +3 -1
  314. package/src/domains/context/index.ts +12 -22
  315. package/src/domains/context/project-metadata.ts +19 -0
  316. package/src/domains/context/prompt-context.ts +9 -10
  317. package/src/domains/context/refresh.ts +29 -21
  318. package/src/domains/context/runtime.ts +17 -0
  319. package/src/domains/context/wiki/generate.ts +39 -34
  320. package/src/domains/context/wiki/plan.ts +1 -1
  321. package/src/domains/context/wiki/prompts.ts +21 -8
  322. package/src/domains/dispatch/code-step.ts +20 -1
  323. package/src/domains/dispatch/extension.ts +158 -21
  324. package/src/domains/dispatch/failure-classification.ts +6 -0
  325. package/src/domains/dispatch/fleet-commit-attribution.ts +56 -0
  326. package/src/domains/dispatch/orphan-recovery.ts +50 -8
  327. package/src/domains/dispatch/receipt-integrity.ts +5 -0
  328. package/src/domains/dispatch/state.ts +31 -6
  329. package/src/domains/dispatch/transport.ts +2 -1
  330. package/src/domains/dispatch/types.ts +14 -0
  331. package/src/domains/dispatch/worker-spawn.ts +21 -2
  332. package/src/domains/eval/metrics/context.ts +1 -1
  333. package/src/domains/eval/types.ts +0 -1
  334. package/src/domains/evidence/build.ts +41 -1
  335. package/src/domains/lifecycle/migrations/2026-08-18-lmstudio-runtime-id.ts +52 -0
  336. package/src/domains/lifecycle/migrations/index.ts +24 -4
  337. package/src/domains/middleware/hooks-io.ts +12 -0
  338. package/src/domains/middleware/skills-reminder.ts +30 -15
  339. package/src/domains/prompts/compiler.ts +142 -84
  340. package/src/domains/prompts/contract.ts +18 -2
  341. package/src/domains/prompts/extension.ts +39 -7
  342. package/src/domains/prompts/fragment-loader.ts +0 -1
  343. package/src/domains/prompts/fragments/identity/clio.md +2 -4
  344. package/src/domains/prompts/fragments/identity/docs-routing.md +10 -0
  345. package/src/domains/prompts/fragments/identity/self-awareness.md +1 -45
  346. package/src/domains/prompts/fragments/operating/contract.md +4 -50
  347. package/src/domains/prompts/fragments/operating/delegation.md +42 -0
  348. package/src/domains/prompts/fragments/operating/skills.md +26 -0
  349. package/src/domains/prompts/fragments/operating/worker.md +16 -0
  350. package/src/domains/prompts/fragments/safety/auto-edit.md +5 -5
  351. package/src/domains/prompts/fragments/safety/full-auto.md +3 -3
  352. package/src/domains/prompts/fragments/safety/read-only.md +4 -4
  353. package/src/domains/prompts/fragments/safety/suggest.md +2 -2
  354. package/src/domains/prompts/fragments/wiki/page.md +10 -0
  355. package/src/domains/prompts/fragments/wiki/plan.md +10 -0
  356. package/src/domains/prompts/preload.ts +3 -3
  357. package/src/domains/providers/auth/api-key.ts +1 -1
  358. package/src/domains/providers/auth/backend-file.ts +20 -10
  359. package/src/domains/providers/auth/backend-memory.ts +59 -4
  360. package/src/domains/providers/auth/boot-status.ts +65 -0
  361. package/src/domains/providers/auth/oauth.ts +2 -1
  362. package/src/domains/providers/auth/storage.ts +97 -38
  363. package/src/domains/providers/capabilities.ts +12 -4
  364. package/src/domains/providers/contract.ts +15 -4
  365. package/src/domains/providers/extension.ts +18 -6
  366. package/src/domains/providers/model-runtime-capabilities.ts +15 -4
  367. package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +118 -35
  368. package/src/domains/providers/plugins.ts +5 -3
  369. package/src/domains/providers/probe/fingerprint.ts +25 -5
  370. package/src/domains/providers/registry.ts +31 -10
  371. package/src/domains/providers/runtimes/boot-manifest.ts +55 -0
  372. package/src/domains/providers/runtimes/builtins.ts +2 -2
  373. package/src/domains/providers/runtimes/common/lmstudio-http.ts +423 -0
  374. package/src/domains/providers/runtimes/common/local-synth.ts +6 -7
  375. package/src/domains/providers/runtimes/local-native/lmstudio.ts +241 -0
  376. package/src/domains/providers/support.ts +6 -3
  377. package/src/domains/providers/types/local-model-quirks.ts +7 -9
  378. package/src/domains/providers/types/runtime-descriptor.ts +12 -1
  379. package/src/domains/providers/types/target-descriptor.ts +22 -0
  380. package/src/domains/resources/contract.ts +0 -1
  381. package/src/domains/resources/extension.ts +1 -3
  382. package/src/domains/resources/loader.ts +3 -4
  383. package/src/domains/resources/prompts/loader.ts +16 -2
  384. package/src/domains/resources/prompts/substitute.ts +1 -65
  385. package/src/domains/resources/skills/content-hash.ts +2 -0
  386. package/src/domains/resources/skills/install.ts +17 -0
  387. package/src/domains/resources/skills/loader.ts +17 -10
  388. package/src/domains/resources/skills/marketplace.ts +55 -9
  389. package/src/domains/safety/action-classifier.ts +4 -2
  390. package/src/domains/safety/audit.ts +8 -2
  391. package/src/domains/safety/extension.ts +1 -1
  392. package/src/domains/session/compaction/branch-summary.ts +3 -2
  393. package/src/domains/session/compaction/cut-point.ts +2 -1
  394. package/src/domains/session/compaction/tokens.ts +2 -1
  395. package/src/domains/session/context-ledger.ts +14 -0
  396. package/src/domains/session/contract.ts +15 -0
  397. package/src/domains/session/decision-board.ts +190 -0
  398. package/src/domains/session/entries.ts +66 -3
  399. package/src/domains/session/extension.ts +93 -12
  400. package/src/domains/session/retry.ts +10 -18
  401. package/src/domains/session/session-artifacts.ts +107 -0
  402. package/src/domains/session/task-board.ts +207 -13
  403. package/src/domains/session/tree/active-path.ts +44 -5
  404. package/src/domains/session/tree/fork.ts +26 -27
  405. package/src/domains/session/tree/preview.ts +2 -2
  406. package/src/domains/session/workspace/git-probe.ts +17 -11
  407. package/src/domains/user-tasks/store.ts +297 -0
  408. package/src/engine/acp/errors.ts +96 -0
  409. package/src/engine/acp/server.ts +1728 -146
  410. package/src/engine/acp/transport.ts +135 -14
  411. package/src/engine/acp/types.ts +26 -0
  412. package/src/engine/agent.ts +3 -3
  413. package/src/engine/ai.ts +32 -27
  414. package/src/engine/alcf-oauth.ts +26 -19
  415. package/src/engine/api-registry.ts +223 -0
  416. package/src/engine/apis/index.ts +3 -7
  417. package/src/engine/apis/llamacpp-residency.ts +49 -9
  418. package/src/engine/apis/lmstudio-residency.ts +5 -21
  419. package/src/engine/apis/lmstudio.ts +243 -0
  420. package/src/engine/apis/ollama-native.ts +24 -3
  421. package/src/engine/apis/openai-completions.ts +170 -91
  422. package/src/engine/apis/residency.ts +139 -3
  423. package/src/engine/apis/types.ts +16 -0
  424. package/src/engine/env-api-keys.ts +98 -0
  425. package/src/engine/gemma-channel-filter.ts +223 -0
  426. package/src/engine/instrumented-tui.ts +192 -0
  427. package/src/engine/messages.ts +14 -0
  428. package/src/engine/models.ts +42 -0
  429. package/src/engine/oauth.ts +16 -12
  430. package/src/engine/prompt-templates.ts +1 -0
  431. package/src/engine/provider-payload.ts +16 -59
  432. package/src/engine/strip-tokenizer-sentinels.ts +1 -1
  433. package/src/engine/truncate.ts +9 -0
  434. package/src/engine/tui.ts +17 -9
  435. package/src/engine/types.ts +3 -6
  436. package/src/engine/worker-runtime-capabilities.ts +5 -0
  437. package/src/engine/worker-runtime.ts +1 -1
  438. package/src/engine/worker-tools.ts +9 -4
  439. package/src/entry/boot-options.ts +50 -0
  440. package/src/entry/orchestrator.ts +288 -150
  441. package/src/interactive/application-controller.ts +89 -2
  442. package/src/interactive/chat-loop.ts +266 -41
  443. package/src/interactive/chat-panel.ts +173 -47
  444. package/src/interactive/chat-renderer.ts +262 -72
  445. package/src/interactive/clio-editor.ts +3 -8
  446. package/src/interactive/command-fallbacks.ts +2 -2
  447. package/src/interactive/context-overlay.ts +27 -1
  448. package/src/interactive/editor-submit.ts +228 -24
  449. package/src/interactive/export-html/ansi-to-html.ts +161 -0
  450. package/src/interactive/export-html/index.ts +51 -0
  451. package/src/interactive/export-html/template.ts +45 -0
  452. package/src/interactive/export-html/tool-renderer.ts +54 -0
  453. package/src/interactive/footer/dashboard.ts +4 -0
  454. package/src/interactive/footer/notifications.ts +1 -1
  455. package/src/interactive/footer/widgets.ts +20 -2
  456. package/src/interactive/footer-panel.ts +2 -2
  457. package/src/interactive/format-time.ts +14 -2
  458. package/src/interactive/interactive-application.ts +201 -17
  459. package/src/interactive/interactive-event-projection.ts +6 -1
  460. package/src/interactive/interactive-input-runtime.ts +50 -4
  461. package/src/interactive/interactive-presentation.ts +151 -19
  462. package/src/interactive/interactive-shell.ts +268 -14
  463. package/src/interactive/interactive-slash-runtime.ts +176 -114
  464. package/src/interactive/interactive-tickers.ts +38 -7
  465. package/src/interactive/keybinding-manager.ts +1 -1
  466. package/src/interactive/layout.ts +40 -3
  467. package/src/interactive/overlay-frame.ts +1 -1
  468. package/src/interactive/overlay-general-openers.ts +58 -1
  469. package/src/interactive/overlay-key-routing.ts +3 -0
  470. package/src/interactive/overlay-lifecycle.ts +13 -0
  471. package/src/interactive/overlay-permission-lifecycle.ts +2 -1
  472. package/src/interactive/overlay-session-lifecycle.ts +69 -12
  473. package/src/interactive/overlays/decisions.ts +300 -0
  474. package/src/interactive/overlays/help-reference.ts +15 -10
  475. package/src/interactive/overlays/model-selector.ts +34 -16
  476. package/src/interactive/overlays/session-selector.ts +18 -0
  477. package/src/interactive/overlays/settings.ts +105 -17
  478. package/src/interactive/overlays/skills-hub.ts +4 -4
  479. package/src/interactive/overlays/tree-selector.ts +41 -6
  480. package/src/interactive/render-trace.ts +499 -90
  481. package/src/interactive/renderers/compaction-summary.ts +2 -2
  482. package/src/interactive/renderers/diff.ts +115 -104
  483. package/src/interactive/renderers/mermaid.ts +53 -0
  484. package/src/interactive/renderers/tool-execution.ts +386 -133
  485. package/src/interactive/renderers/worker-entry.ts +20 -4
  486. package/src/interactive/session-switch-settlement.ts +10 -0
  487. package/src/interactive/slash-autocomplete.ts +6 -114
  488. package/src/interactive/slash-commands.ts +135 -47
  489. package/src/interactive/slash-spec.ts +9 -38
  490. package/src/interactive/status/controller.ts +5 -1
  491. package/src/interactive/stdout-backpressure.ts +99 -0
  492. package/src/interactive/stream-pacer.ts +530 -0
  493. package/src/interactive/stream-pacing-policy.ts +66 -0
  494. package/src/interactive/tasks-overlay.ts +368 -14
  495. package/src/interactive/terminal-lease.ts +485 -0
  496. package/src/interactive/theme/tokens.ts +1 -1
  497. package/src/interactive/turn-context.ts +4 -3
  498. package/src/interactive/turn-persistence.ts +30 -13
  499. package/src/interactive/turn-queues.ts +12 -0
  500. package/src/interactive/turn-recovery.ts +25 -8
  501. package/src/interactive/turn-runtime.ts +79 -12
  502. package/src/interactive/turn-state.ts +10 -0
  503. package/src/interactive/view/artifacts.ts +114 -4
  504. package/src/interactive/view/view-overlay.ts +3 -0
  505. package/src/interactive/welcome-dashboard.ts +17 -16
  506. package/src/interactive/worker-receipts.ts +52 -3
  507. package/src/interactive/worker-stream.ts +5 -1
  508. package/src/tools/agent-tools.ts +23 -3
  509. package/src/tools/artifact.ts +2 -2
  510. package/src/tools/ask-user.ts +23 -13
  511. package/src/tools/bash.ts +30 -2
  512. package/src/tools/bootstrap.ts +34 -431
  513. package/src/tools/builtin-tool-catalog.ts +265 -0
  514. package/src/tools/codewiki/code-nav-surface.ts +29 -0
  515. package/src/tools/codewiki/code-nav.ts +8 -22
  516. package/src/tools/codewiki/shared.ts +41 -38
  517. package/src/tools/context/docs-engine.ts +14 -3
  518. package/src/tools/context/index.ts +107 -28
  519. package/src/tools/context/surface.ts +19 -0
  520. package/src/tools/core-bootstrap.ts +168 -0
  521. package/src/tools/credential-present.ts +5 -5
  522. package/src/tools/dispatch-admission.ts +533 -0
  523. package/src/tools/dispatch-background.ts +54 -0
  524. package/src/tools/dispatch-event-text.ts +6 -0
  525. package/src/tools/dispatch-plan.ts +9 -4
  526. package/src/tools/dispatch-run-events.ts +238 -0
  527. package/src/tools/dispatch-runner.ts +2370 -0
  528. package/src/tools/dispatch-scout-admission.ts +295 -0
  529. package/src/tools/dispatch-types.ts +77 -0
  530. package/src/tools/dispatch.ts +67 -3161
  531. package/src/tools/find.ts +4 -2
  532. package/src/tools/grep.ts +2 -2
  533. package/src/tools/lazy-tool.ts +60 -0
  534. package/src/tools/ledger.ts +3 -3
  535. package/src/tools/monitor-surface.ts +36 -0
  536. package/src/tools/monitor.ts +2 -32
  537. package/src/tools/observers.ts +2 -2
  538. package/src/tools/registry.ts +39 -27
  539. package/src/tools/safe-exec.ts +2 -2
  540. package/src/tools/steer-surface.ts +17 -0
  541. package/src/tools/steer.ts +2 -13
  542. package/src/tools/tasks.ts +108 -11
  543. package/src/tools/truncate.ts +25 -184
  544. package/src/tools/verify/frontend.ts +3 -1
  545. package/src/tools/verify/index.ts +3 -38
  546. package/src/tools/verify/surface.ts +46 -0
  547. package/src/tools/web-fetch-surface.ts +23 -0
  548. package/src/tools/web-fetch.ts +2 -20
  549. package/src/tools/write.ts +7 -2
  550. package/src/worker/entry.ts +39 -2
  551. package/src/worker/spec-contract.ts +26 -5
  552. package/dist/chunk-7SS2CTV2.js +0 -61361
  553. package/dist/chunk-DKGKUHFA.js +0 -924
  554. package/dist/chunk-GEP36Y4X.js +0 -12796
  555. package/dist/chunk-XYWBQRDM.js +0 -137
  556. package/dist/clio-BZVGEUFJ.js +0 -58
  557. package/dist/configure-S7S6F6CL.js +0 -32
  558. package/docs/html/agents_blueprint.html +0 -936
  559. package/docs/html/alcf_blueprint.html +0 -324
  560. package/docs/html/architecture_blueprint.html +0 -850
  561. package/docs/html/commands_blueprint.html +0 -939
  562. package/docs/html/config_knobs_audit_blueprint.html +0 -178
  563. package/docs/html/configuration_blueprint.html +0 -1080
  564. package/docs/html/context_blueprint.html +0 -603
  565. package/docs/html/documentation_blueprint.html +0 -832
  566. package/docs/html/environment_blueprint.html +0 -404
  567. package/docs/html/eval_blueprint.html +0 -743
  568. package/docs/html/evals_internal_blueprint.html +0 -190
  569. package/docs/html/evolution_blueprint.html +0 -674
  570. package/docs/html/extensions_blueprint.html +0 -2065
  571. package/docs/html/fleet_dispatch_blueprint.html +0 -286
  572. package/docs/html/index.html +0 -919
  573. package/docs/html/lifecycle_blueprint.html +0 -723
  574. package/docs/html/memory_blueprint.html +0 -699
  575. package/docs/html/middleware_blueprint.html +0 -664
  576. package/docs/html/models_blueprint.html +0 -2366
  577. package/docs/html/observability_blueprint.html +0 -683
  578. package/docs/html/provider_adapter_blueprint.html +0 -245
  579. package/docs/html/safety_blueprint.html +0 -1386
  580. package/docs/html/shared.css +0 -571
  581. package/docs/html/shared.js +0 -143
  582. package/docs/html/skills_blueprint.html +0 -671
  583. package/docs/html/soak_blueprint.html +0 -182
  584. package/docs/html/tool_usage_blueprint.html +0 -350
  585. package/docs/html/tools_blueprint.html +0 -2249
  586. package/docs/html/trace_blueprint.html +0 -235
  587. package/docs/html/tui_design_blueprint.html +0 -374
  588. package/docs/html/validation_blueprint.html +0 -961
  589. package/docs/html/worker_dispatch_blueprint.html +0 -231
  590. package/src/core/release.ts +0 -2
  591. package/src/domains/providers/runtimes/common/lmstudio-logger.ts +0 -32
  592. package/src/domains/providers/runtimes/local-native/lmstudio-native.ts +0 -491
  593. package/src/engine/apis/lmstudio-native.ts +0 -1438
  594. package/src/engine/apis/thinking-replay.ts +0 -11
  595. package/src/tools/string-enum.ts +0 -15
@@ -0,0 +1,2456 @@
1
+ import { createRequire as __clioCreateRequire } from "node:module"; const require = __clioCreateRequire(import.meta.url);
2
+ import {
3
+ AGENT_AUTOMATION_AUTHORITIES,
4
+ artifactDefaultPath,
5
+ classify,
6
+ detectValidationCommand,
7
+ extractCommandDeleteTargets,
8
+ extractCommandWriteTargets,
9
+ inlineShellScript,
10
+ tokenizeShellLike,
11
+ toolMutationPaths
12
+ } from "./chunk-5UUP6MWO.js";
13
+ import {
14
+ isVerificationScriptName
15
+ } from "./chunk-7MNJORFF.js";
16
+ import {
17
+ canonicalizeExistingPath
18
+ } from "./chunk-ECH6PKUQ.js";
19
+ import {
20
+ ToolNames
21
+ } from "./chunk-CGKSTWHD.js";
22
+ import {
23
+ foreignAgentDirs
24
+ } from "./chunk-IHXBNWMM.js";
25
+ import {
26
+ require_dist
27
+ } from "./chunk-APJ265NV.js";
28
+ import {
29
+ clioConfigDir
30
+ } from "./chunk-BNAZZHFG.js";
31
+ import {
32
+ resolvePackageRoot
33
+ } from "./chunk-IWHMRKLL.js";
34
+ import {
35
+ __toESM,
36
+ init_esm_shims
37
+ } from "./chunk-3R73A4XB.js";
38
+
39
+ // src/domains/agents/result-contract.ts
40
+ init_esm_shims();
41
+ import { createHash } from "node:crypto";
42
+ import path from "node:path";
43
+
44
+ // src/core/json-payload.ts
45
+ init_esm_shims();
46
+ function jsonPayloadCandidates(text) {
47
+ const trimmed = text.trim();
48
+ const candidates = [trimmed];
49
+ const fenced = /```[A-Za-z0-9_-]*\s*\n?([\s\S]*?)```/.exec(trimmed)?.[1]?.trim();
50
+ if (fenced !== void 0 && fenced.length > 0) candidates.push(fenced);
51
+ const start = trimmed.indexOf("{");
52
+ const end = trimmed.lastIndexOf("}");
53
+ if (start >= 0 && end > start) candidates.push(trimmed.slice(start, end + 1));
54
+ return candidates;
55
+ }
56
+ function parseJsonObjectPayload(text) {
57
+ let parsedSomething = false;
58
+ for (const candidate of jsonPayloadCandidates(text)) {
59
+ let value;
60
+ try {
61
+ value = JSON.parse(candidate);
62
+ } catch {
63
+ continue;
64
+ }
65
+ parsedSomething = true;
66
+ if (value === null || typeof value !== "object" || Array.isArray(value)) continue;
67
+ return { ok: true, value };
68
+ }
69
+ return { ok: false, reason: parsedSomething ? "not-object" : "not-json" };
70
+ }
71
+
72
+ // src/domains/agents/result-contract.ts
73
+ function canonical(value) {
74
+ if (value === null) return "null";
75
+ if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
76
+ if (typeof value === "number") {
77
+ if (!Number.isFinite(value)) throw new Error("result contract cannot digest non-finite numbers");
78
+ return JSON.stringify(value);
79
+ }
80
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
81
+ if (typeof value === "object") {
82
+ const record = value;
83
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonical(record[key])}`).join(",")}}`;
84
+ }
85
+ throw new Error(`result contract cannot digest ${typeof value}`);
86
+ }
87
+ function digest(value) {
88
+ return createHash("sha256").update(canonical(value), "utf8").digest("hex");
89
+ }
90
+ function resultContractDigest(contract) {
91
+ return digest(contract);
92
+ }
93
+ function resultContractSourceId(contract) {
94
+ return `agent-result-contract:${contract.kind}:${resultContractDigest(contract)}`;
95
+ }
96
+ function failure(contract, quality, reason) {
97
+ return {
98
+ conformance: "fail",
99
+ quality,
100
+ sourceId: resultContractSourceId(contract),
101
+ validatorDigest: digest({ contract, reason }),
102
+ reason
103
+ };
104
+ }
105
+ function success(contract, quality, value, extra = {}) {
106
+ return {
107
+ conformance: "pass",
108
+ quality,
109
+ sourceId: resultContractSourceId(contract),
110
+ validatorDigest: digest({ contract, value }),
111
+ ...extra
112
+ };
113
+ }
114
+ function parseJson(output) {
115
+ if (output === null || output.trim().length === 0) return { ok: false, reason: "missing final result" };
116
+ const parsed = parseJsonObjectPayload(output);
117
+ if (parsed.ok) return parsed;
118
+ return {
119
+ ok: false,
120
+ reason: parsed.reason === "not-object" ? "result must be a JSON object" : "result must be valid JSON"
121
+ };
122
+ }
123
+ function hasOnlyKeys(value, keys) {
124
+ const allowed = new Set(keys);
125
+ return Object.keys(value).every((key) => allowed.has(key));
126
+ }
127
+ function string(value) {
128
+ return typeof value === "string" && value.trim().length > 0;
129
+ }
130
+ function parseFindings(value) {
131
+ if (!Array.isArray(value)) return null;
132
+ const findings = [];
133
+ for (const entry of value) {
134
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return null;
135
+ const finding = entry;
136
+ const line = finding.line;
137
+ if (!hasOnlyKeys(finding, ["claim", "path", "line"]) || !string(finding.claim) || !string(finding.path) || typeof line !== "number" || !Number.isSafeInteger(line) || line < 1) {
138
+ return null;
139
+ }
140
+ findings.push({ claim: finding.claim, path: finding.path, line });
141
+ }
142
+ return findings;
143
+ }
144
+ var RESULT_CONTRACT_KINDS = [
145
+ "architect-plan",
146
+ "scout-report",
147
+ "verifier-report",
148
+ "debugger-report",
149
+ "research-report",
150
+ "mutation-report",
151
+ "provenance-report",
152
+ "external-delegation"
153
+ ];
154
+ function parseSubtasks(value) {
155
+ if (!Array.isArray(value) || value.length > 4) return null;
156
+ const subtasks = [];
157
+ const ids = /* @__PURE__ */ new Set();
158
+ for (const entry of value) {
159
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return null;
160
+ const subtask = entry;
161
+ if (!hasOnlyKeys(subtask, ["id", "task", "dependencies", "expectedResultContract", "requestedAuthority"]) || !string(subtask.id) || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u.test(subtask.id) || ids.has(subtask.id) || !string(subtask.task) || Buffer.byteLength(subtask.task, "utf8") > 4096 || !Array.isArray(subtask.dependencies) || subtask.dependencies.length > 4 || subtask.dependencies.some((dependency) => !string(dependency)) || new Set(subtask.dependencies).size !== subtask.dependencies.length || !RESULT_CONTRACT_KINDS.includes(subtask.expectedResultContract) || !AGENT_AUTOMATION_AUTHORITIES.includes(subtask.requestedAuthority)) {
162
+ return null;
163
+ }
164
+ ids.add(subtask.id);
165
+ subtasks.push({
166
+ id: subtask.id,
167
+ task: subtask.task,
168
+ dependencies: [...subtask.dependencies],
169
+ expectedResultContract: subtask.expectedResultContract,
170
+ requestedAuthority: subtask.requestedAuthority
171
+ });
172
+ }
173
+ for (const subtask of subtasks) {
174
+ if (subtask.dependencies.some((dependency) => dependency === subtask.id || !ids.has(dependency))) return null;
175
+ }
176
+ const remaining = new Set(ids);
177
+ const completed = /* @__PURE__ */ new Set();
178
+ while (remaining.size > 0) {
179
+ const ready = subtasks.filter(
180
+ (subtask) => remaining.has(subtask.id) && subtask.dependencies.every((dependency) => completed.has(dependency))
181
+ );
182
+ if (ready.length === 0) return null;
183
+ for (const subtask of ready) {
184
+ remaining.delete(subtask.id);
185
+ completed.add(subtask.id);
186
+ }
187
+ }
188
+ return subtasks;
189
+ }
190
+ function isContextHandbookResult(value) {
191
+ if (!hasOnlyKeys(value, ["projectName", "identity", "conventions", "invariants", "sections"])) return false;
192
+ if (!string(value.projectName) || !string(value.identity)) return false;
193
+ if (!Array.isArray(value.conventions) || value.conventions.some((entry) => !string(entry))) return false;
194
+ if (!Array.isArray(value.invariants) || value.invariants.some((entry) => !string(entry))) return false;
195
+ if (!Array.isArray(value.sections)) return false;
196
+ return value.sections.every((entry) => {
197
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return false;
198
+ const section = entry;
199
+ return hasOnlyKeys(section, ["title", "body"]) && string(section.title) && string(section.body);
200
+ });
201
+ }
202
+ function validateContextHandbook(contract, output) {
203
+ const parsed = parseJson(output);
204
+ if (!parsed.ok) return failure(contract, "fail", parsed.reason);
205
+ if (!isContextHandbookResult(parsed.value)) {
206
+ return failure(
207
+ contract,
208
+ "fail",
209
+ "context handbook result must carry projectName, identity, conventions[], invariants[], and sections[{title, body}] and nothing else"
210
+ );
211
+ }
212
+ return success(contract, "unmeasured", parsed.value);
213
+ }
214
+ function salvageScoutClaims(value) {
215
+ const grounded = [];
216
+ const ungrounded = [];
217
+ if (!Array.isArray(value)) return { grounded, ungrounded };
218
+ for (const entry of value) {
219
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) continue;
220
+ const finding = entry;
221
+ if (!string(finding.claim)) continue;
222
+ const line = finding.line;
223
+ if (string(finding.path) && typeof line === "number" && Number.isSafeInteger(line) && line >= 1) {
224
+ grounded.push({ claim: finding.claim, path: finding.path, line });
225
+ continue;
226
+ }
227
+ ungrounded.push(finding.claim);
228
+ }
229
+ return { grounded, ungrounded };
230
+ }
231
+ function degradedScout(contract, value, reason) {
232
+ const salvaged = salvageScoutClaims(value.findings);
233
+ if (salvaged.grounded.length === 0 && salvaged.ungrounded.length === 0) return null;
234
+ const degradedReason = `${reason} (accepted as a degraded reconnaissance result; findings are leads, not validation evidence)`;
235
+ const scout = {
236
+ findings: salvaged.grounded,
237
+ citations: salvaged.grounded.map(({ path: citedPath, line }) => ({ path: citedPath, line })),
238
+ needsSplit: false,
239
+ proposedSubtasks: [],
240
+ ungroundedClaims: salvaged.ungrounded,
241
+ degradedReason
242
+ };
243
+ return success(contract, "unmeasured", scout, { scout, reason: degradedReason });
244
+ }
245
+ function validateScout(contract, output) {
246
+ const parsed = parseJson(output);
247
+ if (!parsed.ok) return failure(contract, "fail", parsed.reason);
248
+ const value = parsed.value;
249
+ const degrade = (reason) => degradedScout(contract, value, reason) ?? failure(contract, "fail", reason);
250
+ if (!hasOnlyKeys(value, ["findings", "needsSplit", "proposedSubtasks"])) {
251
+ return degrade("Scout result has unknown fields");
252
+ }
253
+ const findings = parseFindings(value.findings);
254
+ const proposedSubtasks = parseSubtasks(value.proposedSubtasks);
255
+ if (findings === null || typeof value.needsSplit !== "boolean" || proposedSubtasks === null) {
256
+ return degrade(
257
+ "Scout result must carry findings as {claim, path, line} objects, needsSplit, and 0..4 typed proposed subtasks"
258
+ );
259
+ }
260
+ if (value.needsSplit !== proposedSubtasks.length > 0) {
261
+ return degrade("Scout needsSplit must agree with proposedSubtasks");
262
+ }
263
+ if (findings.length === 0 && !value.needsSplit) {
264
+ return degrade("Scout result must carry at least one cited finding, or set needsSplit");
265
+ }
266
+ if (value.needsSplit && findings.length > 0) {
267
+ return degrade("Scout split recommendations carry typed subtasks instead of findings");
268
+ }
269
+ const scout = {
270
+ findings,
271
+ citations: findings.map(({ path: citedPath, line }) => ({ path: citedPath, line })),
272
+ needsSplit: value.needsSplit,
273
+ proposedSubtasks
274
+ };
275
+ return success(contract, "pass", scout, { scout });
276
+ }
277
+ function parseChecks(value) {
278
+ if (!Array.isArray(value) || value.length === 0) return null;
279
+ const checks = [];
280
+ for (const entry of value) {
281
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return null;
282
+ const check = entry;
283
+ if (!hasOnlyKeys(check, ["name", "passed", "evidence"]) || !string(check.name) || typeof check.passed !== "boolean" || !string(check.evidence)) {
284
+ return null;
285
+ }
286
+ checks.push({ name: check.name, passed: check.passed, evidence: check.evidence });
287
+ }
288
+ return checks;
289
+ }
290
+ function validateScoutCitation(input, citation) {
291
+ const cwd = path.resolve(input.cwd);
292
+ const citedPath = path.resolve(cwd, citation.path);
293
+ const relative = path.relative(cwd, citedPath);
294
+ if (relative === "" || relative.startsWith(`..${path.sep}`) || relative === ".." || path.isAbsolute(relative)) {
295
+ return { ok: false, reason: `Scout citation path escapes the workspace: ${citation.path}` };
296
+ }
297
+ const content = input.filesystem.readFile(citedPath);
298
+ if (content === null) return { ok: false, reason: `Scout citation cannot be read: ${citation.path}` };
299
+ const lineCount = content.split(/\r\n|\r|\n/u).length;
300
+ if (citation.line > lineCount) {
301
+ return { ok: false, reason: `Scout citation is past end of file: ${citation.path}:${citation.line}` };
302
+ }
303
+ if (input.observedReadRanges !== void 0) {
304
+ const spans = input.observedReadRanges.get(citedPath) ?? [];
305
+ if (!spans.some(([start, end]) => citation.line >= start && citation.line <= end)) {
306
+ const observed = spans.length === 0 ? "this run never read that file" : `this run read only ${spans.map(([start, end]) => `${start}-${end}`).join(", ")}`;
307
+ return {
308
+ ok: false,
309
+ reason: `Scout citation is not grounded in a live read: ${citation.path}:${citation.line} (${observed})`
310
+ };
311
+ }
312
+ }
313
+ return {
314
+ ok: true,
315
+ evidence: { ...citation, contentDigest: createHash("sha256").update(content, "utf8").digest("hex") }
316
+ };
317
+ }
318
+ function validateScoutCitations(input, citations) {
319
+ const evidence = [];
320
+ for (const citation of citations) {
321
+ const checked = validateScoutCitation(input, citation);
322
+ if (!checked.ok) return checked;
323
+ evidence.push(checked.evidence);
324
+ }
325
+ return { ok: true, evidence };
326
+ }
327
+ function groundDegradedScout(contract, input, scout, degradedReason) {
328
+ const grounded = [];
329
+ const evidence = [];
330
+ const demoted = [];
331
+ for (const finding of scout.findings) {
332
+ const checked = validateScoutCitation(input, { path: finding.path, line: finding.line });
333
+ if (checked.ok) {
334
+ grounded.push(finding);
335
+ evidence.push(checked.evidence);
336
+ continue;
337
+ }
338
+ demoted.push(finding.claim);
339
+ }
340
+ const degraded = {
341
+ findings: grounded,
342
+ citations: grounded.map(({ path: citedPath, line }) => ({ path: citedPath, line })),
343
+ needsSplit: false,
344
+ proposedSubtasks: [],
345
+ ungroundedClaims: [...scout.ungroundedClaims ?? [], ...demoted],
346
+ degradedReason
347
+ };
348
+ return success(contract, "unmeasured", { scout: degraded, evidence }, { scout: degraded, reason: degradedReason });
349
+ }
350
+ function validateVerifier(contract, output) {
351
+ const parsed = parseJson(output);
352
+ if (!parsed.ok) return failure(contract, "fail", parsed.reason);
353
+ const value = parsed.value;
354
+ if (!hasOnlyKeys(value, ["verdict", "checks"]) || value.verdict !== "pass" && value.verdict !== "fail") {
355
+ return failure(contract, "fail", "Verifier result must carry pass or fail verdict and checks");
356
+ }
357
+ const checks = parseChecks(value.checks);
358
+ if (checks === null || value.verdict === "pass" !== checks.every((check) => check.passed)) {
359
+ return failure(contract, "fail", "Verifier verdict must agree with every typed check");
360
+ }
361
+ const verifier = { verdict: value.verdict, checks };
362
+ return success(contract, value.verdict, verifier, { verifier });
363
+ }
364
+ function parseVerifierResult(output) {
365
+ const validation = validateVerifier({ kind: "verifier-report" }, output);
366
+ return validation.verifier ?? null;
367
+ }
368
+ function validateCodeReport(contract, output) {
369
+ const parsed = parseJson(output);
370
+ if (!parsed.ok) return failure(contract, "unmeasured", parsed.reason);
371
+ const value = parsed.value;
372
+ if (!hasOnlyKeys(value, ["passed", "exitCode", "checks", "artifactPaths", "outputExcerpt"]) || typeof value.passed !== "boolean" || typeof value.exitCode !== "number" || !Number.isSafeInteger(value.exitCode) || typeof value.outputExcerpt !== "string" || !Array.isArray(value.artifactPaths) || value.artifactPaths.some((entry) => !string(entry))) {
373
+ return failure(
374
+ contract,
375
+ "unmeasured",
376
+ "Code result must carry passed, integer exitCode, checks, artifactPaths, and outputExcerpt"
377
+ );
378
+ }
379
+ const checks = parseChecks(value.checks);
380
+ if (checks === null) return failure(contract, "unmeasured", "Code result must carry typed checks");
381
+ if (value.passed !== (value.exitCode === 0) || value.passed !== checks.every((check) => check.passed)) {
382
+ return failure(contract, "unmeasured", "Code result passed must agree with its exit code and every check");
383
+ }
384
+ return success(contract, "unmeasured", value);
385
+ }
386
+ var RESULT_COMMIT_MESSAGE_MAX_BYTES = 1e3;
387
+ var AUTHORSHIP_FIELDS = ["commitMessage", "summary"];
388
+ function hasControlCharacters(value) {
389
+ for (const character of value) {
390
+ const code = character.codePointAt(0) ?? 0;
391
+ if (code === 127) return true;
392
+ if (code < 32 && character !== "\n" && character !== "\r" && character !== " ") return true;
393
+ }
394
+ return false;
395
+ }
396
+ function normalizeAuthored(value) {
397
+ return value.replace(/\r\n/gu, "\n").replace(/\r/gu, "\n").trim();
398
+ }
399
+ function validateAuthorship(value) {
400
+ for (const field of AUTHORSHIP_FIELDS) {
401
+ const raw = value[field];
402
+ if (raw === void 0 || raw === null) continue;
403
+ if (typeof raw !== "string") return `${field} must be a string when present`;
404
+ if (raw.trim().length === 0) continue;
405
+ if (Buffer.byteLength(raw, "utf8") > RESULT_COMMIT_MESSAGE_MAX_BYTES) {
406
+ return `${field} must be at most ${RESULT_COMMIT_MESSAGE_MAX_BYTES} bytes`;
407
+ }
408
+ if (hasControlCharacters(raw)) return `${field} must not contain control characters`;
409
+ }
410
+ return null;
411
+ }
412
+ function resultContractAuthorship(contract, output) {
413
+ const empty = { commitMessage: null, summary: null };
414
+ if (contract.kind !== "mutation-report" && contract.kind !== "architect-plan") return empty;
415
+ const parsed = parseJson(output);
416
+ if (!parsed.ok || validateAuthorship(parsed.value) !== null) return empty;
417
+ const read = (field) => {
418
+ const raw = parsed.value[field];
419
+ if (typeof raw !== "string") return null;
420
+ const normalized = normalizeAuthored(raw);
421
+ return normalized.length === 0 ? null : normalized;
422
+ };
423
+ return { commitMessage: read("commitMessage"), summary: read("summary") };
424
+ }
425
+ function validateDebugger(contract, output) {
426
+ const parsed = parseJson(output);
427
+ if (!parsed.ok) return failure(contract, "unmeasured", parsed.reason);
428
+ const value = parsed.value;
429
+ if (!hasOnlyKeys(value, ["diagnosis", "reproduction", "evidence"]) || !string(value.diagnosis) || value.reproduction !== "reproduced" && value.reproduction !== "not-reproduced" && value.reproduction !== "unknown" || !Array.isArray(value.evidence) || value.evidence.some((entry) => !string(entry))) {
430
+ return failure(
431
+ contract,
432
+ "unmeasured",
433
+ "Debugger result must carry diagnosis, reproduction status, and evidence without a verdict"
434
+ );
435
+ }
436
+ return success(contract, "unmeasured", value);
437
+ }
438
+ function validateResearch(contract, output, networkAllowed) {
439
+ const parsed = parseJson(output);
440
+ if (!parsed.ok) return failure(contract, "unmeasured", parsed.reason);
441
+ const value = parsed.value;
442
+ if (!hasOnlyKeys(value, ["source", "findings"]) || value.source !== "local" && value.source !== "external" || !Array.isArray(value.findings)) {
443
+ return failure(
444
+ contract,
445
+ "unmeasured",
446
+ "Research result must distinguish local or external sources and carry findings"
447
+ );
448
+ }
449
+ if (value.source === "external" && !networkAllowed) {
450
+ return failure(contract, "unmeasured", "external research requires an allowed network posture");
451
+ }
452
+ for (const finding of value.findings) {
453
+ if (finding === null || typeof finding !== "object" || Array.isArray(finding)) {
454
+ return failure(contract, "unmeasured", "Research findings must be objects");
455
+ }
456
+ const record = finding;
457
+ if (!hasOnlyKeys(record, ["claim", "evidence"]) || !string(record.claim) || !string(record.evidence)) {
458
+ return failure(contract, "unmeasured", "Research findings must carry claim and evidence");
459
+ }
460
+ }
461
+ return success(contract, "unmeasured", value);
462
+ }
463
+ function describeWriteSet(effects, cwd) {
464
+ if (effects.mutatedPaths.size === 0) return "this run wrote nothing";
465
+ const written = [...effects.mutatedPaths].map((target) => path.relative(cwd, target) || target).sort().slice(0, MUTATION_WRITE_SET_REASON_LIMIT);
466
+ const more = effects.mutatedPaths.size - written.length;
467
+ return `this run wrote ${written.join(", ")}${more > 0 ? ` (+${more} more)` : ""}`;
468
+ }
469
+ var MUTATION_WRITE_SET_REASON_LIMIT = 8;
470
+ function pathIsPresent(filesystem, target) {
471
+ if (filesystem.pathExists === void 0) return true;
472
+ return filesystem.pathExists(target);
473
+ }
474
+ var MUTATION_VALIDATION_EXAMPLE = '{"name":"npm test","passed":true,"evidence":"exit 0"}';
475
+ function mutationValidationsReason(value) {
476
+ const empty = Array.isArray(value) && value.length === 0;
477
+ if (empty) {
478
+ return `Mutation result must carry typed validation results: validations was empty. Emit one entry per check this run actually made, each shaped like ${MUTATION_VALIDATION_EXAMPLE}. A run that only read files still names the read and cites what it saw.`;
479
+ }
480
+ return `Mutation result must carry typed validation results: every validations entry is shaped like ${MUTATION_VALIDATION_EXAMPLE}, with a string name, a boolean passed, a string evidence, and no other keys.`;
481
+ }
482
+ function validateMutation(contract, input) {
483
+ const parsed = parseJson(input.output);
484
+ if (!parsed.ok) return failure(contract, "unmeasured", parsed.reason);
485
+ const value = parsed.value;
486
+ if (!hasOnlyKeys(value, ["mutatedPaths", "validations", "commitMessage", "summary"]) || !Array.isArray(value.mutatedPaths) || value.mutatedPaths.some((entry) => !string(entry))) {
487
+ return failure(contract, "unmeasured", "Mutation result must carry mutatedPaths and validations");
488
+ }
489
+ const authorship = validateAuthorship(value);
490
+ if (authorship !== null) return failure(contract, "unmeasured", authorship);
491
+ const validations = parseChecks(value.validations);
492
+ if (validations === null) return failure(contract, "unmeasured", mutationValidationsReason(value.validations));
493
+ const reportedFailure = !validations.every((check) => check.passed);
494
+ const effects = input.observedRunEffects;
495
+ if (effects === void 0) return success(contract, reportedFailure ? "fail" : "pass", value);
496
+ const cwd = path.resolve(input.cwd);
497
+ let unverifiedMutation = false;
498
+ for (const reported of value.mutatedPaths) {
499
+ const target = path.resolve(cwd, reported);
500
+ if (effects.mutatedPaths.has(target)) continue;
501
+ if (effects.failedMutationPaths.has(target)) {
502
+ return failure(
503
+ contract,
504
+ "unmeasured",
505
+ `Mutation result names a path whose only write this run attempted was refused: ${reported} (${describeWriteSet(effects, cwd)})`
506
+ );
507
+ }
508
+ if (pathIsPresent(input.filesystem, target)) {
509
+ unverifiedMutation = true;
510
+ continue;
511
+ }
512
+ return failure(
513
+ contract,
514
+ "unmeasured",
515
+ `Mutation result names a path this run never wrote and that does not exist: ${reported} (${describeWriteSet(effects, cwd)})`
516
+ );
517
+ }
518
+ if (reportedFailure) return success(contract, "fail", value);
519
+ const measured = !unverifiedMutation && effects.validationCommands.size > 0;
520
+ return success(contract, measured ? "pass" : "unmeasured", value);
521
+ }
522
+ function validateProvenance(contract, output) {
523
+ const parsed = parseJson(output);
524
+ if (!parsed.ok) return failure(contract, "unmeasured", parsed.reason);
525
+ const value = parsed.value;
526
+ if (!hasOnlyKeys(value, ["confirmedFacts", "missingEvidence", "nextInspections"]) || ![value.confirmedFacts, value.missingEvidence, value.nextInspections].every(
527
+ (entry) => Array.isArray(entry) && entry.every((item) => string(item))
528
+ )) {
529
+ return failure(
530
+ contract,
531
+ "unmeasured",
532
+ "Provenance result must carry confirmedFacts, missingEvidence, and nextInspections"
533
+ );
534
+ }
535
+ return success(contract, "unmeasured", value);
536
+ }
537
+ function validateArchitect(contract, input) {
538
+ const artifactPath = path.resolve(input.cwd, contract.path);
539
+ const content = input.filesystem.readFile(artifactPath);
540
+ if (content === null || content.trim().length === 0) {
541
+ return failure(contract, "unmeasured", `required plan artifact is missing or empty: ${contract.path}`);
542
+ }
543
+ return success(contract, "unmeasured", {
544
+ path: contract.path,
545
+ artifactDigest: createHash("sha256").update(content, "utf8").digest("hex")
546
+ });
547
+ }
548
+ function parseScoutResult(output) {
549
+ const validation = validateScout({ kind: "scout-report" }, output);
550
+ if (validation.conformance !== "pass" || validation.scout === void 0) return null;
551
+ return validation.scout.degradedReason === void 0 ? validation.scout : null;
552
+ }
553
+ function validateResultContract(input) {
554
+ switch (input.contract.kind) {
555
+ case "architect-plan":
556
+ return validateArchitect(input.contract, input);
557
+ case "scout-report": {
558
+ const validation = validateScout(input.contract, input.output);
559
+ if (validation.scout === void 0) return validation;
560
+ const degradedReason = validation.scout.degradedReason;
561
+ if (degradedReason !== void 0) {
562
+ return groundDegradedScout(input.contract, input, validation.scout, degradedReason);
563
+ }
564
+ const citations = validateScoutCitations(input, validation.scout.citations);
565
+ return citations.ok ? success(
566
+ input.contract,
567
+ "pass",
568
+ { scout: validation.scout, evidence: citations.evidence },
569
+ { scout: validation.scout }
570
+ ) : failure(input.contract, "fail", citations.reason);
571
+ }
572
+ case "verifier-report":
573
+ return validateVerifier(input.contract, input.output);
574
+ case "debugger-report":
575
+ return validateDebugger(input.contract, input.output);
576
+ case "research-report":
577
+ return validateResearch(input.contract, input.output, input.networkAllowed);
578
+ case "mutation-report":
579
+ return validateMutation(input.contract, input);
580
+ case "provenance-report":
581
+ return validateProvenance(input.contract, input.output);
582
+ case "external-delegation":
583
+ return success(input.contract, "unmeasured", { external: true });
584
+ case "artifact-report":
585
+ return success(input.contract, "unmeasured", { artifact: true });
586
+ case "context-handbook":
587
+ return validateContextHandbook(input.contract, input.output);
588
+ case "code-report":
589
+ return validateCodeReport(input.contract, input.output);
590
+ }
591
+ }
592
+ function validateRecipeResult(input) {
593
+ if (input.contract === null) return null;
594
+ if (!input.reachedTerminalResult) {
595
+ return {
596
+ applicable: false,
597
+ fact: {
598
+ sourceId: resultContractSourceId(input.contract),
599
+ validatorDigest: digest({ contract: input.contract, reached: false }),
600
+ conformance: "not-reached",
601
+ quality: "unmeasured"
602
+ }
603
+ };
604
+ }
605
+ const validation = validateResultContract({
606
+ contract: input.contract,
607
+ output: input.output,
608
+ cwd: input.cwd,
609
+ networkAllowed: input.networkAllowed,
610
+ filesystem: input.filesystem,
611
+ ...input.observedRunEffects !== void 0 ? { observedRunEffects: input.observedRunEffects } : {}
612
+ });
613
+ return {
614
+ applicable: true,
615
+ validation,
616
+ fact: {
617
+ sourceId: validation.sourceId,
618
+ validatorDigest: validation.validatorDigest,
619
+ conformance: validation.conformance,
620
+ quality: validation.quality
621
+ }
622
+ };
623
+ }
624
+ var RESULT_CONTRACT_REPAIR_LIMIT = 2;
625
+ var RESULT_CONTRACT_ANCHOR_LIMIT = 12;
626
+ function resultContractShape(contract) {
627
+ switch (contract.kind) {
628
+ case "architect-plan":
629
+ return `a plan artifact written to ${contract.path}, then optionally {"commitMessage":"...","summary":"..."} describing it`;
630
+ case "scout-report":
631
+ return '{"findings":[{"claim":"what you observed","path":"src/file.ts","line":1}],"needsSplit":false,"proposedSubtasks":[]} or {"findings":[],"needsSplit":true,"proposedSubtasks":[{"id":"inspect","task":"Inspect the boundary","dependencies":[],"expectedResultContract":"scout-report","requestedAuthority":"read-only"}]} or, if you cannot produce a citation you are sure of, at least {"findings":[{"claim":"what you observed"}]}, which is accepted as an ungrounded lead rather than losing the run';
632
+ case "verifier-report":
633
+ return '{"verdict":"pass","checks":[{"name":"npm run typecheck","passed":true,"evidence":"exit 0"}]}';
634
+ case "debugger-report":
635
+ return '{"diagnosis":"...","reproduction":"reproduced","evidence":["..."]}';
636
+ case "research-report":
637
+ return '{"source":"local","findings":[{"claim":"...","evidence":"..."}]}';
638
+ case "mutation-report":
639
+ return '{"mutatedPaths":["src/file.ts"],"validations":[{"name":"npm test","passed":true,"evidence":"exit 0"}],"commitMessage":"optional: the commit message for this change","summary":"optional: one line"}';
640
+ case "provenance-report":
641
+ return '{"confirmedFacts":["..."],"missingEvidence":["..."],"nextInspections":["..."]}';
642
+ case "external-delegation":
643
+ return "any final text";
644
+ case "artifact-report":
645
+ return "any final text; the artifact you wrote at the location the task named is the result";
646
+ case "context-handbook":
647
+ return '{"projectName":"string","identity":"one sentence","conventions":[],"invariants":[],"sections":[{"title":"Architecture","body":"markdown that cites `real/path.ts` tokens"}]}';
648
+ case "code-report":
649
+ return '{"passed":false,"exitCode":1,"checks":[{"name":"test","passed":false,"evidence":"exit 1"}],"artifactPaths":["/path/command.log"],"outputExcerpt":"..."}';
650
+ }
651
+ }
652
+ function resultContractRepairMessage(input) {
653
+ const last = input.attempt >= RESULT_CONTRACT_REPAIR_LIMIT;
654
+ const lines = [
655
+ last ? "FINAL RESULT REQUIRED IN THIS RESPONSE. Your previous response still did not satisfy this run's result contract." : "Your previous response did not satisfy this run's result contract.",
656
+ `Validator reason: ${input.reason}`,
657
+ "Tool use is over. Answer from the evidence you already gathered.",
658
+ `Emit exactly this shape and nothing else: ${resultContractShape(input.contract)}`,
659
+ "Do not add prose, code fences, or commentary around it. Do not describe work you intend to do next."
660
+ ];
661
+ if (input.anchors.length > 0) {
662
+ lines.push(
663
+ "",
664
+ "Locations you read successfully in this run (use these exact path:line values):",
665
+ ...input.anchors.slice(-RESULT_CONTRACT_ANCHOR_LIMIT).map((anchor) => `- ${anchor}`)
666
+ );
667
+ }
668
+ return lines.join("\n");
669
+ }
670
+ var RESULT_CONTRACT_REPAIR_TOOL = "result_contract";
671
+ function resultContractRepairMessages(input, origin) {
672
+ const id = `clio-result-contract-repair-${input.attempt}`;
673
+ const timestamp = Date.now();
674
+ return [
675
+ {
676
+ role: "assistant",
677
+ content: [{ type: "toolCall", id, name: RESULT_CONTRACT_REPAIR_TOOL, arguments: {} }],
678
+ stopReason: "toolUse",
679
+ usage: {
680
+ input: 0,
681
+ output: 0,
682
+ cacheRead: 0,
683
+ cacheWrite: 0,
684
+ totalTokens: 0,
685
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
686
+ },
687
+ ...origin,
688
+ timestamp
689
+ },
690
+ {
691
+ role: "toolResult",
692
+ toolCallId: id,
693
+ toolName: RESULT_CONTRACT_REPAIR_TOOL,
694
+ content: [{ type: "text", text: resultContractRepairMessage(input) }],
695
+ isError: true,
696
+ timestamp
697
+ }
698
+ ];
699
+ }
700
+ function parseResultContract(value, sourcePath) {
701
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
702
+ throw new Error(`agent recipe: ${sourcePath}: resultContract must be an object`);
703
+ }
704
+ const record = value;
705
+ if (typeof record.kind !== "string") throw new Error(`agent recipe: ${sourcePath}: resultContract.kind is required`);
706
+ const keys = Object.keys(record);
707
+ const only = (...allowed) => keys.every((key) => allowed.includes(key));
708
+ if (record.kind === "architect-plan") {
709
+ if (!only("kind", "path") || !string(record.path)) {
710
+ throw new Error(`agent recipe: ${sourcePath}: architect-plan requires only non-empty path`);
711
+ }
712
+ if (path.isAbsolute(record.path) || record.path.split(/[\\/]/u).includes("..")) {
713
+ throw new Error(`agent recipe: ${sourcePath}: architect-plan path must be workspace-relative`);
714
+ }
715
+ return { kind: "architect-plan", path: record.path };
716
+ }
717
+ const kinds = [
718
+ "scout-report",
719
+ "verifier-report",
720
+ "debugger-report",
721
+ "research-report",
722
+ "mutation-report",
723
+ "provenance-report",
724
+ "external-delegation",
725
+ "artifact-report",
726
+ "context-handbook"
727
+ ];
728
+ if (!kinds.includes(record.kind) || !only("kind")) {
729
+ throw new Error(`agent recipe: ${sourcePath}: resultContract.kind is unsupported or has unknown keys`);
730
+ }
731
+ return { kind: record.kind };
732
+ }
733
+
734
+ // src/domains/safety/finish-contract.ts
735
+ init_esm_shims();
736
+ var FINISH_CONTRACT_ADVISORY_MESSAGE = "[Clio Coder] finish-contract advisory: you changed files this turn without recording validation evidence or an explicit limitation. Run a verification command or state what could not be verified.";
737
+ var DEFAULT_RECENT_ENTRY_LIMIT = 80;
738
+ var LIMITATION_PATTERNS = [
739
+ /\b(?:blocked by|blocker|blockers|unable to|not able to|could not|couldn't|cannot|can't)\b/i,
740
+ /\b(?:did not|didn't|have not|haven't|has not|hasn't|was not able|wasn't able)\b/i,
741
+ /\b(?:not complete|incomplete)\b/i,
742
+ /\b(?:not|un)(?:\s|-)?(?:validated|verified|tested)\b/i,
743
+ /^\s*Tests\s*:\s*(?:not run|not executed|not available|failed|blocked|skipped)\b/im,
744
+ /^\s*Known gaps?\s*:\s*(?!\s*(?:none|no\b|n\/a|not applicable)\b).+/im,
745
+ /\bremaining(?:\s+\w+){0,3}\s+(?:work|issue|issues|gap|gaps|blocker|blockers)\b/i
746
+ ];
747
+ function assessFinishContract(input) {
748
+ const assistantText = input.assistantText.trim();
749
+ const sessionEntries = input.sessionEntries ?? [];
750
+ const assistantTurnId = input.assistantTurnId ?? null;
751
+ const window = recentEntries(sessionEntries, assistantTurnId, input.recentEntryLimit ?? DEFAULT_RECENT_ENTRY_LIMIT);
752
+ const mutatedPaths = mutatingReceipts(window);
753
+ if (mutatedPaths.length === 0) {
754
+ return { kind: "ok", reason: "no_mutation", evidence: [], mutatedPaths };
755
+ }
756
+ const evidence = collectValidationEvidence(window);
757
+ if (evidence.length > 0) {
758
+ return { kind: "ok", reason: "validation_evidence", evidence, mutatedPaths };
759
+ }
760
+ if (hasExplicitLimitation(assistantText)) {
761
+ return { kind: "ok", reason: "explicit_limitation", evidence: [], mutatedPaths };
762
+ }
763
+ return {
764
+ kind: "engage",
765
+ reason: "unvalidated_mutation",
766
+ message: FINISH_CONTRACT_ADVISORY_MESSAGE,
767
+ evidence: [],
768
+ mutatedPaths
769
+ };
770
+ }
771
+ function hasExplicitLimitation(text) {
772
+ const normalized = text.trim();
773
+ if (normalized.length === 0) return false;
774
+ return LIMITATION_PATTERNS.some((pattern) => pattern.test(normalized));
775
+ }
776
+ function mutatingReceipts(recent) {
777
+ const mutationCalls = /* @__PURE__ */ new Map();
778
+ const paths = [];
779
+ const seen = /* @__PURE__ */ new Set();
780
+ for (const entry of recent) {
781
+ const bashMutation = bashExecutionMutationPaths(entry);
782
+ if (bashMutation !== null) {
783
+ for (const path5 of bashMutation) pushPath(paths, seen, path5);
784
+ continue;
785
+ }
786
+ const call = mutatingToolCall(entry);
787
+ if (call !== null) {
788
+ mutationCalls.set(call.toolCallId, call);
789
+ continue;
790
+ }
791
+ const resultId = successfulToolResultId(entry);
792
+ if (resultId !== null) {
793
+ const candidate = mutationCalls.get(resultId);
794
+ if (candidate !== void 0) {
795
+ for (const path5 of candidate.paths) pushPath(paths, seen, path5);
796
+ }
797
+ }
798
+ }
799
+ return paths;
800
+ }
801
+ function mutationPathsForTool(toolName, args) {
802
+ if (toolName === ToolNames.Bash) {
803
+ const command = typeof args?.command === "string" ? args.command : null;
804
+ if (command === null) return [];
805
+ return [...extractCommandWriteTargets(command), ...extractCommandDeleteTargets(command)];
806
+ }
807
+ return toolMutationPaths(toolName, args);
808
+ }
809
+ function mutatingToolCall(entry) {
810
+ const record = asRecord(entry);
811
+ if (record?.kind !== "message" || record.role !== "tool_call") return null;
812
+ const payload = asRecord(record.payload);
813
+ if (payload === null) return null;
814
+ const toolName = stringFromFirst(payload, ["name", "toolName", "tool"]);
815
+ if (toolName === null) return null;
816
+ const args = asRecord(payload.args ?? payload.arguments ?? payload.input) ?? void 0;
817
+ const paths = mutationPathsForTool(toolName, args);
818
+ if (paths.length === 0) return null;
819
+ const toolCallId = stringFromFirst(payload, ["toolCallId", "tool_call_id", "id"]) ?? turnIdOf(entry);
820
+ if (toolCallId === null) return null;
821
+ return { toolCallId, paths };
822
+ }
823
+ function bashExecutionMutationPaths(entry) {
824
+ const record = asRecord(entry);
825
+ if (record?.kind !== "bashExecution") return null;
826
+ if (typeof record.command !== "string") return null;
827
+ if (record.cancelled === true || record.exitCode !== 0) return null;
828
+ return [...extractCommandWriteTargets(record.command), ...extractCommandDeleteTargets(record.command)];
829
+ }
830
+ function pushPath(paths, seen, path5) {
831
+ if (path5.length === 0 || seen.has(path5)) return;
832
+ seen.add(path5);
833
+ paths.push(path5);
834
+ }
835
+ function collectValidationEvidence(recent) {
836
+ const evidence = [];
837
+ const toolCalls = /* @__PURE__ */ new Map();
838
+ const dispatchCalls = /* @__PURE__ */ new Map();
839
+ const seen = /* @__PURE__ */ new Set();
840
+ for (const entry of recent) {
841
+ const protectedArtifact = protectedArtifactEvidence(entry);
842
+ if (protectedArtifact !== null) {
843
+ pushEvidence(evidence, seen, protectedArtifact);
844
+ continue;
845
+ }
846
+ const call = bashValidationCall(entry);
847
+ if (call !== null) {
848
+ toolCalls.set(call.toolCallId, call);
849
+ continue;
850
+ }
851
+ const typedValidationCall = validationToolCall(entry);
852
+ if (typedValidationCall !== null) {
853
+ toolCalls.set(typedValidationCall.toolCallId, typedValidationCall);
854
+ continue;
855
+ }
856
+ const dispatchCall = dispatchEvidenceCall(entry);
857
+ if (dispatchCall !== null) {
858
+ dispatchCalls.set(dispatchCall.toolCallId, dispatchCall);
859
+ continue;
860
+ }
861
+ const resultId = successfulToolResultId(entry);
862
+ if (resultId !== null) {
863
+ const dispatchCandidate = dispatchCalls.get(resultId);
864
+ const dispatchReceipt = dispatchReceiptEvidence(entry, dispatchCandidate);
865
+ if (dispatchReceipt !== null) {
866
+ pushEvidence(evidence, seen, dispatchReceipt);
867
+ continue;
868
+ }
869
+ const candidate = toolCalls.get(resultId);
870
+ if (candidate !== void 0) {
871
+ pushEvidence(evidence, seen, validationEvidence(candidate));
872
+ }
873
+ continue;
874
+ }
875
+ const bashExecution = bashExecutionEvidence(entry);
876
+ if (bashExecution !== null) pushEvidence(evidence, seen, bashExecution);
877
+ }
878
+ return evidence;
879
+ }
880
+ function recentEntries(entries, assistantTurnId, recentEntryLimit) {
881
+ const boundedLimit = Number.isFinite(recentEntryLimit) && recentEntryLimit > 0 ? Math.floor(recentEntryLimit) : DEFAULT_RECENT_ENTRY_LIMIT;
882
+ const assistantIndex = assistantTurnId === null ? -1 : entries.findIndex((entry) => turnIdOf(entry) === assistantTurnId);
883
+ const endExclusive = assistantIndex >= 0 ? assistantIndex : entries.length;
884
+ let startInclusive = Math.max(0, endExclusive - boundedLimit);
885
+ for (let index = endExclusive - 1; index >= startInclusive; index -= 1) {
886
+ if (isUserMessageEntry(entries[index])) {
887
+ startInclusive = index + 1;
888
+ break;
889
+ }
890
+ }
891
+ return entries.slice(startInclusive, endExclusive);
892
+ }
893
+ function bashValidationCall(entry) {
894
+ const record = asRecord(entry);
895
+ if (record?.kind !== "message" || record.role !== "tool_call") return null;
896
+ const payload = asRecord(record.payload);
897
+ if (payload === null) return null;
898
+ const toolName = stringFromFirst(payload, ["name", "toolName", "tool"]);
899
+ if (toolName !== "bash") return null;
900
+ const args = asRecord(payload.args ?? payload.arguments ?? payload.input);
901
+ const command = typeof args?.command === "string" ? args.command : null;
902
+ if (command === null) return null;
903
+ const detected = detectValidationCommand(command);
904
+ if (detected.kind !== "validation") return null;
905
+ const toolCallId = stringFromFirst(payload, ["toolCallId", "tool_call_id", "id"]) ?? turnIdOf(entry);
906
+ if (toolCallId === null) return null;
907
+ const candidate = {
908
+ toolCallId,
909
+ command: detected.matched
910
+ };
911
+ const turnId = turnIdOf(entry);
912
+ if (turnId !== null) candidate.turnId = turnId;
913
+ return candidate;
914
+ }
915
+ function dispatchEvidenceCall(entry) {
916
+ const record = asRecord(entry);
917
+ if (record?.kind !== "message" || record.role !== "tool_call") return null;
918
+ const payload = asRecord(record.payload);
919
+ if (payload === null) return null;
920
+ const toolName = stringFromFirst(payload, ["name", "toolName", "tool"]);
921
+ if (toolName !== "dispatch") return null;
922
+ const args = asRecord(payload.args ?? payload.arguments ?? payload.input);
923
+ const { agentId, task } = dispatchCallDescriptor(args);
924
+ const toolCallId = stringFromFirst(payload, ["toolCallId", "tool_call_id", "id"]) ?? turnIdOf(entry);
925
+ if (toolCallId === null) return null;
926
+ const candidate = {
927
+ toolCallId,
928
+ command: `agent=${agentId}${task.length > 0 ? ` task=${task}` : ""}`
929
+ };
930
+ const turnId = turnIdOf(entry);
931
+ if (turnId !== null) candidate.turnId = turnId;
932
+ return candidate;
933
+ }
934
+ function dispatchCallDescriptor(args) {
935
+ const defaultAgent = stringFromFirst(args ?? {}, ["agent", "agent_id", "agentId"]);
936
+ const tasks = Array.isArray(args?.tasks) ? args.tasks : null;
937
+ if (tasks !== null && tasks.length > 0) {
938
+ const first = tasks[0];
939
+ if (typeof first === "string") {
940
+ return { agentId: defaultAgent ?? "coder", task: first.trim() };
941
+ }
942
+ const firstRecord = asRecord(first);
943
+ const itemAgent = firstRecord === null ? null : stringFromFirst(firstRecord, ["agent", "agent_id", "agentId"]);
944
+ const itemTask = typeof firstRecord?.task === "string" ? firstRecord.task.trim() : "";
945
+ return { agentId: itemAgent ?? defaultAgent ?? "coder", task: itemTask };
946
+ }
947
+ const singleTask = typeof args?.task === "string" ? args.task.trim() : "";
948
+ return { agentId: defaultAgent ?? "coder", task: singleTask };
949
+ }
950
+ function validationToolCall(entry) {
951
+ const record = asRecord(entry);
952
+ if (record?.kind !== "message" || record.role !== "tool_call") return null;
953
+ const payload = asRecord(record.payload);
954
+ if (payload === null) return null;
955
+ const toolName = stringFromFirst(payload, ["name", "toolName", "tool"]);
956
+ if (toolName === null) return null;
957
+ const summary = typedValidationSummary(toolName, payload);
958
+ if (summary === null) return null;
959
+ const toolCallId = stringFromFirst(payload, ["toolCallId", "tool_call_id", "id"]) ?? turnIdOf(entry);
960
+ if (toolCallId === null) return null;
961
+ const candidate = {
962
+ toolCallId,
963
+ command: summary
964
+ };
965
+ const turnId = turnIdOf(entry);
966
+ if (turnId !== null) candidate.turnId = turnId;
967
+ return candidate;
968
+ }
969
+ function typedValidationSummary(toolName, payload) {
970
+ const args = asRecord(payload.args ?? payload.arguments ?? payload.input);
971
+ if (toolName === "verify") {
972
+ const check = typeof args?.check === "string" ? args.check.trim() : "";
973
+ if (check === "frontend") {
974
+ const path5 = typeof args?.path === "string" && args.path.trim().length > 0 ? args.path.trim() : "artifact";
975
+ return `verify frontend ${path5}`;
976
+ }
977
+ if (!isVerificationScriptName(check)) return null;
978
+ return `npm run ${check}`;
979
+ }
980
+ return null;
981
+ }
982
+ function successfulToolResultId(entry) {
983
+ const record = asRecord(entry);
984
+ if (record?.kind !== "message" || record.role !== "tool_result") return null;
985
+ const payload = asRecord(record.payload);
986
+ if (payload === null) return null;
987
+ if (payload.isError === true || payload.error === true) return null;
988
+ const result = asRecord(payload.result);
989
+ const details = asRecord(result?.details);
990
+ if (details?.kind === "error") return null;
991
+ return stringFromFirst(payload, ["toolCallId", "tool_call_id", "id"]);
992
+ }
993
+ function dispatchReceiptEvidence(entry, candidate) {
994
+ const record = asRecord(entry);
995
+ if (record?.kind !== "message" || record.role !== "tool_result") return null;
996
+ const payload = asRecord(record.payload);
997
+ if (payload === null) return null;
998
+ const toolName = stringFromFirst(payload, ["toolName", "name", "tool"]);
999
+ if (toolName !== null && toolName !== "dispatch") return null;
1000
+ const result = asRecord(payload.result);
1001
+ const details = asRecord(result?.details);
1002
+ if (details === null) return null;
1003
+ const summary = passedDispatchReceiptSummary(details, candidate);
1004
+ if (summary === null) return null;
1005
+ const evidence = {
1006
+ kind: "dispatch_receipt",
1007
+ summary
1008
+ };
1009
+ const turnId = candidate?.turnId ?? turnIdOf(entry);
1010
+ if (turnId !== null && turnId !== void 0) evidence.turnId = turnId;
1011
+ return evidence;
1012
+ }
1013
+ function passedDispatchReceiptSummary(details, candidate) {
1014
+ if (Array.isArray(details.runs)) {
1015
+ const runs = details.runs.map(asRecord);
1016
+ if (runs.length === 0 || runs.some((run) => run === null || run.exitCode !== 0)) return null;
1017
+ if (typeof details.failedCount === "number" && details.failedCount !== 0) return null;
1018
+ const first = runs[0];
1019
+ const runId = typeof first?.runId === "string" && first.runId.length > 0 ? first.runId : "unknown";
1020
+ const agentId = dispatchReceiptAgentId(typeof first?.agentId === "string" ? first.agentId : void 0, candidate);
1021
+ const extra = runs.length > 1 ? ` (+${runs.length - 1} more)` : "";
1022
+ return `dispatch receipt passed: run ${runId} agent ${agentId}${extra}`;
1023
+ }
1024
+ if (details.exitCode === 0) {
1025
+ const runId = typeof details.runId === "string" && details.runId.length > 0 ? details.runId : "unknown";
1026
+ const agentId = dispatchReceiptAgentId(typeof details.agentId === "string" ? details.agentId : void 0, candidate);
1027
+ return `dispatch receipt passed: run ${runId} agent ${agentId}`;
1028
+ }
1029
+ return null;
1030
+ }
1031
+ function dispatchReceiptAgentId(receiptAgentId, candidate) {
1032
+ if (receiptAgentId !== void 0 && receiptAgentId.length > 0) return receiptAgentId;
1033
+ return candidate?.command.match(/^agent=([^\s]+)/)?.[1] ?? "unknown";
1034
+ }
1035
+ function bashExecutionEvidence(entry) {
1036
+ const record = asRecord(entry);
1037
+ if (record?.kind !== "bashExecution") return null;
1038
+ if (typeof record.command !== "string") return null;
1039
+ if (record.cancelled === true) return null;
1040
+ if (record.exitCode !== 0) return null;
1041
+ const detected = detectValidationCommand(record.command);
1042
+ if (detected.kind !== "validation") return null;
1043
+ const evidence = {
1044
+ kind: "validation_command",
1045
+ summary: `validation command passed: ${detected.matched}`
1046
+ };
1047
+ const turnId = turnIdOf(entry);
1048
+ if (turnId !== null) evidence.turnId = turnId;
1049
+ return evidence;
1050
+ }
1051
+ function protectedArtifactEvidence(entry) {
1052
+ const record = asRecord(entry);
1053
+ if (record?.kind !== "protectedArtifact" || record.action !== "protect") return null;
1054
+ const artifact = asRecord(record.artifact);
1055
+ const path5 = typeof artifact?.path === "string" && artifact.path.trim().length > 0 ? artifact.path.trim() : null;
1056
+ if (path5 === null) return null;
1057
+ const evidence = {
1058
+ kind: "protected_artifact",
1059
+ summary: `protected artifact recorded: ${path5}`
1060
+ };
1061
+ const turnId = turnIdOf(entry);
1062
+ if (turnId !== null) evidence.turnId = turnId;
1063
+ return evidence;
1064
+ }
1065
+ function validationEvidence(candidate) {
1066
+ const evidence = {
1067
+ kind: "validation_command",
1068
+ summary: `validation command passed: ${candidate.command}`
1069
+ };
1070
+ if (candidate.turnId !== void 0) evidence.turnId = candidate.turnId;
1071
+ return evidence;
1072
+ }
1073
+ function pushEvidence(evidence, seen, item) {
1074
+ const key = `${item.kind}\0${item.summary}\0${item.turnId ?? ""}`;
1075
+ if (seen.has(key)) return;
1076
+ seen.add(key);
1077
+ evidence.push(item);
1078
+ }
1079
+ function isUserMessageEntry(entry) {
1080
+ const record = asRecord(entry);
1081
+ if (record?.kind !== "message" || record.role !== "user") return false;
1082
+ const payload = asRecord(record.payload);
1083
+ return payload?.synthetic !== true;
1084
+ }
1085
+ function turnIdOf(entry) {
1086
+ const record = asRecord(entry);
1087
+ return typeof record?.turnId === "string" ? record.turnId : null;
1088
+ }
1089
+ function stringFromFirst(record, keys) {
1090
+ for (const key of keys) {
1091
+ const value = record[key];
1092
+ if (typeof value === "string" && value.length > 0) return value;
1093
+ }
1094
+ return null;
1095
+ }
1096
+ function asRecord(value) {
1097
+ return value !== null && typeof value === "object" ? value : null;
1098
+ }
1099
+
1100
+ // src/domains/safety/autonomy.ts
1101
+ init_esm_shims();
1102
+ var AUTONOMY_LEVELS = ["read-only", "suggest", "auto-edit", "full-auto"];
1103
+ var DEFAULT_AUTONOMY_LEVEL = "auto-edit";
1104
+ function isAutonomyLevel(value) {
1105
+ return typeof value === "string" && AUTONOMY_LEVELS.includes(value);
1106
+ }
1107
+ var DEFAULT_AUTONOMY_EXPOSURE = "local";
1108
+ function mapAutonomy(level, actionClass, options = {}) {
1109
+ if (actionClass === "git_destructive") return "deny";
1110
+ if ((level === "auto-edit" || level === "suggest") && options.exposure === "outward") return "ask";
1111
+ if (actionClass === "read") return "allow";
1112
+ if (level === "read-only") return "deny";
1113
+ if (level === "suggest") return "ask";
1114
+ switch (actionClass) {
1115
+ case "write":
1116
+ return "allow";
1117
+ case "dispatch":
1118
+ if (options.dispatchPlanScale === true && level !== "full-auto") return "ask";
1119
+ return "allow";
1120
+ case "execute": {
1121
+ if (options.executeRecognized !== false) return "allow";
1122
+ return level === "full-auto" ? "allow" : "ask";
1123
+ }
1124
+ case "unknown":
1125
+ return "ask";
1126
+ default:
1127
+ return "ask";
1128
+ }
1129
+ }
1130
+ function autonomyDenyRejection(level, tool, actionClass) {
1131
+ return {
1132
+ short: `${tool} denied: autonomy level is ${level}`,
1133
+ detail: `Clio is at autonomy ${level}: ${actionClass} actions are denied without prompting. Describe the change you would make instead, so the operator can apply it or raise the autonomy level.`,
1134
+ hints: ["Propose the exact edit or command as text.", "The operator can change the level in /settings."]
1135
+ };
1136
+ }
1137
+ function autonomyAskRejection(level, tool, actionClass, exposure = DEFAULT_AUTONOMY_EXPOSURE) {
1138
+ if (exposure === "outward") {
1139
+ return {
1140
+ short: `${tool} needs approval: outward-facing gate at autonomy ${level}`,
1141
+ detail: `The call declared exposure=outward, so answering it publishes or sends something outside the workspace. Autonomy ${level} auto-answers local gates and parks outward-facing ones for the operator.`,
1142
+ hints: [
1143
+ "Approving resumes only this call.",
1144
+ "Autonomy full-auto answers outward gates too; the safety net still applies there."
1145
+ ]
1146
+ };
1147
+ }
1148
+ return {
1149
+ short: `${tool} needs approval (${actionClass}) at autonomy ${level}`,
1150
+ detail: `Autonomy ${level} routes ${actionClass} actions through operator approval. The call is parked until the operator approves it once or cancels it.`,
1151
+ hints: [
1152
+ "Approving resumes only this call.",
1153
+ "Recognized commands can be added to .clio-coder/safety.yaml.",
1154
+ // The sanctioned pivots for a gated shell command: typed verification
1155
+ // and read-class observe tools run without approval at this level,
1156
+ // and models otherwise stall retrying denied bash for checks or
1157
+ // directory listings.
1158
+ ...actionClass === "execute" ? [
1159
+ 'A declared package.json check runs without approval through the verify tool: verify(check="<script>").',
1160
+ "Read-only inspection runs without approval through the ls, read, grep, and find tools."
1161
+ ] : []
1162
+ ]
1163
+ };
1164
+ }
1165
+
1166
+ // src/domains/safety/path-policy.ts
1167
+ init_esm_shims();
1168
+ import { homedir } from "node:os";
1169
+ import path2 from "node:path";
1170
+ var ORDERED_KINDS = ["zeroAccessPaths", "readOnlyPaths", "noWritePaths", "noDeletePaths"];
1171
+ function isSameOrDescendant(candidatePath, policyPath) {
1172
+ const candidate = path2.resolve(candidatePath);
1173
+ const policy = path2.resolve(policyPath);
1174
+ const relative = path2.relative(policy, candidate);
1175
+ return relative === "" || !relative.startsWith("..") && !path2.isAbsolute(relative);
1176
+ }
1177
+ function expandTilde(rawPath) {
1178
+ if (rawPath === "~") return homedir();
1179
+ if (rawPath.startsWith("~/")) return path2.join(homedir(), rawPath.slice(2));
1180
+ return rawPath;
1181
+ }
1182
+ function normalizePolicyEntry(rawPath, root) {
1183
+ const expanded = expandTilde(rawPath.trim());
1184
+ const resolved = path2.isAbsolute(expanded) ? path2.resolve(expanded) : path2.resolve(root, expanded);
1185
+ return canonicalizeExistingPath(resolved);
1186
+ }
1187
+ function blocksOperation(kind, operation) {
1188
+ if (kind === "zeroAccessPaths") return true;
1189
+ if (kind === "readOnlyPaths" || kind === "noWritePaths") return operation === "write" || operation === "delete";
1190
+ return operation === "delete";
1191
+ }
1192
+ function compilePathPolicy(input, root = process.cwd()) {
1193
+ const resolvedRoot = canonicalizeExistingPath(path2.resolve(root));
1194
+ const entries = [];
1195
+ const diagnostics = [];
1196
+ for (const kind of ORDERED_KINDS) {
1197
+ for (const rawPath of input[kind] ?? []) {
1198
+ const trimmed = rawPath.trim();
1199
+ if (trimmed.length === 0) {
1200
+ diagnostics.push(`${kind}: path must not be empty`);
1201
+ continue;
1202
+ }
1203
+ const entry = { kind, path: normalizePolicyEntry(trimmed, resolvedRoot), source: trimmed };
1204
+ const pattern = compilePathPattern(trimmed, resolvedRoot);
1205
+ if (pattern !== null) entry.pattern = pattern;
1206
+ entries.push(entry);
1207
+ }
1208
+ }
1209
+ return {
1210
+ root: resolvedRoot,
1211
+ entries: entries.sort((a, b) => a.kind.localeCompare(b.kind) || a.path.localeCompare(b.path)),
1212
+ diagnostics
1213
+ };
1214
+ }
1215
+ function evaluatePathPolicy(policy, operation, targetPath, cwd = policy.root) {
1216
+ const expanded = expandTilde(targetPath);
1217
+ const lexicalTarget = path2.isAbsolute(expanded) ? path2.resolve(expanded) : path2.resolve(cwd, expanded);
1218
+ const resolvedTarget = canonicalizeExistingPath(lexicalTarget);
1219
+ const normalizedResolvedTarget = normalizeSeparators(resolvedTarget);
1220
+ const normalizedRelativeTarget = normalizeSeparators(path2.relative(policy.root, resolvedTarget));
1221
+ const normalizedRawTarget = normalizeSeparators(targetPath);
1222
+ for (const entry of policy.entries) {
1223
+ if (!blocksOperation(entry.kind, operation)) continue;
1224
+ if (!matchesEntry(entry, resolvedTarget, normalizedResolvedTarget, normalizedRelativeTarget, normalizedRawTarget))
1225
+ continue;
1226
+ return {
1227
+ kind: "block",
1228
+ reasonCode: `path-policy:${entry.kind}`,
1229
+ reason: `${operation} denied by ${entry.kind} entry ${entry.source}`,
1230
+ matchedPath: entry.path,
1231
+ policyKind: entry.kind
1232
+ };
1233
+ }
1234
+ return { kind: "allow" };
1235
+ }
1236
+ function matchesEntry(entry, resolvedTarget, normalizedResolvedTarget, normalizedRelativeTarget, normalizedRawTarget) {
1237
+ if (isSameOrDescendant(resolvedTarget, entry.path)) return true;
1238
+ if (entry.pattern === void 0) return false;
1239
+ return entry.pattern.test(normalizedResolvedTarget) || entry.pattern.test(normalizedRelativeTarget) || entry.pattern.test(normalizedRawTarget);
1240
+ }
1241
+ function compilePathPattern(rawPath, root) {
1242
+ if (!rawPath.includes("*")) return null;
1243
+ const expanded = expandTilde(rawPath.trim());
1244
+ const source = path2.isAbsolute(expanded) ? expanded : path2.join(root, expanded);
1245
+ const normalized = normalizeSeparators(canonicalizePatternSource(source));
1246
+ const relative = normalizeSeparators(path2.isAbsolute(expanded) ? expanded : expanded);
1247
+ const absolutePattern = segmentPattern(normalized);
1248
+ const relativePattern = segmentPattern(relative);
1249
+ return new RegExp(`(?:${absolutePattern})|(?:${relativePattern})`);
1250
+ }
1251
+ function canonicalizePatternSource(patternPath) {
1252
+ const wildcardIndex = patternPath.indexOf("*");
1253
+ if (wildcardIndex === -1) return canonicalizeExistingPath(patternPath);
1254
+ const fixedPrefix = patternPath.slice(0, wildcardIndex);
1255
+ const separatorIndex = fixedPrefix.lastIndexOf(path2.sep);
1256
+ if (separatorIndex === -1) return patternPath;
1257
+ const prefix = patternPath.slice(0, separatorIndex + 1);
1258
+ const suffix = patternPath.slice(separatorIndex + 1);
1259
+ return path2.join(canonicalizeExistingPath(prefix), suffix);
1260
+ }
1261
+ function segmentPattern(rawPath) {
1262
+ const escaped = rawPath.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*");
1263
+ return `(?:^|/)${escaped}(?:$|/)`;
1264
+ }
1265
+ function normalizeSeparators(value) {
1266
+ return value.split(path2.sep).join("/");
1267
+ }
1268
+
1269
+ // src/domains/safety/policy-engine.ts
1270
+ init_esm_shims();
1271
+ import { createHash as createHash3 } from "node:crypto";
1272
+ import path4 from "node:path";
1273
+
1274
+ // src/domains/safety/default-path-policy.ts
1275
+ init_esm_shims();
1276
+ var DEFAULT_DAMAGE_CONTROL_PATH_POLICY = {
1277
+ zeroAccessPaths: [
1278
+ ".env",
1279
+ ".env.local",
1280
+ ".env.development",
1281
+ ".env.production",
1282
+ ".env.staging",
1283
+ ".env.test",
1284
+ ".env.*.local",
1285
+ "*.env",
1286
+ "~/.ssh/",
1287
+ "~/.gnupg/",
1288
+ "~/.aws/",
1289
+ "~/.config/gcloud/",
1290
+ "*-credentials.json",
1291
+ "*serviceAccount*.json",
1292
+ "*service-account*.json",
1293
+ "~/.azure/",
1294
+ "~/.kube/",
1295
+ "kubeconfig",
1296
+ "*-secret.yaml",
1297
+ "secrets.yaml",
1298
+ "~/.docker/",
1299
+ "*.pem",
1300
+ "*.key",
1301
+ "*.p12",
1302
+ "*.pfx",
1303
+ "*.tfstate",
1304
+ "*.tfstate.backup",
1305
+ ".terraform/",
1306
+ ".vercel/",
1307
+ ".netlify/",
1308
+ "firebase-adminsdk*.json",
1309
+ "serviceAccountKey.json",
1310
+ ".supabase/",
1311
+ "~/.netrc",
1312
+ "~/.npmrc",
1313
+ "~/.pypirc",
1314
+ "~/.git-credentials",
1315
+ ".git-credentials",
1316
+ // Remote URLs in the repo git config can embed access tokens, and .git
1317
+ // is hidden from grep/find but not from direct reads without this entry.
1318
+ ".git/config",
1319
+ // Clio's own provider secret store. The literal covers a repo-level file
1320
+ // of the same name on purpose; the expanded clioConfigDir() form is
1321
+ // appended at policy construction (policy-engine.ts), since this static
1322
+ // list cannot call config helpers.
1323
+ "credentials.yaml",
1324
+ "dump.sql",
1325
+ "backup.sql",
1326
+ "*.dump"
1327
+ ],
1328
+ readOnlyPaths: [
1329
+ "/etc/",
1330
+ "/usr/",
1331
+ "/bin/",
1332
+ "/sbin/",
1333
+ "/boot/",
1334
+ "/root/",
1335
+ "~/.bash_history",
1336
+ "~/.zsh_history",
1337
+ "~/.node_repl_history",
1338
+ "~/.bashrc",
1339
+ "~/.zshrc",
1340
+ "~/.profile",
1341
+ "~/.bash_profile",
1342
+ "*.min.js",
1343
+ "*.min.css",
1344
+ "*.bundle.js",
1345
+ "*.chunk.js",
1346
+ "dist/",
1347
+ "build/",
1348
+ ".next/",
1349
+ ".nuxt/",
1350
+ ".output/",
1351
+ "node_modules/",
1352
+ "__pycache__/",
1353
+ ".venv/",
1354
+ "venv/",
1355
+ "target/"
1356
+ ],
1357
+ // Another agent's own configuration. Clio reads these roots for skills,
1358
+ // prompts, and rule prose and has no reason to author them, so the guard is
1359
+ // on writes and not only on deletes.
1360
+ noWritePaths: [...foreignAgentDirs()],
1361
+ noDeletePaths: [
1362
+ "~/.claude/",
1363
+ "CLAUDE.md",
1364
+ "LICENSE",
1365
+ "LICENSE.*",
1366
+ "COPYING",
1367
+ "COPYING.*",
1368
+ "NOTICE",
1369
+ "PATENTS",
1370
+ "README.md",
1371
+ "README.*"
1372
+ ]
1373
+ };
1374
+ function mergePathPolicyInputs(base, override) {
1375
+ return {
1376
+ zeroAccessPaths: [...base.zeroAccessPaths ?? [], ...override.zeroAccessPaths ?? []],
1377
+ readOnlyPaths: [...base.readOnlyPaths ?? [], ...override.readOnlyPaths ?? []],
1378
+ noWritePaths: [...base.noWritePaths ?? [], ...override.noWritePaths ?? []],
1379
+ noDeletePaths: [...base.noDeletePaths ?? [], ...override.noDeletePaths ?? []]
1380
+ };
1381
+ }
1382
+
1383
+ // src/domains/safety/project-policy.ts
1384
+ init_esm_shims();
1385
+ var import_yaml = __toESM(require_dist(), 1);
1386
+ import { createHash as createHash2 } from "node:crypto";
1387
+ import { existsSync, readFileSync } from "node:fs";
1388
+ import path3 from "node:path";
1389
+ var POLICY_RELATIVE_PATH = path3.join(".clio-coder", "safety.yaml");
1390
+ var ACTION_CLASSES = /* @__PURE__ */ new Set([
1391
+ "read",
1392
+ "write",
1393
+ "execute",
1394
+ "dispatch",
1395
+ "system_modify",
1396
+ "git_destructive",
1397
+ "unknown"
1398
+ ]);
1399
+ var PATH_POLICY_KEYS = ["zeroAccessPaths", "readOnlyPaths", "noWritePaths", "noDeletePaths"];
1400
+ var ROOT_KEYS = /* @__PURE__ */ new Set(["version", "commands", "tasks", "disableDefaultPathPolicy", ...PATH_POLICY_KEYS]);
1401
+ var COMMAND_KEYS = /* @__PURE__ */ new Set([
1402
+ "id",
1403
+ "command",
1404
+ "cwd",
1405
+ "timeoutMs",
1406
+ "maxOutputBytes",
1407
+ "actionClass",
1408
+ "shellOperators",
1409
+ "env",
1410
+ "requireConfirmation",
1411
+ "rationale",
1412
+ "owner",
1413
+ "comment"
1414
+ ]);
1415
+ var ENV_KEYS = /* @__PURE__ */ new Set(["mode", "allow"]);
1416
+ var ID_RE = /^[a-zA-Z0-9][a-zA-Z0-9_.:-]*$/;
1417
+ function projectSafetyPolicyPath(cwd = process.cwd()) {
1418
+ let cursor = path3.resolve(cwd);
1419
+ while (true) {
1420
+ const candidate = path3.join(cursor, POLICY_RELATIVE_PATH);
1421
+ if (existsSync(candidate)) return candidate;
1422
+ const parent = path3.dirname(cursor);
1423
+ if (parent === cursor) return null;
1424
+ cursor = parent;
1425
+ }
1426
+ }
1427
+ function loadProjectSafetyPolicy(cwd = process.cwd()) {
1428
+ const policyPath = projectSafetyPolicyPath(cwd);
1429
+ if (policyPath === null) {
1430
+ return {
1431
+ path: null,
1432
+ hash: null,
1433
+ valid: true,
1434
+ errors: [],
1435
+ commands: [],
1436
+ pathPolicy: {},
1437
+ disableDefaultPathPolicy: false
1438
+ };
1439
+ }
1440
+ let raw;
1441
+ try {
1442
+ raw = readFileSync(policyPath, "utf8");
1443
+ } catch (err) {
1444
+ return {
1445
+ path: policyPath,
1446
+ hash: null,
1447
+ valid: false,
1448
+ errors: [`cannot read project safety policy: ${err instanceof Error ? err.message : String(err)}`],
1449
+ commands: [],
1450
+ pathPolicy: {},
1451
+ disableDefaultPathPolicy: false
1452
+ };
1453
+ }
1454
+ const hash = sha256(raw);
1455
+ try {
1456
+ const parsed = (0, import_yaml.parse)(raw);
1457
+ return validateProjectSafetyPolicy(parsed, policyPath, hash);
1458
+ } catch (err) {
1459
+ return {
1460
+ path: policyPath,
1461
+ hash,
1462
+ valid: false,
1463
+ errors: [`cannot parse project safety policy: ${err instanceof Error ? err.message : String(err)}`],
1464
+ commands: [],
1465
+ pathPolicy: {},
1466
+ disableDefaultPathPolicy: false
1467
+ };
1468
+ }
1469
+ }
1470
+ function validateProjectSafetyPolicy(value, policyPath, hash) {
1471
+ const errors = [];
1472
+ const commands = [];
1473
+ if (!isPlainRecord(value)) {
1474
+ return {
1475
+ path: policyPath,
1476
+ hash,
1477
+ valid: false,
1478
+ errors: ["policy root must be a mapping"],
1479
+ commands: [],
1480
+ pathPolicy: {},
1481
+ disableDefaultPathPolicy: false
1482
+ };
1483
+ }
1484
+ for (const key of Object.keys(value)) {
1485
+ if (!ROOT_KEYS.has(key)) errors.push(`unknown root key '${key}'`);
1486
+ }
1487
+ if (value.version !== 1) errors.push("version must be 1");
1488
+ appendCommandPolicies(commands, errors, value.commands, "commands");
1489
+ appendCommandPolicies(commands, errors, value.tasks, "tasks");
1490
+ const pathPolicy = parsePathPolicy(value, errors);
1491
+ const disableDefaultPathPolicy = value.disableDefaultPathPolicy === void 0 ? false : booleanField(value, "disableDefaultPathPolicy", "policy", errors);
1492
+ const ids = /* @__PURE__ */ new Set();
1493
+ for (const command of commands) {
1494
+ if (ids.has(command.id)) errors.push(`duplicate command id '${command.id}'`);
1495
+ ids.add(command.id);
1496
+ }
1497
+ return {
1498
+ path: policyPath,
1499
+ hash,
1500
+ valid: errors.length === 0,
1501
+ errors,
1502
+ commands: errors.length === 0 ? commands : [],
1503
+ pathPolicy: errors.length === 0 ? pathPolicy : {},
1504
+ disableDefaultPathPolicy: errors.length === 0 ? disableDefaultPathPolicy : false
1505
+ };
1506
+ }
1507
+ function parsePathPolicy(value, errors) {
1508
+ const out = {};
1509
+ for (const key of PATH_POLICY_KEYS) {
1510
+ const parsed = parsePathList(value[key], key, errors);
1511
+ if (parsed !== void 0) out[key] = parsed;
1512
+ }
1513
+ return out;
1514
+ }
1515
+ function parsePathList(value, key, errors) {
1516
+ if (value === void 0) return void 0;
1517
+ if (!Array.isArray(value)) {
1518
+ errors.push(`${key} must be an array`);
1519
+ return void 0;
1520
+ }
1521
+ const out = [];
1522
+ for (let index = 0; index < value.length; index += 1) {
1523
+ const item = value[index];
1524
+ const label = `${key}[${index}]`;
1525
+ if (typeof item !== "string" || item.trim().length === 0) {
1526
+ errors.push(`${label} must be a non-empty string`);
1527
+ continue;
1528
+ }
1529
+ const trimmed = item.trim();
1530
+ if (path3.isAbsolute(trimmed)) {
1531
+ errors.push(`${label} must be relative to the policy root`);
1532
+ continue;
1533
+ }
1534
+ const normalized = path3.normalize(trimmed);
1535
+ const segments = normalized.split(path3.sep).filter((segment) => segment.length > 0);
1536
+ if (segments.some((segment) => segment === "..")) {
1537
+ errors.push(`${label} must not escape the policy root with '..'`);
1538
+ continue;
1539
+ }
1540
+ out.push(trimmed);
1541
+ }
1542
+ return out;
1543
+ }
1544
+ function appendCommandPolicies(out, errors, value, field) {
1545
+ if (value === void 0) return;
1546
+ if (!Array.isArray(value)) {
1547
+ errors.push(`${field} must be an array`);
1548
+ return;
1549
+ }
1550
+ for (let index = 0; index < value.length; index += 1) {
1551
+ const parsed = parseCommandPolicy(value[index], `${field}[${index}]`);
1552
+ if (parsed.errors.length > 0) {
1553
+ errors.push(...parsed.errors);
1554
+ continue;
1555
+ }
1556
+ if (parsed.command !== void 0) out.push(parsed.command);
1557
+ }
1558
+ }
1559
+ function parseCommandPolicy(value, label) {
1560
+ const errors = [];
1561
+ if (!isPlainRecord(value)) return { errors: [`${label} must be a mapping`] };
1562
+ for (const key of Object.keys(value)) {
1563
+ if (!COMMAND_KEYS.has(key)) errors.push(`${label}: unknown key '${key}'`);
1564
+ }
1565
+ const id = stringField(value, "id", label, errors);
1566
+ const command = stringField(value, "command", label, errors);
1567
+ const cwd = optionalStringField(value, "cwd", label, errors);
1568
+ const timeoutMs = optionalPositiveInt(value, "timeoutMs", label, errors);
1569
+ const maxOutputBytes = optionalPositiveInt(value, "maxOutputBytes", label, errors);
1570
+ const actionClass = parseActionClass(value.actionClass, label, errors);
1571
+ const shellOperators = parseShellOperators(value.shellOperators, label, errors);
1572
+ const env = parseEnvPolicy(value.env, label, errors);
1573
+ const requireConfirmation = value.requireConfirmation === void 0 ? false : booleanField(value, "requireConfirmation", label, errors);
1574
+ const rationale = optionalStringField(value, "rationale", label, errors);
1575
+ const owner = optionalStringField(value, "owner", label, errors);
1576
+ const comment = optionalStringField(value, "comment", label, errors);
1577
+ if (id && !ID_RE.test(id)) errors.push(`${label}.id must match ${ID_RE}`);
1578
+ if (command && command.trim().length === 0) errors.push(`${label}.command must not be empty`);
1579
+ if (cwd !== void 0) {
1580
+ if (path3.isAbsolute(cwd)) {
1581
+ errors.push(`${label}.cwd must be relative to the policy root`);
1582
+ } else {
1583
+ const normalized = path3.normalize(cwd);
1584
+ const segments = normalized.split(path3.sep).filter((segment) => segment.length > 0);
1585
+ if (segments.some((segment) => segment === "..")) {
1586
+ errors.push(`${label}.cwd must not escape the policy root with '..'`);
1587
+ }
1588
+ }
1589
+ }
1590
+ if (errors.length > 0 || id === void 0 || command === void 0 || actionClass === void 0 || env === void 0) {
1591
+ return { errors };
1592
+ }
1593
+ const parsed = {
1594
+ id,
1595
+ command,
1596
+ actionClass,
1597
+ shellOperators,
1598
+ env,
1599
+ requireConfirmation
1600
+ };
1601
+ if (cwd !== void 0) parsed.cwd = cwd;
1602
+ if (timeoutMs !== void 0) parsed.timeoutMs = timeoutMs;
1603
+ if (maxOutputBytes !== void 0) parsed.maxOutputBytes = maxOutputBytes;
1604
+ if (rationale !== void 0) parsed.rationale = rationale;
1605
+ if (owner !== void 0) parsed.owner = owner;
1606
+ if (comment !== void 0) parsed.comment = comment;
1607
+ return { command: parsed, errors: [] };
1608
+ }
1609
+ function parseActionClass(value, label, errors) {
1610
+ if (typeof value !== "string") {
1611
+ errors.push(`${label}.actionClass must be a string`);
1612
+ return void 0;
1613
+ }
1614
+ if (!ACTION_CLASSES.has(value)) {
1615
+ errors.push(`${label}.actionClass is not supported: ${value}`);
1616
+ return void 0;
1617
+ }
1618
+ return value;
1619
+ }
1620
+ function parseShellOperators(value, label, errors) {
1621
+ if (value === void 0) return "deny";
1622
+ if (value === "deny" || value === "allow") return value;
1623
+ errors.push(`${label}.shellOperators must be 'deny' or 'allow'`);
1624
+ return "deny";
1625
+ }
1626
+ function parseEnvPolicy(value, label, errors) {
1627
+ if (value === void 0) return { mode: "none", allow: [] };
1628
+ if (!isPlainRecord(value)) {
1629
+ errors.push(`${label}.env must be a mapping`);
1630
+ return void 0;
1631
+ }
1632
+ for (const key of Object.keys(value)) {
1633
+ if (!ENV_KEYS.has(key)) errors.push(`${label}.env: unknown key '${key}'`);
1634
+ }
1635
+ const mode = value.mode;
1636
+ if (mode !== "none" && mode !== "allowlist") {
1637
+ errors.push(`${label}.env.mode must be 'none' or 'allowlist'`);
1638
+ return void 0;
1639
+ }
1640
+ const allow = value.allow;
1641
+ if (allow === void 0) return { mode, allow: [] };
1642
+ if (!Array.isArray(allow) || allow.some((entry) => typeof entry !== "string" || entry.length === 0)) {
1643
+ errors.push(`${label}.env.allow must be an array of non-empty strings`);
1644
+ return void 0;
1645
+ }
1646
+ if (mode === "none" && allow.length > 0) {
1647
+ errors.push(`${label}.env.allow requires env.mode='allowlist'`);
1648
+ return void 0;
1649
+ }
1650
+ return { mode, allow: [...allow] };
1651
+ }
1652
+ function stringField(record, key, label, errors) {
1653
+ const value = record[key];
1654
+ if (typeof value !== "string" || value.length === 0) {
1655
+ errors.push(`${label}.${key} must be a non-empty string`);
1656
+ return void 0;
1657
+ }
1658
+ return value;
1659
+ }
1660
+ function optionalStringField(record, key, label, errors) {
1661
+ const value = record[key];
1662
+ if (value === void 0) return void 0;
1663
+ if (typeof value !== "string" || value.length === 0) {
1664
+ errors.push(`${label}.${key} must be a non-empty string when present`);
1665
+ return void 0;
1666
+ }
1667
+ return value;
1668
+ }
1669
+ function optionalPositiveInt(record, key, label, errors) {
1670
+ const value = record[key];
1671
+ if (value === void 0) return void 0;
1672
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
1673
+ errors.push(`${label}.${key} must be a positive integer`);
1674
+ return void 0;
1675
+ }
1676
+ return value;
1677
+ }
1678
+ function booleanField(record, key, label, errors) {
1679
+ const value = record[key];
1680
+ if (typeof value === "boolean") return value;
1681
+ errors.push(`${label}.${key} must be a boolean`);
1682
+ return false;
1683
+ }
1684
+ function isPlainRecord(value) {
1685
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1686
+ }
1687
+ function sha256(raw) {
1688
+ return createHash2("sha256").update(raw, "utf8").digest("hex");
1689
+ }
1690
+
1691
+ // src/domains/safety/rejection-feedback.ts
1692
+ init_esm_shims();
1693
+ var HARD_BLOCK_CLASSES = ["git_destructive"];
1694
+ function buildShort(ctx) {
1695
+ const postureSuffix = ctx.posture ? ` in ${ctx.posture}` : "";
1696
+ return `${ctx.tool} blocked: ${ctx.actionClass}${postureSuffix}`;
1697
+ }
1698
+ function buildDetail(ctx) {
1699
+ const lines = [`Clio refused to run ${ctx.tool}.`];
1700
+ for (const reason of ctx.reasons) {
1701
+ lines.push(`- ${reason}`);
1702
+ }
1703
+ if (ctx.ruleId) {
1704
+ lines.push(`rule: ${ctx.ruleId}`);
1705
+ }
1706
+ return lines.join("\n");
1707
+ }
1708
+ function buildHints(ctx) {
1709
+ const hints = [];
1710
+ const hardBlock = HARD_BLOCK_CLASSES.includes(ctx.actionClass) || ctx.ruleId !== void 0;
1711
+ if (hardBlock) {
1712
+ hints.push("This is a hard block; confirmation cannot override it.");
1713
+ return hints;
1714
+ }
1715
+ if (ctx.actionClass === "system_modify") {
1716
+ hints.push("Operator confirmation is required for this action.");
1717
+ }
1718
+ return hints;
1719
+ }
1720
+ function formatRejection(ctx) {
1721
+ return {
1722
+ short: buildShort(ctx),
1723
+ detail: buildDetail(ctx),
1724
+ hints: buildHints(ctx)
1725
+ };
1726
+ }
1727
+ var MODEL_REJECTION_PIVOT = "Do not retry this action through another tool unless a hint above names one; pivot or report the blocker.";
1728
+ var MODEL_REJECTION_MAX_LINES = 16;
1729
+ var MODEL_REJECTION_MAX_LINE_CHARS = 300;
1730
+ function formatModelRejection(reason, rejection) {
1731
+ const lines = [];
1732
+ const seen = /* @__PURE__ */ new Set();
1733
+ const push = (line) => {
1734
+ const bounded = line.length > MODEL_REJECTION_MAX_LINE_CHARS ? `${line.slice(0, MODEL_REJECTION_MAX_LINE_CHARS)}\u2026` : line;
1735
+ const trimmed = bounded.trim();
1736
+ if (trimmed.length === 0) return;
1737
+ const normalized = trimmed.replace(/^-\s+/, "");
1738
+ if (seen.has(normalized) || normalized === MODEL_REJECTION_PIVOT) return;
1739
+ seen.add(normalized);
1740
+ lines.push(trimmed);
1741
+ };
1742
+ for (const line of reason.split("\n")) push(line);
1743
+ if (rejection) {
1744
+ for (const line of rejection.detail.split("\n")) push(line);
1745
+ for (const hint of rejection.hints) push(hint);
1746
+ }
1747
+ const bodyBudget = MODEL_REJECTION_MAX_LINES - 1;
1748
+ const body = lines.length > bodyBudget ? [...lines.slice(0, bodyBudget - 1), "(further detail elided)"] : lines;
1749
+ return [...body, MODEL_REJECTION_PIVOT].join("\n");
1750
+ }
1751
+
1752
+ // src/domains/safety/rule-pack-loader.ts
1753
+ init_esm_shims();
1754
+ var import_yaml2 = __toESM(require_dist(), 1);
1755
+ import { readFileSync as readFileSync2 } from "node:fs";
1756
+ import { join } from "node:path";
1757
+
1758
+ // src/domains/safety/rule-compiler.ts
1759
+ init_esm_shims();
1760
+ function asString(value, ruleId, field) {
1761
+ if (typeof value !== "string" || value.length === 0) {
1762
+ throw new Error(`damage-control rule '${ruleId}': expected string for ${field}`);
1763
+ }
1764
+ return value;
1765
+ }
1766
+ function compileDamageControlRule(raw, index) {
1767
+ if (typeof raw.id !== "string" || raw.id.length === 0) {
1768
+ throw new Error(`damage-control rule at index ${index}: missing or non-string 'id'`);
1769
+ }
1770
+ const id = raw.id;
1771
+ const description = asString(raw.description, id, "description");
1772
+ const patternString = asString(raw.pattern, id, "pattern");
1773
+ const klass = asString(raw.class, id, "class");
1774
+ if (typeof raw.block !== "boolean") {
1775
+ throw new Error(`damage-control rule '${id}': expected boolean for block`);
1776
+ }
1777
+ if (raw.ask !== void 0 && typeof raw.ask !== "boolean") {
1778
+ throw new Error(`damage-control rule '${id}': expected boolean for ask`);
1779
+ }
1780
+ let pattern;
1781
+ try {
1782
+ pattern = new RegExp(patternString, "i");
1783
+ } catch (err) {
1784
+ const msg = err instanceof Error ? err.message : String(err);
1785
+ throw new Error(`damage-control rule '${id}': invalid pattern: ${msg}`);
1786
+ }
1787
+ return {
1788
+ id,
1789
+ description,
1790
+ pattern,
1791
+ class: klass,
1792
+ block: raw.block,
1793
+ ...raw.ask !== void 0 ? { ask: raw.ask } : {}
1794
+ };
1795
+ }
1796
+
1797
+ // src/domains/safety/rule-pack-loader.ts
1798
+ function compilePackRules(rawRules, packId) {
1799
+ if (rawRules === void 0 || rawRules === null) return [];
1800
+ if (!Array.isArray(rawRules)) {
1801
+ throw new Error(`damage-control pack '${packId}': expected array at 'rules'`);
1802
+ }
1803
+ return rawRules.map((rule, index) => compileDamageControlRule(rule, index));
1804
+ }
1805
+ function emptyRuleset(version) {
1806
+ return { version, rules: [] };
1807
+ }
1808
+ function loadRulePacks(yamlPath) {
1809
+ const raw = readFileSync2(yamlPath, "utf8");
1810
+ const parsed = (0, import_yaml2.parse)(raw);
1811
+ if (!parsed || typeof parsed !== "object") {
1812
+ throw new Error(`damage-control rules at ${yamlPath}: expected mapping at document root`);
1813
+ }
1814
+ const version = typeof parsed.version === "number" ? parsed.version : 0;
1815
+ if (version === 1) {
1816
+ const baseRules = compilePackRules(parsed.rules, "base");
1817
+ return {
1818
+ base: { version: 1, rules: baseRules }
1819
+ };
1820
+ }
1821
+ if (version !== 2) {
1822
+ throw new Error(`damage-control rules at ${yamlPath}: unsupported version ${String(parsed.version)}`);
1823
+ }
1824
+ if (!Array.isArray(parsed.packs)) {
1825
+ throw new Error(`damage-control rules at ${yamlPath}: expected array at 'packs'`);
1826
+ }
1827
+ const out = {
1828
+ base: emptyRuleset(2)
1829
+ };
1830
+ for (const rawPack of parsed.packs) {
1831
+ const packId = rawPack.id;
1832
+ if (packId !== "base") {
1833
+ throw new Error(`damage-control rules at ${yamlPath}: unknown pack id ${String(packId)}`);
1834
+ }
1835
+ const rules = compilePackRules(rawPack.rules, packId);
1836
+ out[packId] = { version: 2, rules };
1837
+ }
1838
+ return out;
1839
+ }
1840
+ function loadDefaultRulePacks() {
1841
+ return loadRulePacks(join(resolvePackageRoot(), "damage-control-rules.yaml"));
1842
+ }
1843
+ var cachedPacks = null;
1844
+ function getCachedDefaultRulePacks() {
1845
+ if (cachedPacks === null) cachedPacks = loadDefaultRulePacks();
1846
+ return cachedPacks;
1847
+ }
1848
+
1849
+ // src/domains/safety/policy-engine.ts
1850
+ var BUILTIN_ALLOWLIST = [
1851
+ { id: "builtin:pwd", re: /^pwd$/ },
1852
+ { id: "builtin:ls", re: /^ls(?:\s+(-[A-Za-z0-9]+|\.[/\w.-]*|[/\w.-]+))*$/ },
1853
+ { id: "builtin:git-status", re: /^git\s+status(?:\s+--short|\s+--branch|\s+-sb)*$/ },
1854
+ { id: "builtin:git-diff", re: /^git\s+diff(?:\s+--cached|\s+--stat|\s+--name-only|\s+--\s+[\w./-]+)*$/ },
1855
+ { id: "builtin:git-log", re: /^git\s+log\s+--oneline(?:\s+-n\s+[1-9]\d{0,2})?(?:\s+--\s+[\w./-]+)?$/ },
1856
+ { id: "builtin:npm-test", re: /^npm\s+(?:test|run\s+test)(?:\s+--\s+[\w=./:-]+(?:\s+[\w=./:-]+)*)?$/ },
1857
+ { id: "builtin:npm-lint", re: /^npm\s+run\s+lint(?:\s+--\s+[\w=./:-]+(?:\s+[\w=./:-]+)*)?$/ },
1858
+ { id: "builtin:npm-build", re: /^npm\s+run\s+build(?:\s+--\s+[\w=./:-]+(?:\s+[\w=./:-]+)*)?$/ },
1859
+ { id: "builtin:npm-typecheck", re: /^npm\s+run\s+typecheck(?:\s+--\s+[\w=./:-]+(?:\s+[\w=./:-]+)*)?$/ },
1860
+ { id: "builtin:npm-ci-script", re: /^npm\s+run\s+ci(?:\s+--\s+[\w=./:-]+(?:\s+[\w=./:-]+)*)?$/ },
1861
+ { id: "builtin:pytest", re: /^pytest(?:\s+[\w=./:-]+)*$/ },
1862
+ { id: "builtin:python-pytest", re: /^python(?:3(?:\.\d+)?)?\s+-m\s+pytest(?:\s+[\w=./:-]+)*$/ },
1863
+ { id: "builtin:cargo-test", re: /^cargo\s+test(?:\s+[\w=./:-]+)*$/ },
1864
+ { id: "builtin:go-test", re: /^go\s+test(?:\s+[\w=./:-]+)*$/ },
1865
+ { id: "builtin:make-test", re: /^make\s+test(?:\s+[\w=./:-]+)*$/ }
1866
+ ];
1867
+ var BASH_RECOGNIZED_FORM_HINT = "Recognized forms run without asking: one command per bash call, or a && chain whose every step is recognized, with cwd passed as the cwd argument instead of a leading cd.";
1868
+ var EXECUTION_TOOLS = /* @__PURE__ */ new Set([ToolNames.Bash, ToolNames.Verify]);
1869
+ var WRITE_ROOT_REFUSED_TOOLS = /* @__PURE__ */ new Set([
1870
+ ToolNames.Bash,
1871
+ ToolNames.Verify,
1872
+ ToolNames.Dispatch
1873
+ ]);
1874
+ var WRITE_ROOT_TOOLS = /* @__PURE__ */ new Set([ToolNames.Write, ToolNames.Edit, ToolNames.Artifact]);
1875
+ function writeRootTargetPath(call) {
1876
+ if (!WRITE_ROOT_TOOLS.has(call.tool)) return null;
1877
+ const target = pathArg(call.args);
1878
+ if (target !== null) return target;
1879
+ if (call.tool === ToolNames.Artifact) return artifactDefaultPath(call.args?.kind);
1880
+ return null;
1881
+ }
1882
+ function isWriteConfinementEscape(call, actionClass) {
1883
+ if (WRITE_ROOT_REFUSED_TOOLS.has(call.tool)) return true;
1884
+ return actionClass === "execute" || actionClass === "dispatch";
1885
+ }
1886
+ function evaluateWriteRoots(roots, writeRootCwd, call) {
1887
+ if (roots.length === 0) return null;
1888
+ const target = writeRootTargetPath(call);
1889
+ if (target === null) return null;
1890
+ const resolved = path4.resolve(writeRootCwd, target);
1891
+ for (const root of roots) {
1892
+ if (resolved === root || resolved.startsWith(`${root}${path4.sep}`)) return null;
1893
+ }
1894
+ return `write target '${target}' resolves to '${resolved}', which is outside the permitted write roots for this run: ${roots.join(", ")}`;
1895
+ }
1896
+ function createSafetyPolicyEngine(options = {}) {
1897
+ const cwd = canonicalizeExistingPath(path4.resolve(options.cwd ?? process.cwd()));
1898
+ const writeRootCwd = path4.resolve(options.cwd ?? process.cwd());
1899
+ const writeRoots = (options.writeRoots ?? []).map((root) => path4.resolve(root));
1900
+ const packs = options.rulePacks ?? getCachedDefaultRulePacks();
1901
+ const projectPolicy = options.projectPolicy ?? loadProjectSafetyPolicy(cwd);
1902
+ const projectPolicyRoot = projectPolicy.path === null ? cwd : path4.dirname(path4.dirname(canonicalizeExistingPath(projectPolicy.path)));
1903
+ const pathPolicyInput = projectPolicy.disableDefaultPathPolicy ? projectPolicy.pathPolicy : mergePathPolicyInputs(DEFAULT_DAMAGE_CONTROL_PATH_POLICY, projectPolicy.pathPolicy);
1904
+ const expandedDefaults = projectPolicy.disableDefaultPathPolicy ? pathPolicyInput : mergePathPolicyInputs(pathPolicyInput, { zeroAccessPaths: clioCredentialStorePaths() });
1905
+ const pathPolicy = compilePathPolicy(expandedDefaults, projectPolicyRoot);
1906
+ const zeroAccessPolicy = {
1907
+ root: pathPolicy.root,
1908
+ entries: pathPolicy.entries.filter((entry) => entry.kind === "zeroAccessPaths"),
1909
+ diagnostics: []
1910
+ };
1911
+ const sourcedRules = packs.base.rules.map((rule) => ({ rule, source: "damage-control:base" }));
1912
+ return {
1913
+ evaluate(call, posture) {
1914
+ const rawClassification = classify(call);
1915
+ const command = commandArg(call.args);
1916
+ const callCwd = cwdArg(call.args, cwd);
1917
+ const scan = damageControlScan(call);
1918
+ const hit = scan ? matchSourcedRule(scan, sourcedRules) : null;
1919
+ const classification = effectiveClassification(rawClassification, hit?.match);
1920
+ const base = baseDecision(call, classification, callCwd, posture, command);
1921
+ const writeRootReason = evaluateWriteRoots(writeRoots, writeRootCwd, call);
1922
+ if (writeRootReason !== null) {
1923
+ return blockDecision(base, {
1924
+ ruleId: "write-root",
1925
+ reasonCode: "write-root",
1926
+ reasons: [writeRootReason],
1927
+ policySource: "builtin-classifier"
1928
+ });
1929
+ }
1930
+ if (writeRoots.length > 0 && isWriteConfinementEscape(call, classification.actionClass)) {
1931
+ return blockDecision(base, {
1932
+ ruleId: "write-root",
1933
+ reasonCode: "write-root",
1934
+ reasons: [
1935
+ `tool '${call.tool}' (${classification.actionClass}) can mutate the filesystem outside the permitted write roots and is blocked under write-root confinement`
1936
+ ],
1937
+ policySource: "builtin-classifier"
1938
+ });
1939
+ }
1940
+ const askRule = hit?.match.ask === true && hit.match.block !== true;
1941
+ if (!askRule && (classification.actionClass === "git_destructive" || hit?.match.actionClass === "git_destructive" || hit?.match.block === true)) {
1942
+ const blockInput = {
1943
+ reasonCode: hit ? `damage-control:${hit.match.ruleId}` : "classification:git_destructive",
1944
+ reasons: [...classification.reasons, ...hit ? [hit.match.reason] : []],
1945
+ policySource: hit?.source ?? "damage-control:base"
1946
+ };
1947
+ if (hit?.match.ruleId !== void 0) blockInput.ruleId = hit.match.ruleId;
1948
+ if (hit?.match !== void 0) blockInput.match = hit.match;
1949
+ return blockDecision(base, blockInput);
1950
+ }
1951
+ if (!projectPolicy.valid && EXECUTION_TOOLS.has(call.tool)) {
1952
+ const blockInput = {
1953
+ ruleId: "project-policy-invalid",
1954
+ reasonCode: "project-policy-invalid",
1955
+ reasons: [`project safety policy is invalid and execution fails closed: ${projectPolicy.errors.join("; ")}`],
1956
+ policySource: "project-policy-invalid"
1957
+ };
1958
+ if (projectPolicy.hash !== null) blockInput.policyHash = projectPolicy.hash;
1959
+ if (projectPolicy.path !== null) blockInput.projectPolicyPath = projectPolicy.path;
1960
+ return blockDecision(base, blockInput);
1961
+ }
1962
+ const pathBlock = evaluateProjectPathPolicy(pathPolicy, call, callCwd);
1963
+ if (pathBlock !== null) {
1964
+ const blockInput = {
1965
+ ruleId: pathBlock.reasonCode,
1966
+ reasonCode: pathBlock.reasonCode,
1967
+ reasons: [pathBlock.reason],
1968
+ policySource: "project-policy"
1969
+ };
1970
+ if (projectPolicy.hash !== null) blockInput.policyHash = projectPolicy.hash;
1971
+ if (projectPolicy.path !== null) blockInput.projectPolicyPath = projectPolicy.path;
1972
+ return blockDecision(base, blockInput);
1973
+ }
1974
+ if (call.tool === ToolNames.Bash && command !== null) {
1975
+ const secretRead = evaluateBashZeroAccessRead(zeroAccessPolicy, command, callCwd);
1976
+ if (secretRead !== null) {
1977
+ const blockInput = {
1978
+ ruleId: "secret_path_bash",
1979
+ reasonCode: "secret_path_bash",
1980
+ reasons: [
1981
+ `bash read of zero-access path blocked: '${secretRead.token}' matches ${secretRead.entrySource}. Check presence with exit codes only (grep -sq "^NAME=" <file>); have the user supply values through their own terminal (read -s), never through chat or command output.`
1982
+ ],
1983
+ policySource: "project-policy"
1984
+ };
1985
+ if (projectPolicy.hash !== null) blockInput.policyHash = projectPolicy.hash;
1986
+ if (projectPolicy.path !== null) blockInput.projectPolicyPath = projectPolicy.path;
1987
+ return blockDecision(base, blockInput);
1988
+ }
1989
+ }
1990
+ if (askRule && hit !== null && posture !== "confirmed") {
1991
+ return askDecision(base, {
1992
+ ruleId: hit.match.ruleId,
1993
+ reasonCode: `damage-control:${hit.match.ruleId}`,
1994
+ reasons: [...classification.reasons, hit.match.reason, "damage-control rule requires confirmation"],
1995
+ policySource: hit.source,
1996
+ match: hit.match
1997
+ });
1998
+ }
1999
+ if (askRule && hit !== null && posture === "confirmed") {
2000
+ return allowDecision(base, {
2001
+ ruleId: hit.match.ruleId,
2002
+ reasonCode: `damage-control:${hit.match.ruleId}`,
2003
+ reasons: [...classification.reasons, hit.match.reason, "damage-control rule confirmed by operator"],
2004
+ policySource: hit.source,
2005
+ match: hit.match
2006
+ });
2007
+ }
2008
+ if (classification.actionClass === "system_modify") {
2009
+ const input = {
2010
+ ruleId: "system-modify-confirm",
2011
+ reasonCode: "system-modify-confirm",
2012
+ reasons: [...classification.reasons, "system-level changes require one-shot confirmation at every autonomy level"],
2013
+ policySource: "builtin-classifier"
2014
+ };
2015
+ return posture === "confirmed" ? allowDecision(base, input) : askDecision(base, input);
2016
+ }
2017
+ if (call.tool === ToolNames.Bash && classification.actionClass === "execute") {
2018
+ const bash = evaluateBashPolicy(command ?? "", callCwd, cwd, posture, projectPolicy);
2019
+ if (bash.kind === "block") return blockDecision(base, bash);
2020
+ if (bash.kind === "ask") return askDecision(base, bash);
2021
+ return allowDecision(base, bash);
2022
+ }
2023
+ const allowInput = {
2024
+ reasonCode: "allowed",
2025
+ reasons: classification.reasons,
2026
+ policySource: hit?.source ?? "none"
2027
+ };
2028
+ if (hit?.match.ruleId !== void 0) allowInput.ruleId = hit.match.ruleId;
2029
+ if (hit?.match !== void 0) allowInput.match = hit.match;
2030
+ if (classification.actionClass === "execute") allowInput.execRecognition = "recognized";
2031
+ return allowDecision(base, allowInput);
2032
+ },
2033
+ metadata() {
2034
+ return {
2035
+ version: 1,
2036
+ rulePackHash: rulePackHash(packs),
2037
+ rulePackVersion: packs.base.version,
2038
+ activeRuleIds: sourcedRules.map((entry) => entry.rule.id),
2039
+ projectPolicyPath: projectPolicy.path,
2040
+ projectPolicyHash: projectPolicy.hash,
2041
+ projectPolicyValid: projectPolicy.valid,
2042
+ projectPolicyErrors: [...projectPolicy.errors, ...pathPolicy.diagnostics],
2043
+ cwd
2044
+ };
2045
+ }
2046
+ };
2047
+ }
2048
+ function clioCredentialStorePaths() {
2049
+ try {
2050
+ return [path4.join(clioConfigDir(), "credentials.yaml")];
2051
+ } catch {
2052
+ return [];
2053
+ }
2054
+ }
2055
+ var SAFE_PRESENCE_RE = /^\s*grep\s+(?:-(?:q|sq|qs)\s+)('[^']*'|"[^"]*"|\S+)\s+\S+\s*$/;
2056
+ function isSafePresenceCheck(command) {
2057
+ const match = SAFE_PRESENCE_RE.exec(command);
2058
+ if (!match || match[1] === void 0) return false;
2059
+ const pattern = stripQuotes(match[1]);
2060
+ return /^\^[A-Za-z_][A-Za-z0-9_]*=/.test(pattern);
2061
+ }
2062
+ function stripQuotes(token) {
2063
+ if (token.length >= 2) {
2064
+ const first = token[0];
2065
+ const last = token[token.length - 1];
2066
+ if (first === '"' && last === '"' || first === "'" && last === "'") return token.slice(1, -1);
2067
+ }
2068
+ return token;
2069
+ }
2070
+ function bashPathTokenCandidates(command) {
2071
+ const candidates = [];
2072
+ for (const token of tokenizeShellLike(command)) {
2073
+ if (token.length === 0) continue;
2074
+ if (token === ";" || token === "&&" || token === "||" || token === "|" || token === ">" || token === ">>") continue;
2075
+ if (token === "<" || token === "<<") continue;
2076
+ if (/\s/.test(token)) continue;
2077
+ if (token.startsWith("-") && !token.includes("/") && !token.includes("=")) continue;
2078
+ candidates.push(token);
2079
+ const eq = token.indexOf("=");
2080
+ if (eq > 0 && eq < token.length - 1) candidates.push(token.slice(eq + 1));
2081
+ }
2082
+ return candidates;
2083
+ }
2084
+ function evaluateBashZeroAccessRead(zeroAccessPolicy, command, callCwd) {
2085
+ if (zeroAccessPolicy.entries.length === 0) return null;
2086
+ if (isSafePresenceCheck(command)) return null;
2087
+ for (const token of bashPathTokenCandidates(command)) {
2088
+ const decision = evaluatePathPolicy(zeroAccessPolicy, "read", token, callCwd);
2089
+ if (decision.kind === "block") {
2090
+ return { token, entrySource: `zeroAccessPaths entry ${decision.matchedPath}` };
2091
+ }
2092
+ }
2093
+ return null;
2094
+ }
2095
+ function evaluateProjectPathPolicy(policy, call, callCwd) {
2096
+ if (policy.entries.length === 0) return null;
2097
+ for (const target of pathPolicyTargets(call)) {
2098
+ const decision = evaluatePathPolicy(policy, target.operation, target.path, callCwd);
2099
+ if (decision.kind === "block") return decision;
2100
+ }
2101
+ return null;
2102
+ }
2103
+ function pathPolicyTargets(call) {
2104
+ const args = call.args;
2105
+ switch (call.tool) {
2106
+ case ToolNames.Read:
2107
+ case ToolNames.Ls:
2108
+ case ToolNames.Grep:
2109
+ case ToolNames.Find: {
2110
+ const target = pathArg(args) ?? ".";
2111
+ return [{ operation: "read", path: target }];
2112
+ }
2113
+ case ToolNames.Write:
2114
+ case ToolNames.Edit: {
2115
+ const target = pathArg(args);
2116
+ return target === null ? [] : [{ operation: "write", path: target }];
2117
+ }
2118
+ case ToolNames.Artifact:
2119
+ return [{ operation: "write", path: pathArg(args) ?? artifactDefaultPath(args?.kind) }];
2120
+ case ToolNames.CredentialPresent:
2121
+ return [];
2122
+ case ToolNames.Bash: {
2123
+ const command = commandArg(args);
2124
+ if (command === null) return [];
2125
+ return [
2126
+ ...extractCommandWriteTargets(command).map((target) => ({ operation: "write", path: target })),
2127
+ ...extractCommandDeleteTargets(command).map((target) => ({ operation: "delete", path: target }))
2128
+ ];
2129
+ }
2130
+ default:
2131
+ return [];
2132
+ }
2133
+ }
2134
+ function evaluateBashPolicy(command, callCwd, workspaceRoot, posture, policy) {
2135
+ if (command.trim().length === 0) {
2136
+ return {
2137
+ kind: "block",
2138
+ ruleId: "bash-empty-command",
2139
+ reasonCode: "bash-empty-command",
2140
+ reasons: ["bash command must not be empty"],
2141
+ policySource: "builtin-command-allowlist"
2142
+ };
2143
+ }
2144
+ const projectMatch = matchingProjectCommand(policy, command, callCwd);
2145
+ if (projectMatch) {
2146
+ const base = {
2147
+ ruleId: projectMatch.id,
2148
+ reasonCode: `project-policy:${projectMatch.id}`,
2149
+ reasons: [`allowed by project safety policy command '${projectMatch.id}'`],
2150
+ policySource: "project-policy",
2151
+ execRecognition: "recognized"
2152
+ };
2153
+ if (policy.hash !== null) base.policyHash = policy.hash;
2154
+ if (policy.path !== null) base.projectPolicyPath = policy.path;
2155
+ if (projectMatch.requireConfirmation && posture !== "confirmed") {
2156
+ return {
2157
+ ...base,
2158
+ kind: "ask",
2159
+ reasons: [...base.reasons, "project policy requires confirmation"]
2160
+ };
2161
+ }
2162
+ return { ...base, kind: "allow" };
2163
+ }
2164
+ if (!isUnderOrSame(callCwd, workspaceRoot)) {
2165
+ return {
2166
+ kind: "block",
2167
+ ruleId: "bash-cwd-escape",
2168
+ reasonCode: "bash-cwd-escape",
2169
+ reasons: [
2170
+ `bash cwd '${callCwd}' escapes workspace root '${workspaceRoot}'; use a typed tool or a project policy entry with explicit cwd`
2171
+ ],
2172
+ policySource: "builtin-command-allowlist"
2173
+ };
2174
+ }
2175
+ const recognitionCommand = inlineShellScript(command) ?? command;
2176
+ if (hasCommandSubstitution(recognitionCommand) && posture !== "confirmed") {
2177
+ return {
2178
+ kind: "ask",
2179
+ ruleId: "bash-command-substitution",
2180
+ reasonCode: "bash-command-substitution",
2181
+ reasons: ["command substitution hides the executed content from the safety net and requires one-shot confirmation"],
2182
+ policySource: "builtin-command-allowlist",
2183
+ execRecognition: "unrecognized"
2184
+ };
2185
+ }
2186
+ const chain = recognizeCommandChain(recognitionCommand, callCwd, workspaceRoot, policy);
2187
+ if (chain !== null) {
2188
+ const chainReasons = [`every step of the && chain is recognized: ${chain.ruleIds.join(", ")}`];
2189
+ if (chain.requiresConfirmation && posture !== "confirmed") {
2190
+ return {
2191
+ kind: "ask",
2192
+ ruleId: "bash-recognized-chain",
2193
+ reasonCode: "bash-recognized-chain",
2194
+ reasons: [...chainReasons, "project policy requires confirmation for one step"],
2195
+ policySource: "builtin-command-allowlist",
2196
+ execRecognition: "recognized"
2197
+ };
2198
+ }
2199
+ return {
2200
+ kind: "allow",
2201
+ ruleId: "bash-recognized-chain",
2202
+ reasonCode: "bash-recognized-chain",
2203
+ reasons: chainReasons,
2204
+ policySource: "builtin-command-allowlist",
2205
+ execRecognition: "recognized"
2206
+ };
2207
+ }
2208
+ if (hasSequencingOperators(command)) {
2209
+ return {
2210
+ kind: "allow",
2211
+ ruleId: "bash-shell-operators",
2212
+ reasonCode: "bash-shell-operators",
2213
+ reasons: [
2214
+ "shell operators defeat per-command recognition; the autonomy level decides admission",
2215
+ BASH_RECOGNIZED_FORM_HINT
2216
+ ],
2217
+ policySource: "builtin-command-allowlist",
2218
+ execRecognition: "unrecognized"
2219
+ };
2220
+ }
2221
+ for (const entry of BUILTIN_ALLOWLIST) {
2222
+ if (entry.re.test(recognitionCommand)) {
2223
+ return {
2224
+ kind: "allow",
2225
+ ruleId: entry.id,
2226
+ reasonCode: entry.id,
2227
+ reasons: [`matched built-in no-prompt command allowlist '${entry.id}'`],
2228
+ policySource: "builtin-command-allowlist",
2229
+ execRecognition: "recognized"
2230
+ };
2231
+ }
2232
+ }
2233
+ return {
2234
+ kind: "allow",
2235
+ ruleId: "bash-unrecognized",
2236
+ reasonCode: "bash-unrecognized",
2237
+ reasons: [
2238
+ "bash command is outside the no-prompt set; the autonomy level decides admission",
2239
+ BASH_RECOGNIZED_FORM_HINT
2240
+ ],
2241
+ policySource: "builtin-command-allowlist",
2242
+ execRecognition: "unrecognized"
2243
+ };
2244
+ }
2245
+ function baseDecision(call, classification, cwd, posture, command) {
2246
+ const out = {
2247
+ classification,
2248
+ tool: call.tool,
2249
+ actionClass: classification.actionClass,
2250
+ cwd
2251
+ };
2252
+ if (posture !== void 0) out.posture = posture;
2253
+ if (command !== null) out.command = command;
2254
+ return out;
2255
+ }
2256
+ function allowDecision(base, input) {
2257
+ return { ...base, ...input, kind: "allow" };
2258
+ }
2259
+ function askDecision(base, input) {
2260
+ const rejectionInput = {
2261
+ tool: base.tool,
2262
+ actionClass: base.classification.actionClass,
2263
+ reasons: input.reasons
2264
+ };
2265
+ if (base.posture !== void 0) rejectionInput.posture = base.posture;
2266
+ if (input.ruleId !== void 0) rejectionInput.ruleId = input.ruleId;
2267
+ const rejection = formatRejection(rejectionInput);
2268
+ return { ...base, ...input, kind: "ask", rejection };
2269
+ }
2270
+ function blockDecision(base, input) {
2271
+ const rejectionInput = {
2272
+ tool: base.tool,
2273
+ actionClass: base.classification.actionClass,
2274
+ reasons: input.reasons
2275
+ };
2276
+ if (base.posture !== void 0) rejectionInput.posture = base.posture;
2277
+ if (input.ruleId !== void 0) rejectionInput.ruleId = input.ruleId;
2278
+ const rejection = formatRejection(rejectionInput);
2279
+ return { ...base, ...input, kind: "block", rejection };
2280
+ }
2281
+ function matchSourcedRule(commandString, rules) {
2282
+ for (const entry of rules) {
2283
+ if (entry.rule.pattern.test(commandString)) {
2284
+ const match = {
2285
+ ruleId: entry.rule.id,
2286
+ reason: `matched ${entry.rule.id}: ${entry.rule.description}`,
2287
+ actionClass: entry.rule.class,
2288
+ block: entry.rule.block
2289
+ };
2290
+ if (entry.rule.ask !== void 0) match.ask = entry.rule.ask;
2291
+ return { match, source: entry.source };
2292
+ }
2293
+ }
2294
+ return null;
2295
+ }
2296
+ var ACTION_CLASSES2 = /* @__PURE__ */ new Set([
2297
+ "read",
2298
+ "write",
2299
+ "execute",
2300
+ "system_modify",
2301
+ "git_destructive",
2302
+ "dispatch"
2303
+ ]);
2304
+ function effectiveClassification(classification, match) {
2305
+ if (!match || !ACTION_CLASSES2.has(match.actionClass)) return classification;
2306
+ const actionClass = match.actionClass;
2307
+ if (actionClass === classification.actionClass) return classification;
2308
+ return {
2309
+ actionClass,
2310
+ reasons: [...classification.reasons, `damage-control:${match.ruleId}`]
2311
+ };
2312
+ }
2313
+ function matchingProjectCommand(policy, command, cwd) {
2314
+ if (!policy.valid || policy.path === null) return null;
2315
+ const policyRoot = path4.dirname(path4.dirname(policy.path));
2316
+ for (const entry of policy.commands) {
2317
+ if (entry.command !== command) continue;
2318
+ if (entry.shellOperators === "deny" && hasShellOperators(command)) continue;
2319
+ const allowedCwd = entry.cwd !== void 0 ? path4.resolve(policyRoot, entry.cwd) : policyRoot;
2320
+ if (!isUnderOrSame(cwd, allowedCwd)) continue;
2321
+ return entry;
2322
+ }
2323
+ return null;
2324
+ }
2325
+ var CHAIN_BREAKING_TOKENS = /* @__PURE__ */ new Set([";", "|", "||", ">", ">>", "<", "<<"]);
2326
+ var CHAIN_MAX_SEGMENTS = 6;
2327
+ function recognizeCommandChain(command, callCwd, workspaceRoot, policy) {
2328
+ const segments = [];
2329
+ let current = [];
2330
+ for (const token of tokenizeShellLike(command)) {
2331
+ if (token === "&&") {
2332
+ segments.push(current);
2333
+ current = [];
2334
+ continue;
2335
+ }
2336
+ if (CHAIN_BREAKING_TOKENS.has(token)) return null;
2337
+ current.push(token);
2338
+ }
2339
+ segments.push(current);
2340
+ if (segments.length < 2 || segments.length > CHAIN_MAX_SEGMENTS) return null;
2341
+ const ruleIds = [];
2342
+ let requiresConfirmation = false;
2343
+ for (const segment of segments) {
2344
+ if (segment.length === 0) return null;
2345
+ if (segment[0] === "cd") {
2346
+ if (segment.length !== 2) return null;
2347
+ const target = segment[1] ?? "";
2348
+ if (target.startsWith("~") || !isUnderOrSame(path4.resolve(callCwd, target), workspaceRoot)) return null;
2349
+ ruleIds.push("builtin:cd-workspace");
2350
+ continue;
2351
+ }
2352
+ const rendered = segment.join(" ");
2353
+ const projectMatch = matchingProjectCommand(policy, rendered, callCwd);
2354
+ if (projectMatch) {
2355
+ ruleIds.push(projectMatch.id);
2356
+ if (projectMatch.requireConfirmation) requiresConfirmation = true;
2357
+ continue;
2358
+ }
2359
+ const builtin = BUILTIN_ALLOWLIST.find((entry) => entry.re.test(rendered));
2360
+ if (builtin === void 0) return null;
2361
+ ruleIds.push(builtin.id);
2362
+ }
2363
+ return { ruleIds, requiresConfirmation };
2364
+ }
2365
+ function hasSequencingOperators(command) {
2366
+ return /(\|\||&&|;|\||>>?|<|\n|\r)/.test(command);
2367
+ }
2368
+ function hasCommandSubstitution(command) {
2369
+ return /(`|\$\()/.test(command);
2370
+ }
2371
+ function hasShellOperators(command) {
2372
+ return hasSequencingOperators(command) || hasCommandSubstitution(command);
2373
+ }
2374
+ function isUnderOrSame(child, parent) {
2375
+ const rel = path4.relative(
2376
+ canonicalizeExistingPath(path4.resolve(parent)),
2377
+ canonicalizeExistingPath(path4.resolve(child))
2378
+ );
2379
+ return rel === "" || !rel.startsWith("..") && !path4.isAbsolute(rel);
2380
+ }
2381
+ function commandArg(args) {
2382
+ return typeof args?.command === "string" ? args.command : null;
2383
+ }
2384
+ function pathArg(args) {
2385
+ if (!args) return null;
2386
+ const candidate = args.path ?? args.file_path ?? args.filePath;
2387
+ return typeof candidate === "string" && candidate.length > 0 ? candidate : null;
2388
+ }
2389
+ function cwdArg(args, fallback) {
2390
+ const resolved = typeof args?.cwd === "string" && args.cwd.length > 0 ? path4.resolve(fallback, args.cwd) : fallback;
2391
+ return canonicalizeExistingPath(resolved);
2392
+ }
2393
+ var CONTENT_BEARING_TOOLS = /* @__PURE__ */ new Set([ToolNames.Write, ToolNames.Edit, ToolNames.Artifact]);
2394
+ function damageControlScan(call) {
2395
+ if (!CONTENT_BEARING_TOOLS.has(call.tool)) return serializeArgs(call.args);
2396
+ const pathArg2 = call.args?.path;
2397
+ return typeof pathArg2 === "string" ? pathArg2 : "";
2398
+ }
2399
+ function serializeArgs(args) {
2400
+ if (!args) return "";
2401
+ const parts = [];
2402
+ for (const v of Object.values(args)) {
2403
+ if (v == null) continue;
2404
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") parts.push(String(v));
2405
+ else {
2406
+ try {
2407
+ parts.push(JSON.stringify(v));
2408
+ } catch {
2409
+ }
2410
+ }
2411
+ }
2412
+ return parts.join(" ");
2413
+ }
2414
+ function rulePackHash(packs) {
2415
+ const payload = {
2416
+ base: packPayload(packs.base.rules)
2417
+ };
2418
+ return createHash3("sha256").update(JSON.stringify(payload), "utf8").digest("hex");
2419
+ }
2420
+ function packPayload(rules) {
2421
+ return rules.map((rule) => ({
2422
+ id: rule.id,
2423
+ description: rule.description,
2424
+ pattern: rule.pattern.source,
2425
+ class: rule.class,
2426
+ block: rule.block,
2427
+ ...rule.ask !== void 0 ? { ask: rule.ask } : {}
2428
+ }));
2429
+ }
2430
+
2431
+ export {
2432
+ parseJsonObjectPayload,
2433
+ resultContractSourceId,
2434
+ parseVerifierResult,
2435
+ resultContractAuthorship,
2436
+ parseScoutResult,
2437
+ validateResultContract,
2438
+ validateRecipeResult,
2439
+ RESULT_CONTRACT_REPAIR_LIMIT,
2440
+ resultContractRepairMessages,
2441
+ parseResultContract,
2442
+ AUTONOMY_LEVELS,
2443
+ DEFAULT_AUTONOMY_LEVEL,
2444
+ isAutonomyLevel,
2445
+ DEFAULT_AUTONOMY_EXPOSURE,
2446
+ mapAutonomy,
2447
+ autonomyDenyRejection,
2448
+ autonomyAskRejection,
2449
+ DEFAULT_RECENT_ENTRY_LIMIT,
2450
+ assessFinishContract,
2451
+ typedValidationSummary,
2452
+ formatModelRejection,
2453
+ WRITE_ROOT_REFUSED_TOOLS,
2454
+ createSafetyPolicyEngine
2455
+ };
2456
+ //# sourceMappingURL=chunk-STBPMHSX.js.map