@cleocode/cleo 2026.10.27 → 2026.11.1-canary.3

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 (184) hide show
  1. package/README.md +28 -365
  2. package/dist/.source-stamp.json +4 -0
  3. package/dist/cli/{adapter-ZIB3OAPD.js → adapter-H6LGOEXR.js} +3 -3
  4. package/dist/cli/{add-TBJUMJW7.js → add-JTLUT54D.js} +3 -3
  5. package/dist/cli/{add-batch-GGKVPTS5.js → add-batch-ZK7SIYA5.js} +3 -3
  6. package/dist/cli/{admin-PEYBYMZN.js → admin-AA4M7JBW.js} +15 -5
  7. package/dist/cli/{admin-NGPG2LSO.js → admin-EPHDAQHU.js} +3 -3
  8. package/dist/cli/{adr-2XVNDYZ7.js → adr-6DUCKEGZ.js} +3 -3
  9. package/dist/cli/{analyze-A44RDCSX.js → analyze-C7PDKTLW.js} +3 -3
  10. package/dist/cli/{archive-QQB6M2RF.js → archive-4XUV2TNE.js} +3 -3
  11. package/dist/cli/{archive-stats-K4YKKYQV.js → archive-stats-P35RNTOG.js} +3 -3
  12. package/dist/cli/{attention-KAHDF6KD.js → attention-CRI2XJ62.js} +3 -3
  13. package/dist/cli/{backup-IYFULQTM.js → backup-JMHKBESP.js} +6 -5
  14. package/dist/cli/{blockers-KYNJJKZQ.js → blockers-GIYEINJM.js} +3 -3
  15. package/dist/cli/{briefing-J6PDDZ2C.js → briefing-CS6JNKV7.js} +3 -3
  16. package/dist/cli/{cancel-A4OCUVDI.js → cancel-I5WG5BR4.js} +3 -3
  17. package/dist/cli/{chunk-SXXOC6KZ.js → canon-docs-ZXWC4AFI.js} +51 -4
  18. package/dist/cli/{check-HHT4J5ZM.js → check-3KDTDUNQ.js} +3 -3
  19. package/dist/cli/{check-ZT6O5JDS.js → check-4XUOQNLG.js} +15 -19
  20. package/dist/cli/{chunk-PGR4RWQL.js → chunk-2AT5ALNQ.js} +2 -2
  21. package/dist/cli/{chunk-TUUKU4H7.js → chunk-5AL6B3J2.js} +7 -3
  22. package/dist/cli/{owner-override-cap-53HYZHIL.js → chunk-6C5X2J6S.js} +3 -2
  23. package/dist/cli/{chunk-OIS3BDSL.js → chunk-K6I66OSL.js} +2 -2
  24. package/dist/cli/chunk-RYGY5LG4.js +89 -0
  25. package/dist/cli/{chunk-JRDAITPC.js → chunk-V3ZUZYBZ.js} +12 -12
  26. package/dist/cli/{claim-MFFSWD64.js → claim-XGA4LKYO.js} +3 -3
  27. package/dist/cli/{cli-5PJ2B6ZH.js → cli-2U6BPCPH.js} +3 -3
  28. package/dist/cli/{complete-2IN4YEWL.js → complete-NCS2BCZB.js} +4 -4
  29. package/dist/cli/{complexity-JVEIZE5M.js → complexity-KJHXAWTC.js} +3 -3
  30. package/dist/cli/{compliance-MC3X2M34.js → compliance-GP6P63ZI.js} +3 -3
  31. package/dist/cli/{conduit-XYYPJYUF.js → conduit-NJL4YUDZ.js} +3 -3
  32. package/dist/cli/{config-Z5BXGRZB.js → config-FMLBYCVS.js} +5 -5
  33. package/dist/cli/{consensus-T6UARQTD.js → consensus-ALD6ADOC.js} +3 -3
  34. package/dist/cli/{context-M4F3YXZE.js → context-CVZBNZUZ.js} +3 -3
  35. package/dist/cli/{contribution-76FJYJKR.js → contribution-YQG6IHME.js} +3 -3
  36. package/dist/cli/{current-EFRCVSWL.js → current-GNHA4AK3.js} +3 -3
  37. package/dist/cli/{daemon-UAAMDN6C.js → daemon-VGOWNKWU.js} +3 -3
  38. package/dist/cli/{dash-ZEBYLYPV.js → dash-EP46P2SO.js} +3 -3
  39. package/dist/cli/{decompose-XTK2DXSM.js → decompose-J2RHLS4P.js} +3 -3
  40. package/dist/cli/{decomposition-ZDACSPOU.js → decomposition-RILQKAX7.js} +3 -3
  41. package/dist/cli/{delete-6YZADILS.js → delete-CBMXPES6.js} +3 -3
  42. package/dist/cli/{deps-WTASB2GB.js → deps-GPBTDYTW.js} +3 -3
  43. package/dist/cli/{detect-IITE5EBW.js → detect-OGQCROVY.js} +3 -3
  44. package/dist/cli/{diagnostics-MFUOSLSQ.js → diagnostics-4I2JUW5R.js} +3 -3
  45. package/dist/cli/{docs-NF5COLOB.js → docs-MNOBMDNV.js} +148 -16
  46. package/dist/cli/{docs-AIZLN3ZE.js → docs-YI47KPN7.js} +150 -93
  47. package/dist/cli/{doctor-4DWCNDOP.js → doctor-AYEY3IAL.js} +9 -4
  48. package/dist/cli/{doctor-tool-locks-VXKJZKAY.js → doctor-tool-locks-3ZT2LXRY.js} +2 -2
  49. package/dist/cli/{canon-docs-2GTOLF7Y.js → doctor-type-matrix-DD233VK2.js} +8 -8
  50. package/dist/cli/{done-A7ITQSJT.js → done-TZ5KBKEY.js} +73 -5
  51. package/dist/cli/{export-OTDQUAQQ.js → export-2PN64KYZ.js} +3 -3
  52. package/dist/cli/{export-tasks-BKVXMFLD.js → export-tasks-VDM7OZG5.js} +3 -3
  53. package/dist/cli/{find-BWKYKZEN.js → find-5XG5EKE3.js} +3 -3
  54. package/dist/cli/{focus-772SYK7Z.js → focus-ZL4LN3ME.js} +3 -3
  55. package/dist/cli/{goal-TG74GKBK.js → goal-TIQWF5PH.js} +2 -2
  56. package/dist/cli/{grade-BZYBWDA7.js → grade-4Q4VAS6D.js} +3 -3
  57. package/dist/cli/{graph-LB2TBRH6.js → graph-T5NO6IO5.js} +3 -3
  58. package/dist/cli/{history-XIYAXOUF.js → history-LMP6PSUW.js} +3 -3
  59. package/dist/cli/{import-C2QISQC7.js → import-RDVVHET4.js} +3 -3
  60. package/dist/cli/{import-tasks-P6FYXAAS.js → import-tasks-GQAD4JBU.js} +3 -3
  61. package/dist/cli/index.js +141 -103
  62. package/dist/cli/{inject-EDZVSPTW.js → inject-Y4LSXMTC.js} +3 -3
  63. package/dist/cli/{intelligence-OE7SIVR5.js → intelligence-HCB3COXY.js} +3 -3
  64. package/dist/cli/{issue-S67EZXN7.js → issue-7NLECBVN.js} +3 -3
  65. package/dist/cli/{labels-M7GIBYAQ.js → labels-BEKX5LIS.js} +3 -3
  66. package/dist/cli/{lifecycle-NO5QDJ2J.js → lifecycle-NXFDMNMA.js} +3 -3
  67. package/dist/cli/{list-VH6DPZ4V.js → list-H4DJHDQR.js} +3 -3
  68. package/dist/cli/{llm-P35U52I5.js → llm-24YWOGPN.js} +3 -3
  69. package/dist/cli/{log-XIPOX6UM.js → log-DKATGXVT.js} +3 -3
  70. package/dist/cli/{manifest-A5F2YD35.js → manifest-6W3LB3O4.js} +3 -3
  71. package/dist/cli/{map-AOMU72RY.js → map-LQUG4G6N.js} +3 -3
  72. package/dist/cli/{memory-4WNZWJG7.js → memory-FQKOIV43.js} +3 -3
  73. package/dist/cli/{memory-GK5WDYWT.js → memory-Z47B7XNL.js} +2 -2
  74. package/dist/cli/{migrate-claude-mem-OTSYHYBZ.js → migrate-claude-mem-MSXQ33SR.js} +3 -3
  75. package/dist/cli/{next-YUZ4X7NB.js → next-OU44ZHDS.js} +3 -3
  76. package/dist/cli/{nexus-YHPP3RZU.js → nexus-2O3D5KBD.js} +23 -15
  77. package/dist/cli/{nexus-RYH4IAG3.js → nexus-X6YLFJLZ.js} +2 -2
  78. package/dist/cli/{ops-XPJK3WBN.js → ops-MKGFUA4I.js} +3 -3
  79. package/dist/cli/{orchestrate-TWV43SAR.js → orchestrate-CIYXIPWJ.js} +3 -3
  80. package/dist/cli/{orchestrate-AYBDAVOQ.js → orchestrate-HSYARDD4.js} +2 -2
  81. package/dist/cli/owner-override-cap-3B7UXQXA.js +29 -0
  82. package/dist/cli/{phase-CFH2NZU5.js → phase-NJDN3EIY.js} +3 -3
  83. package/dist/cli/{pipeline-LW5HWLBI.js → pipeline-KQGUL5BQ.js} +2 -2
  84. package/dist/cli/{pivot-LEBKDG22.js → pivot-K3FL4H4Z.js} +3 -3
  85. package/dist/cli/{plan-6VK5I4BT.js → plan-TDBKUPE6.js} +3 -3
  86. package/dist/cli/{playbook-4GKDYZYW.js → playbook-UF7FCJ4U.js} +3 -3
  87. package/dist/cli/{promote-4V4ZLAL3.js → promote-Q3A7C7GI.js} +3 -3
  88. package/dist/cli/{provenance-2SFRY42O.js → provenance-J7OB5YAT.js} +3 -3
  89. package/dist/cli/{provider-G6MBFVHR.js → provider-T37WWONR.js} +3 -3
  90. package/dist/cli/{reason-2AYZ6SGZ.js → reason-D6W5GCAS.js} +3 -3
  91. package/dist/cli/{reconcile-7CBD7AHH.js → reconcile-FK2AVHTK.js} +3 -3
  92. package/dist/cli/{relates-FP43JAOK.js → relates-4UYF5ZTG.js} +3 -3
  93. package/dist/cli/{release-NW7XHRVB.js → release-NHPX6QOO.js} +128 -3
  94. package/dist/cli/{release-OWPFPUQG.js → release-XT2L6BZ6.js} +80 -4
  95. package/dist/cli/release-authorize-IV4MF6OV.js +84 -0
  96. package/dist/cli/{reorder-M457CQRR.js → reorder-3WRIRG5F.js} +3 -3
  97. package/dist/cli/{reparent-53T6IUUS.js → reparent-OBDMBMHO.js} +3 -3
  98. package/dist/cli/{req-ORMMU5PV.js → req-IC66XI36.js} +3 -3
  99. package/dist/cli/{research-LFGCGIEP.js → research-637G66T4.js} +3 -3
  100. package/dist/cli/{restore-ZYRIZQUJ.js → restore-VJ6UVTFO.js} +3 -3
  101. package/dist/cli/review-2VXZEKW6.js +131 -0
  102. package/dist/cli/{roadmap-EK6LXBQ4.js → roadmap-H26JEE34.js} +3 -3
  103. package/dist/cli/{run-PZJCIRFD.js → run-SUK5J7SL.js} +42 -2
  104. package/dist/cli/{safestop-NO57J3RK.js → safestop-G5LYH3B6.js} +3 -3
  105. package/dist/cli/{saga-NIJMIJE5.js → saga-CPNWFLGG.js} +3 -3
  106. package/dist/cli/{selfimprove-6HSEV3RG.js → selfimprove-T2IZGLE5.js} +3 -3
  107. package/dist/cli/{selfimprove-IYKG5PF4.js → selfimprove-UI4KV3LE.js} +2 -2
  108. package/dist/cli/{sequence-T42KPEEQ.js → sequence-DRHAPZP5.js} +3 -3
  109. package/dist/cli/{service-CBEZWGHC.js → service-L3XGRXZZ.js} +3 -3
  110. package/dist/cli/{session-XDADM7O4.js → session-WYKXKKPM.js} +3 -3
  111. package/dist/cli/{session-TFVW42XK.js → session-XGFWBFKH.js} +2 -2
  112. package/dist/cli/{show-I2TEU6MP.js → show-244XCU7Q.js} +3 -3
  113. package/dist/cli/{skills-ZBYRPFRL.js → skills-V4IH2LVI.js} +3 -3
  114. package/dist/cli/{snapshot-GMN7LZ4A.js → snapshot-BM2YSX23.js} +3 -3
  115. package/dist/cli/{start-FQWDRGJE.js → start-OUWHVDCT.js} +3 -3
  116. package/dist/cli/{stats-D2AU77GH.js → stats-SNANUIE4.js} +3 -3
  117. package/dist/cli/{sticky-FKZFQYNV.js → sticky-AXOXP64I.js} +3 -3
  118. package/dist/cli/{stop-ZCL7RZPH.js → stop-7XU7WJZP.js} +3 -3
  119. package/dist/cli/{sync-WM4JLGXF.js → sync-I7D4OMS5.js} +3 -3
  120. package/dist/cli/{tasks-RU6AAJKD.js → tasks-S4UR3OMN.js} +2 -2
  121. package/dist/cli/{tasks-54WNYQZS.js → tasks-VI633MO2.js} +3 -3
  122. package/dist/cli/{testing-S7FCPHW5.js → testing-444KOW72.js} +3 -3
  123. package/dist/cli/{token-BDRGEO3E.js → token-LEAY3YMC.js} +3 -3
  124. package/dist/cli/{update-BD77ORMO.js → update-2FKKXUZ5.js} +3 -3
  125. package/dist/cli/{verify-VOJRQQE2.js → verify-FJPE666T.js} +4 -4
  126. package/dist/cli/{worktree-2HHJT5BS.js → worktree-MRP3XN33.js} +3 -3
  127. package/package.json +16 -13
  128. package/studio-dist/client/_app/immutable/chunks/BBz_Ewst.js +3 -0
  129. package/studio-dist/client/_app/immutable/chunks/{CwQ8nAoh.js → BaXJwlsi.js} +1 -1
  130. package/studio-dist/client/_app/immutable/chunks/CERVObi_.js +1 -0
  131. package/studio-dist/client/_app/immutable/chunks/{Dv66614U.js → D4A9lkPs.js} +1 -1
  132. package/studio-dist/client/_app/immutable/entry/{app.C2oQiIiz.js → app.BxBC-th_.js} +2 -2
  133. package/studio-dist/client/_app/immutable/entry/start.Dm43jY1A.js +1 -0
  134. package/studio-dist/client/_app/immutable/nodes/{0.DaE6eVaG.js → 0.D7Aem8uF.js} +1 -1
  135. package/studio-dist/client/_app/immutable/nodes/{1.D0Mb3i_7.js → 1.Cf9AgcGX.js} +1 -1
  136. package/studio-dist/client/_app/immutable/nodes/{15.ChRO-4dz.js → 15.C1O3unvk.js} +1 -1
  137. package/studio-dist/client/_app/immutable/nodes/{16.fltF2wse.js → 16.DTsmQMdQ.js} +1 -1
  138. package/studio-dist/client/_app/immutable/nodes/{17.DnYjMhyj.js → 17.C7L1u1QT.js} +1 -1
  139. package/studio-dist/client/_app/immutable/nodes/{18.DYVIAC9a.js → 18.DJGn1-Wv.js} +1 -1
  140. package/studio-dist/client/_app/immutable/nodes/{20.CVBcbjBW.js → 20.CN2uQb5o.js} +1 -1
  141. package/studio-dist/client/_app/immutable/nodes/{24.Bfswm8Y5.js → 24.Bpbm5FXA.js} +1 -1
  142. package/studio-dist/client/_app/immutable/nodes/25.B91AFY13.js +39 -0
  143. package/studio-dist/client/_app/immutable/nodes/{26.D3RoTHoD.js → 26.3enLLx87.js} +1 -1
  144. package/studio-dist/client/_app/immutable/nodes/{3.ezhkaJvt.js → 3.yCO3XTid.js} +1 -1
  145. package/studio-dist/client/_app/version.json +1 -1
  146. package/studio-dist/server/chunks/{0-BKQLYEeE.js → 0-DEtAbPw-.js} +3 -3
  147. package/studio-dist/server/chunks/1-h2LMUAHs.js +9 -0
  148. package/studio-dist/server/chunks/{15-DuSvkmiZ.js → 15-BiobEJ2m.js} +3 -3
  149. package/studio-dist/server/chunks/{16-DJVePzvS.js → 16-6xqNydr5.js} +3 -3
  150. package/studio-dist/server/chunks/{17-D8K9-EPJ.js → 17-DUBxUGhx.js} +3 -3
  151. package/studio-dist/server/chunks/{18-D5FUPGnv.js → 18-CENJ-ItU.js} +3 -3
  152. package/studio-dist/server/chunks/{20-CmHgNZFV.js → 20-CEgw_UdZ.js} +3 -3
  153. package/studio-dist/server/chunks/{24-CfuHWHgd.js → 24-6ZDsPicw.js} +3 -3
  154. package/studio-dist/server/chunks/{25-bm3BDSum.js → 25-Cyivs_zg.js} +207 -6
  155. package/studio-dist/server/chunks/{26-DRlhAbrf.js → 26-D2WIE_xK.js} +3 -3
  156. package/studio-dist/server/chunks/{3-BrE1-gTA.js → 3-cYpnCm5s.js} +3 -3
  157. package/studio-dist/server/chunks/{_layout.svelte-BeqNMCvk.js → _layout.svelte-DCokBVYx.js} +4 -4
  158. package/studio-dist/server/chunks/{_page.svelte-DnmZYECG.js → _page.svelte-4E3K3DKC.js} +3 -3
  159. package/studio-dist/server/chunks/{_page.svelte-Bchzzf-O.js → _page.svelte-BHV_avWv.js} +3 -3
  160. package/studio-dist/server/chunks/{_page.svelte-CJVF_ySt.js → _page.svelte-BKKxE82V.js} +3 -3
  161. package/studio-dist/server/chunks/{_page.svelte-DIx7Hcw-.js → _page.svelte-BT9WBxAM.js} +3 -3
  162. package/studio-dist/server/chunks/{_page.svelte-Ds0GMs_q.js → _page.svelte-Bk7asdHX.js} +4 -4
  163. package/studio-dist/server/chunks/{_page.svelte-V7BTZinR.js → _page.svelte-DG8jeXro.js} +6 -6
  164. package/studio-dist/server/chunks/{_page.svelte-DM-MP-Vl.js → _page.svelte-DdYeG19n.js} +3 -3
  165. package/studio-dist/server/chunks/{_page.svelte-CkXCx1iz.js → _page.svelte-NTbWZ5Ki.js} +3 -3
  166. package/studio-dist/server/chunks/{_page.svelte-B7xxx6BP.js → _page.svelte-QmZ5QQQz.js} +3 -3
  167. package/studio-dist/server/chunks/{_server.ts-DdCviCsN.js → _server.ts-B66SEpEd.js} +3 -3
  168. package/studio-dist/server/chunks/{_server.ts-Ck-oreiA.js → _server.ts-C1Eh35Y2.js} +3 -3
  169. package/studio-dist/server/chunks/{_server.ts-b2YIpIsi.js → _server.ts-C2pAmHkw.js} +3 -3
  170. package/studio-dist/server/chunks/{_server.ts-Cd_KHxRr.js → _server.ts-DFYxm8-q.js} +2 -2
  171. package/studio-dist/server/chunks/{_server.ts-YtGAClvW.js → _server.ts-DWzrbU8h.js} +3 -3
  172. package/studio-dist/server/chunks/{client-DR3j81J8.js → client-BHWQAkS2.js} +2 -2
  173. package/studio-dist/server/chunks/{error.svelte-NY-X_Qdt.js → error.svelte-IFrcuYEO.js} +3 -3
  174. package/studio-dist/server/chunks/{http-Dz72j56i.js → http-BkrDO_qh.js} +2 -2
  175. package/studio-dist/server/chunks/{internal-y7btLoII.js → internal-DftpbYWE.js} +2 -2
  176. package/studio-dist/server/chunks/{operations-registry-DKEHmggR.js → operations-registry-zTLuSrVZ.js} +194 -1
  177. package/studio-dist/server/chunks/{stores-Cm-Qj76R.js → stores-BW6MI1eb.js} +2 -2
  178. package/studio-dist/server/index.js +1 -1
  179. package/studio-dist/server/manifest.js +17 -17
  180. package/studio-dist/client/_app/immutable/chunks/BXnpxR_0.js +0 -1
  181. package/studio-dist/client/_app/immutable/chunks/Js_lZuOv.js +0 -3
  182. package/studio-dist/client/_app/immutable/entry/start.BolRa540.js +0 -1
  183. package/studio-dist/client/_app/immutable/nodes/25.B_7VqHJ6.js +0 -39
  184. package/studio-dist/server/chunks/1-ClOr3JN3.js +0 -9
@@ -15,7 +15,7 @@
15
15
  })();
16
16
  import {
17
17
  lazyOperation
18
- } from "./chunk-JRDAITPC.js";
18
+ } from "./chunk-V3ZUZYBZ.js";
19
19
  import {
20
20
  defineTypedHandler,
21
21
  lafsError,
@@ -29,13 +29,14 @@ import {
29
29
  } from "./chunk-XD25OCUE.js";
30
30
 
31
31
  // packages/cleo/src/dispatch/domains/docs.ts
32
- import { randomUUID } from "node:crypto";
32
+ import { createHash, randomUUID } from "node:crypto";
33
33
  import { readFile } from "node:fs/promises";
34
34
  import { join, resolve } from "node:path";
35
35
  import { DocKindRegistry } from "@cleocode/contracts/docs-taxonomy.js";
36
36
  import { ExitCode } from "@cleocode/contracts/exit-codes.js";
37
37
  import { LLM_OUTPUT_MODES } from "@cleocode/contracts/operations/docs";
38
38
  import { parseChangesetFrontmatter } from "@cleocode/core/changesets/parse-frontmatter";
39
+ import { resolveInvokingCheckout } from "@cleocode/core/core-paths";
39
40
  import { readAuditLog, verifyAuditTrail, writeAuditEntry } from "@cleocode/core/docs/docs-audit";
40
41
  import { createDocsReadModel } from "@cleocode/core/docs/docs-read-model";
41
42
  import {
@@ -75,6 +76,12 @@ var allocateAdrSlug = lazyOperation(
75
76
  var allocateAutoSlugForDispatch = lazyOperation(
76
77
  async () => (await import("@cleocode/core/docs/numbering")).allocateAutoSlugForDispatch
77
78
  );
79
+ var docNameViolation = lazyOperation(
80
+ async () => (await import("@cleocode/core/docs/doc-name-rule")).docNameViolation
81
+ );
82
+ var describeDocNameViolation = lazyOperation(
83
+ async () => (await import("@cleocode/core/docs/doc-name-rule")).describeDocNameViolation
84
+ );
78
85
  var exportDocument = lazyOperation(
79
86
  async () => (await import("@cleocode/core/docs/export-document")).exportDocument
80
87
  );
@@ -130,6 +137,12 @@ var triageDocs = lazyOperation(
130
137
  var computeDocsStats = lazyOperation(
131
138
  async () => (await import("@cleocode/core/docs/stats")).computeDocsStats
132
139
  );
140
+ var runDocsModernize = lazyOperation(
141
+ async () => (await import("@cleocode/core/docs/modernize/index")).runDocsModernize
142
+ );
143
+ var scanDocsRot = lazyOperation(
144
+ async () => (await import("@cleocode/core/docs/rot")).scanDocsRot
145
+ );
133
146
  var runLegacyDocMigration = lazyOperation(
134
147
  async () => (await import("@cleocode/core/docs/legacy-doc-migration")).runLegacyDocMigration
135
148
  );
@@ -284,12 +297,9 @@ var _docsTypedHandler = defineTypedHandler("docs", {
284
297
  const model = createDocsReadModel();
285
298
  const backend = await currentAttachmentBackend();
286
299
  if (isProjectScope) {
287
- const docs = await model.listProjectDocs({
288
- kind: typeFilter,
289
- limit: Number.isFinite(effectiveLimit) ? effectiveLimit : void 0
290
- });
300
+ const docs = await model.listProjectDocs({ kind: typeFilter });
291
301
  const totalCount = docs.length;
292
- const projected = docs.map((doc) => ({
302
+ const allProjected = docs.map((doc) => ({
293
303
  id: doc.id,
294
304
  // Full content hash — a SHA-256 field in a machine envelope must be the
295
305
  // real digest, never a display-truncated form (T11294: the per-doc
@@ -312,12 +322,13 @@ var _docsTypedHandler = defineTypedHandler("docs", {
312
322
  ownerId: doc.ownerId,
313
323
  ownerType: doc.ownerType
314
324
  }));
315
- projected.sort(
325
+ allProjected.sort(
316
326
  (a, b) => comparator(
317
327
  { sha256: a.sha256, slug: a.slug, createdAt: a.createdAt },
318
328
  { sha256: b.sha256, slug: b.slug, createdAt: b.createdAt }
319
329
  )
320
330
  );
331
+ const projected = Number.isFinite(effectiveLimit) ? allProjected.slice(0, effectiveLimit) : allProjected;
321
332
  const truncated = totalCount > projected.length;
322
333
  if (truncated) {
323
334
  hintParts.push(
@@ -332,6 +343,12 @@ var _docsTypedHandler = defineTypedHandler("docs", {
332
343
  ...typeFilter !== void 0 ? { type: typeFilter } : {},
333
344
  count: projected.length,
334
345
  ...truncated ? { totalCount } : {},
346
+ population: {
347
+ matched: totalCount,
348
+ returned: projected.length,
349
+ truncated,
350
+ limit: Number.isFinite(effectiveLimit) ? effectiveLimit : null
351
+ },
335
352
  limit: Number.isFinite(effectiveLimit) ? effectiveLimit : 0,
336
353
  orderBy,
337
354
  ...hintParts.length > 0 ? { hint: hintParts.join(" ") } : {},
@@ -381,6 +398,12 @@ var _docsTypedHandler = defineTypedHandler("docs", {
381
398
  ...typeFilter !== void 0 ? { type: typeFilter } : {},
382
399
  count: slicedOwner.length,
383
400
  ...truncatedOwner ? { totalCount: totalCountOwner } : {},
401
+ population: {
402
+ matched: totalCountOwner,
403
+ returned: slicedOwner.length,
404
+ truncated: truncatedOwner,
405
+ limit: Number.isFinite(effectiveLimit) ? effectiveLimit : null
406
+ },
384
407
  limit: Number.isFinite(effectiveLimit) ? effectiveLimit : 0,
385
408
  orderBy,
386
409
  ...hintParts.length > 0 ? { hint: hintParts.join(" ") } : {},
@@ -572,7 +595,12 @@ var _docsTypedHandler = defineTypedHandler("docs", {
572
595
  apply,
573
596
  repair,
574
597
  ...params.olderThanDays !== void 0 ? { olderThanDays: params.olderThanDays } : {},
575
- ...backupReceiptPath !== void 0 ? { backupReceiptPath } : {}
598
+ ...typeof params.only === "string" ? {
599
+ only: params.only.split(",").map((c) => c.trim()).filter((c) => c.length > 0)
600
+ } : {},
601
+ ...backupReceiptPath !== void 0 ? { backupReceiptPath } : {},
602
+ // T14188: mirrors and git come from the invoking checkout.
603
+ checkoutRoot: resolveInvokingCheckout()
576
604
  });
577
605
  if (!result.ok) {
578
606
  return lafsError(result.error.code, result.error.message, "doctor");
@@ -616,6 +644,33 @@ var _docsTypedHandler = defineTypedHandler("docs", {
616
644
  "migrate"
617
645
  );
618
646
  },
647
+ // ── docs.modernize (T14113 — messy docs folder → linked CleoDocs) ─────────
648
+ modernize: async (params) => {
649
+ let result;
650
+ try {
651
+ result = await runDocsModernize(
652
+ getProjectRoot(),
653
+ {
654
+ ...params,
655
+ exclude: params.exclude?.split(",").map((e) => e.trim()).filter(Boolean)
656
+ },
657
+ // T14188: the folder and plan files are the invoking checkout's.
658
+ { checkoutRoot: resolveInvokingCheckout() }
659
+ );
660
+ } catch (err) {
661
+ const code = err instanceof Error && "code" in err ? String(err.code) : "";
662
+ if (!code.startsWith("E_MODERNIZE")) throw err;
663
+ return lafsError(
664
+ code,
665
+ // @sync-invariant none:input-shape a stale plan, missing root or unknown receipt is refused; the receipt holds any earlier step
666
+ err instanceof Error ? err.message : code,
667
+ "modernize"
668
+ );
669
+ }
670
+ const counts = result.plan?.counts;
671
+ const summary = result.mode === "rollback" ? `rolled back ${result.receipt?.runId} (${result.receipt?.ops.length ?? 0} steps reversed)` : (result.plan?.mode === "store" ? `${result.plan.storeRows?.length ?? 0} stored docs to re-kind or re-slug` : `${result.plan?.rows.length ?? 0} files: ${counts?.keep ?? 0} keep, ${counts?.rename ?? 0} rename, ${counts?.["merge-into"] ?? 0} merge, ${counts?.delete ?? 0} delete, ${result.plan?.linkFixes?.length ?? 0} link-fix in kept files`) + (result.receipt ? `; applied as ${result.receipt.runId} (${result.receipt.ops.length} steps, ${result.receipt.edgeCount} graph edges)` : "; dry run, nothing written");
672
+ return lafsSuccess({ ...result, summary }, "modernize");
673
+ },
619
674
  // ── docs.add ───────────────────────────────────────────────────────────────
620
675
  add: async (params) => {
621
676
  const {
@@ -629,7 +684,8 @@ var _docsTypedHandler = defineTypedHandler("docs", {
629
684
  slug: rawSlug,
630
685
  title: rawTitle,
631
686
  type: rawType,
632
- strict: strictMode
687
+ strict: strictMode,
688
+ newBecause
633
689
  } = params;
634
690
  if (!ownerId) {
635
691
  return lafsError("E_INVALID_INPUT", "ownerId is required", "add");
@@ -841,6 +897,71 @@ var _docsTypedHandler = defineTypedHandler("docs", {
841
897
  );
842
898
  }
843
899
  }
900
+ if (slug !== void 0) {
901
+ const nameCheck = await docNameViolation(slug, type);
902
+ if (nameCheck !== null) {
903
+ if (adrNumber !== void 0) releaseReservedSlug(slug);
904
+ return lafsError(
905
+ "E_DOC_NAME_RULE",
906
+ // @sync-invariant none:input-shape a slug that breaks the doc naming rule is refused before anything is written
907
+ await describeDocNameViolation(slug, nameCheck),
908
+ "add",
909
+ `cleo docs add ... --slug ${nameCheck.suggestion}`,
910
+ { suggestion: nameCheck.suggestion, reasons: nameCheck.reasons }
911
+ );
912
+ }
913
+ }
914
+ {
915
+ const reason = typeof newBecause === "string" ? newBecause.trim() : void 0;
916
+ const dup = await import("@cleocode/core/docs/duplicate-check");
917
+ if (reason !== void 0 && reason.length < dup.NEW_BECAUSE_MIN_LENGTH) {
918
+ if (adrNumber !== void 0 && slug !== void 0) releaseReservedSlug(slug);
919
+ return lafsError(
920
+ "E_INVALID_INPUT",
921
+ // @sync-invariant none:input-shape a too-short --new-because reason is refused before anything is written
922
+ `--new-because needs a reason of at least ${dup.NEW_BECAUSE_MIN_LENGTH} characters saying why this is not an existing doc`,
923
+ "add"
924
+ );
925
+ }
926
+ const bytes = typeof inlineContent === "string" ? Buffer.from(inlineContent, "utf-8") : filePath ? await readFile(resolve(String(filePath))).catch(() => null) : null;
927
+ const text = bytes?.toString("utf-8");
928
+ const title = (typeof rawTitle === "string" && rawTitle.trim() ? rawTitle : null) ?? (text ? dup.markdownTitle(text) : null);
929
+ const match = type === void 0 ? null : dup.findDuplicateDoc(
930
+ {
931
+ ...slug !== void 0 ? { slug } : {},
932
+ ...type !== void 0 ? { kind: type } : {},
933
+ ...title !== null ? { title } : {},
934
+ ...text !== void 0 ? { content: text } : {},
935
+ ...bytes ? { sha256: createHash("sha256").update(bytes).digest("hex") } : {}
936
+ },
937
+ await dup.loadDuplicateCandidates(projectRoot, { kind: type }),
938
+ await dup.readDuplicateThresholds(projectRoot)
939
+ );
940
+ if (match && reason === void 0) {
941
+ if (adrNumber !== void 0 && slug !== void 0) releaseReservedSlug(slug);
942
+ const { message, fix } = dup.describeDuplicate(match);
943
+ return lafsError(
944
+ "E_DOC_DUPLICATE",
945
+ // @sync-invariant none:input-shape a likely duplicate doc is refused before anything is written
946
+ message,
947
+ "add",
948
+ fix,
949
+ {
950
+ existing: { slug: match.slug, kind: match.kind, title: match.title },
951
+ rule: match.rule,
952
+ score: match.score
953
+ }
954
+ );
955
+ }
956
+ if (match && reason !== void 0) {
957
+ await dup.recordNewBecause(projectRoot, {
958
+ slug: slug ?? null,
959
+ kind: type ?? null,
960
+ resembles: match,
961
+ reason
962
+ });
963
+ }
964
+ }
844
965
  if (slug !== void 0 && adrNumber === void 0) {
845
966
  const reservation = await reserveSlugForDispatch(projectRoot, {
846
967
  kind: type ?? "",
@@ -1541,7 +1662,8 @@ var QUERY_OPS = /* @__PURE__ */ new Set([
1541
1662
  "audit",
1542
1663
  "doctor",
1543
1664
  "triage",
1544
- "stats"
1665
+ "stats",
1666
+ "rot"
1545
1667
  ]);
1546
1668
  var MUTATE_OPS = /* @__PURE__ */ new Set([
1547
1669
  "add",
@@ -1555,7 +1677,8 @@ var MUTATE_OPS = /* @__PURE__ */ new Set([
1555
1677
  "sync",
1556
1678
  "import",
1557
1679
  "doctor",
1558
- "migrate"
1680
+ "migrate",
1681
+ "modernize"
1559
1682
  ]);
1560
1683
  async function dispatchDocsLegacyQuery(operation, params) {
1561
1684
  const projectRoot = getProjectRoot();
@@ -1614,7 +1737,7 @@ async function dispatchDocsLegacyQuery(operation, params) {
1614
1737
  return readAuditLog(projectRoot, params["slug"]);
1615
1738
  case "status": {
1616
1739
  const model = createDocsReadModel();
1617
- return model.status(projectRoot);
1740
+ return model.status(projectRoot, resolveInvokingCheckout());
1618
1741
  }
1619
1742
  case "triage":
1620
1743
  return triageDocs({
@@ -1625,6 +1748,11 @@ async function dispatchDocsLegacyQuery(operation, params) {
1625
1748
  });
1626
1749
  case "stats":
1627
1750
  return computeDocsStats({ projectRoot });
1751
+ case "rot":
1752
+ return scanDocsRot({
1753
+ projectRoot: resolveInvokingCheckout(),
1754
+ ...typeof params["changed"] === "string" ? { changed: params["changed"] } : {}
1755
+ });
1628
1756
  default:
1629
1757
  throw new Error(`Unsupported docs query operation: ${operation}`);
1630
1758
  }
@@ -1645,6 +1773,8 @@ async function dispatchDocsLegacyMutate(operation, params) {
1645
1773
  toPath: String(params["toPath"]),
1646
1774
  attachmentId: typeof params["attachmentId"] === "string" ? params["attachmentId"] : void 0,
1647
1775
  projectRoot,
1776
+ // T14135: run from a canonical CLEO worktree, a relative --to lands in it.
1777
+ cwd: process.cwd(),
1648
1778
  // T13919: a dry run runs the real checks and writes nothing.
1649
1779
  ...params["dryRun"] === true ? { dryRun: true } : {}
1650
1780
  });
@@ -1855,7 +1985,8 @@ var DocsHandler = class {
1855
1985
  "audit",
1856
1986
  "doctor",
1857
1987
  "triage",
1858
- "stats"
1988
+ "stats",
1989
+ "rot"
1859
1990
  ],
1860
1991
  mutate: [
1861
1992
  "add",
@@ -1869,7 +2000,8 @@ var DocsHandler = class {
1869
2000
  "sync",
1870
2001
  "import",
1871
2002
  "doctor",
1872
- "migrate"
2003
+ "migrate",
2004
+ "modernize"
1873
2005
  ]
1874
2006
  };
1875
2007
  }
@@ -1877,4 +2009,4 @@ var DocsHandler = class {
1877
2009
  export {
1878
2010
  DocsHandler
1879
2011
  };
1880
- //# sourceMappingURL=docs-NF5COLOB.js.map
2012
+ //# sourceMappingURL=docs-MNOBMDNV.js.map
@@ -25,14 +25,11 @@ import {
25
25
  dispatchFromCli,
26
26
  dispatchRaw,
27
27
  handleRawError
28
- } from "./chunk-OIS3BDSL.js";
28
+ } from "./chunk-K6I66OSL.js";
29
29
  import "./chunk-6N6WGLXE.js";
30
30
  import {
31
31
  resolve
32
32
  } from "./chunk-4VASFLUW.js";
33
- import {
34
- loadCanonRegistry
35
- } from "./chunk-SXXOC6KZ.js";
36
33
  import {
37
34
  UnknownFlagError,
38
35
  assertKnownFlags
@@ -49,11 +46,11 @@ import {
49
46
  } from "./chunk-ESI6Q4OS.js";
50
47
  import "./chunk-6JSJNYH2.js";
51
48
  import "./chunk-D7BAQNV2.js";
52
- import "./chunk-JRDAITPC.js";
49
+ import "./chunk-V3ZUZYBZ.js";
53
50
 
54
51
  // packages/cleo/src/cli/commands/docs.ts
55
52
  import { existsSync } from "node:fs";
56
- import { appendFile, mkdir as mkdir3, readdir, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
53
+ import { mkdir as mkdir3, readdir, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
57
54
  import { dirname as dirname3, isAbsolute, join as join4, resolve as resolve3 } from "node:path";
58
55
  import {
59
56
  DocKindConfigError,
@@ -67,11 +64,6 @@ import {
67
64
  resolveWorktreeRouting
68
65
  } from "@cleocode/core/core-paths";
69
66
  import { createDocsReadModel } from "@cleocode/core/docs/docs-read-model";
70
- import {
71
- checkSlugSimilarity,
72
- DEFAULT_SIMILARITY_MODE,
73
- DEFAULT_SIMILARITY_THRESHOLD
74
- } from "@cleocode/core/docs/similarity-check";
75
67
  import { CleoError as CleoError2 } from "@cleocode/core/errors";
76
68
  import { pushWarning } from "@cleocode/core/output";
77
69
  import { getProjectRoot as getProjectRoot3 } from "@cleocode/core/project-scope";
@@ -291,6 +283,118 @@ var migrateCommand = defineCommand({
291
283
  }
292
284
  });
293
285
 
286
+ // packages/cleo/src/cli/commands/docs/modernize.ts
287
+ var modernizeCommand = defineCommand({
288
+ meta: {
289
+ name: "modernize",
290
+ description: "Turn a messy docs folder into clean, linked CleoDocs (T14113). Dry run by default: per file a kind, a stable slug (no task, session or date tokens) and keep/rename/merge-into/delete with reasons; ambiguous classify and merge calls go to System One (cleo decide) with a rule fallback. --apply stores docs, merges duplicates, records supersession, moves files and writes wikilink edges, with a receipt under .cleo/modernize/; --rollback <runId> reverses a run."
291
+ },
292
+ args: {
293
+ path: {
294
+ type: "positional",
295
+ description: "Folder to modernize (required unless --rollback or --plan)",
296
+ required: false
297
+ },
298
+ apply: {
299
+ type: "boolean",
300
+ description: "Execute the plan (default: dry run, nothing written)"
301
+ },
302
+ plan: {
303
+ type: "string",
304
+ description: "Apply this saved plan JSON verbatim (with --apply)"
305
+ },
306
+ out: {
307
+ type: "string",
308
+ description: "Save the plan JSON to this path for review"
309
+ },
310
+ rollback: {
311
+ type: "string",
312
+ description: "Roll back the apply run with this id (from the receipt)"
313
+ },
314
+ exclude: {
315
+ type: "string",
316
+ description: "Extra folder names to skip, comma-separated"
317
+ },
318
+ overrides: {
319
+ type: "string",
320
+ description: "Reviewer decisions JSON keyed by path; they win over rules and System One"
321
+ },
322
+ kind: {
323
+ type: "string",
324
+ description: "Use this kind for every file instead of classifying (bulk ingest)"
325
+ },
326
+ wikilinks: {
327
+ type: "boolean",
328
+ description: "Rewrite relative links to planned docs as [[slug]] wikilinks (default: keep markdown links that render on GitHub, re-pointed at the new paths)"
329
+ },
330
+ "allow-dirty": {
331
+ type: "boolean",
332
+ description: "Apply even when the root is not a clean git checkout (default: refuse)"
333
+ },
334
+ store: {
335
+ type: "boolean",
336
+ description: "Modernize the docs already in the store: re-kind, re-slug (old slugs stay as aliases), renumber colliding ADRs"
337
+ },
338
+ "store-only": {
339
+ type: "boolean",
340
+ description: "Ingest every file into the store (task-owned) and leave the folder untouched"
341
+ }
342
+ },
343
+ async run({ args }) {
344
+ await dispatchFromCli(
345
+ "mutate",
346
+ "docs",
347
+ "modernize",
348
+ {
349
+ ...typeof args.path === "string" ? { path: args.path } : {},
350
+ apply: args.apply === true,
351
+ ...typeof args.plan === "string" ? { planFile: args.plan } : {},
352
+ ...typeof args.out === "string" ? { out: args.out } : {},
353
+ ...typeof args.rollback === "string" ? { rollback: args.rollback } : {},
354
+ ...typeof args.exclude === "string" ? { exclude: args.exclude } : {},
355
+ ...typeof args.overrides === "string" ? { overrides: args.overrides } : {},
356
+ ...typeof args.kind === "string" ? { kind: args.kind } : {},
357
+ storeOnly: args["store-only"] === true,
358
+ allowDirty: args["allow-dirty"] === true,
359
+ wikilinks: args.wikilinks === true,
360
+ store: args.store === true
361
+ },
362
+ { command: "docs modernize" }
363
+ );
364
+ }
365
+ });
366
+
367
+ // packages/cleo/src/cli/commands/docs/rot.ts
368
+ var rotCommand = defineCommand({
369
+ meta: {
370
+ name: "rot",
371
+ description: "Docs-rot report from git alone (T14111): docs whose covers: code changed after them (stale), duplicate docs, and orphaned docs (dead covers globs, unreferenced docs/ files). --changed <base>..<head> also lists the docs a merged change left stale; --strict exits 2 on any finding."
372
+ },
373
+ args: {
374
+ changed: {
375
+ type: "string",
376
+ description: "Git range <base>..<head> of a merged change to flag docs for"
377
+ },
378
+ strict: {
379
+ type: "boolean",
380
+ description: "Exit 2 when the report finds stale, duplicate, orphaned or flagged docs"
381
+ }
382
+ },
383
+ async run({ args }) {
384
+ const params = typeof args.changed === "string" ? { changed: args.changed } : {};
385
+ if (args.strict !== true) {
386
+ await dispatchFromCli("query", "docs", "rot", params, { command: "docs rot" });
387
+ return;
388
+ }
389
+ const response = await dispatchRaw("query", "docs", "rot", params);
390
+ handleRawError(response, { command: "docs rot", operation: "docs.rot" });
391
+ const report = response.data;
392
+ cliOutput(report, { command: "docs rot", message: report.summary });
393
+ const findings = report.stale.length + report.duplicates.length + report.orphans.length + report.flagged.length;
394
+ if (findings > 0) process.exit(2);
395
+ }
396
+ });
397
+
294
398
  // packages/cleo/src/cli/commands/docs/set-alias.ts
295
399
  import { ExitCode as ExitCode3 } from "@cleocode/contracts/exit-codes.js";
296
400
  import { setDisplayAlias } from "@cleocode/core/docs/display-alias";
@@ -1272,7 +1376,7 @@ async function runGapCheck(_projectRoot, filterId) {
1272
1376
  var addCommand = defineCommand({
1273
1377
  meta: {
1274
1378
  name: "add",
1275
- description: 'Usage: cleo docs add <OWNER-ID> [<file>] [--content <text> | --url <url>] [--slug <kebab>] [--type <kind>]\n OWNER-ID is the FIRST positional; the file is the SECOND. Examples:\n cleo docs add T123 ./docs/rfc.md --slug my-rfc --type spec\n cleo docs add T123 --content "# Note" --slug my-note\n\nAttach 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."
1379
+ description: 'Usage: cleo docs add <OWNER-ID> [<file>] [--content <text> | --url <url>] [--slug <kebab>] [--type <kind>]\n OWNER-ID is the FIRST positional; the file is the SECOND. Examples:\n cleo docs add T123 ./docs/rfc.md --slug my-rfc --type spec\n cleo docs add T123 --content "# Note" --slug my-note\n\nAttach 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 --new-because <why> Add a doc that resembles an existing one; the reason is\n recorded in .cleo/audit/docs-new-because.jsonl (T14109)\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 Likely duplicate (same content, or similar slug, title or text) \u2192 E_DOC_DUPLICATE,\n naming the existing doc, `cleo docs fetch <slug>` and `cleo docs update <slug>`,\n unless --new-because <why> (T14109)\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."
1276
1380
  },
1277
1381
  args: {
1278
1382
  "owner-id": {
@@ -1317,9 +1421,13 @@ var addCommand = defineCommand({
1317
1421
  type: "string",
1318
1422
  description: "Taxonomy classification \u2014 run `cleo docs schema` to enumerate registered kinds (T9637 / T9788 / T11142)"
1319
1423
  },
1424
+ "new-because": {
1425
+ type: "string",
1426
+ description: "Why this doc is new although it resembles an existing one (T14109). A likely duplicate (same content, or a similar slug, title or text) is refused with E_DOC_DUPLICATE and routed to `cleo docs update <slug>` unless this states the reason (15+ characters); the reason is recorded in .cleo/audit/docs-new-because.jsonl."
1427
+ },
1320
1428
  "allow-similar": {
1321
1429
  type: "boolean",
1322
- 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."
1430
+ description: "Deprecated (T14109): needs --new-because <reason>, which is what lets a likely duplicate through."
1323
1431
  },
1324
1432
  strict: {
1325
1433
  type: "boolean",
@@ -1399,83 +1507,17 @@ var addCommand = defineCommand({
1399
1507
  process.exit(ExitCode6.FILE_ERROR);
1400
1508
  }
1401
1509
  }
1402
- if (args.slug && args.type) {
1403
- const projectRoot = await getProjectRoot3();
1404
- let warnThreshold = DEFAULT_SIMILARITY_THRESHOLD;
1405
- let mode = DEFAULT_SIMILARITY_MODE;
1406
- try {
1407
- const canon = loadCanonRegistry(projectRoot);
1408
- if (canon?.similarity) {
1409
- warnThreshold = canon.similarity.warnThreshold;
1410
- mode = canon.similarity.mode;
1411
- }
1412
- } catch {
1413
- }
1414
- try {
1415
- const sim = await checkSlugSimilarity({
1416
- slug: args.slug,
1417
- type: args.type,
1418
- projectRoot,
1419
- threshold: warnThreshold
1420
- });
1421
- if (sim.mostSimilarSlug !== null) {
1422
- const scoreFixed = sim.score.toFixed(2);
1423
- const hint = `Similar to '${sim.mostSimilarSlug}' (score ${scoreFixed}) \u2014 did you mean: cleo docs update ${sim.mostSimilarSlug}? Pass --allow-similar to bypass.`;
1424
- if (mode === "block" && !allowSimilar) {
1425
- cliError(hint, ExitCode6.VALIDATION_ERROR, {
1426
- name: "E_SLUG_SIMILARITY",
1427
- fix: `Use \`cleo docs update ${sim.mostSimilarSlug}\` if updating, or pass --allow-similar to add as a new doc.`,
1428
- alternatives: [
1429
- {
1430
- action: `update '${sim.mostSimilarSlug}' instead`,
1431
- command: `cleo docs update ${sim.mostSimilarSlug}`
1432
- },
1433
- {
1434
- action: "bypass the similarity check",
1435
- command: `cleo docs add ${ownerId} ${fileArg ?? `--url ${url}`} --slug ${args.slug} --type ${args.type} --allow-similar`
1436
- }
1437
- ],
1438
- details: {
1439
- proposedSlug: args.slug,
1440
- mostSimilarSlug: sim.mostSimilarSlug,
1441
- score: sim.score,
1442
- threshold: warnThreshold,
1443
- kind: args.type
1444
- }
1445
- });
1446
- process.exit(ExitCode6.VALIDATION_ERROR);
1447
- }
1448
- humanInfo(hint);
1449
- if (allowSimilar) {
1450
- const auditLine = `${JSON.stringify({
1451
- ts: (/* @__PURE__ */ new Date()).toISOString(),
1452
- reason: "allow-similar-bypass",
1453
- proposedSlug: args.slug,
1454
- mostSimilarSlug: sim.mostSimilarSlug,
1455
- score: sim.score,
1456
- threshold: warnThreshold,
1457
- kind: args.type,
1458
- ownerId
1459
- })}
1460
- `;
1461
- try {
1462
- await mkdir3(join4(projectRoot, ".cleo", "audit"), { recursive: true });
1463
- await appendFile(
1464
- join4(projectRoot, ".cleo", "audit", "similar-bypass.jsonl"),
1465
- auditLine,
1466
- "utf-8"
1467
- );
1468
- } catch {
1469
- }
1470
- }
1471
- }
1472
- } catch (err) {
1473
- if (process.env["CLEO_DEBUG"]) {
1474
- humanInfo(
1475
- `similarity check skipped: ${err instanceof Error ? err.message : String(err)}`
1476
- );
1510
+ const newBecause = args["new-because"];
1511
+ if (allowSimilar && typeof newBecause !== "string") {
1512
+ cliError(
1513
+ "--allow-similar no longer bypasses the duplicate check on its own; say why this doc is new",
1514
+ 6,
1515
+ {
1516
+ name: "E_VALIDATION",
1517
+ fix: 'Pass --new-because "<why this is not the existing doc>" (recorded in .cleo/audit/docs-new-because.jsonl), or update the existing doc with cleo docs update <slug>.'
1477
1518
  }
1478
- }
1519
+ );
1520
+ process.exit(6);
1479
1521
  }
1480
1522
  await dispatchFromCli(
1481
1523
  "mutate",
@@ -1492,7 +1534,8 @@ var addCommand = defineCommand({
1492
1534
  ...args.slug ? { slug: args.slug } : {},
1493
1535
  ...args.title ? { title: args.title } : {},
1494
1536
  ...args.type ? { type: args.type } : {},
1495
- ...args.strict === true ? { strict: true } : {}
1537
+ ...args.strict === true ? { strict: true } : {},
1538
+ ...typeof newBecause === "string" ? { newBecause } : {}
1496
1539
  },
1497
1540
  { command: "docs add" }
1498
1541
  );
@@ -2562,7 +2605,7 @@ function parseOlderThanDays(raw) {
2562
2605
  var doctorCommand = defineCommand({
2563
2606
  meta: {
2564
2607
  name: "doctor",
2565
- description: "Docs store health diagnostics + safe repairs: dangling local-file pointers, docVersion skew vs the audit log, empty topics/related_tasks provenance, docs_wikilinks drift, legacy store surfaces, blob-store coverage, stale drafts. Dry-run by default; --apply executes repairs (a fresh backup is created automatically first); --repair runs only the hash-verified legacy-bytes \u2192 blob-store backfill (T13653)."
2608
+ description: "Docs store health diagnostics + safe repairs: dangling local-file pointers, docVersion skew vs the audit log, empty topics/related_tasks provenance, docs_wikilinks drift, legacy store surfaces, blob-store coverage, stale drafts, publication-ledger drift (rows whose mirror is gone or stale; T14117). Dry-run by default; --apply executes repairs (a fresh backup is created automatically first); --repair runs only the hash-verified legacy-bytes \u2192 blob-store backfill (T13653); --only <class,\u2026> scopes the plan and --apply to those repair classes (T14174)."
2566
2609
  },
2567
2610
  args: {
2568
2611
  apply: {
@@ -2577,17 +2620,27 @@ var doctorCommand = defineCommand({
2577
2620
  type: "string",
2578
2621
  description: "Age threshold in days for the stale-drafts check (default 30)"
2579
2622
  },
2623
+ only: {
2624
+ type: "string",
2625
+ description: "Comma-separated repair classes to plan and apply (T14174): dangling-pointers, doc-version-skew, empty-provenance, wikilinks-drift, legacy-blob-coverage, conduit-history-gaps, publication-ledger-drift. Other classes are left untouched."
2626
+ },
2580
2627
  ...docsOutputArgs
2581
2628
  },
2582
2629
  async run({ args }) {
2583
2630
  const apply = args.apply === true;
2584
2631
  const repair = args.repair === true;
2585
2632
  const olderThanDays = parseOlderThanDays(args["older-than-days"]);
2633
+ const only = typeof args.only === "string" ? args.only : void 0;
2586
2634
  await dispatchFromCli(
2587
2635
  apply || repair ? "mutate" : "query",
2588
2636
  "docs",
2589
2637
  "doctor",
2590
- { apply, repair, ...olderThanDays !== void 0 ? { olderThanDays } : {} },
2638
+ {
2639
+ apply,
2640
+ repair,
2641
+ ...olderThanDays !== void 0 ? { olderThanDays } : {},
2642
+ ...only !== void 0 ? { only } : {}
2643
+ },
2591
2644
  { command: "docs doctor" }
2592
2645
  );
2593
2646
  }
@@ -2875,8 +2928,12 @@ var docsCommand = defineCommand({
2875
2928
  triage: triageCommand,
2876
2929
  // T13363 — docs.stats adoption metrics (read-only)
2877
2930
  stats: statsCommand,
2931
+ // T14111 — docs.rot stale/duplicate/orphaned report (read-only, git only)
2932
+ rot: rotCommand,
2878
2933
  // T13360 — docs.migrate legacy docs import (harness-bracketed, idempotent)
2879
2934
  migrate: migrateCommand,
2935
+ // T14113 — docs.modernize (dry run / apply / rollback)
2936
+ modernize: modernizeCommand,
2880
2937
  // Drift status — agent-facing, kept per cleodocs-rearch-design §6 (T13361 retired gap-check only)
2881
2938
  status: statusCommand,
2882
2939
  // T9645 reingest — retained: the docs-reingest workflow invokes `docs sync --from/--for/--name`
@@ -2897,4 +2954,4 @@ export {
2897
2954
  _llmOutputCommand,
2898
2955
  docsCommand
2899
2956
  };
2900
- //# sourceMappingURL=docs-AIZLN3ZE.js.map
2957
+ //# sourceMappingURL=docs-YI47KPN7.js.map