@iowarp/clio-coder 0.3.4 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (376) hide show
  1. package/CHANGELOG.md +65 -2
  2. package/CONTRIBUTING.md +6 -6
  3. package/README.md +16 -5
  4. package/dist/{acp-S5R4RR5B.js → acp-SK4MD6MM.js} +11 -11
  5. package/dist/{agents-P6DMMVZY.js → agents-2FN2K6ME.js} +33 -26
  6. package/dist/assets/codewiki.json +1 -1
  7. package/dist/{auth-2XCZLPKS.js → auth-QIYZWM5I.js} +15 -15
  8. package/dist/{chunk-EKMEHE4H.js → chunk-33YXPOE3.js} +2 -3
  9. package/dist/{chunk-VAWWTKDP.js → chunk-3HAPLH5M.js} +11 -11
  10. package/dist/{chunk-YCWGATWI.js → chunk-465YSENW.js} +3 -3
  11. package/dist/{chunk-4OC57DA6.js → chunk-4DGYLA73.js} +53 -2
  12. package/dist/{chunk-UZHIZC5S.js → chunk-4DWFMQDR.js} +61 -76
  13. package/dist/{chunk-ZWMF7253.js → chunk-5C3AQNDW.js} +328 -9
  14. package/dist/{chunk-22NAGB7X.js → chunk-5C77SEEY.js} +5 -94
  15. package/dist/{chunk-WPQLXFOZ.js → chunk-5FR74PWO.js} +3 -2
  16. package/dist/{chunk-35MKKU5R.js → chunk-5UJ6ECTS.js} +18 -10
  17. package/dist/{chunk-BRXQQJFP.js → chunk-6M7VS3J3.js} +571 -50
  18. package/dist/{chunk-QQK64KLB.js → chunk-6TUKSZVF.js} +141 -23
  19. package/dist/{chunk-N4CZJQRK.js → chunk-AB4XIIVB.js} +8 -6
  20. package/dist/{chunk-KRPY7NTG.js → chunk-BMWK7ZIZ.js} +14 -20
  21. package/dist/{chunk-4BPJXDWC.js → chunk-C4JBQ5SR.js} +30 -14
  22. package/dist/{chunk-ZYKPLLNQ.js → chunk-CEYBNUGC.js} +821 -83
  23. package/dist/{chunk-VEZEGCGW.js → chunk-D4MDIG46.js} +20 -18
  24. package/dist/chunk-DJNLUABN.js +843 -0
  25. package/dist/{chunk-BP4OYD6A.js → chunk-DMD2AGVS.js} +21 -2
  26. package/dist/{chunk-KOHPCX4K.js → chunk-DOOEX22V.js} +2 -2
  27. package/dist/chunk-DQA7QLMD.js +123 -0
  28. package/dist/chunk-DR52UMZW.js +21 -0
  29. package/dist/{chunk-3HZ5RWN2.js → chunk-EBEFWSGL.js} +9 -7
  30. package/dist/{chunk-EDRHSCIE.js → chunk-EELBMBT6.js} +128 -13
  31. package/dist/{chunk-HV5X7OR2.js → chunk-EOOQZZDE.js} +16 -14
  32. package/dist/{chunk-WR67VIZY.js → chunk-FOT2FX5J.js} +63 -5
  33. package/dist/{chunk-BPGS2WCQ.js → chunk-GEYXPTRF.js} +2 -1
  34. package/dist/{chunk-FYYLNIL5.js → chunk-GH5622CP.js} +2 -2
  35. package/dist/{chunk-BEY543CS.js → chunk-GOXNB3AO.js} +5 -2
  36. package/dist/chunk-GWS3VEIW.js +195 -0
  37. package/dist/{chunk-G4BMMOKF.js → chunk-HVDIIIQW.js} +2 -2
  38. package/dist/chunk-HWUFFB6L.js +83 -0
  39. package/dist/{chunk-X6COSD2O.js → chunk-J7PIKKWC.js} +8 -436
  40. package/dist/{chunk-NILBFAPG.js → chunk-JNXPYBB4.js} +2 -2
  41. package/dist/{chunk-4VP4KH3K.js → chunk-JRIO5UD2.js} +4 -4
  42. package/dist/{chunk-K6WL7QZT.js → chunk-JTSEDYVQ.js} +7 -7
  43. package/dist/{chunk-QKMUKYO7.js → chunk-KCMKRQX4.js} +236 -84
  44. package/dist/chunk-KZ2H5X4G.js +1026 -0
  45. package/dist/{chunk-A2GZF7DC.js → chunk-LADCF22A.js} +13 -13
  46. package/dist/chunk-LCGCVYZ4.js +57 -0
  47. package/dist/chunk-M4AKACEO.js +382 -0
  48. package/dist/{chunk-POHLU5DW.js → chunk-M6L6IDJG.js} +3 -3
  49. package/dist/{chunk-4JUF2NNX.js → chunk-MXI6J5JF.js} +7 -7
  50. package/dist/{chunk-X4RCMKVQ.js → chunk-NDINPTJ4.js} +2 -2
  51. package/dist/{chunk-TTNYS3EA.js → chunk-OB5HIGJY.js} +1 -1
  52. package/dist/{chunk-7RXG6QRZ.js → chunk-OBMAI2DP.js} +61 -840
  53. package/dist/{chunk-5M54SPOL.js → chunk-ODFEOB4F.js} +161 -5
  54. package/dist/chunk-PD3MESLB.js +242 -0
  55. package/dist/{chunk-ED4KHGC3.js → chunk-PPAMZ32Z.js} +9 -2
  56. package/dist/{chunk-VMNQ6OZA.js → chunk-QCTRSGHQ.js} +963 -786
  57. package/dist/chunk-RVG5JXAL.js +41 -0
  58. package/dist/{chunk-RD5U66HV.js → chunk-SROCI7ZU.js} +7 -7
  59. package/dist/{chunk-MFFY33HR.js → chunk-THKY7CD7.js} +466 -205
  60. package/dist/{chunk-34475P3I.js → chunk-TSHXZTOQ.js} +5 -4
  61. package/dist/{chunk-PCZJO5TI.js → chunk-UFQ3F4FW.js} +13 -178
  62. package/dist/{chunk-AD2SYQYC.js → chunk-UHXRNZ2J.js} +121 -3
  63. package/dist/chunk-UND3GU2L.js +103 -0
  64. package/dist/{chunk-QQL5RT5M.js → chunk-UUANF5CR.js} +2323 -2114
  65. package/dist/{chunk-VJWL6YS5.js → chunk-UUVG37B4.js} +2 -2
  66. package/dist/chunk-UVDSQ6LW.js +472 -0
  67. package/dist/{chunk-QWU7ZBO7.js → chunk-VQNODYQ4.js} +215 -56
  68. package/dist/chunk-VREKEFLL.js +37 -0
  69. package/dist/{chunk-2TZWSW76.js → chunk-WHGPSPT5.js} +2 -2
  70. package/dist/{chunk-TW3WDMVS.js → chunk-WHJYKASB.js} +2 -2
  71. package/dist/{chunk-MEQ45TQ4.js → chunk-WJHBC77E.js} +21 -7
  72. package/dist/{chunk-HXG4IURW.js → chunk-X2KV5FXT.js} +2 -2
  73. package/dist/{chunk-YHZX5GEU.js → chunk-XAKHZX5N.js} +2 -2
  74. package/dist/{chunk-2LZI5CAG.js → chunk-XEGB6BCN.js} +228 -36
  75. package/dist/{chunk-E25LMLRW.js → chunk-YD734TPH.js} +2 -2
  76. package/dist/{verifiers-4UUM6TEE.js → chunk-YTYFXUI3.js} +121 -372
  77. package/dist/{chunk-3JLKSKD7.js → chunk-ZGH7FGS5.js} +17 -7
  78. package/dist/{chunk-VSNATDE6.js → chunk-ZZMN5OM4.js} +2 -2
  79. package/dist/cli/index.js +34 -32
  80. package/dist/{clio-J5JIOIDS.js → clio-WBVQEBKO.js} +7 -7
  81. package/dist/{code-nav-AXCXSBHX.js → code-nav-FGGFIE7L.js} +7 -7
  82. package/dist/codewiki/build-worker.js +4 -4
  83. package/dist/{components-KELWS457.js → components-F7OEATSO.js} +5 -5
  84. package/dist/{config-OEBMIN2U.js → config-TRBL3RCF.js} +48 -41
  85. package/dist/{configure-PUQOSIXQ.js → configure-OLCVPHNM.js} +17 -17
  86. package/dist/{context-URSXPBCK.js → context-MJIJ6GOX.js} +12 -12
  87. package/dist/{context-EKDCKUUZ.js → context-WFPKQSM6.js} +26 -9
  88. package/dist/{context-MGSE4Z2T.js → context-XEWE3MOJ.js} +44 -37
  89. package/dist/{context-clear-KDAJRNUK.js → context-clear-KNOS2JPB.js} +44 -37
  90. package/dist/{context-index-BZ4UYMTC.js → context-index-SSR5ECNE.js} +3 -3
  91. package/dist/{context-working-set-SBKMPPI2.js → context-working-set-EUXAZI6N.js} +14 -13
  92. package/dist/{dispatch-runner-MSWN72NK.js → dispatch-runner-B7MTOVKL.js} +321 -60
  93. package/dist/{docs-2C2LTVT2.js → docs-FLJTIDSE.js} +5 -5
  94. package/dist/{doctor-7BSE27PJ.js → doctor-RN4YKO2X.js} +15 -15
  95. package/dist/{eval-IZGDOO4H.js → eval-RUBJVSNQ.js} +52 -236
  96. package/dist/{evidence-SR7WXB5B.js → evidence-JZNBUOQZ.js} +39 -33
  97. package/dist/{evolve-K7VE2CBX.js → evolve-FJVC4KKI.js} +39 -33
  98. package/dist/{extensions-QVDOHDGJ.js → extensions-IQL36S7K.js} +5 -5
  99. package/dist/{fleet-7XMJNQNF.js → fleet-BDKYJFCP.js} +243 -370
  100. package/dist/fleet-commands-ZFIWZSB3.js +70 -0
  101. package/dist/fleet-graph-Y6HPXIVF.js +125 -0
  102. package/dist/fleet-new-RDVJLHHH.js +48 -0
  103. package/dist/{fleet-preflight-AQNAH644.js → fleet-preflight-BHSNPBMH.js} +2 -2
  104. package/dist/fleet-validate-BIYREGIK.js +79 -0
  105. package/dist/{init-JGNPAYXT.js → init-LQUB5COQ.js} +57 -48
  106. package/dist/library-NJAHIGG4.js +217 -0
  107. package/dist/memory-OG6HOYKM.js +472 -0
  108. package/dist/{models-ZMMLFJNN.js → models-5ZG5XY7J.js} +23 -22
  109. package/dist/{monitor-2F3T5KHP.js → monitor-TJ7AMTGB.js} +69 -35
  110. package/dist/{orchestrator-ORHT43JB.js → orchestrator-WZYB54DM.js} +4868 -1189
  111. package/dist/{paths-UXLN5YYZ.js → paths-XUC7GS6E.js} +5 -5
  112. package/dist/{reset-NXGTYNUO.js → reset-PXQT45IY.js} +8 -8
  113. package/dist/{run-RF4WJGMT.js → run-FQ74YF62.js} +82 -62
  114. package/dist/{share-UT3W6E4M.js → share-FW7SVCL3.js} +34 -10
  115. package/dist/{skills-PSACKC5Q.js → skills-7E7IRB3R.js} +25 -9
  116. package/dist/{skills-eval-WJSI55RZ.js → skills-eval-LI75W6OK.js} +43 -35
  117. package/dist/{targets-PIIRAOYS.js → targets-4CIFKCTW.js} +27 -24
  118. package/dist/{terminal-lease-ULWXWNVY.js → terminal-lease-WUZY7ZV5.js} +5 -4
  119. package/dist/{uninstall-FZCQCDKC.js → uninstall-7FV7IP4E.js} +5 -5
  120. package/dist/{upgrade-346TZ6AV.js → upgrade-K2HVIVMQ.js} +21 -20
  121. package/dist/{usage-6KKXR32N.js → usage-GTZELZQX.js} +159 -59
  122. package/dist/verifiers-RLAHT27O.js +336 -0
  123. package/dist/{verify-X5HDROLA.js → verify-BX3BRKH5.js} +7 -6
  124. package/dist/{wiki-generate-7STOCIFZ.js → wiki-generate-ASIFASCN.js} +58 -48
  125. package/dist/worker/entry.js +98 -84
  126. package/dist/{workspace-G4ZWUIPR.js → workspace-ZJ6BFM3Q.js} +4 -4
  127. package/docs/README.md +4 -3
  128. package/docs/acp.md +1 -1
  129. package/docs/alcf-provider.md +1 -1
  130. package/docs/architecture.md +2 -2
  131. package/docs/artifact-placement.md +1 -2
  132. package/docs/artifact-versions.md +10 -6
  133. package/docs/built-in-agents.md +26 -2
  134. package/docs/capacity-and-scheduling.md +1 -1
  135. package/docs/commands-and-modes.md +90 -8
  136. package/docs/configuration-and-targets.md +90 -2
  137. package/docs/context-engine.md +4 -2
  138. package/docs/context-working-set.md +4 -4
  139. package/docs/development-pipeline.md +1 -1
  140. package/docs/dispatch-architecture-rationale.md +1 -1
  141. package/docs/documentation-coverage.md +4 -4
  142. package/docs/documentation-guide.md +4 -4
  143. package/docs/eval-runner.md +1 -1
  144. package/docs/evals-internal.md +4 -45
  145. package/docs/evidence-and-memory.md +70 -10
  146. package/docs/evolution.md +1 -1
  147. package/docs/exit-codes-and-output.md +4 -1
  148. package/docs/extensions-and-sharing.md +6 -2
  149. package/docs/fleet-demo-runbook.md +2 -2
  150. package/docs/fleet-dispatch.md +224 -11
  151. package/docs/git-commit-provenance.md +2 -2
  152. package/docs/glossary.md +1 -1
  153. package/docs/installation-and-lifecycle.md +2 -2
  154. package/docs/middleware-and-components.md +20 -2
  155. package/docs/model-catalog.md +1 -1
  156. package/docs/observability.md +55 -8
  157. package/docs/proactive-memory.md +26 -16
  158. package/docs/prompt-envelope-and-tools.md +4 -2
  159. package/docs/provider-adapter-cookbook.md +1 -1
  160. package/docs/release-cut-checklist.md +83 -65
  161. package/docs/resource-library.md +59 -0
  162. package/docs/safety-model.md +29 -7
  163. package/docs/scientific-validation.md +3 -3
  164. package/docs/session-lifecycle.md +37 -1
  165. package/docs/skills-marketplace.md +16 -3
  166. package/docs/tool-usage.md +14 -7
  167. package/docs/trace-store.md +1 -1
  168. package/docs/troubleshooting.md +1 -1
  169. package/docs/tui-design.md +38 -4
  170. package/docs/worker-dispatch-mechanics.md +3 -3
  171. package/package.json +7 -4
  172. package/src/cli/agents.ts +2 -3
  173. package/src/cli/argv.ts +14 -1
  174. package/src/cli/fleet-commands.ts +37 -0
  175. package/src/cli/fleet-graph.ts +102 -0
  176. package/src/cli/fleet-new.ts +36 -0
  177. package/src/cli/fleet-preflight.ts +121 -0
  178. package/src/cli/fleet-validate.ts +30 -0
  179. package/src/cli/fleet.ts +188 -335
  180. package/src/cli/index.ts +4 -2
  181. package/src/cli/library.ts +190 -0
  182. package/src/cli/memory.ts +272 -10
  183. package/src/cli/modes/json-stream.ts +2 -2
  184. package/src/cli/modes/print.ts +12 -1
  185. package/src/cli/run.ts +22 -2
  186. package/src/cli/share.ts +13 -1
  187. package/src/cli/targets.ts +12 -3
  188. package/src/cli/usage.ts +160 -20
  189. package/src/core/bus-events.ts +7 -0
  190. package/src/core/commit-attribution.ts +4 -4
  191. package/src/core/config.ts +130 -0
  192. package/src/core/defaults.ts +81 -0
  193. package/src/core/response-model-id.ts +134 -0
  194. package/src/core/toml.ts +62 -0
  195. package/src/core/workspace-files.ts +0 -1
  196. package/src/domains/agents/builtins/architect.md +2 -1
  197. package/src/domains/agents/builtins/oracle.md +33 -0
  198. package/src/domains/agents/catalog.ts +18 -5
  199. package/src/domains/agents/fleet-contract.ts +278 -16
  200. package/src/domains/agents/index.ts +14 -0
  201. package/src/domains/agents/recipe.ts +54 -14
  202. package/src/domains/agents/result-contract.ts +242 -5
  203. package/src/domains/config/classify.ts +4 -0
  204. package/src/domains/context/bootstrap.ts +36 -27
  205. package/src/domains/context/project-metadata.ts +19 -63
  206. package/src/domains/context/prompt-context.ts +8 -0
  207. package/src/domains/context/working-set/policies/index.ts +3 -4
  208. package/src/domains/dispatch/active-route-planner.ts +14 -0
  209. package/src/domains/dispatch/backoff.ts +2 -1
  210. package/src/domains/dispatch/budget-envelope.ts +396 -0
  211. package/src/domains/dispatch/capability-match.ts +1 -0
  212. package/src/domains/dispatch/checkout-writer-lease.ts +175 -0
  213. package/src/domains/dispatch/contract.ts +36 -0
  214. package/src/domains/dispatch/delegation-plan.ts +167 -0
  215. package/src/domains/dispatch/execution-plan.ts +76 -5
  216. package/src/domains/dispatch/execution-role.ts +3 -1
  217. package/src/domains/dispatch/execution-scheduler.ts +183 -67
  218. package/src/domains/dispatch/extension.ts +339 -36
  219. package/src/domains/dispatch/fleet-gate.ts +14 -0
  220. package/src/domains/dispatch/fleet-plan.ts +63 -3
  221. package/src/domains/dispatch/fleet-run.ts +737 -0
  222. package/src/domains/dispatch/gate-role-prompts.ts +9 -0
  223. package/src/domains/dispatch/host-verification.ts +178 -0
  224. package/src/domains/dispatch/index.ts +38 -0
  225. package/src/domains/dispatch/intent.ts +159 -0
  226. package/src/domains/dispatch/orphan-recovery.ts +1 -0
  227. package/src/domains/dispatch/receipt-integrity.ts +12 -4
  228. package/src/domains/dispatch/state.ts +37 -3
  229. package/src/domains/dispatch/types.ts +61 -9
  230. package/src/domains/dispatch/validation.ts +80 -6
  231. package/src/domains/dispatch/worker-spawn.ts +14 -3
  232. package/src/domains/eval/metrics/evidence.ts +0 -116
  233. package/src/domains/eval/metrics/invariants.ts +1 -1
  234. package/src/domains/eval/runners/clio-run.ts +1 -10
  235. package/src/domains/eval/runners/external-command.ts +2 -29
  236. package/src/domains/eval/schema/suite.ts +0 -7
  237. package/src/domains/eval/suites/run.ts +1 -7
  238. package/src/domains/evidence/trust-status.ts +10 -1
  239. package/src/domains/memory/index.ts +22 -0
  240. package/src/domains/memory/operations.ts +58 -1
  241. package/src/domains/memory/promotion.ts +281 -0
  242. package/src/domains/memory/prompt-section.ts +25 -5
  243. package/src/domains/memory/proposal.ts +51 -7
  244. package/src/domains/memory/task-bank.ts +3 -2
  245. package/src/domains/memory/task-memory-handoff.ts +181 -24
  246. package/src/domains/memory/task-memory-policy.ts +3 -1
  247. package/src/domains/memory/types.ts +37 -0
  248. package/src/domains/memory/validate.ts +178 -0
  249. package/src/domains/middleware/index.ts +15 -0
  250. package/src/domains/middleware/memory-intervention.ts +35 -25
  251. package/src/domains/middleware/runtime.ts +6 -0
  252. package/src/domains/middleware/skills-reminder.ts +19 -4
  253. package/src/domains/middleware/stalled-turn.ts +43 -1
  254. package/src/domains/middleware/types.ts +10 -0
  255. package/src/domains/middleware/watchdog.ts +281 -0
  256. package/src/domains/observability/contract.ts +9 -2
  257. package/src/domains/observability/cost.ts +31 -4
  258. package/src/domains/observability/extension.ts +2 -2
  259. package/src/domains/observability/index.ts +10 -0
  260. package/src/domains/observability/out-of-turn-usage.ts +223 -0
  261. package/src/domains/providers/index.ts +3 -0
  262. package/src/domains/providers/model-discovery.ts +9 -0
  263. package/src/domains/providers/runtime-resolution.ts +38 -1
  264. package/src/domains/providers/runtimes/common/probe-helpers.ts +97 -16
  265. package/src/domains/providers/types/context-window-slots.ts +18 -0
  266. package/src/domains/providers/types/runtime-descriptor.ts +3 -1
  267. package/src/domains/resources/index.ts +20 -0
  268. package/src/domains/resources/library.ts +326 -0
  269. package/src/domains/resources/skills/marketplace.ts +37 -12
  270. package/src/domains/safety/call-target.ts +211 -14
  271. package/src/domains/safety/decision-presentation.ts +268 -0
  272. package/src/domains/safety/redaction.ts +73 -0
  273. package/src/domains/session/context-ledger.ts +10 -1
  274. package/src/domains/session/decision-board.ts +4 -0
  275. package/src/domains/session/entries.ts +3 -0
  276. package/src/domains/session/handoff.ts +629 -0
  277. package/src/domains/session/history.ts +68 -19
  278. package/src/domains/session/usage.ts +24 -7
  279. package/src/domains/share/archive.ts +67 -2
  280. package/src/engine/acp/event-mapper.ts +7 -0
  281. package/src/engine/acp/server.ts +29 -2
  282. package/src/engine/apis/lmstudio.ts +25 -4
  283. package/src/engine/apis/openai-completions.ts +147 -22
  284. package/src/engine/claude/sdk-runtime.ts +8 -2
  285. package/src/engine/claude/tool-safety.ts +13 -0
  286. package/src/engine/loop-guard.ts +27 -3
  287. package/src/engine/worker-events.ts +4 -3
  288. package/src/engine/worker-runtime.ts +59 -54
  289. package/src/entry/orchestrator.ts +55 -1
  290. package/src/interactive/bus-notices.ts +26 -0
  291. package/src/interactive/chat-loop-messages.ts +22 -0
  292. package/src/interactive/chat-loop.ts +248 -1
  293. package/src/interactive/chat-renderer.ts +41 -3
  294. package/src/interactive/clio-editor.ts +44 -7
  295. package/src/interactive/context-overlay.ts +43 -5
  296. package/src/interactive/cost-overlay.ts +70 -11
  297. package/src/interactive/council-dispatch.ts +30 -0
  298. package/src/interactive/council-grid.ts +213 -0
  299. package/src/interactive/council.ts +99 -0
  300. package/src/interactive/dispatch-board.ts +471 -50
  301. package/src/interactive/fleet-run-preview.ts +307 -0
  302. package/src/interactive/footer/notifications.ts +219 -0
  303. package/src/interactive/footer/widgets.ts +13 -0
  304. package/src/interactive/handoff-round.ts +56 -0
  305. package/src/interactive/interactive-application.ts +49 -2
  306. package/src/interactive/interactive-event-projection.ts +9 -1
  307. package/src/interactive/interactive-input-runtime.ts +11 -1
  308. package/src/interactive/interactive-presentation.ts +11 -1
  309. package/src/interactive/interactive-slash-runtime.ts +52 -2
  310. package/src/interactive/interactive-subscriptions.ts +14 -2
  311. package/src/interactive/memory-overlay.ts +89 -4
  312. package/src/interactive/oracle.ts +179 -0
  313. package/src/interactive/overlay-ask-user-lifecycle.ts +7 -1
  314. package/src/interactive/overlay-frame.ts +5 -2
  315. package/src/interactive/overlay-general-openers.ts +230 -2
  316. package/src/interactive/overlay-key-routing.ts +58 -2
  317. package/src/interactive/overlay-lifecycle.ts +52 -5
  318. package/src/interactive/overlay-permission-lifecycle.ts +33 -8
  319. package/src/interactive/overlay-resource-openers.ts +11 -3
  320. package/src/interactive/overlay-session-lifecycle.ts +234 -2
  321. package/src/interactive/overlay-transitions.ts +11 -0
  322. package/src/interactive/overlays/ask-user.ts +74 -30
  323. package/src/interactive/overlays/decisions.ts +3 -1
  324. package/src/interactive/overlays/fleet-run-approval.ts +208 -0
  325. package/src/interactive/overlays/handoff-review.ts +185 -0
  326. package/src/interactive/overlays/library-install-confirm.ts +151 -0
  327. package/src/interactive/overlays/list-overlay.ts +168 -2
  328. package/src/interactive/overlays/settings.ts +101 -4
  329. package/src/interactive/overlays/side-question.ts +139 -0
  330. package/src/interactive/overlays/skills-hub.ts +401 -15
  331. package/src/interactive/permission-hint.ts +35 -0
  332. package/src/interactive/permission-overlay.ts +95 -45
  333. package/src/interactive/renderers/tool-execution.ts +19 -49
  334. package/src/interactive/session-last-turn.ts +8 -1
  335. package/src/interactive/session-usage-reseed.ts +36 -10
  336. package/src/interactive/side-question.ts +171 -0
  337. package/src/interactive/slash-commands.ts +434 -7
  338. package/src/interactive/slash-spec.ts +19 -6
  339. package/src/interactive/status/summary.ts +5 -0
  340. package/src/interactive/status/types.ts +5 -0
  341. package/src/interactive/terminal-lease.ts +1 -0
  342. package/src/interactive/theme/tokens.ts +30 -0
  343. package/src/interactive/turn-context.ts +96 -23
  344. package/src/interactive/turn-middleware.ts +16 -1
  345. package/src/interactive/turn-runtime.ts +37 -8
  346. package/src/interactive/turn-state.ts +3 -0
  347. package/src/interactive/watchdog-run.ts +75 -0
  348. package/src/interactive/worker-progress.ts +440 -0
  349. package/src/interactive/worker-share.ts +56 -1
  350. package/src/interactive/worker-stream.ts +58 -110
  351. package/src/tools/agent-tools.ts +28 -3
  352. package/src/tools/ask-user.ts +21 -1
  353. package/src/tools/bootstrap.ts +3 -0
  354. package/src/tools/compete-worktrees.ts +13 -79
  355. package/src/tools/context/index.ts +2 -2
  356. package/src/tools/dispatch-admission.ts +242 -8
  357. package/src/tools/dispatch-arguments.ts +65 -1
  358. package/src/tools/dispatch-event-text.ts +19 -0
  359. package/src/tools/dispatch-plan.ts +136 -6
  360. package/src/tools/dispatch-runner.ts +319 -13
  361. package/src/tools/dispatch-types.ts +20 -1
  362. package/src/tools/dispatch.ts +96 -3
  363. package/src/tools/monitor.ts +31 -0
  364. package/src/tools/profiles.ts +18 -4
  365. package/src/tools/registry.ts +15 -5
  366. package/src/tools/result-disposition.ts +156 -0
  367. package/src/tools/result-shaping.ts +59 -1
  368. package/src/tools/task-worktree.ts +238 -0
  369. package/src/tools/verify/authoring.ts +116 -55
  370. package/src/tools/verify/scripts.ts +62 -0
  371. package/src/tools/worker-evidence.ts +21 -1
  372. package/src/worker/spec-contract.ts +44 -3
  373. package/dist/chunk-EFADSJET.js +0 -18
  374. package/dist/chunk-HC4CLZ2Y.js +0 -68
  375. package/dist/memory-4ALKDJ4Q.js +0 -246
  376. package/src/domains/eval/metrics/chaos-stream.ts +0 -93
@@ -7,10 +7,21 @@ export type ResultContract =
7
7
  | { kind: "architect-plan"; path: string }
8
8
  | { kind: "scout-report" }
9
9
  | { kind: "verifier-report" }
10
+ | { kind: "council-report" }
10
11
  | { kind: "debugger-report" }
11
12
  | { kind: "research-report" }
12
13
  | { kind: "mutation-report" }
13
14
  | { kind: "provenance-report" }
15
+ | { kind: "delegation-plan" }
16
+ /**
17
+ * The advisory answer `/oracle` asks for. It exists as its own kind because
18
+ * the answer shape is the whole point of the command: a verdict, the
19
+ * strongest challenge the advisor can mount, the evidence that would change
20
+ * its mind, and the prior decisions it read. Folding it onto
21
+ * `research-report` would ask for claims and evidence instead, and the
22
+ * challenge and the reversal condition would have nowhere to go.
23
+ */
24
+ | { kind: "oracle-report" }
14
25
  | { kind: "external-delegation" }
15
26
  /**
16
27
  * The run's postcondition is the artifact it left on disk, so its terminal
@@ -58,6 +69,7 @@ export interface ResultContractValidation {
58
69
  scout?: ScoutResult;
59
70
  /** Parsed Verifier data for the dispatch review-gate projection. */
60
71
  verifier?: VerifierResult;
72
+ delegationPlan?: DelegationPlanResult;
61
73
  reason?: string;
62
74
  }
63
75
 
@@ -211,6 +223,159 @@ export interface VerifierResult {
211
223
  checks: ReadonlyArray<VerifierCheck>;
212
224
  }
213
225
 
226
+ export interface DelegationPlanResultTask {
227
+ id: string;
228
+ agent: string;
229
+ description: string;
230
+ depends_on: ReadonlyArray<string>;
231
+ writes: ReadonlyArray<string>;
232
+ mode?: "sequential" | "parallel";
233
+ }
234
+
235
+ export interface DelegationPlanResult {
236
+ tasks: ReadonlyArray<DelegationPlanResultTask>;
237
+ }
238
+
239
+ export function parseDelegationPlanResult(output: string | null): DelegationPlanResult | null {
240
+ const parsed = parseJson(output);
241
+ if (
242
+ !parsed.ok ||
243
+ !hasOnlyKeys(parsed.value, ["tasks"]) ||
244
+ !Array.isArray(parsed.value.tasks) ||
245
+ parsed.value.tasks.length === 0
246
+ )
247
+ return null;
248
+ const tasks: DelegationPlanResultTask[] = [];
249
+ for (const raw of parsed.value.tasks) {
250
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
251
+ const task = raw as Record<string, unknown>;
252
+ if (
253
+ !hasOnlyKeys(task, ["id", "agent", "description", "depends_on", "writes", "mode"]) ||
254
+ !string(task.id) ||
255
+ !string(task.agent) ||
256
+ !string(task.description) ||
257
+ !Array.isArray(task.depends_on) ||
258
+ task.depends_on.some((entry) => !string(entry)) ||
259
+ !Array.isArray(task.writes) ||
260
+ task.writes.some((entry) => !string(entry)) ||
261
+ (task.mode !== undefined && task.mode !== "sequential" && task.mode !== "parallel")
262
+ )
263
+ return null;
264
+ tasks.push({
265
+ id: task.id,
266
+ agent: task.agent,
267
+ description: task.description,
268
+ depends_on: [...task.depends_on] as string[],
269
+ writes: [...task.writes] as string[],
270
+ ...(task.mode !== undefined ? { mode: task.mode as "sequential" | "parallel" } : {}),
271
+ });
272
+ }
273
+ return { tasks };
274
+ }
275
+
276
+ export interface CouncilReportMember {
277
+ label: string;
278
+ runId: string;
279
+ round: number;
280
+ answer: string;
281
+ verdict?: string;
282
+ failed?: { reason: string };
283
+ }
284
+
285
+ export interface CouncilReport {
286
+ members: CouncilReportMember[];
287
+ synthesis: {
288
+ kind: "none" | "judge" | "vote";
289
+ text?: string;
290
+ verdict?: string;
291
+ tally?: Record<string, number>;
292
+ judgeRunId?: string;
293
+ };
294
+ }
295
+
296
+ export function parseCouncilReport(output: string | null): CouncilReport | null {
297
+ if (output === null) return null;
298
+ const parsed = parseJsonObjectPayload(output);
299
+ if (!parsed.ok) return null;
300
+ const value = parsed.value;
301
+ if (!hasOnlyKeys(value, ["members", "synthesis"]) || !Array.isArray(value.members) || value.members.length === 0) {
302
+ return null;
303
+ }
304
+ const members: CouncilReportMember[] = [];
305
+ for (const raw of value.members) {
306
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
307
+ const member = raw as Record<string, unknown>;
308
+ if (
309
+ !hasOnlyKeys(member, ["label", "runId", "round", "answer", "verdict", "failed"]) ||
310
+ !string(member.label) ||
311
+ !string(member.runId) ||
312
+ !Number.isInteger(member.round) ||
313
+ Number(member.round) < 1 ||
314
+ typeof member.answer !== "string" ||
315
+ (member.verdict !== undefined && !string(member.verdict))
316
+ )
317
+ return null;
318
+ let failed: { reason: string } | undefined;
319
+ if (member.failed !== undefined) {
320
+ if (
321
+ member.failed === null ||
322
+ typeof member.failed !== "object" ||
323
+ Array.isArray(member.failed) ||
324
+ !hasOnlyKeys(member.failed as Record<string, unknown>, ["reason"]) ||
325
+ !string((member.failed as Record<string, unknown>).reason)
326
+ )
327
+ return null;
328
+ failed = { reason: (member.failed as { reason: string }).reason };
329
+ }
330
+ members.push({
331
+ label: member.label,
332
+ runId: member.runId,
333
+ round: Number(member.round),
334
+ answer: member.answer,
335
+ ...(member.verdict !== undefined ? { verdict: member.verdict as string } : {}),
336
+ ...(failed !== undefined ? { failed } : {}),
337
+ });
338
+ }
339
+ if (value.synthesis === null || typeof value.synthesis !== "object" || Array.isArray(value.synthesis)) return null;
340
+ const rawSynthesis = value.synthesis as Record<string, unknown>;
341
+ if (!hasOnlyKeys(rawSynthesis, ["kind", "text", "verdict", "tally", "judgeRunId"])) return null;
342
+ if (rawSynthesis.kind !== "none" && rawSynthesis.kind !== "judge" && rawSynthesis.kind !== "vote") return null;
343
+ for (const key of ["text", "verdict", "judgeRunId"] as const) {
344
+ if (rawSynthesis[key] !== undefined && !string(rawSynthesis[key])) return null;
345
+ }
346
+ let tally: Record<string, number> | undefined;
347
+ if (rawSynthesis.tally !== undefined) {
348
+ if (rawSynthesis.tally === null || typeof rawSynthesis.tally !== "object" || Array.isArray(rawSynthesis.tally))
349
+ return null;
350
+ tally = {};
351
+ for (const [key, count] of Object.entries(rawSynthesis.tally as Record<string, unknown>)) {
352
+ if (!key || !Number.isInteger(count) || Number(count) < 0) return null;
353
+ tally[key] = Number(count);
354
+ }
355
+ }
356
+ return {
357
+ members,
358
+ synthesis: {
359
+ kind: rawSynthesis.kind,
360
+ ...(rawSynthesis.text !== undefined ? { text: rawSynthesis.text as string } : {}),
361
+ ...(rawSynthesis.verdict !== undefined ? { verdict: rawSynthesis.verdict as string } : {}),
362
+ ...(tally !== undefined ? { tally } : {}),
363
+ ...(rawSynthesis.judgeRunId !== undefined ? { judgeRunId: rawSynthesis.judgeRunId as string } : {}),
364
+ },
365
+ };
366
+ }
367
+
368
+ /**
369
+ * Parsed `oracle-report` payload. `/oracle` renders these four fields into the
370
+ * operator note it hands the main agent, so the shape is the answer shape.
371
+ */
372
+ export interface OracleResult {
373
+ verdict: string;
374
+ challenge: string;
375
+ changesMyMind: string;
376
+ citedDecisions: ReadonlyArray<string>;
377
+ }
378
+
214
379
  /**
215
380
  * Parsed `code-report` payload. `passed` restates `exitCode === 0` so a
216
381
  * downstream reader never has to know a runner's exit conventions, and
@@ -269,7 +434,7 @@ function success(
269
434
  contract: ResultContract,
270
435
  quality: ResultContractQuality,
271
436
  value: unknown,
272
- extra: Pick<ResultContractValidation, "scout" | "verifier" | "reason"> = {},
437
+ extra: Pick<ResultContractValidation, "scout" | "verifier" | "delegationPlan" | "reason"> = {},
273
438
  ): ResultContractValidation {
274
439
  return {
275
440
  conformance: "pass",
@@ -335,6 +500,7 @@ const RESULT_CONTRACT_KINDS: ReadonlyArray<ResultContract["kind"]> = [
335
500
  "architect-plan",
336
501
  "scout-report",
337
502
  "verifier-report",
503
+ "council-report",
338
504
  "debugger-report",
339
505
  "research-report",
340
506
  "mutation-report",
@@ -664,6 +830,47 @@ export function parseVerifierResult(output: string | null): VerifierResult | nul
664
830
  return validation.verifier ?? null;
665
831
  }
666
832
 
833
+ /**
834
+ * An advisory opinion is never a correctness signal about the route that
835
+ * produced it, so conformance is judged on the shape alone and quality stays
836
+ * `unmeasured`. `citedDecisions` may be empty: an honest advisor that found no
837
+ * settled decision bearing on the question must be able to say so.
838
+ */
839
+ function validateOracle(contract: ResultContract, output: string | null): ResultContractValidation {
840
+ const parsed = parseJson(output);
841
+ if (!parsed.ok) return failure(contract, "unmeasured", parsed.reason);
842
+ const value = parsed.value;
843
+ if (
844
+ !hasOnlyKeys(value, ["verdict", "challenge", "changesMyMind", "citedDecisions"]) ||
845
+ !string(value.verdict) ||
846
+ !string(value.challenge) ||
847
+ !string(value.changesMyMind) ||
848
+ !Array.isArray(value.citedDecisions) ||
849
+ value.citedDecisions.some((entry) => !string(entry))
850
+ ) {
851
+ return failure(
852
+ contract,
853
+ "unmeasured",
854
+ 'Oracle result must carry verdict, challenge, changesMyMind, and a citedDecisions array of strings, for example {"verdict":"consistent with decision routing.target","challenge":"...","changesMyMind":"...","citedDecisions":["routing.target"]}',
855
+ );
856
+ }
857
+ return success(contract, "unmeasured", value);
858
+ }
859
+
860
+ /** Parse an oracle answer under the same schema its recipe contract enforces. */
861
+ export function parseOracleResult(output: string | null): OracleResult | null {
862
+ const validation = validateOracle({ kind: "oracle-report" }, output);
863
+ if (validation.conformance !== "pass") return null;
864
+ const parsed = parseJson(output);
865
+ if (!parsed.ok) return null;
866
+ return {
867
+ verdict: String(parsed.value.verdict),
868
+ challenge: String(parsed.value.challenge),
869
+ changesMyMind: String(parsed.value.changesMyMind),
870
+ citedDecisions: (parsed.value.citedDecisions as ReadonlyArray<string>).map((entry) => String(entry)),
871
+ };
872
+ }
873
+
667
874
  /**
668
875
  * A code step's result is always `unmeasured` for routing quality. A red suite
669
876
  * is evidence about the repository, not about the route that ran the command,
@@ -1036,6 +1243,12 @@ export function validateResultContract(input: ResultContractValidationInput): Re
1036
1243
  }
1037
1244
  case "verifier-report":
1038
1245
  return validateVerifier(input.contract, input.output);
1246
+ case "council-report": {
1247
+ const report = parseCouncilReport(input.output);
1248
+ return report === null
1249
+ ? failure(input.contract, "fail", "council-report is malformed")
1250
+ : success(input.contract, "unmeasured", { council: report });
1251
+ }
1039
1252
  case "debugger-report":
1040
1253
  return validateDebugger(input.contract, input.output);
1041
1254
  case "research-report":
@@ -1044,6 +1257,18 @@ export function validateResultContract(input: ResultContractValidationInput): Re
1044
1257
  return validateMutation(input.contract, input);
1045
1258
  case "provenance-report":
1046
1259
  return validateProvenance(input.contract, input.output);
1260
+ case "delegation-plan": {
1261
+ const plan = parseDelegationPlanResult(input.output);
1262
+ return plan === null
1263
+ ? failure(
1264
+ input.contract,
1265
+ "fail",
1266
+ "delegation-plan must carry tasks with id, agent, description, depends_on, writes, and optional mode",
1267
+ )
1268
+ : success(input.contract, "unmeasured", plan, { delegationPlan: plan });
1269
+ }
1270
+ case "oracle-report":
1271
+ return validateOracle(input.contract, input.output);
1047
1272
  case "external-delegation":
1048
1273
  return success(input.contract, "unmeasured", { external: true });
1049
1274
  case "artifact-report":
@@ -1120,6 +1345,8 @@ function resultContractShape(contract: ResultContract): string {
1120
1345
  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';
1121
1346
  case "verifier-report":
1122
1347
  return '{"verdict":"pass","checks":[{"name":"npm run typecheck","passed":true,"evidence":"exit 0"}]}';
1348
+ case "council-report":
1349
+ return '{"members":[{"label":"alpha","runId":"run-1","round":1,"answer":"..."}],"synthesis":{"kind":"none"}}';
1123
1350
  case "debugger-report":
1124
1351
  return '{"diagnosis":"...","reproduction":"reproduced","evidence":["..."]}';
1125
1352
  case "research-report":
@@ -1128,6 +1355,10 @@ function resultContractShape(contract: ResultContract): string {
1128
1355
  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"}';
1129
1356
  case "provenance-report":
1130
1357
  return '{"confirmedFacts":["..."],"missingEvidence":["..."],"nextInspections":["..."]}';
1358
+ case "delegation-plan":
1359
+ return '{"tasks":[{"id":"build","agent":"coder","description":"Implement the change","depends_on":[],"writes":["src"],"mode":"parallel"}]}';
1360
+ case "oracle-report":
1361
+ return '{"verdict":"one line","challenge":"the strongest objection you can mount","changesMyMind":"the evidence that would reverse the verdict","citedDecisions":["decision key or task id you relied on"]}';
1131
1362
  case "external-delegation":
1132
1363
  return "any final text";
1133
1364
  case "artifact-report":
@@ -1147,12 +1378,13 @@ export interface ResultContractRepairInput {
1147
1378
  attempt: number;
1148
1379
  /** `path:line` anchors from reads that succeeded in this run. */
1149
1380
  anchors: ReadonlyArray<string>;
1381
+ /** True only when admission preauthorized a larger tool-call revision phase. */
1382
+ toolsAvailable?: boolean;
1150
1383
  }
1151
1384
 
1152
1385
  /**
1153
- * One repair directive. It states the validator's reason, the exact shape, and
1154
- * that tool use is over, so the model's only remaining move is to emit the
1155
- * terminal document from evidence it already has.
1386
+ * One repair directive. It states the validator's reason and exact shape. Tool
1387
+ * use remains closed unless dispatch preauthorized a larger revision phase.
1156
1388
  */
1157
1389
  export function resultContractRepairMessage(input: ResultContractRepairInput): string {
1158
1390
  const last = input.attempt >= RESULT_CONTRACT_REPAIR_LIMIT;
@@ -1161,7 +1393,9 @@ export function resultContractRepairMessage(input: ResultContractRepairInput): s
1161
1393
  ? "FINAL RESULT REQUIRED IN THIS RESPONSE. Your previous response still did not satisfy this run's result contract."
1162
1394
  : "Your previous response did not satisfy this run's result contract.",
1163
1395
  `Validator reason: ${input.reason}`,
1164
- "Tool use is over. Answer from the evidence you already gathered.",
1396
+ input.toolsAvailable === true
1397
+ ? "You may use the admitted tools to repair this validator failure, then emit the required terminal result."
1398
+ : "Tool use is over. Answer from the evidence you already gathered.",
1165
1399
  `Emit exactly this shape and nothing else: ${resultContractShape(input.contract)}`,
1166
1400
  "Do not add prose, code fences, or commentary around it. Do not describe work you intend to do next.",
1167
1401
  ];
@@ -1250,13 +1484,16 @@ export function parseResultContract(value: unknown, sourcePath: string): ResultC
1250
1484
  const kinds = [
1251
1485
  "scout-report",
1252
1486
  "verifier-report",
1487
+ "council-report",
1253
1488
  "debugger-report",
1254
1489
  "research-report",
1255
1490
  "mutation-report",
1256
1491
  "provenance-report",
1492
+ "oracle-report",
1257
1493
  "external-delegation",
1258
1494
  "artifact-report",
1259
1495
  "context-handbook",
1496
+ "delegation-plan",
1260
1497
  ] as const;
1261
1498
  if (!(kinds as ReadonlyArray<string>).includes(record.kind) || !only("kind")) {
1262
1499
  throw new Error(`agent recipe: ${sourcePath}: resultContract.kind is unsupported or has unknown keys`);
@@ -28,6 +28,10 @@ const HOT_RELOAD_FIELDS = new Set<string>([
28
28
  "modelSelector",
29
29
  "terminal.smoothStreaming",
30
30
  "attribution.gitCommits",
31
+ // The watchdog registration reads its settings live on every trigger, so
32
+ // enabling it, retargeting it, or changing its cadence takes effect on the
33
+ // next turn boundary without a restart or a session-routing patch.
34
+ "watchdog",
31
35
  ]);
32
36
 
33
37
  const NEXT_TURN_FIELDS = new Set<string>([
@@ -2,6 +2,7 @@ import { execFileSync } from "node:child_process";
2
2
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { dirname, join, parse } from "node:path";
4
4
  import type { ContextActivityPayload } from "../../core/bus-events.js";
5
+ import { createTomlFileReader, type TomlFileReader, tomlTableAt } from "../../core/toml.js";
5
6
  import { classifyProjectPreload, type ProjectPreloadClass } from "../prompts/preload.js";
6
7
  import { detectProjectType, type ProjectType } from "../session/workspace/project-type.js";
7
8
  import {
@@ -15,7 +16,7 @@ import { type ClioMdSection, type ParsedClioMd, parseClioMd, serializeClioMd, tr
15
16
  import { buildCodewikiCandidate, coordinateCodewikiWrite } from "./codewiki/coordinator.js";
16
17
  import type { Codewiki } from "./codewiki/schema.js";
17
18
  import type { Fingerprint } from "./fingerprint.js";
18
- import { hasTomlTable, type ProjectMetadata, readProjectMetadata } from "./project-metadata.js";
19
+ import { type ProjectMetadata, readProjectMetadata } from "./project-metadata.js";
19
20
  import { renderPromptContext } from "./prompt-context.js";
20
21
  import type { SiblingContextFile } from "./sibling-files.js";
21
22
  import {
@@ -49,6 +50,8 @@ export interface BootstrapIo {
49
50
  export interface BootstrapGenerateInput {
50
51
  cwd: string;
51
52
  expectedProjectName?: string;
53
+ /** Operation-scoped parsed TOML documents shared by bootstrap consumers. */
54
+ tomlFiles?: TomlFileReader;
52
55
  projectType: ProjectType;
53
56
  siblingFiles: ReadonlyArray<SiblingContextFile>;
54
57
  adoption: AdoptionScanResult;
@@ -211,8 +214,8 @@ function projectNameFrom(metadata: ProjectMetadata, cwd: string): string {
211
214
  return titleFromPackageName(parse(cwd).base || "Project");
212
215
  }
213
216
 
214
- function projectName(cwd: string): string {
215
- return projectNameFrom(readProjectMetadata(cwd), cwd);
217
+ function projectName(cwd: string, tomlFiles: TomlFileReader): string {
218
+ return projectNameFrom(readProjectMetadata(cwd, tomlFiles), cwd);
216
219
  }
217
220
 
218
221
  function projectTypeLabel(projectType: ProjectType): string {
@@ -270,8 +273,9 @@ function resolveDefaultIdentity(
270
273
  cwd: string,
271
274
  projectType: ProjectType,
272
275
  files: ReadonlyArray<SiblingContextFile>,
276
+ tomlFiles: TomlFileReader,
273
277
  ): DeterministicIdentity {
274
- const metadata = readProjectMetadata(cwd);
278
+ const metadata = readProjectMetadata(cwd, tomlFiles);
275
279
  const name = projectNameFrom(metadata, cwd);
276
280
  const context = allContextText(files);
277
281
  if (/Clio owns the agent loop/i.test(context) && /pi-(?:ai|SDK)/i.test(context)) {
@@ -303,8 +307,8 @@ function resolveDefaultIdentity(
303
307
  * which tells a reader nothing the directory name did not. A model sentence is
304
308
  * strictly better than that, and only than that.
305
309
  */
306
- function stabilizedIdentity(input: BootstrapGenerateInput, modelIdentity: unknown): string {
307
- const deterministic = resolveDefaultIdentity(input.cwd, input.projectType, input.siblingFiles);
310
+ function stabilizedIdentity(input: BootstrapGenerateInput, modelIdentity: unknown, tomlFiles: TomlFileReader): string {
311
+ const deterministic = resolveDefaultIdentity(input.cwd, input.projectType, input.siblingFiles, tomlFiles);
308
312
  const model = typeof modelIdentity === "string" ? modelIdentity.trim() : "";
309
313
  if (deterministic.bare && model.length > 0) return model;
310
314
  return deterministic.text;
@@ -504,23 +508,21 @@ function cmakeVerificationLines(cwd: string): string[] {
504
508
  * other way. Tox wins when both are declared because it typically wraps the
505
509
  * pytest run.
506
510
  */
507
- function pythonVerificationLines(cwd: string): string[] {
508
- let pyproject = "";
509
- try {
510
- pyproject = readFileSync(join(cwd, "pyproject.toml"), "utf8");
511
- } catch {
512
- pyproject = "";
513
- }
514
- if (existsSync(join(cwd, "tox.ini")) || hasTomlTable(pyproject, "tool.tox")) {
511
+ function pythonVerificationLines(cwd: string, tomlFiles: TomlFileReader): string[] {
512
+ const pyproject = tomlFiles.read("pyproject.toml");
513
+ if (existsSync(join(cwd, "tox.ini")) || (pyproject !== null && tomlTableAt(pyproject, ["tool", "tox"]))) {
515
514
  return ["Run `tox` before handoff."];
516
515
  }
517
- if (existsSync(join(cwd, "pytest.ini")) || hasTomlTable(pyproject, "tool.pytest.ini_options")) {
516
+ if (
517
+ existsSync(join(cwd, "pytest.ini")) ||
518
+ (pyproject !== null && tomlTableAt(pyproject, ["tool", "pytest", "ini_options"]))
519
+ ) {
518
520
  return ["Run `pytest` before handoff."];
519
521
  }
520
522
  return [];
521
523
  }
522
524
 
523
- function verificationSection(cwd: string): ClioMdSection | null {
525
+ function verificationSection(cwd: string, tomlFiles: TomlFileReader): ClioMdSection | null {
524
526
  const scripts = packageScripts(cwd);
525
527
  const pm = packageManager(cwd);
526
528
  const hasScript = (name: string) => typeof scripts[name] === "string";
@@ -557,7 +559,8 @@ function verificationSection(cwd: string): ClioMdSection | null {
557
559
  // Cargo's commands are defined by the toolchain, not by each project, so
558
560
  // naming them for any Cargo workspace carries the same confidence as a
559
561
  // declared package script.
560
- if (existsSync(join(cwd, "Cargo.toml"))) {
562
+ const cargo = tomlFiles.read("Cargo.toml");
563
+ if (cargo !== null && (tomlTableAt(cargo, ["package"]) || tomlTableAt(cargo, ["workspace"]))) {
561
564
  lines.push("Run `cargo build` and `cargo test` before handoff.");
562
565
  }
563
566
  // Go's commands are defined by the toolchain, not by each project, so
@@ -566,7 +569,7 @@ function verificationSection(cwd: string): ClioMdSection | null {
566
569
  if (existsSync(join(cwd, "go.mod"))) {
567
570
  lines.push("Run `go build ./...` and `go test ./...` before handoff.");
568
571
  }
569
- lines.push(...pythonVerificationLines(cwd));
572
+ lines.push(...pythonVerificationLines(cwd, tomlFiles));
570
573
  if (lines.length === 0) return null;
571
574
  return { title: "Verification expectations", body: lines.join(" ") };
572
575
  }
@@ -717,6 +720,7 @@ function stabilizeGeneratedOutput(
717
720
  onModelSectionRetained?: () => void,
718
721
  groundModelOutput = false,
719
722
  ): BootstrapStructuredOutput {
723
+ const tomlFiles = input.tomlFiles ?? createTomlFileReader(input.cwd);
720
724
  const existing = input.existingClioMd;
721
725
  const conventions: string[] = [];
722
726
  for (const convention of existing?.conventions ?? []) pushUnique(conventions, convention);
@@ -727,7 +731,7 @@ function stabilizeGeneratedOutput(
727
731
  for (const invariant of existing?.invariants ?? []) pushUnique(invariants, invariant);
728
732
  for (const invariant of inferInvariants(input.siblingFiles)) pushUnique(invariants, invariant);
729
733
 
730
- const verification = verificationSection(input.cwd);
734
+ const verification = verificationSection(input.cwd, tomlFiles);
731
735
  const inferredSections = inferHeuristicSections(input);
732
736
  const existingSections = existing?.sections ?? [];
733
737
  const ordinarySections: ClioMdSection[] = [];
@@ -754,8 +758,8 @@ function stabilizeGeneratedOutput(
754
758
  }
755
759
  return {
756
760
  ...base,
757
- projectName: existing?.projectName ?? projectName(input.cwd),
758
- identity: existing?.identity ?? stabilizedIdentity(input, base.identity),
761
+ projectName: existing?.projectName ?? projectName(input.cwd, tomlFiles),
762
+ identity: existing?.identity ?? stabilizedIdentity(input, base.identity, tomlFiles),
759
763
  conventions: conventions.slice(0, 6),
760
764
  invariants: invariants.slice(0, 3),
761
765
  sections: [...retainedOrdinarySections, ...(verification ? [verification] : [])],
@@ -819,9 +823,11 @@ function inferHeuristicSections(input: BootstrapGenerateInput): ClioMdSection[]
819
823
  * unavailable or fails.
820
824
  */
821
825
  function heuristicBootstrapOutputSync(input: BootstrapGenerateInput): BootstrapStructuredOutput {
822
- return stabilizeGeneratedOutput(input, {
823
- projectName: projectName(input.cwd),
824
- identity: resolveDefaultIdentity(input.cwd, input.projectType, input.siblingFiles).text,
826
+ const tomlFiles = input.tomlFiles ?? createTomlFileReader(input.cwd);
827
+ const sharedInput = input.tomlFiles ? input : { ...input, tomlFiles };
828
+ return stabilizeGeneratedOutput(sharedInput, {
829
+ projectName: projectName(input.cwd, tomlFiles),
830
+ identity: resolveDefaultIdentity(input.cwd, input.projectType, input.siblingFiles, tomlFiles).text,
825
831
  conventions: inferConventions(input.cwd, input.siblingFiles, input.codewiki),
826
832
  invariants: inferInvariants(input.siblingFiles),
827
833
  sections: inferHeuristicSections(input),
@@ -1229,6 +1235,7 @@ function codewikiEntryPoints(codewiki: Codewiki, limit = 6): string[] {
1229
1235
 
1230
1236
  export async function runBootstrap(input: RunBootstrapInput = {}): Promise<RunBootstrapResult> {
1231
1237
  const cwd = input.cwd ?? process.cwd();
1238
+ const tomlFiles = createTomlFileReader(cwd);
1232
1239
  const projectType = detectProjectType(cwd);
1233
1240
  progress(input, {
1234
1241
  phase: "scan",
@@ -1327,7 +1334,8 @@ export async function runBootstrap(input: RunBootstrapInput = {}): Promise<RunBo
1327
1334
  });
1328
1335
  output = await (input.generate ?? heuristicBootstrapOutput)({
1329
1336
  cwd,
1330
- expectedProjectName: projectName(cwd),
1337
+ expectedProjectName: projectName(cwd, tomlFiles),
1338
+ tomlFiles,
1331
1339
  projectType,
1332
1340
  siblingFiles,
1333
1341
  adoption,
@@ -1342,7 +1350,8 @@ export async function runBootstrap(input: RunBootstrapInput = {}): Promise<RunBo
1342
1350
  output = stabilizeGeneratedOutput(
1343
1351
  {
1344
1352
  cwd,
1345
- expectedProjectName: projectName(cwd),
1353
+ expectedProjectName: projectName(cwd, tomlFiles),
1354
+ tomlFiles,
1346
1355
  projectType,
1347
1356
  siblingFiles,
1348
1357
  adoption,
@@ -1372,7 +1381,7 @@ export async function runBootstrap(input: RunBootstrapInput = {}): Promise<RunBo
1372
1381
  } else {
1373
1382
  output = existingParsed
1374
1383
  ? bootstrapOutputFromParsed(existingParsed)
1375
- : await heuristicBootstrapOutput({ cwd, projectType, siblingFiles, adoption, codewiki });
1384
+ : await heuristicBootstrapOutput({ cwd, tomlFiles, projectType, siblingFiles, adoption, codewiki });
1376
1385
  progress(input, {
1377
1386
  phase: "generate",
1378
1387
  status: "completed",
@@ -19,6 +19,7 @@
19
19
  import { existsSync, readdirSync, readFileSync } from "node:fs";
20
20
  import { join } from "node:path";
21
21
  import { parse as parseYaml } from "yaml";
22
+ import { createTomlFileReader, type TomlFileReader, tomlTableAt } from "../../core/toml.js";
22
23
 
23
24
  /** Longest description worth carrying into a one-sentence identity line. */
24
25
  const MAX_DESCRIPTION_CHARS = 400;
@@ -39,7 +40,7 @@ interface MetadataFragment {
39
40
  description?: string | null;
40
41
  }
41
42
 
42
- type MetadataReader = (cwd: string) => { file: string; fragment: MetadataFragment } | null;
43
+ type MetadataReader = (cwd: string, tomlFiles: TomlFileReader) => { file: string; fragment: MetadataFragment } | null;
43
44
 
44
45
  function readText(cwd: string, relative: string): string | null {
45
46
  try {
@@ -83,54 +84,6 @@ function jsonRecord(cwd: string, relative: string): Record<string, unknown> | nu
83
84
  }
84
85
  }
85
86
 
86
- /**
87
- * One key from one TOML table, without a TOML parser.
88
- *
89
- * Deliberately narrow: it reads `key = "value"` (single-line, single- or
90
- * double-quoted) from the named top-level table and understands nothing else.
91
- * That is the entire shape `description` and `name` take in `pyproject.toml`
92
- * and `Cargo.toml`. A multi-line or otherwise exotic value reads as absent,
93
- * which costs a description and never produces a wrong one.
94
- */
95
- function tomlTableValue(raw: string, table: string, key: string): string | null {
96
- const lines = raw.split(/\r?\n/u);
97
- let inTable = false;
98
- for (const line of lines) {
99
- const trimmed = line.trim();
100
- if (trimmed.startsWith("[")) {
101
- inTable = trimmed === `[${table}]`;
102
- continue;
103
- }
104
- if (!inTable) continue;
105
- const match = /^([A-Za-z0-9_-]+)\s*=\s*(.*)$/u.exec(trimmed);
106
- if (!match || match[1] !== key) continue;
107
- const value = (match[2] ?? "").trim();
108
- const quoted = /^"((?:[^"\\]|\\.)*)"$/u.exec(value) ?? /^'([^']*)'$/u.exec(value);
109
- if (!quoted) return null;
110
- return (quoted[1] ?? "").replace(/\\"/gu, '"');
111
- }
112
- return null;
113
- }
114
-
115
- /**
116
- * Whether a TOML file declares the named top-level table, e.g. `tool.tox`.
117
- * A trimmed line must start with `[table]` and carry nothing after it but an
118
- * optional `# comment`, the only content TOML itself allows there. A raw
119
- * `raw.includes("[table]")` check would also fire on a comment mentioning the
120
- * table or a string value that happens to contain the same bracketed text;
121
- * anchoring to the start of the line rules both out without a TOML parser,
122
- * while still accepting the valid `[table] # comment` form that a plain
123
- * equality check would have missed.
124
- */
125
- export function hasTomlTable(raw: string, table: string): boolean {
126
- const header = `[${table}]`;
127
- return raw.split(/\r?\n/u).some((line) => {
128
- const trimmed = line.trim();
129
- if (!trimmed.startsWith(header)) return false;
130
- return /^\s*(?:#.*)?$/u.test(trimmed.slice(header.length));
131
- });
132
- }
133
-
134
87
  /** One `KEY = value` line from a Doxyfile or an INI-shaped config. */
135
88
  function keyEqualsValue(raw: string, key: string): string | null {
136
89
  const match = new RegExp(`^[ \\t]*${key}[ \\t]*(?:\\+?=)[ \\t]*(.+)$`, "mu").exec(raw);
@@ -182,27 +135,30 @@ const packageJson: MetadataReader = (cwd) => {
182
135
  return { file: "package.json", fragment: { name: name(record.name), description: description(record.description) } };
183
136
  };
184
137
 
185
- const pyprojectToml: MetadataReader = (cwd) => {
186
- const raw = readText(cwd, "pyproject.toml");
187
- if (raw === null) return null;
138
+ const pyprojectToml: MetadataReader = (_cwd, tomlFiles) => {
139
+ const document = tomlFiles.read("pyproject.toml");
140
+ if (document === null) return null;
141
+ const project = tomlTableAt(document, ["project"]);
142
+ const poetry = tomlTableAt(document, ["tool", "poetry"]);
188
143
  // PEP 621 `[project]` first, then Poetry's older table.
189
- const projectName = tomlTableValue(raw, "project", "name") ?? tomlTableValue(raw, "tool.poetry", "name");
190
- const projectDescription =
191
- tomlTableValue(raw, "project", "description") ?? tomlTableValue(raw, "tool.poetry", "description");
192
144
  return {
193
145
  file: "pyproject.toml",
194
- fragment: { name: name(projectName), description: description(projectDescription) },
146
+ fragment: {
147
+ name: name(project?.name) ?? name(poetry?.name),
148
+ description: description(project?.description) ?? description(poetry?.description),
149
+ },
195
150
  };
196
151
  };
197
152
 
198
- const cargoToml: MetadataReader = (cwd) => {
199
- const raw = readText(cwd, "Cargo.toml");
200
- if (raw === null) return null;
153
+ const cargoToml: MetadataReader = (_cwd, tomlFiles) => {
154
+ const document = tomlFiles.read("Cargo.toml");
155
+ if (document === null) return null;
156
+ const packageTable = tomlTableAt(document, ["package"]);
201
157
  return {
202
158
  file: "Cargo.toml",
203
159
  fragment: {
204
- name: name(tomlTableValue(raw, "package", "name")),
205
- description: description(tomlTableValue(raw, "package", "description")),
160
+ name: name(packageTable?.name),
161
+ description: description(packageTable?.description),
206
162
  },
207
163
  };
208
164
  };
@@ -488,13 +444,13 @@ function readmeTitle(content: string): string | null {
488
444
  * a CMake project that names itself but describes itself only in its README
489
445
  * should get both, not one or neither.
490
446
  */
491
- export function readProjectMetadata(cwd: string): ProjectMetadata {
447
+ export function readProjectMetadata(cwd: string, tomlFiles = createTomlFileReader(cwd)): ProjectMetadata {
492
448
  const out: ProjectMetadata = { name: null, description: null, nameSource: null, descriptionSource: null };
493
449
  for (const reader of METADATA_READERS) {
494
450
  if (out.name !== null && out.description !== null) break;
495
451
  let read: ReturnType<MetadataReader>;
496
452
  try {
497
- read = reader(cwd);
453
+ read = reader(cwd, tomlFiles);
498
454
  } catch {
499
455
  continue;
500
456
  }