@cleocode/cleo 2026.10.3 → 2026.10.5-rc.ci.672

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 (352) hide show
  1. package/README.md +7 -0
  2. package/bin/cleo.js +11 -1
  3. package/dist/cli/adapter-OUQRSFZL.js +159 -0
  4. package/dist/cli/add-4SRRJL4U.js +443 -0
  5. package/dist/cli/add-batch-TSWICA5J.js +164 -0
  6. package/dist/cli/admin-L3F6CDYH.js +1428 -0
  7. package/dist/cli/admin-YMU55YCU.js +341 -0
  8. package/dist/cli/adr-XESRMFMP.js +172 -0
  9. package/dist/cli/agent-TAFPIYUU.js +2404 -0
  10. package/dist/cli/agent-outputs-HVJJIPS4.js +84 -0
  11. package/dist/cli/analyze-YQ22QVGW.js +49 -0
  12. package/dist/cli/archive-W5XX6AH7.js +65 -0
  13. package/dist/cli/archive-stats-HP2KPAJU.js +101 -0
  14. package/dist/cli/attention-7JBDCRZX.js +124 -0
  15. package/dist/cli/attention-ZXZANHKC.js +168 -0
  16. package/dist/cli/audit-ICA2SEFN.js +122 -0
  17. package/dist/cli/auth-DXIG5OG7.js +491 -0
  18. package/dist/cli/backfill-LBSYBWLR.js +156 -0
  19. package/dist/cli/backup-NUGBZ7NA.js +588 -0
  20. package/dist/cli/backup-inspect-PDRXI45A.js +26 -0
  21. package/dist/cli/backup-recover-TVKAPERB.js +29 -0
  22. package/dist/cli/blockers-ZOPCGHDW.js +55 -0
  23. package/dist/cli/brain-EJOBKL4A.js +397 -0
  24. package/dist/cli/briefing-RDQJ6WHS.js +213 -0
  25. package/dist/cli/caamp-Z3XPKEHB.js +181 -0
  26. package/dist/cli/cancel-S7EHLCJN.js +85 -0
  27. package/dist/cli/canon-NPF5RODM.js +142 -0
  28. package/dist/cli/canon-docs-2GTOLF7Y.js +26 -0
  29. package/dist/cli/cant-CHNN6O57.js +239 -0
  30. package/dist/cli/changeset-RYPHPN2B.js +246 -0
  31. package/dist/cli/check-C67YAU7W.js +759 -0
  32. package/dist/cli/check-DHMURGHC.js +924 -0
  33. package/dist/cli/checkpoint-UWDFOGYQ.js +128 -0
  34. package/dist/cli/chunk-22YPFZYB.js +91 -0
  35. package/dist/cli/chunk-2MGFDMDF.js +272 -0
  36. package/dist/cli/chunk-47JYZAMS.js +30 -0
  37. package/dist/cli/chunk-4JYHPP4C.js +76 -0
  38. package/dist/cli/chunk-4PEODTHY.js +278 -0
  39. package/dist/cli/chunk-4RGSNUUP.js +950 -0
  40. package/dist/cli/chunk-4VASFLUW.js +35 -0
  41. package/dist/cli/chunk-5PNI276X.js +124 -0
  42. package/dist/cli/chunk-6JSJNYH2.js +114 -0
  43. package/dist/cli/chunk-6N6WGLXE.js +63 -0
  44. package/dist/cli/chunk-75UH3BLS.js +210 -0
  45. package/dist/cli/chunk-7ACK4EIN.js +92 -0
  46. package/dist/cli/chunk-7NMKLVMI.js +107 -0
  47. package/dist/cli/chunk-7SINJXB6.js +110 -0
  48. package/dist/cli/chunk-7UARFJYS.js +46 -0
  49. package/dist/cli/chunk-7ZYPQV4N.js +90 -0
  50. package/dist/cli/chunk-AQCYT3L4.js +72 -0
  51. package/dist/cli/chunk-BQK6XTOH.js +57 -0
  52. package/dist/cli/chunk-C3B7C43W.js +16 -0
  53. package/dist/cli/chunk-CAPAGD5E.js +98 -0
  54. package/dist/cli/chunk-CHGEDCKG.js +77 -0
  55. package/dist/cli/chunk-COXFJ3PT.js +74 -0
  56. package/dist/cli/chunk-D7BAQNV2.js +55 -0
  57. package/dist/cli/chunk-EIUXVHNF.js +258 -0
  58. package/dist/cli/chunk-EUGB6P5V.js +173 -0
  59. package/dist/cli/chunk-EW2CKNNZ.js +304 -0
  60. package/dist/cli/chunk-FSKN4DWC.js +108 -0
  61. package/dist/cli/chunk-FVKLULL7.js +48 -0
  62. package/dist/cli/chunk-GAGUBU4Z.js +280 -0
  63. package/dist/cli/chunk-GKRFH2YG.js +171 -0
  64. package/dist/cli/chunk-GRU74UE4.js +270 -0
  65. package/dist/cli/chunk-GZFVDR44.js +320 -0
  66. package/dist/cli/chunk-HFLLZE6A.js +83 -0
  67. package/dist/cli/chunk-HXRXT3XU.js +78 -0
  68. package/dist/cli/chunk-IIOGUY3B.js +258 -0
  69. package/dist/cli/chunk-ILWWL462.js +60 -0
  70. package/dist/cli/chunk-J5USPJKZ.js +48 -0
  71. package/dist/cli/chunk-KEQS36GD.js +88 -0
  72. package/dist/cli/chunk-L7ONYSV6.js +2497 -0
  73. package/dist/cli/chunk-LVGIFMCV.js +163 -0
  74. package/dist/cli/chunk-NEFMQYLE.js +656 -0
  75. package/dist/cli/chunk-NU6CQQVB.js +4501 -0
  76. package/dist/cli/chunk-OILHT47L.js +252 -0
  77. package/dist/cli/chunk-PKKZPOOP.js +352 -0
  78. package/dist/cli/chunk-PN4WW5VX.js +66 -0
  79. package/dist/cli/chunk-PSJ33WVO.js +56 -0
  80. package/dist/cli/chunk-QA6T2LQ5.js +71 -0
  81. package/dist/cli/chunk-QJJWWCIC.js +288 -0
  82. package/dist/cli/chunk-QKMBWYAS.js +93 -0
  83. package/dist/cli/chunk-QWNKLLLG.js +84 -0
  84. package/dist/cli/chunk-QWUFFDL3.js +85 -0
  85. package/dist/cli/chunk-QY76BRUZ.js +35 -0
  86. package/dist/cli/chunk-R45KVF5Y.js +117 -0
  87. package/dist/cli/chunk-RRKP7HLU.js +66 -0
  88. package/dist/cli/chunk-SXXOC6KZ.js +209 -0
  89. package/dist/cli/chunk-T6GZJYL3.js +108 -0
  90. package/dist/cli/chunk-TLQ37HRE.js +67 -0
  91. package/dist/cli/chunk-TPTNEJZJ.js +52 -0
  92. package/dist/cli/chunk-TYC7G5LF.js +100 -0
  93. package/dist/cli/chunk-UWCZY52R.js +766 -0
  94. package/dist/cli/chunk-VB3LDPL5.js +46 -0
  95. package/dist/cli/chunk-VLEYOOPH.js +31 -0
  96. package/dist/cli/chunk-W3JLYKKY.js +140 -0
  97. package/dist/cli/chunk-WWCYZYSN.js +56 -0
  98. package/dist/cli/chunk-XA5WISNV.js +52 -0
  99. package/dist/cli/chunk-XHRZ7OYZ.js +132 -0
  100. package/dist/cli/chunk-XLPMIGJJ.js +148 -0
  101. package/dist/cli/chunk-YY66LFZF.js +29 -0
  102. package/dist/cli/chunk-ZMGDZMSJ.js +210 -0
  103. package/dist/cli/claim-IGOG2QOC.js +97 -0
  104. package/dist/cli/classify-MG4N7GMQ.js +117 -0
  105. package/dist/cli/cli-A23Q335V.js +51 -0
  106. package/dist/cli/cloud-SILYJVKO.js +731 -0
  107. package/dist/cli/cockpit-RZKLGYPN.js +790 -0
  108. package/dist/cli/code-H2FQCVMW.js +178 -0
  109. package/dist/cli/complete-JLX7NBI4.js +35 -0
  110. package/dist/cli/complexity-OKV7PVZH.js +57 -0
  111. package/dist/cli/compliance-QMGZHJNQ.js +277 -0
  112. package/dist/cli/conduit-4H32UYEA.js +308 -0
  113. package/dist/cli/conduit-EY54WTZA.js +535 -0
  114. package/dist/cli/config-TDYOC7XO.js +519 -0
  115. package/dist/cli/consensus-4XCOFPPT.js +122 -0
  116. package/dist/cli/context-FN4YTGHD.js +142 -0
  117. package/dist/cli/contribution-7RWFBJLC.js +112 -0
  118. package/dist/cli/curator-HKZJCVRL.js +139 -0
  119. package/dist/cli/current-B2NU7V3G.js +43 -0
  120. package/dist/cli/daemon-GBD4TVCD.js +452 -0
  121. package/dist/cli/dash-IGDANIE5.js +71 -0
  122. package/dist/cli/decide-26SXC2V6.js +416 -0
  123. package/dist/cli/decompose-LQ76TEZG.js +78 -0
  124. package/dist/cli/decomposition-WFRWNUSJ.js +122 -0
  125. package/dist/cli/delete-T5DUQHOX.js +69 -0
  126. package/dist/cli/deps-UTPZJBXL.js +462 -0
  127. package/dist/cli/detect-FFZSMVDT.js +46 -0
  128. package/dist/cli/detect-drift-TVCJOFYJ.js +408 -0
  129. package/dist/cli/diagnostics-GN5CVD3N.js +93 -0
  130. package/dist/cli/diagnostics-ZNBKH6GX.js +133 -0
  131. package/dist/cli/docs-CLQKEEUK.js +1710 -0
  132. package/dist/cli/docs-DPPDFTWR.js +3098 -0
  133. package/dist/cli/doctor-GPENOP4N.js +1270 -0
  134. package/dist/cli/doctor-ac-bindings-S3IOEFP4.js +26 -0
  135. package/dist/cli/doctor-acceptance-drift-VNFKPBSQ.js +26 -0
  136. package/dist/cli/doctor-credentials-A5H4JMTF.js +26 -0
  137. package/dist/cli/doctor-db-substrate-FGCISE4Z.js +27 -0
  138. package/dist/cli/doctor-dep-cycles-BKKKHHYG.js +26 -0
  139. package/dist/cli/doctor-exodus-ND2DH2KP.js +26 -0
  140. package/dist/cli/doctor-exodus-residue-4EMRYMEQ.js +26 -0
  141. package/dist/cli/doctor-fk-check-VY3RIQV3.js +26 -0
  142. package/dist/cli/doctor-global-delivery-JN6IGHUL.js +26 -0
  143. package/dist/cli/doctor-heavy-command-hook-XSN44WXU.js +26 -0
  144. package/dist/cli/doctor-legacy-backups-QAG5NHYP.js +26 -0
  145. package/dist/cli/doctor-legacy-reaper-CIRGFE6R.js +26 -0
  146. package/dist/cli/doctor-malformed-ids-WL7HT4TB.js +26 -0
  147. package/dist/cli/doctor-manifest-rows-SMQCKJ2T.js +26 -0
  148. package/dist/cli/doctor-memory-guard-LJDOJG76.js +26 -0
  149. package/dist/cli/doctor-migrations-ETQPFNUP.js +26 -0
  150. package/dist/cli/doctor-nexus-residency-A4JMKBEJ.js +26 -0
  151. package/dist/cli/doctor-project-identity-FFQJ5LNX.js +26 -0
  152. package/dist/cli/doctor-projects-MFUTKRHP.js +30 -0
  153. package/dist/cli/doctor-projects-registry-76X6VTGM.js +26 -0
  154. package/dist/cli/doctor-release-readiness-QJCOPFTK.js +26 -0
  155. package/dist/cli/doctor-repair-AU76FIKY.js +26 -0
  156. package/dist/cli/doctor-row-identity-KW6JYZQE.js +26 -0
  157. package/dist/cli/doctor-skill-fixtures-G3T7JUHS.js +26 -0
  158. package/dist/cli/doctor-split-brain-FUW6L7FS.js +26 -0
  159. package/dist/cli/doctor-superseded-store-DUHGSBLC.js +26 -0
  160. package/dist/cli/doctor-sync-journal-SXMUIKF7.js +26 -0
  161. package/dist/cli/doctor-sync-triggers-6WNJZFRM.js +26 -0
  162. package/dist/cli/doctor-tool-locks-66LSLUBN.js +26 -0
  163. package/dist/cli/doctor-twin-collapse-AYBYBFC4.js +26 -0
  164. package/dist/cli/doctor-worktree-stores-C5XEH5OG.js +26 -0
  165. package/dist/cli/done-R64XRZ32.js +196 -0
  166. package/dist/cli/dynamic-RR4HB7BB.js +29 -0
  167. package/dist/cli/entities-3JIHDIOK.js +361 -0
  168. package/dist/cli/event-45KEWV5H.js +372 -0
  169. package/dist/cli/exists-BEDZS5RI.js +65 -0
  170. package/dist/cli/exodus-DUX62344.js +401 -0
  171. package/dist/cli/export-C5PQIQ5O.js +93 -0
  172. package/dist/cli/export-tasks-CUXDZPJB.js +97 -0
  173. package/dist/cli/federation-ESM35EGG.js +145 -0
  174. package/dist/cli/find-5EUZHHQJ.js +178 -0
  175. package/dist/cli/focus-6AH4KLRO.js +368 -0
  176. package/dist/cli/focus-ANKAIRO5.js +56 -0
  177. package/dist/cli/gateway-2GW2KTK3.js +77 -0
  178. package/dist/cli/gateway-auto-start-EDK7XI6E.js +36 -0
  179. package/dist/cli/gc-CTOQLUSA.js +291 -0
  180. package/dist/cli/go-YJPNHHYT.js +206 -0
  181. package/dist/cli/goal-4GW4G77D.js +213 -0
  182. package/dist/cli/grade-25WSZH4X.js +57 -0
  183. package/dist/cli/graph-JUO3TMF3.js +879 -0
  184. package/dist/cli/history-NACNUQTC.js +138 -0
  185. package/dist/cli/hook-UF7J7R6D.js +57 -0
  186. package/dist/cli/hook-entry.js +244 -19
  187. package/dist/cli/hygiene-3QZ2NI7P.js +55 -0
  188. package/dist/cli/import-7ESGL5SS.js +89 -0
  189. package/dist/cli/import-tasks-FLE2RPBR.js +109 -0
  190. package/dist/cli/index.js +601 -94637
  191. package/dist/cli/init-AN4DZ2XO.js +40 -0
  192. package/dist/cli/inject-HQWA5GWY.js +83 -0
  193. package/dist/cli/install-global-YQV4W5OG.js +95 -0
  194. package/dist/cli/intelligence-OFFNQGHE.js +238 -0
  195. package/dist/cli/intelligence-QIC4SN4X.js +182 -0
  196. package/dist/cli/issue-P5RNMRDG.js +159 -0
  197. package/dist/cli/janitor-ZNCFQ7E3.js +111 -0
  198. package/dist/cli/labels-ORBM3T57.js +88 -0
  199. package/dist/cli/lifecycle-MHR22TVI.js +273 -0
  200. package/dist/cli/list-LB6GCYYU.js +109 -0
  201. package/dist/cli/llm-H7LZWQKX.js +130 -0
  202. package/dist/cli/llm-OTJWPMGI.js +704 -0
  203. package/dist/cli/llm-cost-MLMXEAR4.js +25 -0
  204. package/dist/cli/llm-stream-AAEUNKRP.js +27 -0
  205. package/dist/cli/log-W2A5M2NA.js +76 -0
  206. package/dist/cli/login-SIXFFZSP.js +45 -0
  207. package/dist/cli/logout-G3KVLQQM.js +74 -0
  208. package/dist/cli/manifest-G4T6EBSI.js +354 -0
  209. package/dist/cli/map-VU5M72TK.js +53 -0
  210. package/dist/cli/memory-2E27G2G5.js +1952 -0
  211. package/dist/cli/memory-IAEYKXG6.js +1895 -0
  212. package/dist/cli/migrate-agents-v2-A52QIYNJ.js +33 -0
  213. package/dist/cli/migrate-claude-mem-TPDW3R4Z.js +206 -0
  214. package/dist/cli/next-SVH4LIDY.js +57 -0
  215. package/dist/cli/nexus-FPQV2EAF.js +1059 -0
  216. package/dist/cli/nexus-XSFXN5E6.js +2924 -0
  217. package/dist/cli/ops-K6LHY5J6.js +52 -0
  218. package/dist/cli/orchestrate-CZ2J6W3L.js +1650 -0
  219. package/dist/cli/orchestrate-HURUCQFT.js +950 -0
  220. package/dist/cli/otel-JCNPVJLZ.js +188 -0
  221. package/dist/cli/phase-VDFRASSO.js +221 -0
  222. package/dist/cli/pipeline-TCZPOEDR.js +697 -0
  223. package/dist/cli/pivot-V5MSQZ7L.js +88 -0
  224. package/dist/cli/plan-SQUGCKSM.js +49 -0
  225. package/dist/cli/playbook-L47GLDQD.js +230 -0
  226. package/dist/cli/playbook-NJVO4R6P.js +33 -0
  227. package/dist/cli/project-YZEC7GYF.js +331 -0
  228. package/dist/cli/promote-FINF4HVK.js +53 -0
  229. package/dist/cli/provenance-3TUWVBFR.js +141 -0
  230. package/dist/cli/provenance-YZG33JGM.js +129 -0
  231. package/dist/cli/provider-23JVLLG3.js +213 -0
  232. package/dist/cli/reason-FHN54O7O.js +138 -0
  233. package/dist/cli/reconcile-MOM5T4AX.js +128 -0
  234. package/dist/cli/refresh-memory-3OSTRAVK.js +45 -0
  235. package/dist/cli/relates-6FWUFUS2.js +260 -0
  236. package/dist/cli/release-G2DPSG5M.js +347 -0
  237. package/dist/cli/release-URX6JHZ2.js +525 -0
  238. package/dist/cli/remote-VIULU2T5.js +279 -0
  239. package/dist/cli/renderers-TGSW77J4.js +37 -0
  240. package/dist/cli/reorder-UIPN6TQY.js +87 -0
  241. package/dist/cli/reparent-ALOWWUIU.js +59 -0
  242. package/dist/cli/req-RH2Y3H7A.js +130 -0
  243. package/dist/cli/research-G2Y72HRC.js +366 -0
  244. package/dist/cli/restore-VBR5XNML.js +470 -0
  245. package/dist/cli/revert-32LDNSGP.js +240 -0
  246. package/dist/cli/roadmap-LFU3AG7D.js +62 -0
  247. package/dist/cli/run-HXRILSR7.js +245 -0
  248. package/dist/cli/safestop-Z5UC7NGQ.js +89 -0
  249. package/dist/cli/saga-Y6FZS6AM.js +286 -0
  250. package/dist/cli/schema-OOOKLWUE.js +195 -0
  251. package/dist/cli/self-update-26SOTJMH.js +504 -0
  252. package/dist/cli/selfimprove-4SMOCLUV.js +89 -0
  253. package/dist/cli/selfimprove-EB4RQLP4.js +139 -0
  254. package/dist/cli/sentient-QPETVE53.js +254 -0
  255. package/dist/cli/sentient-RGMWR4XJ.js +1088 -0
  256. package/dist/cli/sequence-H7DSAJPF.js +94 -0
  257. package/dist/cli/service-EK5MGB2D.js +156 -0
  258. package/dist/cli/service-GLOMZWPQ.js +437 -0
  259. package/dist/cli/session-HYUXAQGC.js +708 -0
  260. package/dist/cli/session-N5ISU56W.js +568 -0
  261. package/dist/cli/setup-L3YYV53D.js +323 -0
  262. package/dist/cli/show-364V3KY2.js +123 -0
  263. package/dist/cli/skill-F2KR6HW5.js +84 -0
  264. package/dist/cli/skills-FS3DCP6E.js +929 -0
  265. package/dist/cli/smoke-provider-SKUFMZXU.js +218 -0
  266. package/dist/cli/snapshot-EGXEQ5PR.js +120 -0
  267. package/dist/cli/src-LYLXHXHH.js +80 -0
  268. package/dist/cli/start-6DXOCCKL.js +68 -0
  269. package/dist/cli/stats-UVUQEGYC.js +96 -0
  270. package/dist/cli/status-XLKYLN42.js +157 -0
  271. package/dist/cli/sticky-VSTQ2I2O.js +264 -0
  272. package/dist/cli/sticky-XZXZDCFW.js +339 -0
  273. package/dist/cli/stop-VNOAFWNB.js +43 -0
  274. package/dist/cli/sync-YYPARW3A.js +155 -0
  275. package/dist/cli/tasks-C24KHTFF.js +1118 -0
  276. package/dist/cli/tasks-LBJC7CZV.js +175 -0
  277. package/dist/cli/telemetry-CU7W5WAY.js +120 -0
  278. package/dist/cli/templates-G42JBUY4.js +609 -0
  279. package/dist/cli/testing-HSRITHEY.js +147 -0
  280. package/dist/cli/token-J74E7TSU.js +221 -0
  281. package/dist/cli/tools-BI74W7VE.js +652 -0
  282. package/dist/cli/transcript-RGK7RSGQ.js +440 -0
  283. package/dist/cli/tui-BPUFHYKK.js +51 -0
  284. package/dist/cli/update-2KQWA27S.js +478 -0
  285. package/dist/cli/update-check-entry.js +124 -0
  286. package/dist/cli/update-notice-6SQ6WPQG.js +68 -0
  287. package/dist/cli/upgrade-E2MJAPC7.js +136 -0
  288. package/dist/cli/upgrade-XRSQ4RWH.js +243 -0
  289. package/dist/cli/verify-WASRGDBA.js +191 -0
  290. package/dist/cli/web-NJ3P67EU.js +275 -0
  291. package/dist/cli/workgraph-OHJ2VKJD.js +232 -0
  292. package/dist/cli/worktree-FVQLORFY.js +371 -0
  293. package/dist/cli/worktree-ZJ6JTQNG.js +287 -0
  294. package/package.json +13 -13
  295. package/studio-dist/client/_app/immutable/chunks/{DMj27Pxj.js → AH7dIemv.js} +1 -1
  296. package/studio-dist/client/_app/immutable/chunks/{Dc2owyvE.js → BDgH40i3.js} +1 -1
  297. package/studio-dist/client/_app/immutable/chunks/BXDCSJ7U.js +1 -0
  298. package/studio-dist/client/_app/immutable/chunks/{qq3pe1aT.js → Gg4iYl59.js} +1 -1
  299. package/studio-dist/client/_app/immutable/entry/{app.CHWXLeE9.js → app.DUDNK-37.js} +2 -2
  300. package/studio-dist/client/_app/immutable/entry/start.DNmZ0pA4.js +1 -0
  301. package/studio-dist/client/_app/immutable/nodes/{0.DmKQGvFh.js → 0.DWqP3ail.js} +1 -1
  302. package/studio-dist/client/_app/immutable/nodes/{1.DqJk_REO.js → 1.CZGVUP5B.js} +1 -1
  303. package/studio-dist/client/_app/immutable/nodes/{15.GUy6kdW1.js → 15.DGaflZRS.js} +1 -1
  304. package/studio-dist/client/_app/immutable/nodes/{16.CcnKZ8f9.js → 16.uYlarGXl.js} +1 -1
  305. package/studio-dist/client/_app/immutable/nodes/{17.DAlQF3lQ.js → 17.CZMR0E47.js} +1 -1
  306. package/studio-dist/client/_app/immutable/nodes/{18.vQFr-vFj.js → 18.CBC-AKH_.js} +1 -1
  307. package/studio-dist/client/_app/immutable/nodes/{20.GVOj5j_r.js → 20.BGi43MvQ.js} +1 -1
  308. package/studio-dist/client/_app/immutable/nodes/{24.2-wC6DOh.js → 24.Dv8ftCyR.js} +1 -1
  309. package/studio-dist/client/_app/immutable/nodes/25.DQPVlTDQ.js +39 -0
  310. package/studio-dist/client/_app/immutable/nodes/{26.C3rNqzZF.js → 26.Cl800304.js} +1 -1
  311. package/studio-dist/client/_app/immutable/nodes/{3.b-7pzc3M.js → 3.D_fQKj9f.js} +1 -1
  312. package/studio-dist/client/_app/version.json +1 -1
  313. package/studio-dist/server/chunks/{0-BaCce-O1.js → 0-BpOagOst.js} +3 -3
  314. package/studio-dist/server/chunks/1-D0oRGR-C.js +9 -0
  315. package/studio-dist/server/chunks/{15-DRMOSIC9.js → 15-BcjHz7Tv.js} +3 -3
  316. package/studio-dist/server/chunks/{16-BulXhoZZ.js → 16-CXm8TUk7.js} +3 -3
  317. package/studio-dist/server/chunks/{17-DUEOCM0H.js → 17-mSgUK-cp.js} +3 -3
  318. package/studio-dist/server/chunks/{18-DP7Q8zPs.js → 18-BtfnIw1d.js} +3 -3
  319. package/studio-dist/server/chunks/{20-9ta6MI7q.js → 20-By1e4QDS.js} +3 -3
  320. package/studio-dist/server/chunks/{24-DysdL1ab.js → 24-0i2YLVxG.js} +3 -3
  321. package/studio-dist/server/chunks/{dist-CdoCt9zT.js → 25-DPsrqbrh.js} +797 -10736
  322. package/studio-dist/server/chunks/{26-Df4YXgg0.js → 26-P0XkOibm.js} +3 -3
  323. package/studio-dist/server/chunks/{3-BEhOhYpL.js → 3-WudjUK7e.js} +3 -3
  324. package/studio-dist/server/chunks/{_layout.svelte-CnLcXYG-.js → _layout.svelte-D9m81Rle.js} +4 -4
  325. package/studio-dist/server/chunks/{_page.svelte-DGhXqeRc.js → _page.svelte-2PDLOF83.js} +3 -3
  326. package/studio-dist/server/chunks/{_page.svelte-Dv8tmDEC.js → _page.svelte-BjE3jm2K.js} +3 -3
  327. package/studio-dist/server/chunks/{_page.svelte-C0qgUvzA.js → _page.svelte-Clc0vmiZ.js} +4 -4
  328. package/studio-dist/server/chunks/{_page.svelte-ui7LnWkL.js → _page.svelte-DABdRT3h.js} +3 -3
  329. package/studio-dist/server/chunks/{_page.svelte-HUqTICJR.js → _page.svelte-DG3PjiHj.js} +3 -3
  330. package/studio-dist/server/chunks/{_page.svelte-DzcZljiy.js → _page.svelte-Edy9CaE8.js} +20 -7
  331. package/studio-dist/server/chunks/{_page.svelte-DOl0DGOO.js → _page.svelte-Hrf3-ZR8.js} +3 -3
  332. package/studio-dist/server/chunks/{_page.svelte-Da4q5l93.js → _page.svelte-VB4O4Nl5.js} +3 -3
  333. package/studio-dist/server/chunks/{_page.svelte-CbyEPj2j.js → _page.svelte-d3jEgjsI.js} +3 -3
  334. package/studio-dist/server/chunks/{_server.ts-P4wVVksg.js → _server.ts-BcRFsyo3.js} +4 -5
  335. package/studio-dist/server/chunks/{_server.ts-Cr57EKWf.js → _server.ts-CEWpIFrj.js} +6 -5
  336. package/studio-dist/server/chunks/{_server.ts-CnKVmf_s.js → _server.ts-CLVdVIF9.js} +2 -2
  337. package/studio-dist/server/chunks/{_server.ts-C0evMKCk.js → _server.ts-D_j7aSy5.js} +6 -5
  338. package/studio-dist/server/chunks/{_server.ts-2BazvYRP.js → _server.ts-QEZF8h2q.js} +4 -5
  339. package/studio-dist/server/chunks/{client-CPM4LrO2.js → client-Kp1dP1od.js} +2 -2
  340. package/studio-dist/server/chunks/{error.svelte-ShcZo43D.js → error.svelte-WOCioJVX.js} +3 -3
  341. package/studio-dist/server/chunks/{hooks.server-D2qbf3Zp.js → hooks.server-DH9zkIrJ.js} +3 -1
  342. package/studio-dist/server/chunks/{http-BiQjEmVU.js → http-DT5kxaWk.js} +5 -4
  343. package/studio-dist/server/chunks/{internal-Cgsrya1j.js → internal-z0vqd5_u.js} +3 -3
  344. package/studio-dist/server/chunks/operations-registry-DdzmYzqX.js +10584 -0
  345. package/studio-dist/server/chunks/{stores-BGrpEDMK.js → stores-s2kaL315.js} +2 -2
  346. package/studio-dist/server/index.js +1 -1
  347. package/studio-dist/server/manifest.js +17 -17
  348. package/studio-dist/client/_app/immutable/chunks/Bw6J8NDM.js +0 -1
  349. package/studio-dist/client/_app/immutable/entry/start.C9bwPpvc.js +0 -1
  350. package/studio-dist/client/_app/immutable/nodes/25.D1vYaH8g.js +0 -39
  351. package/studio-dist/server/chunks/1-CVo7Nk6d.js +0 -9
  352. package/studio-dist/server/chunks/25-CYsps38R.js +0 -366
@@ -0,0 +1,3098 @@
1
+ (() => {
2
+ const _installed = Symbol.for('cleocode.cli.sqliteWarningFilter');
3
+ if (globalThis[_installed]) return;
4
+ globalThis[_installed] = true;
5
+ const _origEmitWarning = process.emitWarning;
6
+ process.emitWarning = function(warning, type, code, ctor) {
7
+ if (typeof warning === 'object' && warning.name === 'ExperimentalWarning' && typeof warning.message === 'string' && /SQLite is an experimental feature/i.test(warning.message)) {
8
+ return;
9
+ }
10
+ if (typeof warning === 'string' && /SQLite is an experimental feature/i.test(warning)) {
11
+ return;
12
+ }
13
+ return _origEmitWarning.call(process, warning, type, code, ctor);
14
+ };
15
+ })();
16
+ import {
17
+ getOperationParams,
18
+ paramsToCittyArgs
19
+ } from "./chunk-RRKP7HLU.js";
20
+ import {
21
+ negatedFlag
22
+ } from "./chunk-47JYZAMS.js";
23
+ import "./chunk-C3B7C43W.js";
24
+ import {
25
+ dispatchFromCli,
26
+ dispatchRaw,
27
+ handleRawError
28
+ } from "./chunk-L7ONYSV6.js";
29
+ import "./chunk-6N6WGLXE.js";
30
+ import {
31
+ resolve
32
+ } from "./chunk-4VASFLUW.js";
33
+ import {
34
+ loadCanonRegistry
35
+ } from "./chunk-SXXOC6KZ.js";
36
+ import {
37
+ UnknownFlagError,
38
+ assertKnownFlags
39
+ } from "./chunk-OILHT47L.js";
40
+ import {
41
+ defineCommand,
42
+ showUsage
43
+ } from "./chunk-PKKZPOOP.js";
44
+ import "./chunk-QKMBWYAS.js";
45
+ import {
46
+ cliError,
47
+ cliOutput,
48
+ humanInfo
49
+ } from "./chunk-4RGSNUUP.js";
50
+ import "./chunk-6JSJNYH2.js";
51
+ import "./chunk-D7BAQNV2.js";
52
+ import "./chunk-TYC7G5LF.js";
53
+
54
+ // packages/cleo/src/cli/commands/docs.ts
55
+ import { appendFile, mkdir as mkdir3, readdir, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
56
+ import { dirname as dirname3, isAbsolute, join as join4, resolve as resolve3 } from "node:path";
57
+ import {
58
+ DocKindConfigError,
59
+ DocKindRegistry,
60
+ ExitCode as ExitCode5
61
+ } from "@cleocode/contracts";
62
+ import {
63
+ detectStrayCleoDb,
64
+ getAgentOutputsAbsolute,
65
+ resolveWorktreeFilePath,
66
+ resolveWorktreeRouting
67
+ } from "@cleocode/core/core-paths";
68
+ import { createDocsReadModel } from "@cleocode/core/docs/docs-read-model";
69
+ import { CounterMismatchError } from "@cleocode/core/docs/import/import-orchestrator";
70
+ import {
71
+ checkSlugSimilarity,
72
+ DEFAULT_SIMILARITY_MODE,
73
+ DEFAULT_SIMILARITY_THRESHOLD
74
+ } from "@cleocode/core/docs/similarity-check";
75
+ import { CleoError as CleoError2 } from "@cleocode/core/errors";
76
+ import { pushWarning } from "@cleocode/core/output";
77
+ import { getProjectRoot as getProjectRoot3 } from "@cleocode/core/project-scope";
78
+ import { readJson } from "@cleocode/core/store/json";
79
+ import { describeOperation } from "@cleocode/lafs";
80
+
81
+ // packages/cleo/src/cli/commands/docs/audit.ts
82
+ import { ExitCode } from "@cleocode/contracts/exit-codes.js";
83
+ var auditCommand = defineCommand({
84
+ meta: {
85
+ name: "audit",
86
+ description: "Query the immutable docs audit trail (--slug <slug> or --verify for integrity check)"
87
+ },
88
+ args: {
89
+ slug: {
90
+ type: "string",
91
+ description: "Show audit history for a specific document slug"
92
+ },
93
+ verify: {
94
+ type: "boolean",
95
+ description: "Verify checkpoint chain integrity across the full audit log"
96
+ }
97
+ },
98
+ async run({ args }) {
99
+ if (!args.slug && !args.verify) {
100
+ cliError(
101
+ "Pass --slug <slug> to view history or --verify to check integrity.",
102
+ ExitCode.GENERAL_ERROR
103
+ );
104
+ return;
105
+ }
106
+ await dispatchFromCli(
107
+ "query",
108
+ "docs",
109
+ "audit",
110
+ {
111
+ ...typeof args.slug === "string" ? { slug: args.slug } : {},
112
+ ...args.verify === true ? { verify: true } : {}
113
+ },
114
+ { command: "docs audit" }
115
+ );
116
+ }
117
+ });
118
+
119
+ // packages/cleo/src/cli/commands/docs/graph.ts
120
+ import { ExitCode as ExitCode2 } from "@cleocode/contracts/exit-codes.js";
121
+ import {
122
+ buildDocProvenanceGraph,
123
+ DocProvenanceRootNotFoundError,
124
+ renderProvenanceGraphAsDot
125
+ } from "@cleocode/core/docs/build-provenance-graph";
126
+ var graphCommand = defineCommand({
127
+ meta: {
128
+ name: "graph",
129
+ description: "Traverse the docs provenance graph from a root (slug or task ID) and return a typed DocProvenanceResponse envelope. Walks supersedes/superseded-by chains plus cross-entity edges to owning + related tasks. Default depth=2. Pass --format dot for Graphviz output (still wrapped in the envelope under data.dot)."
130
+ },
131
+ args: {
132
+ root: {
133
+ type: "string",
134
+ description: "Root identifier \u2014 a canonical doc slug (e.g. adr-078-docs-provenance) or a CLEO task ID (T####). Required.",
135
+ required: true
136
+ },
137
+ depth: {
138
+ type: "string",
139
+ description: "Maximum BFS hops from the root (default: 2; minimum: 0)."
140
+ },
141
+ format: {
142
+ type: "string",
143
+ description: "Output format: json (default) | dot."
144
+ },
145
+ backlinks: {
146
+ type: "boolean",
147
+ description: "Hydrate the persisted docs_wikilinks backlink graph (T11826) \u2014 adds bidirectional shares-topic doc\u2194doc edges and any persisted backlinks not surfaced by the on-the-fly BFS. Obsidian-grade graph on existing data."
148
+ }
149
+ },
150
+ async run({ args }) {
151
+ const root = String(args.root);
152
+ const depth = parseDepth(args.depth);
153
+ const format = parseFormat(args.format);
154
+ const hydrateWikilinks = Boolean(args.backlinks);
155
+ try {
156
+ const graph = await buildDocProvenanceGraph({ root, depth, hydrateWikilinks });
157
+ const payload = { ...graph };
158
+ if (format === "dot") {
159
+ payload["dot"] = renderProvenanceGraphAsDot(graph);
160
+ }
161
+ cliOutput(payload, { command: "docs graph", operation: "docs.graph" });
162
+ } catch (err) {
163
+ if (err instanceof DocProvenanceRootNotFoundError) {
164
+ cliError(err.message, ExitCode2.NOT_FOUND, {
165
+ name: "E_DOC_PROVENANCE_ROOT_NOT_FOUND",
166
+ fix: "Verify the root with `cleo docs list --project --type adr` (or your kind) and pass an existing slug or task ID. Slugs and task IDs are matched against attachments.slug and attachment_refs.owner_id respectively.",
167
+ details: { root }
168
+ });
169
+ process.exit(ExitCode2.NOT_FOUND);
170
+ }
171
+ const message = err instanceof Error ? err.message : String(err);
172
+ cliError(`docs graph failed: ${message}`, ExitCode2.GENERAL_ERROR, {
173
+ name: "E_DOCS_GRAPH_FAILED"
174
+ });
175
+ process.exit(ExitCode2.GENERAL_ERROR);
176
+ }
177
+ }
178
+ });
179
+ function parseDepth(raw) {
180
+ if (raw === void 0 || raw === null || raw === "") return 2;
181
+ const parsed = Number.parseInt(String(raw), 10);
182
+ if (!Number.isInteger(parsed) || parsed < 0) {
183
+ cliError(
184
+ `--depth must be a non-negative integer (got '${String(raw)}')`,
185
+ ExitCode2.VALIDATION_ERROR,
186
+ { name: "E_VALIDATION" }
187
+ );
188
+ process.exit(ExitCode2.VALIDATION_ERROR);
189
+ }
190
+ return parsed;
191
+ }
192
+ function parseFormat(raw) {
193
+ if (raw === void 0 || raw === null || raw === "") return "json";
194
+ const candidate = String(raw);
195
+ if (candidate !== "json" && candidate !== "dot") {
196
+ cliError(`--format must be one of: json|dot \u2014 got '${candidate}'`, ExitCode2.VALIDATION_ERROR, {
197
+ name: "E_VALIDATION"
198
+ });
199
+ process.exit(ExitCode2.VALIDATION_ERROR);
200
+ }
201
+ return candidate;
202
+ }
203
+
204
+ // packages/cleo/src/cli/commands/docs/set-alias.ts
205
+ import { ExitCode as ExitCode3 } from "@cleocode/contracts/exit-codes.js";
206
+ import { setDisplayAlias } from "@cleocode/core/docs/display-alias";
207
+ import { CleoError } from "@cleocode/core/errors";
208
+ import { getProjectRoot } from "@cleocode/core/project-scope";
209
+ var setAliasCommand = defineCommand({
210
+ meta: {
211
+ name: "set-alias",
212
+ description: "Assign an explicit display-alias number to a doc, decoupled from its slug (e.g. `cleo docs set-alias adr-051-override-patterns 091`). The number must be unique among type=adr docs. Pass --clear to remove an existing alias (revert to the slug-derived number)."
213
+ },
214
+ args: {
215
+ slug: {
216
+ type: "positional",
217
+ description: "Canonical doc slug to alias (e.g. adr-051-override-patterns). Required.",
218
+ required: true
219
+ },
220
+ number: {
221
+ type: "positional",
222
+ description: "The display-alias number to assign (positive integer). Omit with --clear.",
223
+ required: false
224
+ },
225
+ clear: {
226
+ type: "boolean",
227
+ description: "Clear any existing alias instead of setting one (revert to slug-derived)."
228
+ }
229
+ },
230
+ async run({ args }) {
231
+ const slug = String(args.slug);
232
+ const clear = Boolean(args.clear);
233
+ let displayAlias;
234
+ if (clear) {
235
+ displayAlias = null;
236
+ } else {
237
+ const raw = args.number;
238
+ if (raw === void 0 || raw === null || String(raw).trim() === "") {
239
+ cliError(
240
+ "a positive integer <number> is required (or pass --clear to remove the alias)",
241
+ ExitCode3.VALIDATION_ERROR,
242
+ { name: "E_VALIDATION" }
243
+ );
244
+ process.exit(ExitCode3.VALIDATION_ERROR);
245
+ return;
246
+ }
247
+ const parsed = Number.parseInt(String(raw), 10);
248
+ if (!Number.isInteger(parsed) || parsed < 1) {
249
+ cliError(
250
+ `<number> must be a positive integer (got '${String(raw)}')`,
251
+ ExitCode3.VALIDATION_ERROR,
252
+ { name: "E_VALIDATION" }
253
+ );
254
+ process.exit(ExitCode3.VALIDATION_ERROR);
255
+ return;
256
+ }
257
+ displayAlias = parsed;
258
+ }
259
+ try {
260
+ const result = await setDisplayAlias(getProjectRoot(), { slug, displayAlias });
261
+ cliOutput(result, { command: "docs set-alias", operation: "docs.set-alias" });
262
+ } catch (err) {
263
+ if (err instanceof CleoError) {
264
+ cliError(err.message, err.code, {
265
+ name: typeof err.details?.["code"] === "string" ? err.details["code"] : "E_DOCS_SET_ALIAS",
266
+ ...err.fix ? { fix: err.fix } : {},
267
+ ...err.details ? { details: err.details } : {}
268
+ });
269
+ process.exit(err.code);
270
+ return;
271
+ }
272
+ const message = err instanceof Error ? err.message : String(err);
273
+ cliError(`docs set-alias failed: ${message}`, ExitCode3.GENERAL_ERROR, {
274
+ name: "E_DOCS_SET_ALIAS_FAILED"
275
+ });
276
+ process.exit(ExitCode3.GENERAL_ERROR);
277
+ }
278
+ }
279
+ });
280
+
281
+ // packages/cleo/src/cli/commands/docs-viewer.ts
282
+ import { spawn } from "node:child_process";
283
+ import { ExitCode as ExitCode4 } from "@cleocode/contracts/exit-codes.js";
284
+
285
+ // packages/cleo/src/viewer/pidfile.ts
286
+ import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
287
+ import { dirname, join } from "node:path";
288
+ import { getCleoHome } from "@cleocode/core/core-paths";
289
+ function viewerPidFilePath() {
290
+ return join(getCleoHome(), "viewer.pid");
291
+ }
292
+ async function writeViewerPidFile(record) {
293
+ const path = viewerPidFilePath();
294
+ await mkdir(dirname(path), { recursive: true });
295
+ await writeFile(path, JSON.stringify(record, null, 2), "utf8");
296
+ return path;
297
+ }
298
+ async function readViewerPidFile() {
299
+ try {
300
+ const raw = await readFile(viewerPidFilePath(), "utf8");
301
+ const parsed = JSON.parse(raw);
302
+ if (typeof parsed.pid === "number" && typeof parsed.port === "number" && typeof parsed.host === "string" && typeof parsed.projectRoot === "string" && typeof parsed.startedAt === "number") {
303
+ return parsed;
304
+ }
305
+ return null;
306
+ } catch {
307
+ return null;
308
+ }
309
+ }
310
+ async function removeViewerPidFile() {
311
+ try {
312
+ await unlink(viewerPidFilePath());
313
+ } catch {
314
+ }
315
+ }
316
+ function isProcessAlive(pid) {
317
+ if (!Number.isInteger(pid) || pid <= 0) return false;
318
+ try {
319
+ process.kill(pid, 0);
320
+ return true;
321
+ } catch (err) {
322
+ const e = err;
323
+ if (e.code === "EPERM") return true;
324
+ return false;
325
+ }
326
+ }
327
+
328
+ // packages/cleo/src/cli/docs-viewer-subsystem.ts
329
+ import { mkdir as mkdir2 } from "node:fs/promises";
330
+ import { join as join3 } from "node:path";
331
+ import { getCleoHome as getCleoHome2 } from "@cleocode/core/core-paths";
332
+ import { defineSubsystem } from "@cleocode/runtime/daemon";
333
+
334
+ // packages/cleo/src/viewer/server.ts
335
+ import { createReadStream } from "node:fs";
336
+ import { stat } from "node:fs/promises";
337
+ import { dirname as dirname2, join as join2, normalize, resolve as resolve2 } from "node:path";
338
+ import { fileURLToPath } from "node:url";
339
+ import { searchAllProjectDocs } from "@cleocode/core/docs/docs-ops";
340
+ import { getProjectRoot as getProjectRoot2 } from "@cleocode/core/project-scope";
341
+ import { createAttachmentStore } from "@cleocode/core/store/attachment-store";
342
+
343
+ // packages/cleo/src/viewer/port-allocator.ts
344
+ import { createServer } from "node:http";
345
+ var DEFAULT_START_PORT = 7777;
346
+ var DEFAULT_END_PORT = 7800;
347
+ async function tryListen(requestHandler, opts = {}) {
348
+ const startPort = opts.startPort ?? DEFAULT_START_PORT;
349
+ const endPort = opts.endPort ?? DEFAULT_END_PORT;
350
+ const host = opts.host ?? "127.0.0.1";
351
+ const autoIncrement = opts.autoIncrement ?? true;
352
+ const last = autoIncrement ? endPort : startPort;
353
+ for (let port = startPort; port <= last; port++) {
354
+ const server = createServer(requestHandler);
355
+ try {
356
+ await new Promise((res, rej) => {
357
+ const onError = (err) => {
358
+ server.removeListener("listening", onListen);
359
+ rej(err);
360
+ };
361
+ const onListen = () => {
362
+ server.removeListener("error", onError);
363
+ res();
364
+ };
365
+ server.once("error", onError);
366
+ server.once("listening", onListen);
367
+ server.listen(port, host);
368
+ });
369
+ const addr = server.address();
370
+ const boundPort = addr && typeof addr === "object" && typeof addr.port === "number" ? addr.port : port;
371
+ return { server, port: boundPort, host };
372
+ } catch (err) {
373
+ const e = err;
374
+ server.close();
375
+ if (e.code !== "EADDRINUSE") {
376
+ throw e;
377
+ }
378
+ if (!autoIncrement) {
379
+ throw Object.assign(new Error(`port ${startPort} in use`), { code: "EADDRINUSE" });
380
+ }
381
+ }
382
+ }
383
+ throw Object.assign(
384
+ new Error(
385
+ `no free port in range ${startPort}\u2013${endPort} for viewer (tried ${last - startPort + 1} ports)`
386
+ ),
387
+ { code: "E_NO_PORT" }
388
+ );
389
+ }
390
+
391
+ // packages/cleo/src/viewer/server.ts
392
+ function getViewerAssetsDir() {
393
+ const thisFile = fileURLToPath(import.meta.url);
394
+ return resolve2(dirname2(thisFile), "..", "..", "assets", "viewer");
395
+ }
396
+ var STATIC_MIME = {
397
+ ".html": "text/html; charset=utf-8",
398
+ ".js": "application/javascript; charset=utf-8",
399
+ ".css": "text/css; charset=utf-8",
400
+ ".json": "application/json; charset=utf-8",
401
+ ".svg": "image/svg+xml",
402
+ ".png": "image/png",
403
+ ".ico": "image/x-icon"
404
+ };
405
+ function extOf(p) {
406
+ const i = p.lastIndexOf(".");
407
+ return i < 0 ? "" : p.slice(i).toLowerCase();
408
+ }
409
+ function lafsErrorJson(code, message, fix) {
410
+ const env = {
411
+ success: false,
412
+ error: { code, message }
413
+ };
414
+ if (fix !== void 0) env.error.fix = fix;
415
+ return JSON.stringify(env);
416
+ }
417
+ function lafsSuccessJson(data) {
418
+ return JSON.stringify({ success: true, data });
419
+ }
420
+ function send(res, status, contentType, body) {
421
+ res.writeHead(status, { "Content-Type": contentType, "Cache-Control": "no-store" });
422
+ res.end(body);
423
+ }
424
+ function inferTitle(markdown, fallback) {
425
+ for (const line of markdown.split("\n", 50)) {
426
+ const m = line.match(/^\s*#\s+(.+?)\s*$/);
427
+ if (m) return m[1];
428
+ }
429
+ return fallback;
430
+ }
431
+ async function serveStatic(res, assetsDir, relPath) {
432
+ const safeRel = normalize(relPath).replace(/^[\\/]+/, "");
433
+ const absPath = join2(assetsDir, safeRel);
434
+ const resolvedAssets = resolve2(assetsDir) + "/";
435
+ const resolvedAbs = resolve2(absPath);
436
+ if (`${resolvedAbs}/`.indexOf(resolvedAssets) !== 0 && resolvedAbs !== resolve2(assetsDir)) {
437
+ send(res, 404, "application/json", lafsErrorJson("E_NOT_FOUND", `not found: ${relPath}`));
438
+ return;
439
+ }
440
+ try {
441
+ const s = await stat(absPath);
442
+ if (!s.isFile()) {
443
+ send(res, 404, "application/json", lafsErrorJson("E_NOT_FOUND", `not found: ${relPath}`));
444
+ return;
445
+ }
446
+ const mime = STATIC_MIME[extOf(absPath)] ?? "application/octet-stream";
447
+ res.writeHead(200, {
448
+ "Content-Type": mime,
449
+ "Content-Length": String(s.size),
450
+ "Cache-Control": "no-store"
451
+ });
452
+ createReadStream(absPath).pipe(res);
453
+ } catch {
454
+ send(res, 404, "application/json", lafsErrorJson("E_NOT_FOUND", `not found: ${relPath}`));
455
+ }
456
+ }
457
+ function buildViewerHandler(opts = {}) {
458
+ const projectRoot = opts.projectRoot ?? getProjectRoot2();
459
+ const assetsDir = getViewerAssetsDir();
460
+ const store = createAttachmentStore();
461
+ return async (req, res) => {
462
+ try {
463
+ const method = (req.method ?? "GET").toUpperCase();
464
+ const pathname = (req.url ?? "/").split("?")[0] ?? "/";
465
+ if (method === "GET" && pathname === "/api/health") {
466
+ send(res, 200, "application/json", lafsSuccessJson({ status: "ok" }));
467
+ return;
468
+ }
469
+ if (method !== "GET" && method !== "HEAD") {
470
+ send(
471
+ res,
472
+ 405,
473
+ "application/json",
474
+ lafsErrorJson("E_METHOD_NOT_ALLOWED", `method ${method} not allowed`)
475
+ );
476
+ return;
477
+ }
478
+ if (pathname === "/" || pathname === "/viewer" || pathname === "/viewer/") {
479
+ res.writeHead(302, { Location: "/viewer/index.html" });
480
+ res.end();
481
+ return;
482
+ }
483
+ if (pathname.startsWith("/viewer/")) {
484
+ const rel = pathname.slice("/viewer/".length);
485
+ await serveStatic(res, assetsDir, rel || "index.html");
486
+ return;
487
+ }
488
+ if (pathname === "/api/docs") {
489
+ const rows = await store.listAllInProject(projectRoot);
490
+ const seen = /* @__PURE__ */ new Map();
491
+ for (const r of rows) {
492
+ if (!seen.has(r.metadata.id)) seen.set(r.metadata.id, r);
493
+ }
494
+ const docs = Array.from(seen.values()).map((r) => ({
495
+ id: r.metadata.id,
496
+ slug: r.slug,
497
+ type: r.type,
498
+ sha256: r.metadata.sha256,
499
+ mime: r.metadata.attachment.kind === "blob" ? r.metadata.attachment.mime : null,
500
+ ownerType: r.ownerType,
501
+ ownerId: r.ownerId,
502
+ title: r.slug ?? r.metadata.id,
503
+ createdAt: r.metadata.createdAt
504
+ }));
505
+ send(res, 200, "application/json", lafsSuccessJson({ docs }));
506
+ return;
507
+ }
508
+ if (pathname === "/api/search") {
509
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
510
+ const q = (url.searchParams.get("q") ?? "").trim();
511
+ const limitRaw = url.searchParams.get("limit");
512
+ const type = url.searchParams.get("type") ?? void 0;
513
+ if (q.length === 0) {
514
+ send(
515
+ res,
516
+ 400,
517
+ "application/json",
518
+ lafsErrorJson(
519
+ "E_VALIDATION",
520
+ "query parameter `q` is required",
521
+ "append ?q=<query> to /api/search, e.g. '/api/search?q=release'"
522
+ )
523
+ );
524
+ return;
525
+ }
526
+ const limit = limitRaw ? Math.max(1, Math.min(50, Number.parseInt(limitRaw, 10))) : 10;
527
+ try {
528
+ const result = await searchAllProjectDocs(q, {
529
+ projectRoot,
530
+ limit: Number.isFinite(limit) ? limit : 10,
531
+ type: type && type.length > 0 ? type : void 0
532
+ });
533
+ send(res, 200, "application/json", lafsSuccessJson(result));
534
+ } catch (err) {
535
+ const msg = err instanceof Error ? err.message : String(err);
536
+ const code = err instanceof Error && err.code ? err.code : "E_SEARCH_FAILED";
537
+ send(res, 500, "application/json", lafsErrorJson(code, msg));
538
+ }
539
+ return;
540
+ }
541
+ const apiDocMatch = pathname.match(/^\/api\/docs\/([^/]+)$/);
542
+ if (apiDocMatch) {
543
+ const slug = decodeURIComponent(apiDocMatch[1]);
544
+ const bySlug = await store.findBySlug(slug, projectRoot);
545
+ if (!bySlug) {
546
+ send(
547
+ res,
548
+ 404,
549
+ "application/json",
550
+ lafsErrorJson("E_NOT_FOUND", `no published doc with slug '${slug}'`)
551
+ );
552
+ return;
553
+ }
554
+ const fetched = await store.get(bySlug.metadata.sha256, projectRoot);
555
+ if (!fetched) {
556
+ send(
557
+ res,
558
+ 404,
559
+ "application/json",
560
+ lafsErrorJson(
561
+ "E_NOT_FOUND",
562
+ `doc '${slug}' metadata present but blob bytes missing on disk`
563
+ )
564
+ );
565
+ return;
566
+ }
567
+ const mime = fetched.metadata.attachment.kind === "blob" ? fetched.metadata.attachment.mime : fetched.metadata.attachment.kind === "local-file" ? fetched.metadata.attachment.mime ?? "text/plain" : "application/octet-stream";
568
+ const isText = mime.startsWith("text/") || mime === "application/json";
569
+ const content = isText ? fetched.bytes.toString("utf8") : null;
570
+ const title = content ? inferTitle(content, slug) : slug;
571
+ send(
572
+ res,
573
+ 200,
574
+ "application/json",
575
+ lafsSuccessJson({
576
+ id: fetched.metadata.id,
577
+ slug: bySlug.slug,
578
+ type: bySlug.type,
579
+ title,
580
+ mime,
581
+ sha256: fetched.metadata.sha256,
582
+ content,
583
+ sizeBytes: fetched.bytes.length
584
+ })
585
+ );
586
+ return;
587
+ }
588
+ if (/^\/docs\/[^/]+\/?$/.test(pathname)) {
589
+ await serveStatic(res, assetsDir, "index.html");
590
+ return;
591
+ }
592
+ send(
593
+ res,
594
+ 404,
595
+ "application/json",
596
+ lafsErrorJson("E_NOT_FOUND", `no route for ${method} ${pathname}`)
597
+ );
598
+ } catch (err) {
599
+ const msg = err instanceof Error ? err.message : String(err);
600
+ send(res, 500, "application/json", lafsErrorJson("E_INTERNAL", msg));
601
+ }
602
+ };
603
+ }
604
+ async function startViewer(opts = {}) {
605
+ const handler = buildViewerHandler({ projectRoot: opts.projectRoot });
606
+ return tryListen(handler, {
607
+ startPort: opts.startPort,
608
+ endPort: opts.endPort,
609
+ host: opts.host,
610
+ autoIncrement: opts.autoIncrement
611
+ });
612
+ }
613
+
614
+ // packages/cleo/src/cli/docs-viewer-subsystem.ts
615
+ var VIEWER_DEFAULT_PORT = 7777;
616
+ var VIEWER_DEFAULT_END_PORT = 7800;
617
+ var VIEWER_DEFAULT_HOST = "127.0.0.1";
618
+ var VIEWER_SUBSYSTEM_NAME = "cleo-docs-viewer";
619
+ var SIGTERM_GRACE_ITERATIONS = 20;
620
+ function getViewerPaths() {
621
+ const cleoHome = getCleoHome2();
622
+ return {
623
+ pidFile: viewerPidFilePath(),
624
+ logFile: join3(cleoHome, "viewer.log"),
625
+ logDir: cleoHome
626
+ };
627
+ }
628
+ function isViewerProcessRunning(pid) {
629
+ return isProcessAlive(pid);
630
+ }
631
+ async function getViewerStatus() {
632
+ const record = await readViewerPidFile();
633
+ if (!record) return { running: false, pid: null, port: null, host: null, url: null };
634
+ if (!isProcessAlive(record.pid)) {
635
+ await removeViewerPidFile();
636
+ return { running: false, pid: null, port: null, host: null, url: null };
637
+ }
638
+ return {
639
+ running: true,
640
+ pid: record.pid,
641
+ port: record.port,
642
+ host: record.host,
643
+ url: `http://${record.host}:${record.port}`
644
+ };
645
+ }
646
+ function createDocsViewerSubsystem(opts = {}) {
647
+ const startPort = opts.startPort ?? VIEWER_DEFAULT_PORT;
648
+ const endPort = opts.endPort ?? VIEWER_DEFAULT_END_PORT;
649
+ const host = opts.host ?? VIEWER_DEFAULT_HOST;
650
+ const autoIncrement = !(opts.noAutoPort ?? false);
651
+ let live;
652
+ return defineSubsystem({
653
+ name: VIEWER_SUBSYSTEM_NAME,
654
+ async start() {
655
+ const { pidFile, logDir } = getViewerPaths();
656
+ const existing = await getViewerStatus();
657
+ if (existing.running && existing.pid !== null) {
658
+ const ctx2 = {
659
+ pid: existing.pid,
660
+ pidFile,
661
+ port: existing.port ?? startPort,
662
+ host: existing.host ?? host
663
+ };
664
+ live = ctx2;
665
+ return ctx2;
666
+ }
667
+ await mkdir2(logDir, { recursive: true });
668
+ const handle = await startViewer({
669
+ startPort,
670
+ endPort,
671
+ host,
672
+ autoIncrement
673
+ });
674
+ await writeViewerPidFile({
675
+ pid: process.pid,
676
+ port: handle.port,
677
+ host: handle.host,
678
+ projectRoot: process.cwd(),
679
+ startedAt: Date.now()
680
+ });
681
+ const ctx = {
682
+ pid: process.pid,
683
+ pidFile,
684
+ port: handle.port,
685
+ host: handle.host
686
+ };
687
+ live = ctx;
688
+ return ctx;
689
+ },
690
+ healthProbe() {
691
+ if (live === void 0) {
692
+ const stopped = "stopped";
693
+ return {
694
+ child_id: VIEWER_SUBSYSTEM_NAME,
695
+ pid: 0,
696
+ state: stopped,
697
+ restart_count: 0,
698
+ detail: "not started"
699
+ };
700
+ }
701
+ const alive = isViewerProcessRunning(live.pid);
702
+ const state = alive ? "running" : "stopped";
703
+ return {
704
+ child_id: VIEWER_SUBSYSTEM_NAME,
705
+ pid: alive ? live.pid : 0,
706
+ state,
707
+ restart_count: 0,
708
+ detail: alive ? `url=http://${live.host}:${live.port} pid=${live.pid}` : `pid=${live.pid} exited`
709
+ };
710
+ },
711
+ async shutdown(context) {
712
+ if (!isViewerProcessRunning(context.pid)) {
713
+ await removeViewerPidFile();
714
+ live = void 0;
715
+ return;
716
+ }
717
+ try {
718
+ process.kill(context.pid, "SIGTERM");
719
+ } catch {
720
+ }
721
+ for (let i = 0; i < SIGTERM_GRACE_ITERATIONS; i++) {
722
+ if (!isViewerProcessRunning(context.pid)) break;
723
+ await new Promise((resolve4) => setTimeout(resolve4, 500));
724
+ }
725
+ if (isViewerProcessRunning(context.pid)) {
726
+ try {
727
+ process.kill(context.pid, "SIGKILL");
728
+ } catch {
729
+ }
730
+ }
731
+ await removeViewerPidFile();
732
+ live = void 0;
733
+ }
734
+ });
735
+ }
736
+
737
+ // packages/cleo/src/cli/commands/docs-viewer.ts
738
+ function openInBrowser(url) {
739
+ try {
740
+ let cmd;
741
+ let args;
742
+ if (process.platform === "darwin") {
743
+ cmd = "open";
744
+ args = [url];
745
+ } else if (process.platform === "win32") {
746
+ cmd = "cmd";
747
+ args = ["/c", "start", "", url];
748
+ } else {
749
+ cmd = "xdg-open";
750
+ args = [url];
751
+ }
752
+ spawn(cmd, args, { detached: true, stdio: "ignore" }).unref();
753
+ } catch {
754
+ }
755
+ }
756
+ var serveCommand = defineCommand({
757
+ meta: {
758
+ name: "serve",
759
+ description: "Run docs viewer \u2014 prefer `cleo docs viewer start`"
760
+ },
761
+ args: {
762
+ port: {
763
+ type: "string",
764
+ description: "Starting port (default 7777)",
765
+ default: String(VIEWER_DEFAULT_PORT)
766
+ },
767
+ "end-port": {
768
+ type: "string",
769
+ description: "Last port to try when auto-incrementing (default 7800)",
770
+ default: String(VIEWER_DEFAULT_END_PORT)
771
+ },
772
+ host: {
773
+ type: "string",
774
+ description: "Bind host (default 127.0.0.1)",
775
+ default: VIEWER_DEFAULT_HOST
776
+ },
777
+ "no-auto-port": {
778
+ type: "boolean",
779
+ description: "Disable auto-increment when start port is busy",
780
+ default: false
781
+ }
782
+ },
783
+ async run({ args }) {
784
+ const startPort = Number.parseInt(String(args.port), 10);
785
+ const endPort = Number.parseInt(String(args["end-port"]), 10);
786
+ const host = String(args.host);
787
+ const noAutoPort = negatedFlag(args, "auto-port");
788
+ if (!Number.isFinite(startPort) || startPort < 1 || startPort > 65535) {
789
+ cliError(`invalid --port: ${args.port}`, ExitCode4.VALIDATION_ERROR, { name: "E_VALIDATION" });
790
+ return;
791
+ }
792
+ if (!Number.isFinite(endPort) || endPort < startPort) {
793
+ cliError(`invalid --end-port: ${args["end-port"]}`, ExitCode4.VALIDATION_ERROR, {
794
+ name: "E_VALIDATION"
795
+ });
796
+ return;
797
+ }
798
+ try {
799
+ const subsystem = createDocsViewerSubsystem({ startPort, endPort, host, noAutoPort });
800
+ const ctx = await subsystem.start();
801
+ const { pidFile } = getViewerPaths();
802
+ cliOutput(
803
+ {
804
+ running: true,
805
+ pid: ctx.pid,
806
+ port: ctx.port,
807
+ host: ctx.host,
808
+ url: `http://${ctx.host}:${ctx.port}`,
809
+ pidFile
810
+ },
811
+ {
812
+ command: "docs serve",
813
+ operation: "docs.serve",
814
+ message: `viewer started on http://${ctx.host}:${ctx.port}`
815
+ }
816
+ );
817
+ } catch (err) {
818
+ const e = err;
819
+ if (e.code === "E_NO_PORT" || e.code === "EADDRINUSE") {
820
+ cliError(e.message ?? `port ${startPort} unavailable`, ExitCode4.GENERAL_ERROR, {
821
+ name: e.code === "E_NO_PORT" ? "E_NO_PORT" : "EADDRINUSE"
822
+ });
823
+ return;
824
+ }
825
+ throw err;
826
+ }
827
+ }
828
+ });
829
+ var openCommand = defineCommand({
830
+ meta: {
831
+ name: "open",
832
+ description: "Open docs viewer in browser \u2014 prefer `cleo docs viewer open`"
833
+ },
834
+ args: {
835
+ slug: {
836
+ type: "positional",
837
+ description: "Doc slug to open (omit to open the viewer home page)",
838
+ required: false
839
+ },
840
+ port: {
841
+ type: "string",
842
+ description: "Starting port for the viewer (default 7777)",
843
+ default: String(VIEWER_DEFAULT_PORT)
844
+ },
845
+ "end-port": {
846
+ type: "string",
847
+ description: "Last port to try (default 7800)",
848
+ default: String(VIEWER_DEFAULT_END_PORT)
849
+ },
850
+ host: {
851
+ type: "string",
852
+ description: "Bind host (default 127.0.0.1)",
853
+ default: VIEWER_DEFAULT_HOST
854
+ },
855
+ "no-launch": {
856
+ type: "boolean",
857
+ description: "Skip browser launch; just print the URL",
858
+ default: false
859
+ }
860
+ },
861
+ async run({ args }) {
862
+ const slug = args.slug ? String(args.slug) : void 0;
863
+ let status = await getViewerStatus();
864
+ if (!status.running) {
865
+ const startPort = Number.parseInt(String(args.port), 10);
866
+ const endPort = Number.parseInt(String(args["end-port"]), 10);
867
+ const host = String(args.host);
868
+ try {
869
+ const subsystem = createDocsViewerSubsystem({ startPort, endPort, host });
870
+ await subsystem.start();
871
+ status = await getViewerStatus();
872
+ } catch (err) {
873
+ const e = err;
874
+ cliError(e.message ?? "failed to start docs viewer", ExitCode4.GENERAL_ERROR, {
875
+ name: "E_VIEWER_START_FAILED"
876
+ });
877
+ return;
878
+ }
879
+ }
880
+ if (!status.running || status.pid === null || status.port === null || status.host === null) {
881
+ cliError("timed out waiting for viewer to bind", ExitCode4.GENERAL_ERROR, {
882
+ name: "E_VIEWER_TIMEOUT"
883
+ });
884
+ return;
885
+ }
886
+ const url = slug ? `http://${status.host}:${status.port}/docs/${encodeURIComponent(slug)}` : `http://${status.host}:${status.port}/`;
887
+ if (!negatedFlag(args, "launch")) {
888
+ openInBrowser(url);
889
+ }
890
+ cliOutput(
891
+ {
892
+ opened: true,
893
+ slug: slug ?? null,
894
+ pid: status.pid,
895
+ port: status.port,
896
+ host: status.host,
897
+ url
898
+ },
899
+ {
900
+ command: "docs open",
901
+ operation: "docs.open",
902
+ message: `opened ${url}`
903
+ }
904
+ );
905
+ }
906
+ });
907
+ var stopCommand = defineCommand({
908
+ meta: {
909
+ name: "stop",
910
+ description: "Stop the docs viewer \u2014 prefer `cleo docs viewer stop`"
911
+ },
912
+ args: {
913
+ timeout: {
914
+ type: "string",
915
+ description: "Grace period in seconds before SIGKILL (default 10)",
916
+ default: "10"
917
+ }
918
+ },
919
+ async run({ args }) {
920
+ const record = await readViewerPidFile();
921
+ if (!record) {
922
+ cliOutput(
923
+ { stopped: false, reason: "no pidfile" },
924
+ {
925
+ command: "docs stop",
926
+ operation: "docs.stop",
927
+ message: "viewer not running (no pidfile)"
928
+ }
929
+ );
930
+ return;
931
+ }
932
+ if (!isProcessAlive(record.pid)) {
933
+ await removeViewerPidFile();
934
+ cliOutput(
935
+ { stopped: false, reason: "stale pidfile", pid: record.pid },
936
+ {
937
+ command: "docs stop",
938
+ operation: "docs.stop",
939
+ message: `stale pidfile removed (pid ${record.pid} not alive)`
940
+ }
941
+ );
942
+ return;
943
+ }
944
+ const { pidFile } = getViewerPaths();
945
+ const subsystem = createDocsViewerSubsystem({
946
+ startPort: record.port,
947
+ host: record.host
948
+ });
949
+ await subsystem.shutdown({
950
+ pid: record.pid,
951
+ pidFile,
952
+ port: record.port,
953
+ host: record.host
954
+ });
955
+ const timeoutSec = Math.max(1, Number.parseInt(String(args.timeout), 10) || 10);
956
+ cliOutput(
957
+ {
958
+ stopped: true,
959
+ pid: record.pid,
960
+ graceful: true,
961
+ timeoutSec
962
+ },
963
+ {
964
+ command: "docs stop",
965
+ operation: "docs.stop",
966
+ message: `viewer (pid ${record.pid}) stopped`
967
+ }
968
+ );
969
+ }
970
+ });
971
+ var viewerStatusCommand = defineCommand({
972
+ meta: {
973
+ name: "viewer-status",
974
+ description: "Report viewer state \u2014 prefer `cleo docs viewer status`"
975
+ },
976
+ async run() {
977
+ await runViewerStatus();
978
+ }
979
+ });
980
+ var runViewerStatus = async () => {
981
+ const status = await getViewerStatus();
982
+ if (!status.running || status.pid === null) {
983
+ cliOutput(
984
+ { running: false, pidFile: viewerPidFilePath() },
985
+ {
986
+ command: "docs viewer-status",
987
+ operation: "docs.viewer-status",
988
+ message: "viewer not running"
989
+ }
990
+ );
991
+ return;
992
+ }
993
+ cliOutput(
994
+ {
995
+ running: true,
996
+ pid: status.pid,
997
+ port: status.port,
998
+ host: status.host,
999
+ url: status.url,
1000
+ pidFile: viewerPidFilePath()
1001
+ },
1002
+ {
1003
+ command: "docs viewer-status",
1004
+ operation: "docs.viewer-status",
1005
+ message: `viewer running (pid ${status.pid})`
1006
+ }
1007
+ );
1008
+ };
1009
+ var viewerCommand = defineCommand({
1010
+ meta: {
1011
+ name: "viewer",
1012
+ description: "Manage the docs web viewer lifecycle (start/stop/open/status)"
1013
+ },
1014
+ subCommands: {
1015
+ start: serveCommand,
1016
+ stop: stopCommand,
1017
+ open: openCommand,
1018
+ status: viewerStatusCommand
1019
+ },
1020
+ async run({ cmd, rawArgs }) {
1021
+ const firstArg = rawArgs?.find((a) => !a.startsWith("-"));
1022
+ if (firstArg && cmd.subCommands && firstArg in cmd.subCommands) return;
1023
+ await runViewerStatus();
1024
+ }
1025
+ });
1026
+ var docsViewerSubcommands = {
1027
+ serve: serveCommand,
1028
+ open: openCommand,
1029
+ stop: stopCommand,
1030
+ "viewer-status": viewerStatusCommand,
1031
+ viewer: viewerCommand
1032
+ };
1033
+
1034
+ // packages/cleo/src/cli/commands/docs.ts
1035
+ var docsOutputFlagHelp = " --json Emit the canonical LAFS JSON envelope (also accepted as a global flag)\n --output <mode> Re-render the result as envelope|id|table|count|silent (also accepted as a global flag)";
1036
+ var docsOutputArgs = {
1037
+ json: {
1038
+ type: "boolean",
1039
+ description: "Emit the canonical LAFS JSON envelope (global output flag; accepted here for docs-command consistency)."
1040
+ },
1041
+ output: {
1042
+ type: "string",
1043
+ description: "Output mode: envelope|id|table|count|silent (global output flag; accepted here for docs-command consistency)."
1044
+ }
1045
+ };
1046
+ async function dispatchDocsRaw(gateway, operation, params) {
1047
+ const response = await dispatchRaw(gateway, "docs", operation, params);
1048
+ handleRawError(response, { command: "docs", operation: `docs.${operation}` });
1049
+ return response.data;
1050
+ }
1051
+ async function readDocBodyFromStdin() {
1052
+ if (process.stdin.isTTY) return "";
1053
+ process.stdin.setEncoding("utf-8");
1054
+ let buf = "";
1055
+ for await (const chunk of process.stdin) {
1056
+ buf += chunk;
1057
+ }
1058
+ return buf.replace(/\r?\n$/, "");
1059
+ }
1060
+ async function getScriptNames(projectRoot) {
1061
+ const scriptsDir = join4(projectRoot, "scripts");
1062
+ try {
1063
+ const files = await readdir(scriptsDir);
1064
+ return files.filter((f) => f.endsWith(".sh")).map((f) => f.replace(".sh", "")).sort();
1065
+ } catch {
1066
+ return [];
1067
+ }
1068
+ }
1069
+ async function getIndexedCommands(projectRoot) {
1070
+ const indexPath = join4(projectRoot, "docs", "commands", "COMMANDS-INDEX.json");
1071
+ const index = await readJson(indexPath);
1072
+ if (!index) return [];
1073
+ return index.commands.map((c) => c.name).sort();
1074
+ }
1075
+ async function detectDrift(projectRoot) {
1076
+ const scripts = await getScriptNames(projectRoot);
1077
+ const indexed = await getIndexedCommands(projectRoot);
1078
+ const scriptSet = new Set(scripts);
1079
+ const indexSet = new Set(indexed);
1080
+ const missingFromIndex = scripts.filter((s) => !indexSet.has(s));
1081
+ const missingFromScripts = indexed.filter((i) => !scriptSet.has(i));
1082
+ const warnings = [];
1083
+ if (missingFromIndex.length > 0) {
1084
+ warnings.push(`${missingFromIndex.length} scripts not in COMMANDS-INDEX.json`);
1085
+ }
1086
+ if (missingFromScripts.length > 0) {
1087
+ warnings.push(`${missingFromScripts.length} index entries without scripts`);
1088
+ }
1089
+ let status = "clean";
1090
+ if (missingFromIndex.length > 0 || missingFromScripts.length > 0) {
1091
+ status = missingFromIndex.length > 5 ? "error" : "warning";
1092
+ }
1093
+ return { status, missingFromIndex, missingFromScripts, warnings };
1094
+ }
1095
+ async function runGapCheck(_projectRoot, filterId) {
1096
+ const reviewDir = getAgentOutputsAbsolute();
1097
+ const results = [];
1098
+ try {
1099
+ const files = await readdir(reviewDir);
1100
+ const reviewFiles = files.filter((f) => f.endsWith(".md"));
1101
+ for (const file of reviewFiles) {
1102
+ if (filterId && !file.includes(filterId)) continue;
1103
+ const filePath = join4(reviewDir, file);
1104
+ const content = await readFile2(filePath, "utf-8");
1105
+ const taskMatch = file.match(/^(T\d+)/);
1106
+ const taskId = taskMatch ? taskMatch[1] : "UNKNOWN";
1107
+ const gaps = [];
1108
+ if (!content.includes("## Summary")) {
1109
+ gaps.push("Missing ## Summary section");
1110
+ }
1111
+ if (!content.includes("**Task**:") && !content.includes("**Task:**")) {
1112
+ gaps.push("Missing Task provenance header");
1113
+ }
1114
+ if (gaps.length > 0) {
1115
+ results.push({ file, taskId, gaps });
1116
+ }
1117
+ }
1118
+ } catch {
1119
+ }
1120
+ return results;
1121
+ }
1122
+ var addCommand = defineCommand({
1123
+ meta: {
1124
+ name: "add",
1125
+ description: "Attach a local file or remote URL to a CLEO entity (task, session, observation). Owner type is inferred from the ID prefix: T### \u2192 task, ses_* \u2192 session, O-* \u2192 observation. Use --slug to set a human-friendly alias (unique per project) (T9636).\n\nPositional arguments:\n <owner-id> Owner entity ID (T###, ses_*, O-*) \u2014 required\n [file] Local file path to attach \u2014 optional when --url/--content is set\n\nNamed arguments:\n --url <url> Remote URL to attach (instead of a local file)\n --content <text> Inline document body \u2014 author without a file (T10965); '-' reads stdin\n --desc <text> Free-text description of this attachment\n --labels <csv> Comma-separated labels (e.g. rfc,spec)\n --attached-by <name> Agent identity that created the attachment (default: \"human\")\n --slug <kebab> Human-friendly alias, unique per project (T9636)\n --title <text> Human-readable title \u2014 REQUIRED for --type adr when --slug is omitted (T10360)\n --type <kind> Taxonomy classification \u2014 run `cleo docs schema` for kinds\n --allow-similar Bypass the slug-similarity warn \u2014 every bypass is audited\n to .cleo/audit/similar-bypass.jsonl (T10361)\n --strict Enforce body-schema (requiredSections) \u2014 fail with\n E_DOC_SCHEMA_MISMATCH instead of warning (T10160)\n" + docsOutputFlagHelp + "\n\nValidation behaviors:\n \u2022 Unknown flags \u2192 E_UNKNOWN_FLAG with did-you-mean suggestions (T10359)\n \u2022 Slug collision \u2192 E_SLUG_RESERVED + 3 alternative slugs (T10386)\n \u2022 Near-duplicate slug \u2192 W_SLUG_SIMILAR warning unless --allow-similar (T10361)\n \u2022 For --type adr without --slug, slug auto-allocates as `adr-NNN-<kebab-title>` via the\n central allocator (T10360 \u2014 closes T10153). --title is required in this case."
1126
+ },
1127
+ args: {
1128
+ "owner-id": {
1129
+ type: "positional",
1130
+ description: "Owner entity ID (T###, ses_*, O-*)",
1131
+ required: true
1132
+ },
1133
+ file: {
1134
+ type: "positional",
1135
+ description: "Local file path to attach",
1136
+ required: false
1137
+ },
1138
+ url: {
1139
+ type: "string",
1140
+ description: "Remote URL to attach (instead of a local file)"
1141
+ },
1142
+ content: {
1143
+ type: "string",
1144
+ description: "Inline document body \u2014 author a doc without a pre-existing file (T10965). Pass '-' to read the body from stdin. Mutually exclusive with the file positional and --url."
1145
+ },
1146
+ desc: {
1147
+ type: "string",
1148
+ description: "Free-text description of this attachment"
1149
+ },
1150
+ labels: {
1151
+ type: "string",
1152
+ description: "Comma-separated labels (e.g. rfc,spec)"
1153
+ },
1154
+ "attached-by": {
1155
+ type: "string",
1156
+ description: 'Agent identity that created the attachment (default: "human")'
1157
+ },
1158
+ slug: {
1159
+ type: "string",
1160
+ description: "Human-friendly kebab-case alias for the attachment, unique per project (T9636). Collision returns E_SLUG_RESERVED with 3 alternative suggestions (legacy E_SLUG_TAKEN aliased under details.aliases for one release \u2014 T10386)."
1161
+ },
1162
+ title: {
1163
+ type: "string",
1164
+ description: "Human-readable title used to derive the kebab-slug tail when auto-allocating an ADR slug. REQUIRED when --type adr is set AND --slug is omitted. Slugified via the shared kebabize helper (lowercase, hyphen-separated, diacritics stripped) (T10360)."
1165
+ },
1166
+ type: {
1167
+ type: "string",
1168
+ description: "Taxonomy classification \u2014 run `cleo docs schema` to enumerate registered kinds (T9637 / T9788 / T11142)"
1169
+ },
1170
+ "allow-similar": {
1171
+ type: "boolean",
1172
+ description: "Bypass the T10361 slug-similarity check. Use when you really do mean to add a new doc with a near-duplicate slug (e.g. intentional fork). Every bypass is logged to .cleo/audit/similar-bypass.jsonl."
1173
+ },
1174
+ strict: {
1175
+ type: "boolean",
1176
+ description: "Enforce body-schema validation against the kind's requiredSections (T10160). When set, a missing H2 section fails the write with E_DOC_SCHEMA_MISMATCH. Default (advisory) surfaces missing sections as a W_DOC_SCHEMA_MISMATCH warning."
1177
+ },
1178
+ ...docsOutputArgs
1179
+ },
1180
+ async run({ args, rawArgs }) {
1181
+ try {
1182
+ assertKnownFlags(rawArgs, addCommand.args, "docs add");
1183
+ } catch (err) {
1184
+ if (err instanceof UnknownFlagError) {
1185
+ cliError(err.message, ExitCode5.VALIDATION_ERROR, {
1186
+ name: err.code,
1187
+ fix: err.fix,
1188
+ alternatives: err.suggestions.map((s) => ({ action: s, command: s })),
1189
+ details: { flag: err.flag, knownFlags: err.knownFlags }
1190
+ });
1191
+ process.exit(ExitCode5.VALIDATION_ERROR);
1192
+ }
1193
+ throw err;
1194
+ }
1195
+ const ownerId = args["owner-id"];
1196
+ const fileArg = args.file ?? void 0;
1197
+ const url = args.url ?? void 0;
1198
+ const contentArg = typeof args.content === "string" ? args.content : void 0;
1199
+ const allowSimilar = args["allow-similar"] === true;
1200
+ const sourceCount = (fileArg ? 1 : 0) + (url ? 1 : 0) + (contentArg !== void 0 ? 1 : 0);
1201
+ if (sourceCount === 0) {
1202
+ cliError("provide a file path (positional), --url <url>, or --content <text>", 6, {
1203
+ name: "E_VALIDATION",
1204
+ fix: 'Example: cleo docs add T123 docs/rfc.md --desc "RFC draft" \u2014 or \u2014 cleo docs add T123 --content "# Note" --slug my-note'
1205
+ });
1206
+ process.exit(6);
1207
+ }
1208
+ if (sourceCount > 1) {
1209
+ cliError("the file positional, --url, and --content are mutually exclusive", 6, {
1210
+ name: "E_VALIDATION",
1211
+ fix: "Pass exactly one source: a file path, --url <url>, or --content <text>."
1212
+ });
1213
+ process.exit(6);
1214
+ }
1215
+ let content = contentArg;
1216
+ if (content === "-") {
1217
+ content = await readDocBodyFromStdin();
1218
+ }
1219
+ if (args.type === "adr" && !args.slug && !args.title) {
1220
+ cliError(
1221
+ "--title <text> is required when --type adr is used without --slug \u2014 the allocator needs a title to assemble adr-NNN-<kebab-title>",
1222
+ 6,
1223
+ {
1224
+ name: "E_VALIDATION",
1225
+ fix: 'Re-run with --title "Adopt Drizzle v1 beta" (or pass --slug adr-042-explicit-name to bypass auto-allocation).'
1226
+ }
1227
+ );
1228
+ process.exit(6);
1229
+ }
1230
+ let resolvedFile;
1231
+ if (fileArg) {
1232
+ const routing = resolveWorktreeRouting();
1233
+ const strayDb = detectStrayCleoDb(routing);
1234
+ if (strayDb) {
1235
+ cliError(
1236
+ `stray .cleo/tasks.db detected inside worktree at ${routing.worktreePath}. This is a leaked CLEO state directory.`,
1237
+ 6,
1238
+ {
1239
+ name: "E_STRAY_WORKTREE_DB",
1240
+ fix: `Remove it with: rm -rf ${routing.worktreePath}/.cleo \u2014 then retry. See ADR-068 \xA73 for the worktree DB isolation rationale.`
1241
+ }
1242
+ );
1243
+ process.exit(6);
1244
+ }
1245
+ if (routing.isWorktree && process.env["CLEO_QUIET"] !== "1") {
1246
+ const routingLog = `[T10389] routing SSoT write from worktree cwd ${routing.cwd} \u2192 canonical project root ${routing.canonicalRoot}
1247
+ `;
1248
+ process.stderr.write(routingLog);
1249
+ }
1250
+ resolvedFile = resolveWorktreeFilePath(String(fileArg), routing);
1251
+ }
1252
+ if (args.slug && args.type) {
1253
+ const projectRoot = await getProjectRoot3();
1254
+ let warnThreshold = DEFAULT_SIMILARITY_THRESHOLD;
1255
+ let mode = DEFAULT_SIMILARITY_MODE;
1256
+ try {
1257
+ const canon = loadCanonRegistry(projectRoot);
1258
+ if (canon?.similarity) {
1259
+ warnThreshold = canon.similarity.warnThreshold;
1260
+ mode = canon.similarity.mode;
1261
+ }
1262
+ } catch {
1263
+ }
1264
+ try {
1265
+ const sim = await checkSlugSimilarity({
1266
+ slug: args.slug,
1267
+ type: args.type,
1268
+ projectRoot,
1269
+ threshold: warnThreshold
1270
+ });
1271
+ if (sim.mostSimilarSlug !== null) {
1272
+ const scoreFixed = sim.score.toFixed(2);
1273
+ const hint = `Similar to '${sim.mostSimilarSlug}' (score ${scoreFixed}) \u2014 did you mean: cleo docs update ${sim.mostSimilarSlug}? Pass --allow-similar to bypass.`;
1274
+ if (mode === "block" && !allowSimilar) {
1275
+ cliError(hint, ExitCode5.VALIDATION_ERROR, {
1276
+ name: "E_SLUG_SIMILARITY",
1277
+ fix: `Use \`cleo docs update ${sim.mostSimilarSlug}\` if updating, or pass --allow-similar to add as a new doc.`,
1278
+ alternatives: [
1279
+ {
1280
+ action: `update '${sim.mostSimilarSlug}' instead`,
1281
+ command: `cleo docs update ${sim.mostSimilarSlug}`
1282
+ },
1283
+ {
1284
+ action: "bypass the similarity check",
1285
+ command: `cleo docs add ${ownerId} ${fileArg ?? `--url ${url}`} --slug ${args.slug} --type ${args.type} --allow-similar`
1286
+ }
1287
+ ],
1288
+ details: {
1289
+ proposedSlug: args.slug,
1290
+ mostSimilarSlug: sim.mostSimilarSlug,
1291
+ score: sim.score,
1292
+ threshold: warnThreshold,
1293
+ kind: args.type
1294
+ }
1295
+ });
1296
+ process.exit(ExitCode5.VALIDATION_ERROR);
1297
+ }
1298
+ humanInfo(hint);
1299
+ if (allowSimilar) {
1300
+ const auditLine = `${JSON.stringify({
1301
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
1302
+ reason: "allow-similar-bypass",
1303
+ proposedSlug: args.slug,
1304
+ mostSimilarSlug: sim.mostSimilarSlug,
1305
+ score: sim.score,
1306
+ threshold: warnThreshold,
1307
+ kind: args.type,
1308
+ ownerId
1309
+ })}
1310
+ `;
1311
+ try {
1312
+ await mkdir3(join4(projectRoot, ".cleo", "audit"), { recursive: true });
1313
+ await appendFile(
1314
+ join4(projectRoot, ".cleo", "audit", "similar-bypass.jsonl"),
1315
+ auditLine,
1316
+ "utf-8"
1317
+ );
1318
+ } catch {
1319
+ }
1320
+ }
1321
+ }
1322
+ } catch (err) {
1323
+ if (process.env["CLEO_DEBUG"]) {
1324
+ humanInfo(
1325
+ `similarity check skipped: ${err instanceof Error ? err.message : String(err)}`
1326
+ );
1327
+ }
1328
+ }
1329
+ }
1330
+ await dispatchFromCli(
1331
+ "mutate",
1332
+ "docs",
1333
+ "add",
1334
+ {
1335
+ ownerId,
1336
+ ...resolvedFile ? { file: resolvedFile } : {},
1337
+ ...url ? { url } : {},
1338
+ ...content !== void 0 ? { content } : {},
1339
+ ...args.desc ? { desc: args.desc } : {},
1340
+ ...args.labels ? { labels: args.labels } : {},
1341
+ ...args["attached-by"] ? { attachedBy: args["attached-by"] } : {},
1342
+ ...args.slug ? { slug: args.slug } : {},
1343
+ ...args.title ? { title: args.title } : {},
1344
+ ...args.type ? { type: args.type } : {},
1345
+ ...args.strict === true ? { strict: true } : {}
1346
+ },
1347
+ { command: "docs add" }
1348
+ );
1349
+ }
1350
+ });
1351
+ var listCommand = defineCommand({
1352
+ meta: {
1353
+ name: "list",
1354
+ description: "List attachments. With no scope flag, defaults to project scope and surfaces a hint to narrow with --task, --session, or --observation. --type filters across any scope (T9637/T9638). --limit <N> (default 50) and --orderBy <newest|sha|slug> (default newest) control the browsing window (T9792). Output flags: --json and --output envelope|id|table|count|silent are accepted consistently."
1355
+ },
1356
+ args: {
1357
+ task: {
1358
+ type: "string",
1359
+ description: "Filter by task ID (e.g. T123)"
1360
+ },
1361
+ session: {
1362
+ type: "string",
1363
+ description: "Filter by session ID (e.g. ses_abc123)"
1364
+ },
1365
+ observation: {
1366
+ type: "string",
1367
+ description: "Filter by observation ID (e.g. O-abc123)"
1368
+ },
1369
+ project: {
1370
+ type: "boolean",
1371
+ description: "List ALL attachments in the project (T9638). Mutually exclusive with --task/--session/--observation. Implicit default when no scope is set (T9792)."
1372
+ },
1373
+ type: {
1374
+ type: "string",
1375
+ description: "Filter by classification: spec|adr|research|handoff|note|llm-readme (T9637)"
1376
+ },
1377
+ limit: {
1378
+ type: "string",
1379
+ description: "Maximum number of rows to return (default 50, <=0 for unlimited). When the limit truncates the result set the response carries a hint + totalCount (T9792)."
1380
+ },
1381
+ orderBy: {
1382
+ type: "string",
1383
+ description: "Sort key: newest (default \u2014 most recent first), sha (ascending hex), slug (alphabetical, slug-less rows last) (T9792)."
1384
+ },
1385
+ // T9922 — MVI record projection opt-out flags (surfaced for --help).
1386
+ verbose: {
1387
+ type: "boolean",
1388
+ description: "Return full attachment records instead of the MVI projection (id + slug + type + kind + sha + size + createdAt). T9922."
1389
+ },
1390
+ full: {
1391
+ type: "boolean",
1392
+ description: "Alias for --verbose. T9922."
1393
+ },
1394
+ ...docsOutputArgs
1395
+ },
1396
+ async run({ args }) {
1397
+ const task = args.task ?? void 0;
1398
+ const session = args.session ?? void 0;
1399
+ const observation = args.observation ?? void 0;
1400
+ const project = args.project === true;
1401
+ const type = args.type ?? void 0;
1402
+ const limitRaw = args.limit ?? void 0;
1403
+ const orderByRaw = args.orderBy ?? void 0;
1404
+ const ownerCount = [task, session, observation].filter(Boolean).length;
1405
+ if (ownerCount + (project ? 1 : 0) > 1) {
1406
+ cliError("--task, --session, --observation, and --project are mutually exclusive", 6, {
1407
+ name: "E_VALIDATION"
1408
+ });
1409
+ process.exit(6);
1410
+ }
1411
+ let limit;
1412
+ if (limitRaw !== void 0) {
1413
+ const parsed = Number.parseInt(String(limitRaw), 10);
1414
+ if (Number.isNaN(parsed)) {
1415
+ cliError(`--limit must be an integer \u2014 got '${String(limitRaw)}'`, 6, {
1416
+ name: "E_VALIDATION"
1417
+ });
1418
+ process.exit(6);
1419
+ }
1420
+ limit = parsed;
1421
+ }
1422
+ let orderBy;
1423
+ if (orderByRaw !== void 0) {
1424
+ const candidate = String(orderByRaw);
1425
+ if (candidate !== "newest" && candidate !== "sha" && candidate !== "slug") {
1426
+ cliError(`--orderBy must be one of: newest|sha|slug \u2014 got '${candidate}'`, 6, {
1427
+ name: "E_VALIDATION"
1428
+ });
1429
+ process.exit(6);
1430
+ }
1431
+ orderBy = candidate;
1432
+ }
1433
+ await dispatchFromCli(
1434
+ "query",
1435
+ "docs",
1436
+ "list",
1437
+ {
1438
+ ...task ? { task } : {},
1439
+ ...session ? { session } : {},
1440
+ ...observation ? { observation } : {},
1441
+ ...project ? { project: true } : {},
1442
+ ...type ? { type } : {},
1443
+ ...limit !== void 0 ? { limit } : {},
1444
+ ...orderBy !== void 0 ? { orderBy } : {}
1445
+ },
1446
+ { command: "docs list" }
1447
+ );
1448
+ }
1449
+ });
1450
+ var fetchCommand = defineCommand({
1451
+ meta: {
1452
+ name: "fetch",
1453
+ description: "Retrieve attachment metadata and bytes by slug, attachment ID (att_*), or SHA-256 hex. Files <= 1 MB are returned base64-encoded inline; larger files report the storage path only. Pass --content (alias --decoded) to emit the decoded UTF-8 document body to stdout instead of the LAFS envelope \u2014 the agent-friendly shortcut over piping bytesBase64 through base64 -d (T10970). Output flags: --json and --output envelope|id|table|count|silent are accepted consistently."
1454
+ },
1455
+ args: {
1456
+ "attachment-ref": {
1457
+ type: "positional",
1458
+ description: "Slug, attachment ID (att_*), or SHA-256 hex",
1459
+ required: true
1460
+ },
1461
+ // T10970 — decoded-text content mode. An explicit opt-out from the
1462
+ // default envelope contract (ADR-086) that streams the raw UTF-8 body
1463
+ // to stdout, mirroring the other text-payload commands (export,
1464
+ // llm-output, view --render markdown).
1465
+ content: {
1466
+ type: "boolean",
1467
+ description: "Emit the decoded UTF-8 document body to stdout instead of the LAFS envelope (text docs only). Replaces the `--field /data/bytesBase64 | base64 -d` two-step (T10970)."
1468
+ },
1469
+ decoded: {
1470
+ type: "boolean",
1471
+ description: "Alias for --content (T10970)."
1472
+ },
1473
+ // T9922 — MVI record projection opt-out flags (surfaced for --help).
1474
+ verbose: {
1475
+ type: "boolean",
1476
+ description: "Return the full attachment metadata block instead of the MVI projection. The byte payload is always returned. T9922."
1477
+ },
1478
+ full: {
1479
+ type: "boolean",
1480
+ description: "Alias for --verbose. T9922."
1481
+ },
1482
+ ...docsOutputArgs
1483
+ },
1484
+ async run({ args }) {
1485
+ const ref = String(args["attachment-ref"]);
1486
+ if (args.content === true || args.decoded === true) {
1487
+ const model = createDocsReadModel();
1488
+ const result = await model.fetchDecoded(ref);
1489
+ if (!result.ok) {
1490
+ if (result.reason === "not-found") {
1491
+ cliError(`Doc not found: ${ref}`, ExitCode5.NOT_FOUND, {
1492
+ name: "E_NOT_FOUND",
1493
+ fix: "List available docs with: cleo docs list"
1494
+ });
1495
+ } else {
1496
+ cliError(`Content not retrievable: ${ref}`, ExitCode5.NOT_FOUND, {
1497
+ name: "E_NOT_FOUND",
1498
+ fix: "The doc metadata exists but its blob may be missing. Try: cleo docs publish <slug>"
1499
+ });
1500
+ }
1501
+ process.exit(ExitCode5.NOT_FOUND);
1502
+ }
1503
+ process.stdout.write(result.content);
1504
+ if (!result.content.endsWith("\n")) {
1505
+ process.stdout.write("\n");
1506
+ }
1507
+ return;
1508
+ }
1509
+ await dispatchFromCli(
1510
+ "query",
1511
+ "docs",
1512
+ "fetch",
1513
+ { attachmentRef: ref },
1514
+ { command: "docs fetch" }
1515
+ );
1516
+ }
1517
+ });
1518
+ var removeCommand = defineCommand({
1519
+ meta: {
1520
+ name: "remove",
1521
+ description: "Remove an attachment ref from an owner entity. When refCount reaches zero the blob file is purged from disk. Output flags: --json and --output envelope|id|table|count|silent are accepted consistently."
1522
+ },
1523
+ args: {
1524
+ "attachment-ref": {
1525
+ type: "positional",
1526
+ description: "Attachment ID (att_*) or SHA-256 hex",
1527
+ required: true
1528
+ },
1529
+ from: {
1530
+ type: "string",
1531
+ description: "Owner entity ID to remove the attachment ref from (required)"
1532
+ },
1533
+ ...docsOutputArgs
1534
+ },
1535
+ async run({ args }) {
1536
+ const from = args.from ?? void 0;
1537
+ if (!from) {
1538
+ cliError("--from <ownerId> is required", 6, { name: "E_VALIDATION" });
1539
+ process.exit(6);
1540
+ }
1541
+ await dispatchFromCli(
1542
+ "mutate",
1543
+ "docs",
1544
+ "remove",
1545
+ { attachmentRef: args["attachment-ref"], from },
1546
+ { command: "docs remove" }
1547
+ );
1548
+ }
1549
+ });
1550
+ var supersedeCommandArgs = {
1551
+ oldSlug: {
1552
+ type: "positional",
1553
+ description: "Slug of the doc being replaced",
1554
+ required: true
1555
+ },
1556
+ newSlug: {
1557
+ type: "positional",
1558
+ description: "Slug of the doc that replaces oldSlug",
1559
+ required: true
1560
+ },
1561
+ reason: {
1562
+ type: "string",
1563
+ description: "Optional human-readable reason carried back on the response envelope"
1564
+ }
1565
+ };
1566
+ var supersedeCommand = defineCommand({
1567
+ meta: {
1568
+ name: "supersede",
1569
+ description: "Atomically supersede an older doc with a newer one: flips lifecycle_status to 'superseded' on the old row and links both rows via the supersedes/superseded_by FK pointers. All writes commit in a single SQLite transaction."
1570
+ },
1571
+ args: supersedeCommandArgs,
1572
+ async run({ args, rawArgs }) {
1573
+ pushWarning({
1574
+ code: "W_DEPRECATED_COMMAND",
1575
+ message: "cleo docs supersede is deprecated - use `cleo docs update` for new work (T11179)",
1576
+ deprecated: "docs supersede",
1577
+ replacement: "docs update"
1578
+ });
1579
+ try {
1580
+ assertKnownFlags(rawArgs, supersedeCommandArgs, "docs supersede");
1581
+ } catch (err) {
1582
+ if (err instanceof UnknownFlagError) {
1583
+ cliError(err.message, ExitCode5.VALIDATION_ERROR, { name: "E_VALIDATION" });
1584
+ process.exit(ExitCode5.VALIDATION_ERROR);
1585
+ }
1586
+ throw err;
1587
+ }
1588
+ const oldSlug = args.oldSlug;
1589
+ const newSlug = args.newSlug;
1590
+ const reason = typeof args.reason === "string" && args.reason.length > 0 ? args.reason : void 0;
1591
+ await dispatchFromCli(
1592
+ "mutate",
1593
+ "docs",
1594
+ "supersede",
1595
+ {
1596
+ oldSlug,
1597
+ newSlug,
1598
+ ...reason !== void 0 ? { reason } : {}
1599
+ },
1600
+ { command: "docs supersede" }
1601
+ );
1602
+ }
1603
+ });
1604
+ var generateCommand = defineCommand({
1605
+ meta: {
1606
+ name: "generate",
1607
+ description: "Generate an llms.txt-format document summarising all attachments on a CLEO entity. Internally uses the llmtxt npm package for structural section analysis; falls back to a built-in generator when unavailable. Use --attach to save the output back as an llms-txt attachment on the same entity."
1608
+ },
1609
+ args: {
1610
+ for: {
1611
+ type: "string",
1612
+ description: "Target entity ID (task, session, or observation)",
1613
+ required: true
1614
+ },
1615
+ attach: {
1616
+ type: "boolean",
1617
+ description: "Save the generated llms.txt content back as an attachment on the target entity"
1618
+ }
1619
+ },
1620
+ async run({ args }) {
1621
+ humanInfo(
1622
+ `cleo: docs generate is deprecated \u2014 use \`cleo docs llm-output --for ${args.for} --mode attachment-bundle\` (T11137)`
1623
+ );
1624
+ await dispatchFromCli(
1625
+ "query",
1626
+ "docs",
1627
+ "generate",
1628
+ {
1629
+ for: args.for,
1630
+ ...args.attach ? { attach: true } : {}
1631
+ },
1632
+ { command: "docs generate" }
1633
+ );
1634
+ }
1635
+ });
1636
+ var exportCommand = defineCommand({
1637
+ meta: {
1638
+ name: "export",
1639
+ description: "Generate a rich Markdown export of a CLEO task (frontmatter + body + attachments + memory refs). Uses llmtxt/export.formatMarkdown for canonical serialisation. Use --out <file> to write to disk; omit to print to stdout."
1640
+ },
1641
+ args: {
1642
+ task: {
1643
+ type: "string",
1644
+ description: "Task ID to export (e.g. T947)",
1645
+ required: true
1646
+ },
1647
+ out: {
1648
+ type: "string",
1649
+ description: "Output file path (absolute or relative to project root). Omit for stdout."
1650
+ },
1651
+ "include-attachments": {
1652
+ type: "boolean",
1653
+ default: true,
1654
+ description: "Append attachment manifest section (default: true)"
1655
+ },
1656
+ "include-memory-refs": {
1657
+ type: "boolean",
1658
+ default: false,
1659
+ description: "Append BRAIN memory references section (default: false)"
1660
+ },
1661
+ json: {
1662
+ type: "boolean",
1663
+ description: "Emit result envelope as JSON instead of markdown (returns {markdown, pages, path?})"
1664
+ }
1665
+ },
1666
+ async run({ args }) {
1667
+ const taskId = String(args.task);
1668
+ const includeAttachments = args["include-attachments"] !== false;
1669
+ const includeMemoryRefs = args["include-memory-refs"] === true;
1670
+ const projectRoot = getProjectRoot3();
1671
+ try {
1672
+ const result = await dispatchDocsRaw("query", "llm-output", {
1673
+ mode: "task-export",
1674
+ taskId,
1675
+ includeAttachments,
1676
+ includeMemoryRefs
1677
+ });
1678
+ let writtenPath;
1679
+ if (typeof args.out === "string" && args.out.length > 0) {
1680
+ const outPath = isAbsolute(args.out) ? args.out : resolve3(projectRoot, args.out);
1681
+ await mkdir3(dirname3(outPath), { recursive: true });
1682
+ await writeFile2(outPath, result.content, "utf8");
1683
+ writtenPath = outPath;
1684
+ }
1685
+ if (args.json) {
1686
+ cliOutput(
1687
+ { markdown: result.content, pages: result.sectionCount, path: writtenPath ?? null },
1688
+ { command: "docs export", operation: "docs.export" }
1689
+ );
1690
+ } else {
1691
+ if (writtenPath) {
1692
+ humanInfo(`Wrote ${result.sectionCount} page(s) to ${writtenPath}`);
1693
+ } else {
1694
+ process.stdout.write(result.content);
1695
+ if (!result.content.endsWith("\n")) process.stdout.write("\n");
1696
+ }
1697
+ }
1698
+ } catch (err) {
1699
+ const message = err instanceof Error ? err.message : String(err);
1700
+ cliError(`docs export failed: ${message}`, ExitCode5.GENERAL_ERROR, {
1701
+ name: "E_DOCS_EXPORT_FAILED"
1702
+ });
1703
+ process.exit(ExitCode5.GENERAL_ERROR);
1704
+ }
1705
+ }
1706
+ });
1707
+ var _llmOutputCommand = defineCommand({
1708
+ meta: {
1709
+ name: "llm-output",
1710
+ description: "Unified LLM output: task export (rich Markdown with frontmatter + body + attachments + memory refs) or attachment-bundle (llms.txt summarising all attachments). Replaces `docs export` and `docs generate`."
1711
+ },
1712
+ args: {
1713
+ for: {
1714
+ type: "string",
1715
+ description: "Target entity ID (T###, ses_*, O-*, D-*, L-*, P-*)",
1716
+ required: true
1717
+ },
1718
+ mode: {
1719
+ type: "string",
1720
+ description: "Output mode: 'task-export' or 'attachment-bundle' (auto-detected)"
1721
+ },
1722
+ out: { type: "string", description: "Output file path. Omit for stdout." },
1723
+ "include-attachments": {
1724
+ type: "boolean",
1725
+ default: true,
1726
+ description: "Append attachment manifest (task-export, default: true)"
1727
+ },
1728
+ "include-memory-refs": {
1729
+ type: "boolean",
1730
+ default: false,
1731
+ description: "Append BRAIN memory refs (task-export, default: false)"
1732
+ },
1733
+ attach: {
1734
+ type: "boolean",
1735
+ description: "Save as llms-txt attachment on target (attachment-bundle)"
1736
+ },
1737
+ ...docsOutputArgs
1738
+ },
1739
+ async run({ args }) {
1740
+ const forId = String(args.for);
1741
+ const projectRoot = getProjectRoot3();
1742
+ try {
1743
+ const result = await dispatchDocsRaw("query", "llm-output", {
1744
+ for: forId,
1745
+ ...typeof args.mode === "string" ? { mode: args.mode } : {},
1746
+ includeAttachments: args["include-attachments"] !== false,
1747
+ includeMemoryRefs: args["include-memory-refs"] === true,
1748
+ ...args.attach ? { attach: true } : {}
1749
+ });
1750
+ let writtenPath;
1751
+ if (typeof args.out === "string" && args.out.length > 0) {
1752
+ const outPath = isAbsolute(args.out) ? args.out : resolve3(projectRoot, args.out);
1753
+ await mkdir3(dirname3(outPath), { recursive: true });
1754
+ await writeFile2(outPath, result.content, "utf8");
1755
+ writtenPath = outPath;
1756
+ }
1757
+ if (args.json) {
1758
+ cliOutput(
1759
+ {
1760
+ forId: result.forId,
1761
+ mode: result.mode,
1762
+ content: result.content,
1763
+ sectionCount: result.sectionCount,
1764
+ usedLlmtxtPackage: result.usedLlmtxtPackage,
1765
+ attached: result.attached,
1766
+ attachmentId: result.attachmentId,
1767
+ attachmentSha256: result.attachmentSha256,
1768
+ path: writtenPath ?? null
1769
+ },
1770
+ { command: "docs llm-output", operation: "docs.llm-output" }
1771
+ );
1772
+ } else {
1773
+ if (writtenPath) {
1774
+ humanInfo(
1775
+ `Wrote ${result.sectionCount} ${result.mode === "task-export" ? "page(s)" : "section(s)"} to ${writtenPath}`
1776
+ );
1777
+ } else {
1778
+ process.stdout.write(result.content);
1779
+ if (!result.content.endsWith("\n")) process.stdout.write("\n");
1780
+ }
1781
+ }
1782
+ } catch (err) {
1783
+ const message = err instanceof Error ? err.message : String(err);
1784
+ cliError(`docs llm-output failed: ${message}`, ExitCode5.GENERAL_ERROR, {
1785
+ name: "E_DOCS_LLM_OUTPUT_FAILED"
1786
+ });
1787
+ process.exit(ExitCode5.GENERAL_ERROR);
1788
+ }
1789
+ }
1790
+ });
1791
+ var llmOutputCommand = defineCommand({
1792
+ meta: {
1793
+ name: "llm-output",
1794
+ description: "Unified LLM output: task export (rich Markdown) or attachment-bundle (llms.txt). Replaces `docs export` and `docs generate`."
1795
+ },
1796
+ args: {
1797
+ for: { type: "string", description: "Target entity ID", required: true },
1798
+ mode: {
1799
+ type: "string",
1800
+ description: "Output mode: task-export|attachment-bundle (auto-detected)"
1801
+ },
1802
+ out: { type: "string", description: "Output file path" },
1803
+ "include-attachments": {
1804
+ type: "boolean",
1805
+ default: true,
1806
+ description: "Include attachment manifest (task-export)"
1807
+ },
1808
+ "include-memory-refs": {
1809
+ type: "boolean",
1810
+ default: false,
1811
+ description: "Include memory refs (task-export)"
1812
+ },
1813
+ attach: { type: "boolean", description: "Save as llms-txt attachment (attachment-bundle)" },
1814
+ ...docsOutputArgs
1815
+ },
1816
+ async run({ args }) {
1817
+ const forId = String(args.for);
1818
+ const projectRoot = getProjectRoot3();
1819
+ try {
1820
+ const result = await dispatchDocsRaw("query", "llm-output", {
1821
+ for: forId,
1822
+ ...typeof args.mode === "string" ? { mode: args.mode } : {},
1823
+ includeAttachments: args["include-attachments"] !== false,
1824
+ includeMemoryRefs: args["include-memory-refs"] === true,
1825
+ ...args.attach ? { attach: true } : {}
1826
+ });
1827
+ let writtenPath;
1828
+ if (typeof args.out === "string" && args.out.length > 0) {
1829
+ const outPath = isAbsolute(args.out) ? args.out : resolve3(projectRoot, args.out);
1830
+ await mkdir3(dirname3(outPath), { recursive: true });
1831
+ await writeFile2(outPath, result.content, "utf8");
1832
+ writtenPath = outPath;
1833
+ }
1834
+ if (args.json) {
1835
+ cliOutput(
1836
+ {
1837
+ forId: result.forId,
1838
+ mode: result.mode,
1839
+ content: result.content,
1840
+ sectionCount: result.sectionCount,
1841
+ usedLlmtxtPackage: result.usedLlmtxtPackage,
1842
+ attached: result.attached,
1843
+ attachmentId: result.attachmentId,
1844
+ attachmentSha256: result.attachmentSha256,
1845
+ path: writtenPath ?? null
1846
+ },
1847
+ { command: "docs llm-output", operation: "docs.llm-output" }
1848
+ );
1849
+ } else {
1850
+ if (writtenPath) {
1851
+ humanInfo(
1852
+ `Wrote ${result.sectionCount} ${result.mode === "task-export" ? "page(s)" : "section(s)"} to ${writtenPath}`
1853
+ );
1854
+ } else {
1855
+ process.stdout.write(result.content);
1856
+ if (!result.content.endsWith("\n")) process.stdout.write("\n");
1857
+ }
1858
+ }
1859
+ } catch (err) {
1860
+ const message = err instanceof Error ? err.message : String(err);
1861
+ cliError(`docs llm-output failed: ${message}`, ExitCode5.GENERAL_ERROR, {
1862
+ name: "E_DOCS_LLM_OUTPUT_FAILED"
1863
+ });
1864
+ process.exit(ExitCode5.GENERAL_ERROR);
1865
+ }
1866
+ }
1867
+ });
1868
+ var searchCommand = defineCommand({
1869
+ meta: {
1870
+ name: "search",
1871
+ description: "Search attachments by semantic similarity using llmtxt/similarity.rankBySimilarity. Without --owner, ranks every published doc in the project by content (T9647). Pass --owner to scope to a specific entity (T###, ses_*, O-*) and rank by blob name."
1872
+ },
1873
+ args: {
1874
+ query: {
1875
+ type: "positional",
1876
+ description: "Free-text search query",
1877
+ required: true
1878
+ },
1879
+ owner: {
1880
+ type: "string",
1881
+ description: "Scope search to a specific owner entity ID (legacy name-only ranking)"
1882
+ },
1883
+ type: {
1884
+ type: "string",
1885
+ description: "Filter project-wide search by taxonomy type: spec|adr|research|handoff|note|llm-readme"
1886
+ },
1887
+ limit: {
1888
+ type: "string",
1889
+ description: "Maximum number of results to return (default: 10)"
1890
+ },
1891
+ json: {
1892
+ type: "boolean",
1893
+ description: "Emit LAFS JSON envelope instead of human-readable output"
1894
+ }
1895
+ },
1896
+ async run({ args }) {
1897
+ const limit = args.limit ? Number.parseInt(String(args.limit), 10) : 10;
1898
+ try {
1899
+ const result = await dispatchDocsRaw("query", "search", {
1900
+ query: String(args.query),
1901
+ ...args.owner ? { ownerId: String(args.owner) } : {},
1902
+ limit,
1903
+ ...args.type ? { type: String(args.type) } : {}
1904
+ });
1905
+ cliOutput(result, { command: "docs search", operation: "docs.search" });
1906
+ } catch (err) {
1907
+ const message = err instanceof Error ? err.message : String(err);
1908
+ cliError(`docs search failed: ${message}`, ExitCode5.GENERAL_ERROR, {
1909
+ name: "E_DOCS_SEARCH_FAILED"
1910
+ });
1911
+ process.exit(ExitCode5.GENERAL_ERROR);
1912
+ }
1913
+ }
1914
+ });
1915
+ var findCommand = defineCommand({
1916
+ meta: {
1917
+ name: "find",
1918
+ description: "Find docs similar to a seed slug via llmtxt/similarity.rankBySimilarity. Pass --similar <slug>; results default to the same DocKind as the seed. Use --all-kinds to rank cross-kind, --threshold to set the minimum cosine score, and --limit to cap the number of returned hits.\n\nNamed arguments:\n --similar <slug> Slug of the seed doc to anchor similarity against (required for now)\n --limit <n> Maximum number of hits to return (default 10)\n --threshold <0..1> Minimum cosine score, hits below are dropped (default 0.5)\n --all-kinds Disable the same-kind filter and rank cross-kind\n --json Emit LAFS JSON envelope (default for non-TTY)"
1919
+ },
1920
+ args: {
1921
+ similar: {
1922
+ type: "string",
1923
+ description: "Slug of the seed doc to anchor similarity against"
1924
+ },
1925
+ limit: {
1926
+ type: "string",
1927
+ description: "Maximum number of hits to return (default: 10)"
1928
+ },
1929
+ threshold: {
1930
+ type: "string",
1931
+ description: "Minimum cosine similarity score in [0, 1] (default: 0.5)"
1932
+ },
1933
+ "all-kinds": {
1934
+ type: "boolean",
1935
+ description: "Disable the same-kind filter and rank across every DocKind"
1936
+ },
1937
+ json: {
1938
+ type: "boolean",
1939
+ description: "Emit LAFS JSON envelope instead of human-readable output"
1940
+ }
1941
+ },
1942
+ async run({ args, rawArgs }) {
1943
+ try {
1944
+ assertKnownFlags(rawArgs, findCommand.args, "docs find");
1945
+ } catch (err) {
1946
+ if (err instanceof UnknownFlagError) {
1947
+ cliError(err.message, ExitCode5.VALIDATION_ERROR, {
1948
+ name: err.code,
1949
+ fix: err.fix,
1950
+ alternatives: err.suggestions.map((s) => ({ action: s, command: s })),
1951
+ details: { flag: err.flag, knownFlags: err.knownFlags }
1952
+ });
1953
+ process.exit(ExitCode5.VALIDATION_ERROR);
1954
+ }
1955
+ throw err;
1956
+ }
1957
+ const similarSlug = typeof args.similar === "string" ? args.similar.trim() : "";
1958
+ if (similarSlug.length === 0) {
1959
+ cliError("--similar <slug> is required", ExitCode5.VALIDATION_ERROR, {
1960
+ name: "E_VALIDATION",
1961
+ fix: "Example: `cleo docs find --similar adr-073-above-epic-naming --limit 5`."
1962
+ });
1963
+ process.exit(ExitCode5.VALIDATION_ERROR);
1964
+ }
1965
+ let limit;
1966
+ if (typeof args.limit === "string") {
1967
+ const parsed = Number.parseInt(args.limit, 10);
1968
+ if (!Number.isFinite(parsed) || parsed <= 0) {
1969
+ cliError(
1970
+ `--limit must be a positive integer (got "${args.limit}")`,
1971
+ ExitCode5.VALIDATION_ERROR,
1972
+ {
1973
+ name: "E_VALIDATION"
1974
+ }
1975
+ );
1976
+ process.exit(ExitCode5.VALIDATION_ERROR);
1977
+ }
1978
+ limit = parsed;
1979
+ }
1980
+ let threshold;
1981
+ if (typeof args.threshold === "string") {
1982
+ const parsed = Number.parseFloat(args.threshold);
1983
+ if (!Number.isFinite(parsed) || parsed < 0 || parsed > 1) {
1984
+ cliError(
1985
+ `--threshold must be a number in [0, 1] (got "${args.threshold}")`,
1986
+ ExitCode5.VALIDATION_ERROR,
1987
+ { name: "E_VALIDATION" }
1988
+ );
1989
+ process.exit(ExitCode5.VALIDATION_ERROR);
1990
+ }
1991
+ threshold = parsed;
1992
+ }
1993
+ const allKinds = args["all-kinds"] === true;
1994
+ try {
1995
+ const result = await dispatchDocsRaw("query", "find", {
1996
+ similarSlug,
1997
+ ...limit !== void 0 ? { limit } : {},
1998
+ ...threshold !== void 0 ? { threshold } : {},
1999
+ allKinds
2000
+ });
2001
+ cliOutput(result, { command: "docs find", operation: "docs.find" });
2002
+ } catch (err) {
2003
+ const code = err instanceof Error && typeof err.code === "string" ? err.code : "E_DOCS_FIND_FAILED";
2004
+ const message = err instanceof Error ? err.message : String(err);
2005
+ cliError(`docs find failed: ${message}`, ExitCode5.GENERAL_ERROR, { name: code });
2006
+ process.exit(ExitCode5.GENERAL_ERROR);
2007
+ }
2008
+ }
2009
+ });
2010
+ var mergeCommand = defineCommand({
2011
+ meta: {
2012
+ name: "merge",
2013
+ description: "Merge two attachment text contents using llmtxt/sdk diff primitives. Strategies: three-way (default), cherry-pick, multi-diff."
2014
+ },
2015
+ args: {
2016
+ attA: {
2017
+ type: "positional",
2018
+ description: "First attachment ID or text content",
2019
+ required: true
2020
+ },
2021
+ attB: {
2022
+ type: "positional",
2023
+ description: "Second attachment ID or text content",
2024
+ required: true
2025
+ },
2026
+ strategy: {
2027
+ type: "string",
2028
+ description: "Merge strategy: three-way | cherry-pick | multi-diff (default: three-way)"
2029
+ },
2030
+ base: {
2031
+ type: "string",
2032
+ description: "Base content for three-way merge"
2033
+ },
2034
+ out: {
2035
+ type: "string",
2036
+ description: "Write merged content to this file path"
2037
+ },
2038
+ json: {
2039
+ type: "boolean",
2040
+ description: "Emit LAFS JSON envelope"
2041
+ }
2042
+ },
2043
+ async run({ args }) {
2044
+ const projectRoot = getProjectRoot3();
2045
+ const rawStrategy = args.strategy ?? "three-way";
2046
+ const strategy = rawStrategy === "cherry-pick" || rawStrategy === "multi-diff" ? rawStrategy : "three-way";
2047
+ try {
2048
+ const result = await dispatchDocsRaw("query", "merge", {
2049
+ attA: String(args.attA),
2050
+ attB: String(args.attB),
2051
+ strategy,
2052
+ base: args.base ?? void 0
2053
+ });
2054
+ if (typeof args.out === "string" && args.out.length > 0) {
2055
+ const outPath = isAbsolute(args.out) ? args.out : resolve3(projectRoot, args.out);
2056
+ await mkdir3(dirname3(outPath), { recursive: true });
2057
+ await writeFile2(outPath, result.merged, "utf8");
2058
+ humanInfo(`Wrote merged content to ${outPath}`);
2059
+ }
2060
+ cliOutput(result, { command: "docs merge", operation: "docs.merge" });
2061
+ } catch (err) {
2062
+ const message = err instanceof Error ? err.message : String(err);
2063
+ cliError(`docs merge failed: ${message}`, ExitCode5.GENERAL_ERROR, {
2064
+ name: "E_DOCS_MERGE_FAILED"
2065
+ });
2066
+ process.exit(ExitCode5.GENERAL_ERROR);
2067
+ }
2068
+ }
2069
+ });
2070
+ var queryCommand = defineCommand({
2071
+ meta: {
2072
+ name: "query",
2073
+ description: 'Unified docs query surface: semantic search, similar-doc discovery, and entity ranking. Subsumes the legacy search, find, and rank subcommands into one consistent surface.\n\nModes (mutually exclusive):\n cleo docs query "<text>" Free-text search\n cleo docs query --similar <slug> Find similar to a slug\n cleo docs query --for <id> Rank for an entity\n\nCommon flags: --limit <n>, --type <kind>, --json\nFree-text search flags: --owner <id>\nSimilar-to-slug flags: --threshold <0..1>, --all-kinds\nEntity-ranking flags: --text "<query>"\n\n' + docsOutputFlagHelp
2074
+ },
2075
+ args: {
2076
+ query: {
2077
+ type: "positional",
2078
+ description: "Free-text query for semantic search",
2079
+ required: false
2080
+ },
2081
+ similar: {
2082
+ type: "string",
2083
+ description: "Slug of seed doc to find similar docs against (find mode)"
2084
+ },
2085
+ for: {
2086
+ type: "string",
2087
+ description: "Owner entity ID to rank attachments for (rank mode)"
2088
+ },
2089
+ text: {
2090
+ type: "string",
2091
+ description: "Custom query string for the rank mode (default: owner ID)"
2092
+ },
2093
+ type: {
2094
+ type: "string",
2095
+ description: "Filter by taxonomy type: spec|adr|research|handoff|note|llm-readme"
2096
+ },
2097
+ owner: {
2098
+ type: "string",
2099
+ description: "Scope free-text search to a specific owner entity ID"
2100
+ },
2101
+ limit: {
2102
+ type: "string",
2103
+ description: "Maximum number of results to return (default: 10)"
2104
+ },
2105
+ threshold: {
2106
+ type: "string",
2107
+ description: "Minimum cosine similarity score in [0, 1] (for --similar mode, default: 0.5)"
2108
+ },
2109
+ "all-kinds": {
2110
+ type: "boolean",
2111
+ description: "Disable the same-kind filter and rank across every DocKind (for --similar mode)"
2112
+ },
2113
+ ...docsOutputArgs
2114
+ },
2115
+ async run({ args, rawArgs }) {
2116
+ try {
2117
+ assertKnownFlags(rawArgs, queryCommand.args, "docs query");
2118
+ } catch (err) {
2119
+ if (err instanceof UnknownFlagError) {
2120
+ cliError(err.message, ExitCode5.VALIDATION_ERROR, {
2121
+ name: err.code,
2122
+ fix: err.fix,
2123
+ alternatives: err.suggestions.map((s) => ({ action: s, command: s })),
2124
+ details: { flag: err.flag, knownFlags: err.knownFlags }
2125
+ });
2126
+ process.exit(ExitCode5.VALIDATION_ERROR);
2127
+ }
2128
+ throw err;
2129
+ }
2130
+ const similarSlug = typeof args.similar === "string" ? args.similar.trim() : "";
2131
+ const forId = typeof args.for === "string" ? args.for.trim() : "";
2132
+ const textQuery = typeof args.query === "string" ? String(args.query) : "";
2133
+ const customQuery = typeof args.text === "string" ? String(args.text) : void 0;
2134
+ const typeFilter = typeof args.type === "string" ? String(args.type) : void 0;
2135
+ const ownerScope = typeof args.owner === "string" ? String(args.owner) : void 0;
2136
+ const modes = [];
2137
+ if (similarSlug.length > 0) modes.push("--similar");
2138
+ if (forId.length > 0) modes.push("--for");
2139
+ if (textQuery.length > 0) modes.push("<query>");
2140
+ if (modes.length === 0) {
2141
+ cliError(
2142
+ "docs query requires a query mode: pass a free-text <query>, --similar <slug>, or --for <id>",
2143
+ ExitCode5.VALIDATION_ERROR,
2144
+ {
2145
+ name: "E_VALIDATION",
2146
+ fix: 'Examples: cleo docs query "authentication flow" | cleo docs query --similar adr-073 | cleo docs query --for T123'
2147
+ }
2148
+ );
2149
+ process.exit(ExitCode5.VALIDATION_ERROR);
2150
+ }
2151
+ if (modes.length > 1) {
2152
+ cliError(
2153
+ `docs query modes are mutually exclusive \u2014 got ${modes.join(", ")}. Choose one.`,
2154
+ ExitCode5.VALIDATION_ERROR,
2155
+ {
2156
+ name: "E_VALIDATION",
2157
+ fix: "Pass exactly one of: free-text <query>, --similar <slug>, or --for <id>"
2158
+ }
2159
+ );
2160
+ process.exit(ExitCode5.VALIDATION_ERROR);
2161
+ }
2162
+ let limit;
2163
+ if (typeof args.limit === "string") {
2164
+ const parsed = Number.parseInt(args.limit, 10);
2165
+ if (!Number.isFinite(parsed) || parsed <= 0) {
2166
+ cliError(
2167
+ `--limit must be a positive integer (got "${args.limit}")`,
2168
+ ExitCode5.VALIDATION_ERROR,
2169
+ { name: "E_VALIDATION" }
2170
+ );
2171
+ process.exit(ExitCode5.VALIDATION_ERROR);
2172
+ }
2173
+ limit = parsed;
2174
+ }
2175
+ let threshold;
2176
+ if (typeof args.threshold === "string") {
2177
+ const parsed = Number.parseFloat(args.threshold);
2178
+ if (!Number.isFinite(parsed) || parsed < 0 || parsed > 1) {
2179
+ cliError(
2180
+ `--threshold must be a number in [0, 1] (got "${args.threshold}")`,
2181
+ ExitCode5.VALIDATION_ERROR,
2182
+ { name: "E_VALIDATION" }
2183
+ );
2184
+ process.exit(ExitCode5.VALIDATION_ERROR);
2185
+ }
2186
+ threshold = parsed;
2187
+ }
2188
+ const allKinds = args["all-kinds"] === true;
2189
+ try {
2190
+ if (similarSlug.length > 0) {
2191
+ const result = await dispatchDocsRaw("query", "find", {
2192
+ similarSlug,
2193
+ ...limit !== void 0 ? { limit } : {},
2194
+ ...threshold !== void 0 ? { threshold } : {},
2195
+ allKinds
2196
+ });
2197
+ cliOutput(result, { command: "docs query", operation: "docs.find" });
2198
+ } else if (forId.length > 0) {
2199
+ const result = await dispatchDocsRaw("query", "rank", {
2200
+ ownerId: forId,
2201
+ query: customQuery ?? void 0
2202
+ });
2203
+ cliOutput(result, { command: "docs query", operation: "docs.rank" });
2204
+ } else {
2205
+ const result = await dispatchDocsRaw("query", "search", {
2206
+ query: textQuery,
2207
+ ...ownerScope ? { ownerId: ownerScope } : {},
2208
+ ...limit !== void 0 ? { limit } : {},
2209
+ ...typeFilter ? { type: typeFilter } : {}
2210
+ });
2211
+ cliOutput(result, { command: "docs query", operation: "docs.search" });
2212
+ }
2213
+ } catch (err) {
2214
+ const message = err instanceof Error ? err.message : String(err);
2215
+ const code = err instanceof Error && typeof err.code === "string" ? err.code : "E_DOCS_QUERY_FAILED";
2216
+ cliError(`docs query failed: ${message}`, ExitCode5.GENERAL_ERROR, { name: code });
2217
+ process.exit(ExitCode5.GENERAL_ERROR);
2218
+ }
2219
+ }
2220
+ });
2221
+ var rankCommand = defineCommand({
2222
+ meta: {
2223
+ name: "rank",
2224
+ description: "Rank attachments for an entity by relevance using llmtxt/similarity.rankBySimilarity. Pass --query to use a custom query; otherwise the entity ID anchors the ranking."
2225
+ },
2226
+ args: {
2227
+ for: {
2228
+ type: "string",
2229
+ description: "Owner entity ID (T###, ses_*, O-*)",
2230
+ required: true
2231
+ },
2232
+ query: {
2233
+ type: "string",
2234
+ description: "Optional free-text query to rank against (default: owner ID)"
2235
+ },
2236
+ json: {
2237
+ type: "boolean",
2238
+ description: "Emit LAFS JSON envelope"
2239
+ }
2240
+ },
2241
+ async run({ args }) {
2242
+ try {
2243
+ const result = await dispatchDocsRaw("query", "rank", {
2244
+ ownerId: String(args.for),
2245
+ query: args.query ?? void 0
2246
+ });
2247
+ cliOutput(result, { command: "docs rank", operation: "docs.rank" });
2248
+ } catch (err) {
2249
+ const message = err instanceof Error ? err.message : String(err);
2250
+ cliError(`docs rank failed: ${message}`, ExitCode5.GENERAL_ERROR, {
2251
+ name: "E_DOCS_RANK_FAILED"
2252
+ });
2253
+ process.exit(ExitCode5.GENERAL_ERROR);
2254
+ }
2255
+ }
2256
+ });
2257
+ var docsUpdateOperation = resolve("mutate", "docs", "update")?.def;
2258
+ if (docsUpdateOperation === void 0) {
2259
+ throw new Error("docs.update operation is missing from the registry");
2260
+ }
2261
+ var docsUpdateSchema = describeOperation(docsUpdateOperation, {
2262
+ includeGates: false,
2263
+ includeExamples: true
2264
+ });
2265
+ var docsUpdateArgs = paramsToCittyArgs(getOperationParams("mutate", "docs", "update"));
2266
+ var docsUpdateCliArgs = {
2267
+ ...docsUpdateArgs,
2268
+ ...docsOutputArgs
2269
+ };
2270
+ function toKebabFlag(name) {
2271
+ return name.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
2272
+ }
2273
+ function formatDocsUpdateContractHelp() {
2274
+ const positional = docsUpdateSchema.params.filter((param) => param.cli?.positional === true);
2275
+ const named = docsUpdateSchema.params.filter((param) => param.cli?.positional !== true);
2276
+ const lines = [
2277
+ docsUpdateSchema.description,
2278
+ "",
2279
+ "Positional arguments:",
2280
+ ...positional.map((param) => ` <${param.name}> ${param.description}`),
2281
+ "",
2282
+ "Named arguments:",
2283
+ ...named.map((param) => {
2284
+ const flag = param.cli?.flag ?? toKebabFlag(param.name);
2285
+ const enumSuffix = param.enum !== void 0 ? ` (${param.enum.join("|")})` : "";
2286
+ return ` --${flag} <${param.type}> ${param.description}${enumSuffix}`;
2287
+ })
2288
+ ];
2289
+ if ((docsUpdateSchema.examples ?? []).length > 0) {
2290
+ lines.push("", "Examples:");
2291
+ for (const example of docsUpdateSchema.examples ?? []) {
2292
+ lines.push(` ${example.command}`, ` ${example.description}`);
2293
+ }
2294
+ }
2295
+ lines.push(
2296
+ "",
2297
+ "Output flags:",
2298
+ docsOutputFlagHelp,
2299
+ "",
2300
+ "Renderer support: this command shares the registry schema surfaced by `cleo schema docs.update --format human --include-examples`."
2301
+ );
2302
+ return lines.join("\n");
2303
+ }
2304
+ var updateCommand = defineCommand({
2305
+ meta: {
2306
+ name: "update",
2307
+ description: formatDocsUpdateContractHelp()
2308
+ },
2309
+ args: docsUpdateCliArgs,
2310
+ async run({ args, rawArgs }) {
2311
+ try {
2312
+ assertKnownFlags(rawArgs, updateCommand.args, "docs update");
2313
+ } catch (err) {
2314
+ if (err instanceof UnknownFlagError) {
2315
+ cliError(err.message, ExitCode5.VALIDATION_ERROR, {
2316
+ name: err.code,
2317
+ fix: err.fix,
2318
+ alternatives: err.suggestions.map((s) => ({ action: s, command: s })),
2319
+ details: { flag: err.flag, knownFlags: err.knownFlags }
2320
+ });
2321
+ process.exit(ExitCode5.VALIDATION_ERROR);
2322
+ }
2323
+ throw err;
2324
+ }
2325
+ const slug = String(args.slug);
2326
+ const filePath = typeof args.file === "string" ? args.file : void 0;
2327
+ const inlineContent = typeof args.content === "string" ? args.content : void 0;
2328
+ if (filePath !== void 0 && inlineContent !== void 0) {
2329
+ cliError("--file and --content are mutually exclusive", ExitCode5.VALIDATION_ERROR, {
2330
+ name: "E_VALIDATION",
2331
+ fix: "Use `cleo docs update <slug> --file <path>` OR `--content <text>` (not both)."
2332
+ });
2333
+ process.exit(ExitCode5.VALIDATION_ERROR);
2334
+ }
2335
+ if (filePath === void 0 && inlineContent === void 0 && typeof args.status !== "string") {
2336
+ cliError(
2337
+ "provide --file <path>, --content <text>, or --status <status> alone",
2338
+ ExitCode5.VALIDATION_ERROR,
2339
+ {
2340
+ name: "E_VALIDATION",
2341
+ fix: 'Example: `cleo docs update my-doc --file ./new.md`, `--content "..."`, or `cleo docs update my-doc --status accepted` to change only the lifecycle status.'
2342
+ }
2343
+ );
2344
+ process.exit(ExitCode5.VALIDATION_ERROR);
2345
+ }
2346
+ let resolvedFile;
2347
+ if (filePath !== void 0) {
2348
+ const routing = resolveWorktreeRouting();
2349
+ resolvedFile = resolveWorktreeFilePath(filePath, routing);
2350
+ }
2351
+ await dispatchFromCli(
2352
+ "mutate",
2353
+ "docs",
2354
+ "update",
2355
+ {
2356
+ slug,
2357
+ ...resolvedFile !== void 0 ? { file: resolvedFile } : {},
2358
+ ...args["allow-external"] === true ? { allowExternal: true } : {},
2359
+ ...inlineContent !== void 0 ? { content: inlineContent } : {},
2360
+ ...typeof args.message === "string" ? { message: args.message } : {},
2361
+ ...typeof args.status === "string" ? { status: args.status } : {},
2362
+ ...args["dry-run"] === true ? { dryRun: true } : {},
2363
+ ...args.strict === true ? { strict: true } : {},
2364
+ ...typeof args["attached-by"] === "string" ? { attachedBy: args["attached-by"] } : {}
2365
+ },
2366
+ { command: "docs update" }
2367
+ );
2368
+ }
2369
+ });
2370
+ var versionsCommand = defineCommand({
2371
+ meta: {
2372
+ name: "versions",
2373
+ description: "List all SHA-256 content-address versions of attachments for an entity. Use --name to filter by a specific filename."
2374
+ },
2375
+ args: {
2376
+ for: {
2377
+ type: "string",
2378
+ description: "Owner entity ID (T###, ses_*, O-*)",
2379
+ required: true
2380
+ },
2381
+ name: {
2382
+ type: "string",
2383
+ description: "Optional filename filter (exact match)"
2384
+ },
2385
+ json: {
2386
+ type: "boolean",
2387
+ description: "Emit LAFS JSON envelope"
2388
+ }
2389
+ },
2390
+ async run({ args }) {
2391
+ try {
2392
+ const result = await dispatchDocsRaw("query", "versions", {
2393
+ ownerId: String(args.for),
2394
+ name: args.name ?? void 0
2395
+ });
2396
+ cliOutput(result, { command: "docs versions", operation: "docs.versions" });
2397
+ } catch (err) {
2398
+ const message = err instanceof Error ? err.message : String(err);
2399
+ cliError(`docs versions failed: ${message}`, ExitCode5.GENERAL_ERROR, {
2400
+ name: "E_DOCS_VERSIONS_FAILED"
2401
+ });
2402
+ process.exit(ExitCode5.GENERAL_ERROR);
2403
+ }
2404
+ }
2405
+ });
2406
+ var publishCommand = defineCommand({
2407
+ meta: {
2408
+ name: "publish",
2409
+ description: "Publish a doc to a local file (--target file) or a GitHub PR (--target pr). Default target is file. Use --target pr with a slug-or-id to open/update a PR. Use --dry-run to preview without side effects."
2410
+ },
2411
+ args: {
2412
+ "slug-or-id": {
2413
+ type: "positional",
2414
+ description: "Slug, attachment id, or full sha256 hex of the doc to publish. Required for --target pr."
2415
+ },
2416
+ target: {
2417
+ type: "string",
2418
+ description: "Publish target: file (local git-tracked path) or pr (GitHub PR). Default: file.",
2419
+ default: "file"
2420
+ },
2421
+ for: {
2422
+ type: "string",
2423
+ description: "Owner entity ID whose attachment to publish (T###, ses_*, O-*). Required for --target file."
2424
+ },
2425
+ to: {
2426
+ type: "string",
2427
+ description: "Destination file path (absolute or relative to project root). Required for --target file."
2428
+ },
2429
+ attachment: {
2430
+ type: "string",
2431
+ description: "Specific attachment ID or SHA-256 to publish (default: latest)"
2432
+ },
2433
+ slug: {
2434
+ type: "string",
2435
+ description: "Override the slug used for the branch + filename. Required when <slug-or-id> is an attachment id or sha256 with no stored slug."
2436
+ },
2437
+ type: {
2438
+ type: "string",
2439
+ description: "Override the publish dir taxonomy (spec|adr|research|handoff|note|llm-readme)."
2440
+ },
2441
+ title: {
2442
+ type: "string",
2443
+ description: "Override the PR title. Default: the commit subject `docs(<T####|type>): publish <slug>`."
2444
+ },
2445
+ task: {
2446
+ type: "string",
2447
+ description: "Owning task id (T####) for the commit subject + `Task:` trailer. Default: the doc's single task owner; required when it has several."
2448
+ },
2449
+ body: {
2450
+ type: "string",
2451
+ description: "Override the PR body. Default: an auto-generated summary."
2452
+ },
2453
+ base: {
2454
+ type: "string",
2455
+ description: "Base branch for the PR. Default: main."
2456
+ },
2457
+ "dry-run": {
2458
+ type: "boolean",
2459
+ description: "Preview what would happen without side effects. Reports resolved target, mode, and parameters. No files are written and no git/gh commands are invoked."
2460
+ },
2461
+ json: { type: "boolean", description: "Emit LAFS JSON envelope" }
2462
+ },
2463
+ async run({ args }) {
2464
+ const target = String(args.target ?? "file");
2465
+ const dryRun = args["dry-run"] === true;
2466
+ if (dryRun) {
2467
+ const details = { target, dryRun: true };
2468
+ if (target === "pr") {
2469
+ details.slugOrId = String(args["slug-or-id"] ?? "");
2470
+ if (args.slug) details.slug = String(args.slug);
2471
+ if (args.type) details.type = String(args.type);
2472
+ if (args.title) details.title = String(args.title);
2473
+ if (args.task) details.taskId = String(args.task);
2474
+ } else {
2475
+ details.for = args.for ? String(args.for) : null;
2476
+ details.to = args.to ? String(args.to) : null;
2477
+ if (args.attachment) details.attachment = String(args.attachment);
2478
+ }
2479
+ cliOutput(details, { command: "docs publish", operation: "docs.publish" });
2480
+ return;
2481
+ }
2482
+ if (target === "pr") {
2483
+ const slugOrId = String(args["slug-or-id"] ?? "");
2484
+ if (!slugOrId) {
2485
+ cliError(
2486
+ "docs publish --target pr requires a slug-or-id argument",
2487
+ ExitCode5.GENERAL_ERROR,
2488
+ { name: "E_MISSING_ARG" }
2489
+ );
2490
+ process.exit(ExitCode5.GENERAL_ERROR);
2491
+ }
2492
+ const result = await dispatchDocsRaw("mutate", "publish", {
2493
+ slugOrId,
2494
+ target: "pr",
2495
+ ...typeof args.slug === "string" ? { slug: args.slug } : {},
2496
+ ...typeof args.type === "string" ? { type: args.type } : {},
2497
+ ...typeof args.title === "string" ? { title: args.title } : {},
2498
+ ...typeof args.body === "string" ? { body: args.body } : {},
2499
+ ...typeof args.base === "string" ? { base: args.base } : {},
2500
+ ...typeof args.task === "string" ? { taskId: args.task } : {}
2501
+ });
2502
+ if (result.success) {
2503
+ cliOutput(result.data, { command: "docs publish", operation: "docs.publish" });
2504
+ return;
2505
+ }
2506
+ const e = result.error;
2507
+ cliError(
2508
+ e.message,
2509
+ ExitCode5.GENERAL_ERROR,
2510
+ {
2511
+ name: e.codeName,
2512
+ ...e.fix ? { fix: e.fix } : {},
2513
+ ...e.alternatives ? { alternatives: e.alternatives.map((alt) => ({ action: alt, command: alt })) } : {},
2514
+ ...e.details ? { details: e.details } : {}
2515
+ },
2516
+ { operation: "docs.publish" }
2517
+ );
2518
+ process.exit(ExitCode5.GENERAL_ERROR);
2519
+ }
2520
+ if (!args.for || !args.to) {
2521
+ cliError(
2522
+ "docs publish --target file requires --for <ownerId> and --to <path>",
2523
+ ExitCode5.GENERAL_ERROR,
2524
+ { name: "E_MISSING_ARG" }
2525
+ );
2526
+ process.exit(ExitCode5.GENERAL_ERROR);
2527
+ }
2528
+ try {
2529
+ const result = await dispatchDocsRaw("mutate", "publish", {
2530
+ ownerId: String(args.for),
2531
+ toPath: String(args.to),
2532
+ attachmentId: args.attachment ?? void 0,
2533
+ target: "file"
2534
+ });
2535
+ cliOutput(result, { command: "docs publish", operation: "docs.publish" });
2536
+ } catch (err) {
2537
+ const message = err instanceof Error ? err.message : String(err);
2538
+ cliError(`docs publish failed: ${message}`, ExitCode5.GENERAL_ERROR, {
2539
+ name: "E_DOCS_PUBLISH_FAILED"
2540
+ });
2541
+ process.exit(ExitCode5.GENERAL_ERROR);
2542
+ }
2543
+ }
2544
+ });
2545
+ var publishPrCommand = defineCommand({
2546
+ meta: {
2547
+ name: "publish-pr",
2548
+ description: "[DEPRECATED] Use `docs publish --target pr` instead. Publish an attachment to a GitHub PR. Opens a new PR on branch `docs/<slug>` with frontmatter, or atomically updates the existing open PR for the same slug. Use --dry-run to preview without side effects."
2549
+ },
2550
+ args: {
2551
+ "slug-or-id": {
2552
+ type: "positional",
2553
+ description: "Slug, attachment id, or full sha256 hex of the doc to publish",
2554
+ required: true
2555
+ },
2556
+ slug: { type: "string", description: "Override the slug used for the branch + filename." },
2557
+ type: {
2558
+ type: "string",
2559
+ description: "Override the publish dir taxonomy (spec|adr|research|handoff|note|llm-readme)."
2560
+ },
2561
+ title: {
2562
+ type: "string",
2563
+ description: "Override the PR title. Default: the commit subject `docs(<T####|type>): publish <slug>`."
2564
+ },
2565
+ task: {
2566
+ type: "string",
2567
+ description: "Owning task id (T####) for the commit subject + `Task:` trailer. Default: the doc's single task owner; required when it has several."
2568
+ },
2569
+ body: {
2570
+ type: "string",
2571
+ description: "Override the PR body. Default: an auto-generated summary."
2572
+ },
2573
+ base: { type: "string", description: "Base branch for the PR. Default: main." },
2574
+ "dry-run": { type: "boolean", description: "Preview what would happen without side effects." }
2575
+ },
2576
+ async run({ args }) {
2577
+ humanInfo(
2578
+ "[deprecated] `docs publish-pr` is deprecated. Use `docs publish --target pr <slug-or-id>` instead."
2579
+ );
2580
+ const slugOrId = String(args["slug-or-id"]);
2581
+ if (args["dry-run"] === true) {
2582
+ const details = { slugOrId, target: "pr", dryRun: true };
2583
+ if (args.slug) details.slug = String(args.slug);
2584
+ if (args.type) details.type = String(args.type);
2585
+ if (args.title) details.title = String(args.title);
2586
+ if (args.task) details.taskId = String(args.task);
2587
+ cliOutput(details, { command: "docs publish-pr", operation: "docs.publish" });
2588
+ return;
2589
+ }
2590
+ const result = await dispatchDocsRaw("mutate", "publish", {
2591
+ slugOrId,
2592
+ target: "pr",
2593
+ ...typeof args.slug === "string" ? { slug: args.slug } : {},
2594
+ ...typeof args.type === "string" ? { type: args.type } : {},
2595
+ ...typeof args.title === "string" ? { title: args.title } : {},
2596
+ ...typeof args.body === "string" ? { body: args.body } : {},
2597
+ ...typeof args.base === "string" ? { base: args.base } : {},
2598
+ ...typeof args.task === "string" ? { taskId: args.task } : {}
2599
+ });
2600
+ if (result.success) {
2601
+ cliOutput(result.data, { command: "docs publish-pr", operation: "docs.publish" });
2602
+ return;
2603
+ }
2604
+ const e = result.error;
2605
+ cliError(
2606
+ e.message,
2607
+ ExitCode5.GENERAL_ERROR,
2608
+ {
2609
+ name: e.codeName,
2610
+ ...e.fix ? { fix: e.fix } : {},
2611
+ ...e.alternatives ? { alternatives: e.alternatives.map((alt) => ({ action: alt, command: alt })) } : {},
2612
+ ...e.details ? { details: e.details } : {}
2613
+ },
2614
+ { operation: "docs.publish" }
2615
+ );
2616
+ process.exit(ExitCode5.GENERAL_ERROR);
2617
+ }
2618
+ });
2619
+ var checkCommand = defineCommand({
2620
+ meta: {
2621
+ name: "check",
2622
+ description: "Unified drift management: check documentation for drift, status, and gaps. Use --drift, --status, --gaps for specific checks; runs all by default."
2623
+ },
2624
+ args: {
2625
+ drift: {
2626
+ type: "boolean",
2627
+ description: "Run legacy drift check (scripts/ vs COMMANDS-INDEX.json)"
2628
+ },
2629
+ status: {
2630
+ type: "boolean",
2631
+ description: "Run git\u21C4llmtxt drift check (published files vs docs SSoT)"
2632
+ },
2633
+ gaps: { type: "boolean", description: "Run knowledge transfer gap check (review docs)" },
2634
+ all: {
2635
+ type: "boolean",
2636
+ description: "Run all three checks (default when no mode flag is set)"
2637
+ },
2638
+ quick: { type: "boolean", description: "Drift check only: quick mode (commands only)" },
2639
+ strict: { type: "boolean", description: "Exit with non-zero code on any drift detection" },
2640
+ epic: { type: "string", description: "Gap check only: filter by epic ID" },
2641
+ task: { type: "string", description: "Gap check only: filter by task ID" },
2642
+ json: { type: "boolean", description: "Emit LAFS JSON envelope" }
2643
+ },
2644
+ async run({ args }) {
2645
+ const hasExplicit = args.drift === true || args.status === true || args.gaps === true;
2646
+ const runDrift = args.drift === true || args.all === true || !hasExplicit;
2647
+ const runStatus = args.status === true || args.all === true || !hasExplicit;
2648
+ const runGaps = args.gaps === true || args.all === true || !hasExplicit;
2649
+ const projectRoot = process.cwd();
2650
+ const results = {};
2651
+ let anyDrift = false;
2652
+ try {
2653
+ if (runDrift) {
2654
+ const r = await detectDrift(projectRoot);
2655
+ results.drift = r;
2656
+ if (r.status !== "clean") anyDrift = true;
2657
+ }
2658
+ if (runStatus) {
2659
+ const r = await dispatchDocsRaw("query", "status", {});
2660
+ results.status = r;
2661
+ if (!r.allInSync) anyDrift = true;
2662
+ }
2663
+ if (runGaps) {
2664
+ const r = await runGapCheck(projectRoot, args.epic ?? args.task ?? void 0);
2665
+ results.gaps = r;
2666
+ if (r.length > 0) anyDrift = true;
2667
+ }
2668
+ cliOutput(results, {
2669
+ command: "docs check",
2670
+ message: anyDrift ? "Drift detected \u2014 see results for details" : "All checks passed \u2014 no drift detected"
2671
+ });
2672
+ if (args.strict && anyDrift) process.exit(2);
2673
+ } catch (err) {
2674
+ if (err instanceof CleoError2) {
2675
+ cliError(err.message, err.code, { name: "E_DOCS_CHECK_FAILED" });
2676
+ process.exit(err.code);
2677
+ }
2678
+ throw err;
2679
+ }
2680
+ }
2681
+ });
2682
+ var syncCommand = defineCommand({
2683
+ meta: {
2684
+ name: "sync",
2685
+ description: "Bidirectional docs sync. Use --from <path> --for <ownerId> to ingest a git file as a new blob version. Without --from, runs the legacy drift check between scripts/ and COMMANDS-INDEX.json."
2686
+ },
2687
+ args: {
2688
+ from: {
2689
+ type: "string",
2690
+ description: "Git-tracked file path to ingest as a new blob version (triggers reverse-ingest mode)"
2691
+ },
2692
+ for: {
2693
+ type: "string",
2694
+ description: "Owner entity ID for reverse-ingest mode (T###, ses_*, O-*). Required when --from is set."
2695
+ },
2696
+ name: {
2697
+ type: "string",
2698
+ description: "Override the blob name used in the manifest. Default: basename of --from."
2699
+ },
2700
+ "content-type": {
2701
+ type: "string",
2702
+ description: "IANA MIME type recorded with the new blob version (default: application/octet-stream)"
2703
+ },
2704
+ quick: {
2705
+ type: "boolean",
2706
+ description: "Legacy mode only: quick check (commands only)"
2707
+ },
2708
+ strict: {
2709
+ type: "boolean",
2710
+ description: "Legacy mode only: exit with error on any drift"
2711
+ }
2712
+ },
2713
+ async run({ args }) {
2714
+ if (args.from) {
2715
+ const ownerId = args.for ?? void 0;
2716
+ if (!ownerId) {
2717
+ cliError(
2718
+ "--for <ownerId> is required when --from <path> is set",
2719
+ ExitCode5.VALIDATION_ERROR,
2720
+ { name: "E_VALIDATION" }
2721
+ );
2722
+ process.exit(ExitCode5.VALIDATION_ERROR);
2723
+ }
2724
+ try {
2725
+ const result = await dispatchDocsRaw("mutate", "sync", {
2726
+ ownerId: String(ownerId),
2727
+ fromPath: String(args.from),
2728
+ blobName: args.name ?? void 0,
2729
+ contentType: args["content-type"] ?? void 0
2730
+ });
2731
+ cliOutput(result, { command: "docs sync", operation: "docs.sync" });
2732
+ } catch (err) {
2733
+ const message = err instanceof Error ? err.message : String(err);
2734
+ cliError(`docs sync failed: ${message}`, ExitCode5.GENERAL_ERROR, {
2735
+ name: "E_DOCS_SYNC_FAILED"
2736
+ });
2737
+ process.exit(ExitCode5.GENERAL_ERROR);
2738
+ }
2739
+ return;
2740
+ }
2741
+ try {
2742
+ const projectRoot = process.cwd();
2743
+ const result = await detectDrift(projectRoot);
2744
+ cliOutput(
2745
+ {
2746
+ status: result.status,
2747
+ missingFromIndex: result.missingFromIndex,
2748
+ missingFromScripts: result.missingFromScripts,
2749
+ warnings: result.warnings
2750
+ },
2751
+ {
2752
+ command: "docs",
2753
+ message: result.status === "clean" ? "Documentation is in sync" : `Drift detected: ${result.warnings.join("; ")}`
2754
+ }
2755
+ );
2756
+ if (args.strict && result.status !== "clean") {
2757
+ process.exit(result.status === "error" ? 2 : 1);
2758
+ }
2759
+ } catch (err) {
2760
+ if (err instanceof CleoError2) {
2761
+ cliError(err.message, err.code, { name: "E_DOCS_SYNC_FAILED" });
2762
+ process.exit(err.code);
2763
+ }
2764
+ throw err;
2765
+ }
2766
+ }
2767
+ });
2768
+ var statusCommand = defineCommand({
2769
+ meta: {
2770
+ name: "status",
2771
+ description: "Compare published files on disk against the docs SSoT and report drift. Exits 0 when all entries are in-sync, 2 when any drift is present."
2772
+ },
2773
+ args: {
2774
+ json: {
2775
+ type: "boolean",
2776
+ description: "Emit LAFS JSON envelope"
2777
+ }
2778
+ },
2779
+ async run() {
2780
+ try {
2781
+ const result = await dispatchDocsRaw("query", "status", {});
2782
+ cliOutput(result, { command: "docs status", operation: "docs.status" });
2783
+ if (!result.allInSync) {
2784
+ process.exit(2);
2785
+ }
2786
+ } catch (err) {
2787
+ const message = err instanceof Error ? err.message : String(err);
2788
+ cliError(`docs status failed: ${message}`, ExitCode5.GENERAL_ERROR, {
2789
+ name: "E_DOCS_STATUS_FAILED"
2790
+ });
2791
+ process.exit(ExitCode5.GENERAL_ERROR);
2792
+ }
2793
+ }
2794
+ });
2795
+ var gapCheckCommand = defineCommand({
2796
+ meta: {
2797
+ name: "gap-check",
2798
+ description: "Validate knowledge transfer from review docs to canonical docs"
2799
+ },
2800
+ args: {
2801
+ epic: {
2802
+ type: "string",
2803
+ description: "Filter by epic ID"
2804
+ },
2805
+ task: {
2806
+ type: "string",
2807
+ description: "Filter by task ID"
2808
+ }
2809
+ },
2810
+ async run({ args }) {
2811
+ try {
2812
+ const projectRoot = process.cwd();
2813
+ const filterId = args.epic ?? args.task;
2814
+ const gaps = await runGapCheck(projectRoot, filterId);
2815
+ if (gaps.length === 0) {
2816
+ cliOutput(
2817
+ { gapCount: 0, results: [] },
2818
+ { command: "docs", message: "No documentation gaps found" }
2819
+ );
2820
+ } else {
2821
+ cliOutput(
2822
+ { gapCount: gaps.length, results: gaps },
2823
+ { command: "docs", message: `Found ${gaps.length} document(s) with gaps` }
2824
+ );
2825
+ }
2826
+ } catch (err) {
2827
+ if (err instanceof CleoError2) {
2828
+ cliError(err.message, err.code, { name: "E_DOCS_GAP_CHECK_FAILED" });
2829
+ process.exit(err.code);
2830
+ }
2831
+ throw err;
2832
+ }
2833
+ }
2834
+ });
2835
+ var importCommand = defineCommand({
2836
+ meta: {
2837
+ name: "import",
2838
+ description: "Recursively import .md files from <dir> into the docs SSoT. Auto-classifies type by source-dir (.cleo/adrs/\u2192adr, .cleo/research/\u2192research, .cleo/agent-outputs/\u2192note, docs/\u2192spec). Idempotent via SHA-dedup. Use --dry-run to preview without writing."
2839
+ },
2840
+ args: {
2841
+ dir: {
2842
+ type: "positional",
2843
+ description: "Absolute or project-relative directory to scan recursively",
2844
+ required: true
2845
+ },
2846
+ "dry-run": {
2847
+ type: "boolean",
2848
+ description: "Print what would be imported without writing to the docs SSoT"
2849
+ },
2850
+ force: {
2851
+ type: "boolean",
2852
+ description: "Bypass SHA-dedup and re-import existing content. Bypass is logged to .cleo/audit/import-force-bypass.jsonl for traceability."
2853
+ },
2854
+ "audit-manifest": {
2855
+ type: "string",
2856
+ description: "Override the audit manifest output path. Default: <project-root>/docs-import-<ts>.json"
2857
+ },
2858
+ json: {
2859
+ type: "boolean",
2860
+ description: "Emit LAFS JSON envelope (default for agent callers)"
2861
+ }
2862
+ },
2863
+ async run({ args }) {
2864
+ const projectRoot = getProjectRoot3();
2865
+ const dirArg = String(args.dir);
2866
+ const scanRoot = isAbsolute(dirArg) ? dirArg : resolve3(projectRoot, dirArg);
2867
+ const dryRun = args["dry-run"] === true;
2868
+ const force = args.force === true;
2869
+ const manifestPath = args["audit-manifest"] ? isAbsolute(String(args["audit-manifest"])) ? String(args["audit-manifest"]) : resolve3(projectRoot, String(args["audit-manifest"])) : void 0;
2870
+ if (force) {
2871
+ const auditLine = `${JSON.stringify({
2872
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2873
+ scanRoot,
2874
+ reason: "force-flag-bypass"
2875
+ })}
2876
+ `;
2877
+ try {
2878
+ await mkdir3(join4(projectRoot, ".cleo", "audit"), { recursive: true });
2879
+ await appendFile(
2880
+ join4(projectRoot, ".cleo", "audit", "import-force-bypass.jsonl"),
2881
+ auditLine,
2882
+ "utf-8"
2883
+ );
2884
+ } catch {
2885
+ }
2886
+ }
2887
+ try {
2888
+ const result = await dispatchDocsRaw("mutate", "import", {
2889
+ scanRoot,
2890
+ dryRun,
2891
+ force,
2892
+ manifestPath
2893
+ });
2894
+ cliOutput(result, { command: "docs import", operation: "docs.import" });
2895
+ } catch (err) {
2896
+ if (err instanceof CounterMismatchError) {
2897
+ cliError(err.message, ExitCode5.GENERAL_ERROR, {
2898
+ name: "E_COUNTER_MISMATCH"
2899
+ });
2900
+ process.exit(ExitCode5.GENERAL_ERROR);
2901
+ }
2902
+ const message = err instanceof Error ? err.message : String(err);
2903
+ cliError(`docs import failed: ${message}`, ExitCode5.GENERAL_ERROR, {
2904
+ name: "E_DOCS_IMPORT_FAILED"
2905
+ });
2906
+ process.exit(ExitCode5.GENERAL_ERROR);
2907
+ }
2908
+ }
2909
+ });
2910
+ function toWireKind(meta) {
2911
+ return {
2912
+ kind: meta.kind,
2913
+ label: meta.label,
2914
+ description: meta.description,
2915
+ defaultOwnerKind: meta.defaultOwnerKind,
2916
+ publishDir: meta.publishDir,
2917
+ requiresEntityId: meta.requiresEntityId,
2918
+ ...meta.entityIdPattern ? { entityIdPattern: meta.entityIdPattern.source } : {},
2919
+ isExtension: meta.isExtension === true
2920
+ };
2921
+ }
2922
+ function loadCliRegistry(projectRoot) {
2923
+ try {
2924
+ return { registry: DocKindRegistry.load(projectRoot) };
2925
+ } catch (err) {
2926
+ if (err instanceof DocKindConfigError) {
2927
+ return {
2928
+ registry: DocKindRegistry.builtinOnly(),
2929
+ configError: { source: err.source, message: err.message }
2930
+ };
2931
+ }
2932
+ throw err;
2933
+ }
2934
+ }
2935
+ var schemaCommand = defineCommand({
2936
+ meta: {
2937
+ name: "schema",
2938
+ description: "Emit the canonical doc-kind taxonomy registry (built-ins + project extensions) as a LAFS envelope. The schema is the single source of truth for the --type values accepted by `cleo docs add` and the publish-dir layout used by `cleo docs publish-pr`. (T11142)."
2939
+ },
2940
+ args: {
2941
+ counts: {
2942
+ type: "boolean",
2943
+ description: "Include per-kind attachment counts from the project SSoT"
2944
+ },
2945
+ "include-counts": {
2946
+ type: "boolean",
2947
+ description: "DEPRECATED -- use --counts instead. Accepted for backward compatibility but will be removed in a future release."
2948
+ }
2949
+ },
2950
+ async run({ args }) {
2951
+ const projectRoot = getProjectRoot3();
2952
+ const { registry, configError } = loadCliRegistry(projectRoot);
2953
+ const kinds = registry.list().map(toWireKind);
2954
+ const extensionsCount = kinds.filter((k) => k.isExtension).length;
2955
+ const wantCounts = args.counts === true || args["include-counts"] === true;
2956
+ let counts;
2957
+ if (wantCounts) {
2958
+ counts = {};
2959
+ const { createAttachmentStore: createAttachmentStore2 } = await import("@cleocode/core/store/attachment-store");
2960
+ const store = createAttachmentStore2();
2961
+ for (const k of kinds) counts[k.kind] = 0;
2962
+ try {
2963
+ const rows = await store.listAllInProject(projectRoot);
2964
+ for (const row of rows) {
2965
+ const key = row.type;
2966
+ if (key && key in counts) counts[key] = (counts[key] ?? 0) + 1;
2967
+ }
2968
+ } catch {
2969
+ }
2970
+ }
2971
+ cliOutput(
2972
+ {
2973
+ version: 1,
2974
+ builtinsCount: kinds.length - extensionsCount,
2975
+ extensionsCount,
2976
+ kinds,
2977
+ ...counts ? { counts } : {},
2978
+ ...configError ? { configError } : {}
2979
+ },
2980
+ { command: "docs schema", operation: "docs.schema" }
2981
+ );
2982
+ }
2983
+ });
2984
+ var listTypesCommand = defineCommand({
2985
+ meta: {
2986
+ name: "list-types",
2987
+ description: "DEPRECATED -- use `cleo docs schema` instead. Lists every registered doc kind (T11142)."
2988
+ },
2989
+ args: {
2990
+ counts: {
2991
+ type: "boolean",
2992
+ description: "Include per-kind attachment counts from the project SSoT"
2993
+ },
2994
+ "include-counts": {
2995
+ type: "boolean",
2996
+ description: "DEPRECATED -- use --counts instead."
2997
+ }
2998
+ },
2999
+ async run({ args }) {
3000
+ pushWarning({
3001
+ code: "W_DEPRECATED_COMMAND",
3002
+ message: "cleo docs list-types is deprecated -- use `cleo docs schema` instead (T11142)",
3003
+ deprecated: "docs list-types",
3004
+ replacement: "docs schema"
3005
+ });
3006
+ const projectRoot = getProjectRoot3();
3007
+ const { registry, configError } = loadCliRegistry(projectRoot);
3008
+ const kinds = registry.list().map(toWireKind);
3009
+ const extensionsCount = kinds.filter((k) => k.isExtension).length;
3010
+ const wantCounts = args.counts === true || args["include-counts"] === true;
3011
+ let counts;
3012
+ if (wantCounts) {
3013
+ counts = {};
3014
+ const { createAttachmentStore: createAttachmentStore2 } = await import("@cleocode/core/store/attachment-store");
3015
+ const store = createAttachmentStore2();
3016
+ for (const k of kinds) counts[k.kind] = 0;
3017
+ try {
3018
+ const rows = await store.listAllInProject(projectRoot);
3019
+ for (const row of rows) {
3020
+ const key = row.type;
3021
+ if (key && key in counts) counts[key] = (counts[key] ?? 0) + 1;
3022
+ }
3023
+ } catch {
3024
+ }
3025
+ }
3026
+ cliOutput(
3027
+ {
3028
+ version: 1,
3029
+ builtinsCount: kinds.length - extensionsCount,
3030
+ extensionsCount,
3031
+ kinds,
3032
+ ...counts ? { counts } : {},
3033
+ ...configError ? { configError } : {}
3034
+ },
3035
+ {
3036
+ command: "docs list-types",
3037
+ operation: "docs.list-types"
3038
+ }
3039
+ );
3040
+ }
3041
+ });
3042
+ var docsCommand = defineCommand({
3043
+ meta: {
3044
+ name: "docs",
3045
+ description: "Canonical six-verb docs path: add, update, fetch, list, remove, publish. Unified query: query (subsumes search/find/rank). LLM output: llm-output (replaces generate + export). Advanced: supersede, merge, graph, versions. Audit: audit (query the immutable docs audit trail). Legacy/migration: search, find, rank, sync, status, gap-check, import (use query for new work). Viewer: viewer (start/stop/open/status). Utilities: schema (list-types \u2192 schema)."
3046
+ },
3047
+ subCommands: {
3048
+ // T11136 — Unified drift management (consolidates sync/status/gap-check)
3049
+ check: checkCommand,
3050
+ // Canonical six-verb path (add, update, fetch, list, remove, publish)
3051
+ add: addCommand,
3052
+ update: updateCommand,
3053
+ fetch: fetchCommand,
3054
+ list: listCommand,
3055
+ remove: removeCommand,
3056
+ publish: publishCommand,
3057
+ // T11176 — unified search/find/rank surface (preferred entry point)
3058
+ query: queryCommand,
3059
+ // Advanced primitives
3060
+ supersede: supersedeCommand,
3061
+ generate: generateCommand,
3062
+ export: exportCommand,
3063
+ "llm-output": llmOutputCommand,
3064
+ merge: mergeCommand,
3065
+ // T10164 — DocProvenanceResponse-typed graph (`--root <slug>|<taskId>`).
3066
+ graph: graphCommand,
3067
+ // T11875 — display-alias assignment (`set-alias <slug> <number>`), decoupled from slug.
3068
+ "set-alias": setAliasCommand,
3069
+ // Legacy aliases (use `query` for new work — retained for backward compatibility)
3070
+ search: searchCommand,
3071
+ find: findCommand,
3072
+ rank: rankCommand,
3073
+ versions: versionsCommand,
3074
+ "publish-pr": publishPrCommand,
3075
+ // T11182 — unified docs audit trail
3076
+ audit: auditCommand,
3077
+ // Legacy/migration (use canonical verbs for new work)
3078
+ sync: syncCommand,
3079
+ status: statusCommand,
3080
+ "gap-check": gapCheckCommand,
3081
+ import: importCommand,
3082
+ // Utilities
3083
+ // T9788 — canonical doc-kind taxonomy discovery surface.
3084
+ schema: schemaCommand,
3085
+ "list-types": listTypesCommand,
3086
+ ...docsViewerSubcommands
3087
+ },
3088
+ async run({ cmd, rawArgs }) {
3089
+ const firstArg = rawArgs?.find((a) => !a.startsWith("-"));
3090
+ if (firstArg && cmd.subCommands && firstArg in cmd.subCommands) return;
3091
+ await showUsage(cmd);
3092
+ }
3093
+ });
3094
+ export {
3095
+ _llmOutputCommand,
3096
+ docsCommand
3097
+ };
3098
+ //# sourceMappingURL=docs-DPPDFTWR.js.map