@iowarp/clio-coder 0.3.6 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +22 -6
  3. package/dist/{acp-2BEHC4DL.js → acp-U67UHUK2.js} +14 -14
  4. package/dist/{agents-LNNFTM53.js → agents-YU6SGALZ.js} +42 -35
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{auth-KXXFI2VS.js → auth-5ZPJOIVG.js} +32 -24
  7. package/dist/builtins-C6JMZVV6.js +17 -0
  8. package/dist/{chunk-E25LMLRW.js → chunk-26LEYJZH.js} +2 -2
  9. package/dist/{chunk-TSHXZTOQ.js → chunk-2HEJ2F35.js} +5 -5
  10. package/dist/{chunk-PBTHKCPN.js → chunk-2HFZQUHL.js} +7 -7
  11. package/dist/{chunk-FO5ZOVUY.js → chunk-3BINW3FP.js} +5 -5
  12. package/dist/{chunk-4OC57DA6.js → chunk-4DGYLA73.js} +53 -2
  13. package/dist/{chunk-CKXWIANG.js → chunk-4SPRNWDE.js} +18 -16
  14. package/dist/{chunk-E2ER4LJF.js → chunk-5C3AQNDW.js} +25 -1
  15. package/dist/{chunk-XF5N4U5A.js → chunk-5DHKRSMQ.js} +9 -8
  16. package/dist/{chunk-43AOLP7E.js → chunk-5FR74PWO.js} +2 -1
  17. package/dist/{chunk-EKY57CSP.js → chunk-5H3GB5BO.js} +68 -772
  18. package/dist/{chunk-6XXKFVSN.js → chunk-5Q2VVUKB.js} +4 -4
  19. package/dist/{chunk-DJVECN66.js → chunk-7RFXX52T.js} +295 -3705
  20. package/dist/{chunk-ZXF4XRKW.js → chunk-7RGZWPB6.js} +158 -8
  21. package/dist/{chunk-FYYLNIL5.js → chunk-A2NJGIB3.js} +2 -2
  22. package/dist/{chunk-XXQNGV4M.js → chunk-A3WNZD3P.js} +548 -72
  23. package/dist/{chunk-VEZEGCGW.js → chunk-B5CSFE7B.js} +23 -21
  24. package/dist/{chunk-2SFS6XQE.js → chunk-DGSYXYMX.js} +3 -2
  25. package/dist/chunk-DR52UMZW.js +21 -0
  26. package/dist/{chunk-ZWLZP4ZT.js → chunk-DYIM5TJT.js} +98 -12
  27. package/dist/{chunk-QKMUKYO7.js → chunk-E77JEWSD.js} +270 -125
  28. package/dist/{chunk-LYF7OHWH.js → chunk-EMYUUSFG.js} +12 -467
  29. package/dist/{chunk-4VP4KH3K.js → chunk-EQ63NRB7.js} +8 -8
  30. package/dist/chunk-FBVTI2TJ.js +518 -0
  31. package/dist/{chunk-R46L2BIR.js → chunk-FHJEP5SW.js} +19 -25
  32. package/dist/{chunk-WR67VIZY.js → chunk-GN57SG4G.js} +66 -8
  33. package/dist/{chunk-RD5U66HV.js → chunk-GPIEI3LY.js} +9 -9
  34. package/dist/{chunk-PCZJO5TI.js → chunk-GU2UIAFZ.js} +13 -178
  35. package/dist/chunk-GWS3VEIW.js +195 -0
  36. package/dist/chunk-H7IXIC72.js +103 -0
  37. package/dist/{chunk-3BPUFZDL.js → chunk-HLE42MG7.js} +3 -3
  38. package/dist/{chunk-24I7BN55.js → chunk-IFBNV6H6.js} +3 -3
  39. package/dist/{chunk-QNQHSOLF.js → chunk-IGWKHNIQ.js} +114 -55
  40. package/dist/chunk-IIZWH4XA.js +172 -0
  41. package/dist/{chunk-AD2SYQYC.js → chunk-IJ7RPIYJ.js} +124 -6
  42. package/dist/chunk-J3YUBZWY.js +382 -0
  43. package/dist/{chunk-5JGRAMKL.js → chunk-JOZYP4GM.js} +8 -6
  44. package/dist/{chunk-OH3TOQTB.js → chunk-K4XHGFR5.js} +751 -15
  45. package/dist/{chunk-EYPA3EGJ.js → chunk-KTYTFRMB.js} +178 -14
  46. package/dist/chunk-LU7P4LHA.js +33 -0
  47. package/dist/chunk-ME6CCNFO.js +108 -0
  48. package/dist/chunk-MXKJU4JB.js +1100 -0
  49. package/dist/{chunk-RY3LY4J5.js → chunk-N22QMJKY.js} +21 -18
  50. package/dist/chunk-NMPKI6XL.js +3006 -0
  51. package/dist/chunk-NUGM5KR6.js +165 -0
  52. package/dist/{chunk-22NAGB7X.js → chunk-P43ETTHK.js} +5 -94
  53. package/dist/{chunk-NILBFAPG.js → chunk-PMDBGQSJ.js} +2 -2
  54. package/dist/chunk-PT7HYKEM.js +165 -0
  55. package/dist/chunk-RVG5JXAL.js +41 -0
  56. package/dist/{chunk-GEYXPTRF.js → chunk-RWSI4YD7.js} +2 -2
  57. package/dist/{chunk-4BPJXDWC.js → chunk-TANS5ZJS.js} +35 -19
  58. package/dist/{verifiers-NCBTHHN2.js → chunk-TB5666IT.js} +67 -324
  59. package/dist/{chunk-QM3F2GKX.js → chunk-TLQJPP24.js} +7927 -8088
  60. package/dist/{chunk-G7MUEIGA.js → chunk-TT36MB5S.js} +2 -1
  61. package/dist/chunk-TTHACPOM.js +961 -0
  62. package/dist/{chunk-6US73PDB.js → chunk-TYPGUK6W.js} +7 -7
  63. package/dist/{chunk-MFFY33HR.js → chunk-U6MBIEMB.js} +554 -209
  64. package/dist/chunk-VAWNZU7Z.js +242 -0
  65. package/dist/{chunk-IR4CFBFN.js → chunk-VCBR6CU7.js} +12 -12
  66. package/dist/{chunk-WHJYKASB.js → chunk-VHN4MY6O.js} +2 -2
  67. package/dist/{chunk-XYDYPRZI.js → chunk-VWZOAB7K.js} +10 -10
  68. package/dist/{chunk-ZRGEBJ4T.js → chunk-WLFILSD5.js} +48 -48
  69. package/dist/{chunk-K7T3E2SR.js → chunk-WNIJTQQK.js} +12 -11
  70. package/dist/{chunk-CYQKWTG3.js → chunk-WSB3FPX7.js} +68 -20
  71. package/dist/{chunk-KHSFENX2.js → chunk-WWCZ5F23.js} +116 -14
  72. package/dist/{chunk-XE2VEJHX.js → chunk-WXY7KU3G.js} +2 -2
  73. package/dist/{chunk-PPAMZ32Z.js → chunk-XK56QHLX.js} +6 -1
  74. package/dist/{chunk-KOHPCX4K.js → chunk-XWSF374K.js} +5 -5
  75. package/dist/{chunk-CJUB2JJ2.js → chunk-YS5VLNH5.js} +10 -10
  76. package/dist/{chunk-ZZMN5OM4.js → chunk-ZNLWCMVZ.js} +2 -2
  77. package/dist/{chunk-WHGPSPT5.js → chunk-ZVJ5BLO2.js} +2 -2
  78. package/dist/cli/index.js +33 -31
  79. package/dist/{clio-M2KGYUFZ.js → clio-QVTYJ57A.js} +10 -10
  80. package/dist/{code-nav-GQNL7XA6.js → code-nav-FGGFIE7L.js} +5 -5
  81. package/dist/codewiki/build-worker.js +4 -4
  82. package/dist/{components-5TTYYX6G.js → components-ZFA3SAER.js} +9 -9
  83. package/dist/{config-XUUYQIWO.js → config-LW5IJFQN.js} +65 -55
  84. package/dist/{configure-IHJ7YOMV.js → configure-7XIZCOU4.js} +29 -24
  85. package/dist/{context-75MIWW3U.js → context-L3WL3X7K.js} +54 -43
  86. package/dist/{context-ZQ7SIFJV.js → context-N52ZA626.js} +30 -14
  87. package/dist/{context-74JLXAWD.js → context-Y6Y7QPR6.js} +12 -12
  88. package/dist/{context-clear-GYKWNUML.js → context-clear-MBQRLSDQ.js} +54 -43
  89. package/dist/{context-index-SSR5ECNE.js → context-index-HVMFQHK3.js} +5 -5
  90. package/dist/{context-working-set-UX5KEP4J.js → context-working-set-GS6DSO7F.js} +18 -18
  91. package/dist/{dispatch-runner-GIJBHNFL.js → dispatch-runner-22ZCNOM3.js} +367 -79
  92. package/dist/{docs-6FZSCG5B.js → docs-7LQ23DLM.js} +9 -9
  93. package/dist/{doctor-SVJ5BZCW.js → doctor-M7YEDGAE.js} +27 -23
  94. package/dist/{eval-CG6LLBLD.js → eval-BEC2WHDA.js} +73 -20
  95. package/dist/{evidence-ZYFIEN42.js → evidence-REJUMSKM.js} +70 -59
  96. package/dist/{evolve-QGEXEMDW.js → evolve-PY5ZBA5K.js} +50 -39
  97. package/dist/{extensions-ADGNCJJD.js → extensions-HVKU65YU.js} +7 -7
  98. package/dist/{fleet-S5R4ZOQY.js → fleet-7WZEWRFA.js} +236 -377
  99. package/dist/fleet-commands-UVHWM76J.js +70 -0
  100. package/dist/fleet-graph-6ULH7PES.js +125 -0
  101. package/dist/fleet-new-RDVJLHHH.js +48 -0
  102. package/dist/{fleet-preflight-BHSNPBMH.js → fleet-preflight-J53T6CCE.js} +5 -5
  103. package/dist/fleet-validate-72PC4SLA.js +79 -0
  104. package/dist/{init-5DRU55YR.js → init-OG3TPGQG.js} +69 -57
  105. package/dist/library-CNTMPLRF.js +217 -0
  106. package/dist/{memory-7YKKR6UC.js → memory-6IS7F275.js} +52 -41
  107. package/dist/{models-ZPOLRU2C.js → models-ENRJDA5W.js} +37 -31
  108. package/dist/{monitor-US5F5YGZ.js → monitor-XLDVO7TN.js} +79 -42
  109. package/dist/{orchestrator-E2AL4T5N.js → orchestrator-6KSPYRHA.js} +4126 -762
  110. package/dist/{paths-E7KYAQWE.js → paths-DBXMZMDU.js} +6 -6
  111. package/dist/registry-LG64LTF4.js +11 -0
  112. package/dist/{reset-KZ652EK6.js → reset-RZ4ER727.js} +12 -12
  113. package/dist/{run-SRNBKDWD.js → run-Y2CNK5RU.js} +89 -76
  114. package/dist/{share-CGZE33UP.js → share-A55GYP6Z.js} +37 -13
  115. package/dist/{skills-S2X4DLY5.js → skills-ALC5J6AT.js} +29 -13
  116. package/dist/{skills-eval-W2GGIC4R.js → skills-eval-JPBEBYQU.js} +60 -48
  117. package/dist/support-MIETYA5E.js +38 -0
  118. package/dist/{targets-54SWINWB.js → targets-VGNXIR3S.js} +46 -36
  119. package/dist/{terminal-lease-SAIF2OGY.js → terminal-lease-WOBR64YA.js} +4 -4
  120. package/dist/{uninstall-BVLWXKBT.js → uninstall-ZJF5H5ZN.js} +9 -9
  121. package/dist/{upgrade-JKAR27XC.js → upgrade-FUSUAGHR.js} +31 -28
  122. package/dist/{usage-MSAWCLX4.js → usage-N4MKVHKD.js} +140 -68
  123. package/dist/verifiers-YAWOJ3H2.js +336 -0
  124. package/dist/{verify-X5HDROLA.js → verify-LTDHYBGY.js} +10 -9
  125. package/dist/{wiki-generate-GUSOQ6ZP.js → wiki-generate-6M7GHTBJ.js} +75 -62
  126. package/dist/worker/entry.js +67 -57
  127. package/docs/README.md +3 -2
  128. package/docs/acp.md +1 -1
  129. package/docs/alcf-provider.md +1 -1
  130. package/docs/architecture.md +2 -2
  131. package/docs/artifact-versions.md +11 -6
  132. package/docs/built-in-agents.md +26 -2
  133. package/docs/capacity-and-scheduling.md +1 -1
  134. package/docs/commands-and-modes.md +83 -3
  135. package/docs/configuration-and-targets.md +86 -4
  136. package/docs/context-engine.md +1 -1
  137. package/docs/development-pipeline.md +1 -1
  138. package/docs/dispatch-architecture-rationale.md +1 -1
  139. package/docs/documentation-coverage.md +3 -3
  140. package/docs/documentation-guide.md +3 -3
  141. package/docs/eval-runner.md +1 -1
  142. package/docs/evals-internal.md +1 -1
  143. package/docs/evidence-and-memory.md +74 -10
  144. package/docs/evolution.md +1 -1
  145. package/docs/exit-codes-and-output.md +4 -1
  146. package/docs/extensions-and-sharing.md +6 -2
  147. package/docs/fleet-demo-runbook.md +2 -2
  148. package/docs/fleet-dispatch.md +228 -16
  149. package/docs/git-commit-provenance.md +2 -2
  150. package/docs/glossary.md +22 -2
  151. package/docs/installation-and-lifecycle.md +2 -2
  152. package/docs/middleware-and-components.md +2 -1
  153. package/docs/model-catalog.md +1 -1
  154. package/docs/observability.md +56 -9
  155. package/docs/proactive-memory.md +1 -1
  156. package/docs/prompt-envelope-and-tools.md +4 -2
  157. package/docs/provider-adapter-cookbook.md +1 -1
  158. package/docs/release-cut-checklist.md +83 -65
  159. package/docs/resource-library.md +59 -0
  160. package/docs/safety-model.md +2 -2
  161. package/docs/scientific-validation.md +3 -3
  162. package/docs/session-lifecycle.md +37 -1
  163. package/docs/skills-marketplace.md +16 -3
  164. package/docs/tool-usage.md +14 -7
  165. package/docs/trace-store.md +1 -1
  166. package/docs/troubleshooting.md +1 -1
  167. package/docs/tui-design.md +1 -1
  168. package/docs/worker-dispatch-mechanics.md +3 -3
  169. package/package.json +1 -2
  170. package/src/cli/argv.ts +5 -0
  171. package/src/cli/configure.ts +107 -23
  172. package/src/cli/doctor.ts +5 -1
  173. package/src/cli/evidence.ts +30 -25
  174. package/src/cli/fleet-commands.ts +37 -0
  175. package/src/cli/fleet-graph.ts +102 -0
  176. package/src/cli/fleet-new.ts +36 -0
  177. package/src/cli/fleet-preflight.ts +111 -0
  178. package/src/cli/fleet-validate.ts +30 -0
  179. package/src/cli/fleet.ts +173 -335
  180. package/src/cli/index.ts +3 -1
  181. package/src/cli/library.ts +190 -0
  182. package/src/cli/share.ts +13 -1
  183. package/src/cli/shared.ts +1 -0
  184. package/src/cli/targets.ts +4 -1
  185. package/src/cli/usage.ts +111 -19
  186. package/src/cli/validate-model.ts +60 -5
  187. package/src/core/bus-events.ts +29 -0
  188. package/src/core/commit-attribution.ts +4 -4
  189. package/src/core/config.ts +130 -0
  190. package/src/core/defaults.ts +81 -0
  191. package/src/core/path-boundary.ts +100 -0
  192. package/src/domains/agents/builtins/architect.md +1 -0
  193. package/src/domains/agents/builtins/oracle.md +33 -0
  194. package/src/domains/agents/catalog.ts +13 -1
  195. package/src/domains/agents/extension.ts +2 -11
  196. package/src/domains/agents/fleet-contract.ts +304 -24
  197. package/src/domains/agents/index.ts +14 -0
  198. package/src/domains/agents/recipe.ts +7 -1
  199. package/src/domains/agents/registry.ts +73 -5
  200. package/src/domains/agents/result-contract.ts +360 -15
  201. package/src/domains/agents/write-boundary.ts +15 -50
  202. package/src/domains/config/classify.ts +4 -0
  203. package/src/domains/context/project-rules.ts +51 -1
  204. package/src/domains/dispatch/active-route-planner.ts +14 -0
  205. package/src/domains/dispatch/assignment-reconcile.ts +22 -5
  206. package/src/domains/dispatch/assignment-store.ts +151 -14
  207. package/src/domains/dispatch/backoff.ts +2 -1
  208. package/src/domains/dispatch/capability-match.ts +1 -0
  209. package/src/domains/dispatch/checkout-writer-lease.ts +175 -0
  210. package/src/domains/dispatch/contract.ts +48 -0
  211. package/src/domains/dispatch/delegation-plan.ts +164 -0
  212. package/src/domains/dispatch/execution-plan.ts +76 -5
  213. package/src/domains/dispatch/execution-role.ts +12 -2
  214. package/src/domains/dispatch/execution-scheduler.ts +183 -67
  215. package/src/domains/dispatch/extension.ts +412 -74
  216. package/src/domains/dispatch/fleet-gate.ts +14 -0
  217. package/src/domains/dispatch/fleet-plan.ts +63 -3
  218. package/src/domains/dispatch/fleet-run.ts +791 -0
  219. package/src/domains/dispatch/gate-role-prompts.ts +47 -0
  220. package/src/domains/dispatch/host-verification.ts +178 -0
  221. package/src/domains/dispatch/index.ts +41 -1
  222. package/src/domains/dispatch/intent-requirements.ts +40 -0
  223. package/src/domains/dispatch/intent.ts +235 -0
  224. package/src/domains/dispatch/path-scope.ts +370 -0
  225. package/src/domains/dispatch/receipt-integrity.ts +9 -4
  226. package/src/domains/dispatch/state.ts +36 -3
  227. package/src/domains/dispatch/types.ts +64 -12
  228. package/src/domains/dispatch/validation.ts +69 -6
  229. package/src/domains/dispatch/write-boundary-enforcer.ts +45 -0
  230. package/src/domains/dispatch/write-boundary.ts +201 -22
  231. package/src/domains/eval/metrics/evidence.ts +79 -2
  232. package/src/domains/eval/runners/clio-run.ts +12 -2
  233. package/src/domains/evidence/build.ts +69 -11
  234. package/src/domains/evidence/index.ts +21 -0
  235. package/src/domains/evidence/provenance.ts +46 -11
  236. package/src/domains/evidence/trust-projection.ts +274 -0
  237. package/src/domains/evidence/trust-status.ts +155 -18
  238. package/src/domains/evidence/types.ts +4 -0
  239. package/src/domains/extensions/discovery.ts +88 -1
  240. package/src/domains/extensions/resources.ts +20 -8
  241. package/src/domains/extensions/state.ts +6 -2
  242. package/src/domains/extensions/types.ts +4 -1
  243. package/src/domains/lifecycle/doctor.ts +140 -1
  244. package/src/domains/middleware/index.ts +15 -0
  245. package/src/domains/middleware/watchdog.ts +281 -0
  246. package/src/domains/observability/contract.ts +3 -1
  247. package/src/domains/observability/cost.ts +12 -1
  248. package/src/domains/observability/extension.ts +2 -2
  249. package/src/domains/observability/index.ts +10 -0
  250. package/src/domains/observability/out-of-turn-usage.ts +223 -0
  251. package/src/domains/prompts/contract.ts +3 -5
  252. package/src/domains/providers/extension.ts +30 -2
  253. package/src/domains/resources/common-loader.ts +3 -0
  254. package/src/domains/resources/index.ts +20 -0
  255. package/src/domains/resources/library.ts +326 -0
  256. package/src/domains/resources/prompts/loader.ts +119 -14
  257. package/src/domains/resources/skills/marketplace.ts +37 -12
  258. package/src/domains/safety/policy-engine.ts +5 -5
  259. package/src/domains/safety/run-effects.ts +64 -1
  260. package/src/domains/safety/scope.ts +7 -12
  261. package/src/domains/session/handoff.ts +629 -0
  262. package/src/domains/share/archive.ts +67 -2
  263. package/src/engine/acp/server.ts +4 -1
  264. package/src/engine/prompt-templates.ts +18 -1
  265. package/src/engine/worker-runtime.ts +6 -3
  266. package/src/entry/orchestrator.ts +37 -0
  267. package/src/interactive/bus-notices.ts +26 -0
  268. package/src/interactive/chat-loop.ts +235 -1
  269. package/src/interactive/chat-renderer.ts +22 -0
  270. package/src/interactive/cost-overlay.ts +31 -3
  271. package/src/interactive/council-dispatch.ts +30 -0
  272. package/src/interactive/council-grid.ts +213 -0
  273. package/src/interactive/council.ts +99 -0
  274. package/src/interactive/dispatch-board.ts +311 -17
  275. package/src/interactive/fleet-run-preview.ts +307 -0
  276. package/src/interactive/footer/notifications.ts +219 -0
  277. package/src/interactive/handoff-round.ts +56 -0
  278. package/src/interactive/interactive-application.ts +43 -1
  279. package/src/interactive/interactive-event-projection.ts +23 -1
  280. package/src/interactive/interactive-slash-runtime.ts +52 -2
  281. package/src/interactive/interactive-subscriptions.ts +14 -2
  282. package/src/interactive/oracle.ts +179 -0
  283. package/src/interactive/overlay-ask-user-lifecycle.ts +6 -0
  284. package/src/interactive/overlay-general-openers.ts +190 -1
  285. package/src/interactive/overlay-key-routing.ts +17 -1
  286. package/src/interactive/overlay-lifecycle.ts +41 -1
  287. package/src/interactive/overlay-permission-lifecycle.ts +10 -0
  288. package/src/interactive/overlay-resource-openers.ts +11 -3
  289. package/src/interactive/overlay-session-lifecycle.ts +234 -2
  290. package/src/interactive/overlays/fleet-run-approval.ts +208 -0
  291. package/src/interactive/overlays/handoff-review.ts +185 -0
  292. package/src/interactive/overlays/library-install-confirm.ts +151 -0
  293. package/src/interactive/overlays/list-overlay.ts +168 -2
  294. package/src/interactive/overlays/settings.ts +221 -30
  295. package/src/interactive/overlays/side-question.ts +139 -0
  296. package/src/interactive/overlays/skills-hub.ts +401 -15
  297. package/src/interactive/side-question.ts +171 -0
  298. package/src/interactive/slash-commands.ts +439 -7
  299. package/src/interactive/slash-spec.ts +19 -6
  300. package/src/interactive/theme/tokens.ts +30 -0
  301. package/src/interactive/turn-middleware.ts +15 -1
  302. package/src/interactive/view/artifacts.ts +42 -9
  303. package/src/interactive/view/view-overlay.ts +15 -3
  304. package/src/interactive/watchdog-run.ts +75 -0
  305. package/src/interactive/worker-receipts.ts +14 -2
  306. package/src/interactive/worker-share.ts +56 -1
  307. package/src/interactive/worker-stream.ts +15 -0
  308. package/src/tools/bootstrap.ts +3 -0
  309. package/src/tools/compete-worktrees.ts +13 -79
  310. package/src/tools/dispatch-admission.ts +251 -18
  311. package/src/tools/dispatch-arguments.ts +84 -1
  312. package/src/tools/dispatch-plan.ts +165 -6
  313. package/src/tools/dispatch-runner.ts +364 -23
  314. package/src/tools/dispatch-types.ts +20 -1
  315. package/src/tools/dispatch.ts +72 -2
  316. package/src/tools/monitor.ts +29 -0
  317. package/src/tools/profiles.ts +18 -4
  318. package/src/tools/task-worktree.ts +238 -0
  319. package/src/tools/verify/authoring.ts +61 -1
  320. package/src/tools/verify/scripts.ts +62 -0
  321. package/src/tools/worker-evidence.ts +21 -14
  322. package/src/worker/spec-contract.ts +3 -1
  323. package/dist/chunk-HC4CLZ2Y.js +0 -68
@@ -2,13 +2,16 @@ import { createReadStream, readFileSync, statSync } from "node:fs";
2
2
  import { readFile, realpath, stat } from "node:fs/promises";
3
3
  import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
4
4
  import type { DispatchContract } from "../../domains/dispatch/contract.js";
5
- import {
6
- isReceiptIntegrity,
7
- type ReceiptIntegrityResult,
8
- verifyReceiptIntegrity,
9
- } from "../../domains/dispatch/receipt-integrity.js";
5
+ import { isReceiptIntegrity, verifyReceiptIntegrity } from "../../domains/dispatch/receipt-integrity.js";
10
6
  import type { RunEnvelope, RunReceipt } from "../../domains/dispatch/types.js";
11
7
  import { evidenceDirectory, inspectEvidence, listEvidenceOverviews } from "../../domains/evidence/store.js";
8
+ import { formatTrustSummaryLine } from "../../domains/evidence/trust-projection.js";
9
+ import {
10
+ inspectRunReceiptTrustStatus,
11
+ type ReceiptIntegrityOutcome,
12
+ retiredReceiptIntegrity,
13
+ retiredReceiptIntegrityReason,
14
+ } from "../../domains/evidence/trust-status.js";
12
15
  import type { EvidenceFinding, EvidenceOverview, EvidenceSource } from "../../domains/evidence/types.js";
13
16
  import { type AccountabilitySummary, readAccountabilitySummary } from "../../domains/observability/index.js";
14
17
  import type {
@@ -72,7 +75,7 @@ export interface ViewArtifact {
72
75
  toolName?: string;
73
76
  searchText?: readonly string[];
74
77
  load(): Promise<ViewArtifactLoadResult>;
75
- verify?(): Promise<{ ok: boolean; detail: string }>;
78
+ verify?(): Promise<ViewArtifactVerification>;
76
79
  }
77
80
 
78
81
  export interface ArtifactProvider {
@@ -108,7 +111,14 @@ const ACCOUNTABILITY_TOP_CAUSES = 8;
108
111
  export const VIEW_ARTIFACT_LINE_CAP = 50_000;
109
112
  const JSON_PRETTY_MAX_BYTES = 10 * 1024 * 1024;
110
113
 
111
- export type ReceiptVerifyResult = ReceiptIntegrityResult;
114
+ export type ReceiptVerifyResult = ReceiptIntegrityOutcome;
115
+
116
+ /**
117
+ * What a verify action reports. `retired` marks a seal this build does not
118
+ * verify because its integrity version was retired: not a pass, and not the
119
+ * failure a tampered receipt reports.
120
+ */
121
+ export type ViewArtifactVerification = { ok: true; detail: string } | { ok: false; detail: string; retired?: true };
112
122
 
113
123
  const RECEIPT_REQUIRED_KEYS = [
114
124
  "runId",
@@ -286,6 +296,21 @@ function validateToolStats(value: unknown): ReceiptVerifyResult {
286
296
 
287
297
  type ReadLedgerResult = { ok: true; envelope: RunEnvelope } | { ok: false; reason: string };
288
298
 
299
+ /**
300
+ * The canonical trust line for a receipt the integrity verifier just accepted:
301
+ * the same file and ledger row, projected through the shared boundary so the
302
+ * receipt view spells its verdict exactly as the board and the CLI do.
303
+ */
304
+ export function receiptTrustDetail(stateDir: string, runId: string): string {
305
+ try {
306
+ const receipt = JSON.parse(readFileSync(receiptFilePath(stateDir, runId), "utf8")) as RunReceipt;
307
+ const ledger = readRunEnvelope(stateDir, runId);
308
+ return formatTrustSummaryLine(inspectRunReceiptTrustStatus(receipt, ledger.ok ? ledger.envelope : null).status);
309
+ } catch {
310
+ return "integrity verified; trust status unreadable";
311
+ }
312
+ }
313
+
289
314
  function readRunEnvelope(stateDir: string, runId: string): ReadLedgerResult {
290
315
  const runs = readRunLedger(stateDir);
291
316
  for (const entry of runs) {
@@ -398,6 +423,10 @@ export function verifyReceiptFile(stateDir: string, runId: string): ReceiptVerif
398
423
  if (!isNullableString(r.sessionId)) {
399
424
  return { ok: false, reason: `sessionId invalid: ${String(r.sessionId)}` };
400
425
  }
426
+ // A retired seal is diagnosed ahead of the shape check, which would
427
+ // otherwise report the older version as an invalid integrity block.
428
+ const retired = retiredReceiptIntegrity(r.integrity);
429
+ if (retired !== null) return { ok: false, reason: retiredReceiptIntegrityReason(retired), retired };
401
430
  if (!isReceiptIntegrity(r.integrity)) {
402
431
  return { ok: false, reason: "integrity invalid" };
403
432
  }
@@ -673,8 +702,12 @@ export class ReceiptArtifactProvider implements ArtifactProvider {
673
702
  load: () => loadJsonFileLines(path),
674
703
  verify: async () => {
675
704
  const result = verifyReceiptFile(this.deps.stateDir, env.id);
676
- if (!result.ok) return { ok: false, detail: result.reason };
677
- return { ok: true, detail: "integrity verified" };
705
+ if (!result.ok) {
706
+ return { ok: false, detail: result.reason, ...(result.retired !== undefined ? { retired: true } : {}) };
707
+ }
708
+ // "integrity verified" alone read as a verified result. The
709
+ // canonical line says what the seal proves and what it does not.
710
+ return { ok: true, detail: receiptTrustDetail(this.deps.stateDir, env.id) };
678
711
  },
679
712
  };
680
713
  });
@@ -51,7 +51,9 @@ export type ViewVerificationState =
51
51
  | { status: "idle" }
52
52
  | { status: "running" }
53
53
  | { status: "ok"; detail: string }
54
- | { status: "fail"; detail: string };
54
+ | { status: "fail"; detail: string }
55
+ /** A seal this build does not verify because its version was retired: neither a pass nor a tampered-receipt failure. */
56
+ | { status: "retired"; detail: string };
55
57
 
56
58
  export type ViewScrollAction =
57
59
  | "line-up"
@@ -327,6 +329,7 @@ function verificationText(state: ViewVerificationState | undefined): string {
327
329
  if (!state || state.status === "idle") return "";
328
330
  if (state.status === "running") return `${GLYPH.running} verify running`;
329
331
  if (state.status === "ok") return `${GLYPH.ok} verify ok ${state.detail}`;
332
+ if (state.status === "retired") return `${GLYPH.warn} verify retired ${state.detail}`;
330
333
  return `${GLYPH.error} verify fail ${state.detail}`;
331
334
  }
332
335
 
@@ -343,7 +346,14 @@ export function buildArtifactHeader(
343
346
  if (size.length > 0) parts.push(theme.fg("dim", size));
344
347
  const verify = verificationText(verification);
345
348
  if (verify.length > 0) {
346
- const token = verification?.status === "ok" ? "success" : verification?.status === "fail" ? "error" : "info";
349
+ const token =
350
+ verification?.status === "ok"
351
+ ? "success"
352
+ : verification?.status === "fail"
353
+ ? "error"
354
+ : verification?.status === "retired"
355
+ ? "warning"
356
+ : "info";
347
357
  parts.push(theme.fg(token, verify));
348
358
  }
349
359
  return padAnsi(parts.join(" "), width, ELLIPSIS);
@@ -689,7 +699,9 @@ export class ViewOverlayView implements Component {
689
699
  .then((result) => {
690
700
  this.verifications.set(
691
701
  key,
692
- result.ok ? { status: "ok", detail: result.detail } : { status: "fail", detail: result.detail },
702
+ result.ok
703
+ ? { status: "ok", detail: result.detail }
704
+ : { status: result.retired === true ? "retired" : "fail", detail: result.detail },
693
705
  );
694
706
  })
695
707
  .catch((err) => {
@@ -0,0 +1,75 @@
1
+ /**
2
+ * The watchdog's run: one read-only verifier dispatch, one transcript notice.
3
+ *
4
+ * The middleware registration decides when to fire and what the turn changed;
5
+ * this decides nothing. It dispatches through the ordinary path so admission,
6
+ * the receipt, and the Fleet Runs island treat a watchdog run exactly as they
7
+ * treat any other, reads the failed checks out of the `verifier-report`
8
+ * contract, and hands the operator one line. A passing report produces nothing
9
+ * at all.
10
+ */
11
+
12
+ import { BusChannels } from "../core/bus-events.js";
13
+ import type { SafeEventBus } from "../core/event-bus.js";
14
+ import { parseVerifierResult } from "../domains/agents/index.js";
15
+ import type { DispatchContract, DispatchRequest } from "../domains/dispatch/contract.js";
16
+ import { type AgentRoleFactsResolver, requestExecutionRole } from "../domains/dispatch/execution-role.js";
17
+ import { WATCHDOG_TASK, type WatchdogTrigger, watchdogBriefing } from "../domains/middleware/index.js";
18
+ import { watchdogBlockersNotice } from "./bus-notices.js";
19
+
20
+ /** The builtin recipe the watchdog reviews with. */
21
+ export const WATCHDOG_AGENT_ID = "verifier";
22
+
23
+ export interface WatchdogRunDeps {
24
+ dispatch: DispatchContract;
25
+ bus?: SafeEventBus;
26
+ getAgentRoleFacts?: AgentRoleFactsResolver;
27
+ /** `watchdog.target` when the operator set one; the session's active target otherwise. */
28
+ target?: string | undefined;
29
+ /** One transcript line for the operator. Absent means the finding has nowhere to go and the run is skipped. */
30
+ emitNotice?: (text: string) => void;
31
+ }
32
+
33
+ /**
34
+ * Dispatch one watchdog review and report what it found.
35
+ *
36
+ * Resolves when the run has settled, whatever the outcome. A watchdog that
37
+ * failed is not a turn that failed: the review is advisory, so a broken run
38
+ * stays silent rather than raising an alarm about itself in the middle of the
39
+ * operator's work.
40
+ */
41
+ export async function runWatchdogReview(trigger: WatchdogTrigger, deps: WatchdogRunDeps): Promise<void> {
42
+ if (!deps.emitNotice) return;
43
+ const request: DispatchRequest = {
44
+ agentId: WATCHDOG_AGENT_ID,
45
+ task: WATCHDOG_TASK,
46
+ briefing: watchdogBriefing(trigger),
47
+ executionRole: requestExecutionRole({
48
+ agentId: WATCHDOG_AGENT_ID,
49
+ ...(deps.getAgentRoleFacts ? { resolveFacts: deps.getAgentRoleFacts } : {}),
50
+ }),
51
+ requestOrigin: "internal",
52
+ // Narrowed below whatever the session holds. A reviewer that could write
53
+ // would be a second builder nobody admitted.
54
+ autonomy: "read-only",
55
+ ...(deps.target ? { target: deps.target } : {}),
56
+ };
57
+ const progressBus = deps.dispatch.ownsProgressBus?.(deps.bus) === true ? undefined : deps.bus;
58
+ try {
59
+ const handle = await deps.dispatch.dispatch(request);
60
+ for await (const event of handle.events) {
61
+ const typed = event as { type?: string };
62
+ if (!typed.type || typed.type === "heartbeat") continue;
63
+ progressBus?.emit(BusChannels.DispatchProgress, { runId: handle.runId, agentId: WATCHDOG_AGENT_ID, event });
64
+ }
65
+ const receipt = await handle.finalPromise;
66
+ const report = parseVerifierResult(receipt.output?.text ?? null);
67
+ if (report === null) return;
68
+ const notice = watchdogBlockersNotice(report.checks);
69
+ if (notice === null) return;
70
+ deps.emitNotice(notice.text);
71
+ } catch {
72
+ // An advisory review that could not run says nothing. The receipt, if one
73
+ // was written at all, is in the dispatch board like every other run.
74
+ }
75
+ }
@@ -16,6 +16,8 @@
16
16
  import { existsSync, readFileSync } from "node:fs";
17
17
  import { join } from "node:path";
18
18
  import { clioStateDir } from "../core/xdg.js";
19
+ import type { RunEnvelope, RunReceipt } from "../domains/dispatch/types.js";
20
+ import { inspectRunReceiptTrustStatus } from "../domains/evidence/trust-status.js";
19
21
  import { receiptFilePath } from "./view/artifacts.js";
20
22
  import type { WorkerReceiptFacts, WorkerResultContract } from "./worker-stream.js";
21
23
 
@@ -86,12 +88,22 @@ export function workerReceiptFacts(receipt: Record<string, unknown>): WorkerRece
86
88
 
87
89
  /** Read and project `<state>/receipts/<runId>.json` alone; null when it is absent or unreadable. */
88
90
  function readReceiptFileFacts(runId: string, stateDir: string): WorkerReceiptFacts | null {
91
+ let parsed: unknown;
89
92
  try {
90
- const parsed: unknown = JSON.parse(readFileSync(receiptFilePath(stateDir, runId), "utf8"));
91
- return isRecord(parsed) ? workerReceiptFacts(parsed) : null;
93
+ parsed = JSON.parse(readFileSync(receiptFilePath(stateDir, runId), "utf8"));
92
94
  } catch {
93
95
  return null;
94
96
  }
97
+ if (!isRecord(parsed)) return null;
98
+ const facts = workerReceiptFacts(parsed);
99
+ if (facts === null) return null;
100
+ // The trust verdict is an authenticated read-back: the receipt file
101
+ // against the ledger row it was sealed from. Without the row there is no
102
+ // authentication, and no verdict is better than a guessed one.
103
+ const row = findRunRow(runId, stateDir);
104
+ if (row === null) return facts;
105
+ const trust = inspectRunReceiptTrustStatus(parsed as unknown as RunReceipt, row as unknown as RunEnvelope).status;
106
+ return { ...facts, trust };
95
107
  }
96
108
 
97
109
  /** Read and project `<state>/receipts/<runId>.json`; null when it is absent or unreadable. */
@@ -11,6 +11,7 @@
11
11
  * Pure: no I/O, no bus, no session.
12
12
  */
13
13
 
14
+ import type { CouncilReport } from "../domains/agents/result-contract.js";
14
15
  import { WORKER_OUTPUT_MAX_BYTES } from "../domains/dispatch/event-pump.js";
15
16
  import { truncateUtf8 } from "../tools/truncate-utf8.js";
16
17
  import { type WorkerEntryState, workerAskedByModel } from "./worker-stream.js";
@@ -56,12 +57,66 @@ export const WORKER_SHARE_ORIGIN = "shared by the operator";
56
57
  * produced no text is not worth a turn.
57
58
  */
58
59
  export function formatWorkerShareNote(facts: WorkerShareFacts): string | null {
59
- const text = facts.text.trim();
60
+ return shareNote(facts, facts.text);
61
+ }
62
+
63
+ /** The header every shared note carries, plus one bounded body under it. */
64
+ function shareNote(facts: WorkerShareFacts, body: string): string | null {
65
+ const text = body.trim();
60
66
  if (text.length === 0) return null;
61
67
  const header = `${WORKER_SHARE_NOTE_PREFIX} ${facts.agentId} · run ${facts.runId} · ${outcomeWord(facts.outcome)} · ${WORKER_SHARE_ORIGIN}`;
62
68
  return `${header}\n${truncateUtf8(text, WORKER_SHARE_MAX_BYTES, SHARE_TRUNCATION_MARKER)}`;
63
69
  }
64
70
 
71
+ /** One member's answer under its roster label, or its failure when it produced none. */
72
+ function councilMemberLine(member: CouncilReport["members"][number]): string {
73
+ if (member.failed !== undefined) return `[${member.label}] failed: ${member.failed.reason}`;
74
+ const verdict = member.verdict === undefined ? "" : ` (verdict ${member.verdict})`;
75
+ return `[${member.label}]${verdict} ${member.answer.trim()}`;
76
+ }
77
+
78
+ /** The synthesis line: what the council concluded, and how it concluded it. */
79
+ function councilSynthesisLines(synthesis: CouncilReport["synthesis"]): string[] {
80
+ if (synthesis.kind === "none") return ["[synthesis none] the members answered separately; no synthesis was run."];
81
+ const facts = [
82
+ ...(synthesis.verdict !== undefined ? [`verdict ${synthesis.verdict}`] : []),
83
+ ...(synthesis.tally !== undefined
84
+ ? [
85
+ `tally ${Object.entries(synthesis.tally)
86
+ .map(([key, count]) => `${key}=${count}`)
87
+ .join(" ")}`,
88
+ ]
89
+ : []),
90
+ ...(synthesis.judgeRunId !== undefined ? [`judge run ${synthesis.judgeRunId}`] : []),
91
+ ];
92
+ const head = `[synthesis ${synthesis.kind}]${facts.length > 0 ? ` ${facts.join(" · ")}` : ""}`;
93
+ const text = synthesis.text?.trim() ?? "";
94
+ return text.length > 0 ? [head, text] : [head];
95
+ }
96
+
97
+ /**
98
+ * A whole council as one operator note: every member's labelled answer, in the
99
+ * order the report seals them, then the synthesis. Sharing a synthesis run id
100
+ * means sharing what the council decided, and the members' answers are what it
101
+ * decided from, so they travel together in one bounded block rather than as
102
+ * several notes the main agent would have to reassemble.
103
+ */
104
+ export function formatCouncilShareNote(facts: WorkerShareFacts, report: CouncilReport): string | null {
105
+ const finalRound = report.members.reduce((highest, member) => Math.max(highest, member.round), 1);
106
+ const body = [
107
+ ...report.members.filter((member) => member.round === finalRound).map(councilMemberLine),
108
+ ...councilSynthesisLines(report.synthesis),
109
+ ].join("\n\n");
110
+ return shareNote(facts, body);
111
+ }
112
+
113
+ /** One council member's answer, labelled with the roster label the operator watched it under. */
114
+ export function formatCouncilMemberShareNote(facts: WorkerShareFacts, label: string): string | null {
115
+ const text = facts.text.trim();
116
+ if (text.length === 0) return null;
117
+ return shareNote(facts, `[${label}] ${text}`);
118
+ }
119
+
65
120
  /**
66
121
  * Whether operator text is a shared worker note. The header is the first line,
67
122
  * so the chat loop can tell the honesty rail a worker result entered the turn
@@ -42,6 +42,7 @@ import type {
42
42
  RunAbortedPayload,
43
43
  } from "../core/bus-events.js";
44
44
  import type { RunKind } from "../domains/dispatch/types.js";
45
+ import type { CanonicalTrustStatus } from "../domains/evidence/trust-status.js";
45
46
  import type { WorkerRunOrigin, WorkerRunRuntime, WorkerRunRuntimeKind } from "../domains/session/index.js";
46
47
  import { createWorkerProgressFold, type WorkerProgressFold, type WorkerProgressSnapshot } from "./worker-progress.js";
47
48
 
@@ -88,6 +89,13 @@ export interface WorkerReceiptSummary {
88
89
  /** A receipt's projection: the summary plus the answer it sealed. */
89
90
  export interface WorkerReceiptFacts extends WorkerReceiptSummary {
90
91
  text?: string;
92
+ /**
93
+ * The canonical trust status of the persisted receipt, authenticated
94
+ * against the persisted ledger row it was sealed from. Present only when
95
+ * both could be read back, so a surface that shows it shows an
96
+ * authenticated verdict and never the sealing process's own claim.
97
+ */
98
+ trust?: CanonicalTrustStatus;
91
99
  }
92
100
 
93
101
  /** Reads `receipts/<runId>.json` and projects it; null when it is absent or unreadable. */
@@ -113,6 +121,12 @@ export interface WorkerEntryState {
113
121
  /** Every attempt of this assignment, oldest first. */
114
122
  attempts: WorkerAttempt[];
115
123
  pending: boolean;
124
+ /**
125
+ * Council provenance, present only on a run of a council group. `/share`
126
+ * reads it so a member's answer reaches the main agent under the roster label
127
+ * the operator watched it under, rather than as one unattributed voice.
128
+ */
129
+ council?: { group: string; label: string; color?: string; round: number };
116
130
  receipt?: WorkerReceiptSummary;
117
131
  /** Agent origin: the tool call whose execution spawned this run. */
118
132
  parentToolCallId?: string;
@@ -323,6 +337,7 @@ export function createWorkerStream(options: WorkerStreamOptions = {}): WorkerStr
323
337
  progress: progress.snapshot(),
324
338
  attempts: [{ runId, targetLabel: workerTargetLabel(runtime) }],
325
339
  pending: true,
340
+ ...(payload.council !== undefined ? { council: { ...payload.council } } : {}),
326
341
  ...(payload.parentToolCallId !== undefined ? { parentToolCallId: payload.parentToolCallId } : {}),
327
342
  };
328
343
  byAssignment.set(assignmentId, entry);
@@ -1,3 +1,4 @@
1
+ import type { WorkerRosters } from "../core/defaults.js";
1
2
  import type { SafeEventBus } from "../core/event-bus.js";
2
3
  import type { AgentSpec } from "../domains/agents/spec.js";
3
4
  import type { DispatchContract } from "../domains/dispatch/contract.js";
@@ -22,6 +23,7 @@ export interface ToolBootstrapDeps extends CoreToolBootstrapDeps {
22
23
  getAgentRoleFacts?: AgentRoleFactsResolver;
23
24
  getAutonomy?: () => AutonomyLevel;
24
25
  getCostCeilingUsd?: () => number;
26
+ getWorkerRosters?: () => WorkerRosters;
25
27
  dispatchBackground?: DispatchBackgroundRegistry;
26
28
  }
27
29
 
@@ -44,6 +46,7 @@ export function registerAllTools(registry: ToolRegistry, deps: ToolBootstrapDeps
44
46
  ...(deps.getAgentRoleFacts ? { getAgentRoleFacts: deps.getAgentRoleFacts } : {}),
45
47
  ...(deps.getAutonomy ? { getAutonomy: deps.getAutonomy } : {}),
46
48
  ...(deps.getCostCeilingUsd ? { getCostCeilingUsd: deps.getCostCeilingUsd } : {}),
49
+ ...(deps.getWorkerRosters ? { getWorkerRosters: deps.getWorkerRosters } : {}),
47
50
  ...(deps.dispatchBackground ? { background: deps.dispatchBackground } : {}),
48
51
  };
49
52
  registry.register({
@@ -33,8 +33,15 @@ import {
33
33
  writeFileSync,
34
34
  } from "node:fs";
35
35
  import { hostname } from "node:os";
36
- import { isAbsolute, join, relative, resolve, sep } from "node:path";
36
+ import { join, relative, resolve } from "node:path";
37
37
  import type { RunKind } from "../domains/dispatch/types.js";
38
+ import {
39
+ commitWorktreePath,
40
+ isCanonicalWorktreePathInside,
41
+ mergeWorktreeBranch,
42
+ protectedPathsChangedByWorktreeBranch,
43
+ worktreeBranchDiffStat,
44
+ } from "./task-worktree.js";
38
45
 
39
46
  export interface CandidateWorktree {
40
47
  index: number;
@@ -300,20 +307,7 @@ function isCanonicalDirectoryAt(path: string): boolean {
300
307
  * be mistaken for a child of `/group-a` as it can with textual startsWith.
301
308
  */
302
309
  export function isCanonicalPathInside(parent: string, candidate: string): boolean {
303
- let canonicalParent: string;
304
- let canonicalCandidate: string;
305
- try {
306
- canonicalParent = realpathSync(parent);
307
- } catch {
308
- canonicalParent = resolve(parent);
309
- }
310
- try {
311
- canonicalCandidate = realpathSync(candidate);
312
- } catch {
313
- canonicalCandidate = resolve(candidate);
314
- }
315
- const rel = relative(canonicalParent, canonicalCandidate);
316
- return rel.length > 0 && rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
310
+ return isCanonicalWorktreePathInside(parent, candidate);
317
311
  }
318
312
 
319
313
  function manifestFromUnknown(value: unknown): CompeteGroupManifest | null {
@@ -557,29 +551,12 @@ export function createCandidateWorktree(
557
551
  * builder changed nothing (an empty candidate is a legitimate ranking fact).
558
552
  */
559
553
  export function commitCandidateWork(worktree: CandidateWorktree, message: string): boolean {
560
- git(worktree.path, ["add", "-A"]);
561
- const staged = git(worktree.path, ["status", "--porcelain"]);
562
- if (staged.length === 0) return false;
563
- git(worktree.path, [
564
- "-c",
565
- `user.name=${COMPETE_COMMIT_IDENTITY}`,
566
- "-c",
567
- `user.email=${COMPETE_COMMIT_IDENTITY}@local`,
568
- "commit",
569
- "-m",
570
- message,
571
- "--no-verify",
572
- ]);
573
- return true;
554
+ return commitWorktreePath(worktree.path, COMPETE_COMMIT_IDENTITY, message);
574
555
  }
575
556
 
576
557
  /** One-line stat summary of what a candidate branch changed relative to HEAD. */
577
558
  export function candidateDiffStat(root: string, branch: string): string {
578
- try {
579
- return git(root, ["diff", "--shortstat", `HEAD...${branch}`]) || "no changes";
580
- } catch {
581
- return "diff unavailable";
582
- }
559
+ return worktreeBranchDiffStat(root, branch);
583
560
  }
584
561
 
585
562
  /**
@@ -592,31 +569,7 @@ export function protectedPathsChangedByCompeteBranch(
592
569
  branch: string,
593
570
  protectedPaths: ReadonlyArray<string>,
594
571
  ): string[] {
595
- const canonical = canonicalRoot(root);
596
- const protectedInside = protectedPaths
597
- .map((path) => resolve(path))
598
- .filter((path) => {
599
- const rel = relative(canonical, path);
600
- return rel.length > 0 && rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
601
- });
602
- if (protectedInside.length === 0) return [];
603
- const changed = git(canonical, ["diff", "--name-only", "-z", "--no-renames", `HEAD...${branch}`])
604
- .split("\0")
605
- .filter((path) => path.length > 0)
606
- .map((path) => resolve(canonical, path));
607
- const blocked = new Set<string>();
608
- for (const candidate of changed) {
609
- for (const protectedPath of protectedInside) {
610
- const rel = relative(protectedPath, candidate);
611
- if (
612
- candidate === protectedPath ||
613
- (rel.length > 0 && rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel))
614
- ) {
615
- blocked.add(protectedPath);
616
- }
617
- }
618
- }
619
- return [...blocked].sort();
572
+ return protectedPathsChangedByWorktreeBranch(root, branch, protectedPaths);
620
573
  }
621
574
 
622
575
  function registeredWorktreePaths(root: string): string[] {
@@ -687,26 +640,7 @@ export function removeCandidateWorktree(
687
640
  * the merge and reports failure so the operator decides.
688
641
  */
689
642
  export function mergeWinnerBranch(root: string, branch: string): { ok: true } | { ok: false; reason: string } {
690
- try {
691
- git(root, [
692
- "-c",
693
- `user.name=${COMPETE_COMMIT_IDENTITY}`,
694
- "-c",
695
- `user.email=${COMPETE_COMMIT_IDENTITY}@local`,
696
- "merge",
697
- "--no-edit",
698
- "--no-verify",
699
- branch,
700
- ]);
701
- return { ok: true };
702
- } catch (err) {
703
- try {
704
- git(root, ["merge", "--abort"]);
705
- } catch {
706
- // No merge in progress; nothing to abort.
707
- }
708
- return { ok: false, reason: err instanceof Error ? (err.message.split("\n")[0] ?? "merge failed") : String(err) };
709
- }
643
+ return mergeWorktreeBranch(root, branch, COMPETE_COMMIT_IDENTITY);
710
644
  }
711
645
 
712
646
  /**