@cleocode/core 2026.9.20 → 2026.9.21

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 (855) hide show
  1. package/dist/adapters/discovery.d.ts.map +1 -1
  2. package/dist/adapters/discovery.js +3 -10
  3. package/dist/adapters/discovery.js.map +1 -1
  4. package/dist/adrs/link-pipeline.js +2 -2
  5. package/dist/adrs/link-pipeline.js.map +1 -1
  6. package/dist/adrs/parse.js +3 -3
  7. package/dist/adrs/parse.js.map +1 -1
  8. package/dist/agents/invoke-meta-agent.d.ts.map +1 -1
  9. package/dist/agents/invoke-meta-agent.js +8 -4
  10. package/dist/agents/invoke-meta-agent.js.map +1 -1
  11. package/dist/bootstrap.d.ts +5 -1
  12. package/dist/bootstrap.d.ts.map +1 -1
  13. package/dist/bootstrap.js +41 -21
  14. package/dist/bootstrap.js.map +1 -1
  15. package/dist/cloud/crypto.d.ts +47 -0
  16. package/dist/cloud/crypto.d.ts.map +1 -0
  17. package/dist/cloud/crypto.js +223 -0
  18. package/dist/cloud/crypto.js.map +1 -0
  19. package/dist/cloud/http.d.ts +52 -0
  20. package/dist/cloud/http.d.ts.map +1 -0
  21. package/dist/cloud/http.js +98 -0
  22. package/dist/cloud/http.js.map +1 -0
  23. package/dist/cloud/index.d.ts +19 -0
  24. package/dist/cloud/index.d.ts.map +1 -0
  25. package/dist/cloud/index.js +19 -0
  26. package/dist/cloud/index.js.map +1 -0
  27. package/dist/cloud/journal.d.ts +169 -0
  28. package/dist/cloud/journal.d.ts.map +1 -0
  29. package/dist/cloud/journal.js +428 -0
  30. package/dist/cloud/journal.js.map +1 -0
  31. package/dist/cloud/keys.d.ts +213 -0
  32. package/dist/cloud/keys.d.ts.map +1 -0
  33. package/dist/cloud/keys.js +362 -0
  34. package/dist/cloud/keys.js.map +1 -0
  35. package/dist/cloud/manifest-check.d.ts +36 -0
  36. package/dist/cloud/manifest-check.d.ts.map +1 -0
  37. package/dist/cloud/manifest-check.js +70 -0
  38. package/dist/cloud/manifest-check.js.map +1 -0
  39. package/dist/cloud/projects.d.ts +13 -0
  40. package/dist/cloud/projects.d.ts.map +1 -0
  41. package/dist/cloud/projects.js +11 -0
  42. package/dist/cloud/projects.js.map +1 -0
  43. package/dist/cloud/signing.d.ts +105 -0
  44. package/dist/cloud/signing.d.ts.map +1 -0
  45. package/dist/cloud/signing.js +117 -0
  46. package/dist/cloud/signing.js.map +1 -0
  47. package/dist/cloud/streams.d.ts +6 -0
  48. package/dist/cloud/streams.d.ts.map +1 -0
  49. package/dist/cloud/streams.js +5 -0
  50. package/dist/cloud/streams.js.map +1 -0
  51. package/dist/decide/audit.d.ts +131 -0
  52. package/dist/decide/audit.d.ts.map +1 -0
  53. package/dist/decide/audit.js +98 -0
  54. package/dist/decide/audit.js.map +1 -0
  55. package/dist/decide/budget.d.ts +91 -0
  56. package/dist/decide/budget.d.ts.map +1 -0
  57. package/dist/decide/budget.js +194 -0
  58. package/dist/decide/budget.js.map +1 -0
  59. package/dist/decide/cache.d.ts +49 -0
  60. package/dist/decide/cache.d.ts.map +1 -0
  61. package/dist/decide/cache.js +87 -0
  62. package/dist/decide/cache.js.map +1 -0
  63. package/dist/decide/client.d.ts +103 -0
  64. package/dist/decide/client.d.ts.map +1 -0
  65. package/dist/decide/client.js +262 -0
  66. package/dist/decide/client.js.map +1 -0
  67. package/dist/decide/credentials.d.ts +159 -0
  68. package/dist/decide/credentials.d.ts.map +1 -0
  69. package/dist/decide/credentials.js +383 -0
  70. package/dist/decide/credentials.js.map +1 -0
  71. package/dist/decide/index.d.ts +18 -0
  72. package/dist/decide/index.d.ts.map +1 -0
  73. package/dist/decide/index.js +18 -0
  74. package/dist/decide/index.js.map +1 -0
  75. package/dist/decide/jev-wire.d.ts +155 -0
  76. package/dist/decide/jev-wire.d.ts.map +1 -0
  77. package/dist/decide/jev-wire.js +420 -0
  78. package/dist/decide/jev-wire.js.map +1 -0
  79. package/dist/decide/operations.d.ts +152 -0
  80. package/dist/decide/operations.d.ts.map +1 -0
  81. package/dist/decide/operations.js +199 -0
  82. package/dist/decide/operations.js.map +1 -0
  83. package/dist/decide/provider.d.ts +90 -0
  84. package/dist/decide/provider.d.ts.map +1 -0
  85. package/dist/decide/provider.js +43 -0
  86. package/dist/decide/provider.js.map +1 -0
  87. package/dist/decide/site.d.ts +148 -0
  88. package/dist/decide/site.d.ts.map +1 -0
  89. package/dist/decide/site.js +181 -0
  90. package/dist/decide/site.js.map +1 -0
  91. package/dist/decide/transport.d.ts +67 -0
  92. package/dist/decide/transport.d.ts.map +1 -0
  93. package/dist/decide/transport.js +350 -0
  94. package/dist/decide/transport.js.map +1 -0
  95. package/dist/docs/export-document.js +5908 -2436
  96. package/dist/docs/export-document.js.map +3 -3
  97. package/dist/docs/index.d.ts +2 -2
  98. package/dist/docs/index.d.ts.map +1 -1
  99. package/dist/docs/index.js +1 -1
  100. package/dist/docs/index.js.map +1 -1
  101. package/dist/docs/projection.d.ts.map +1 -1
  102. package/dist/docs/projection.js +2 -3
  103. package/dist/docs/projection.js.map +1 -1
  104. package/dist/docs/publish-pr.d.ts +69 -1
  105. package/dist/docs/publish-pr.d.ts.map +1 -1
  106. package/dist/docs/publish-pr.js +112 -10
  107. package/dist/docs/publish-pr.js.map +1 -1
  108. package/dist/doctor/db-substrate.d.ts +13 -8
  109. package/dist/doctor/db-substrate.d.ts.map +1 -1
  110. package/dist/doctor/db-substrate.js +39 -26
  111. package/dist/doctor/db-substrate.js.map +1 -1
  112. package/dist/doctor/index.d.ts +2 -0
  113. package/dist/doctor/index.d.ts.map +1 -1
  114. package/dist/doctor/index.js +1 -0
  115. package/dist/doctor/index.js.map +1 -1
  116. package/dist/doctor/knowledge.d.ts.map +1 -1
  117. package/dist/doctor/knowledge.js +5 -15
  118. package/dist/doctor/knowledge.js.map +1 -1
  119. package/dist/doctor/nexus-schema-residency.d.ts +1 -1
  120. package/dist/doctor/nexus-schema-residency.d.ts.map +1 -1
  121. package/dist/doctor/nexus-schema-residency.js +3 -0
  122. package/dist/doctor/nexus-schema-residency.js.map +1 -1
  123. package/dist/doctor/project-identity.d.ts +44 -1
  124. package/dist/doctor/project-identity.d.ts.map +1 -1
  125. package/dist/doctor/project-identity.js +237 -10
  126. package/dist/doctor/project-identity.js.map +1 -1
  127. package/dist/doctor/projects.d.ts +170 -0
  128. package/dist/doctor/projects.d.ts.map +1 -0
  129. package/dist/doctor/projects.js +1075 -0
  130. package/dist/doctor/projects.js.map +1 -0
  131. package/dist/doctor/twin-collapse.d.ts +97 -0
  132. package/dist/doctor/twin-collapse.d.ts.map +1 -0
  133. package/dist/doctor/twin-collapse.js +168 -0
  134. package/dist/doctor/twin-collapse.js.map +1 -0
  135. package/dist/doctor/worktree-orphans.d.ts.map +1 -1
  136. package/dist/doctor/worktree-orphans.js +13 -29
  137. package/dist/doctor/worktree-orphans.js.map +1 -1
  138. package/dist/doctor/worktree-stores.d.ts +128 -0
  139. package/dist/doctor/worktree-stores.d.ts.map +1 -0
  140. package/dist/doctor/worktree-stores.js +374 -0
  141. package/dist/doctor/worktree-stores.js.map +1 -0
  142. package/dist/error-catalog.d.ts.map +1 -1
  143. package/dist/error-catalog.js +21 -0
  144. package/dist/error-catalog.js.map +1 -1
  145. package/dist/error-registry.d.ts.map +1 -1
  146. package/dist/error-registry.js +40 -0
  147. package/dist/error-registry.js.map +1 -1
  148. package/dist/gc/cleanup.d.ts.map +1 -1
  149. package/dist/gc/cleanup.js +32 -19
  150. package/dist/gc/cleanup.js.map +1 -1
  151. package/dist/git/bounded-git.d.ts +170 -0
  152. package/dist/git/bounded-git.d.ts.map +1 -0
  153. package/dist/git/bounded-git.js +371 -0
  154. package/dist/git/bounded-git.js.map +1 -0
  155. package/dist/git/work-tree.d.ts +20 -0
  156. package/dist/git/work-tree.d.ts.map +1 -1
  157. package/dist/git/work-tree.js +20 -2
  158. package/dist/git/work-tree.js.map +1 -1
  159. package/dist/hooks/handlers/session-hooks.d.ts +11 -26
  160. package/dist/hooks/handlers/session-hooks.d.ts.map +1 -1
  161. package/dist/hooks/handlers/session-hooks.js +18 -47
  162. package/dist/hooks/handlers/session-hooks.js.map +1 -1
  163. package/dist/hooks/payload-schemas.d.ts +2 -2
  164. package/dist/index.d.ts +6 -5
  165. package/dist/index.d.ts.map +1 -1
  166. package/dist/index.js +5 -3
  167. package/dist/index.js.map +1 -1
  168. package/dist/init/engine-ops.d.ts.map +1 -1
  169. package/dist/init/engine-ops.js +23 -1
  170. package/dist/init/engine-ops.js.map +1 -1
  171. package/dist/init.d.ts +158 -1
  172. package/dist/init.d.ts.map +1 -1
  173. package/dist/init.js +527 -42
  174. package/dist/init.js.map +1 -1
  175. package/dist/injection.d.ts +55 -0
  176. package/dist/injection.d.ts.map +1 -1
  177. package/dist/injection.js +102 -6
  178. package/dist/injection.js.map +1 -1
  179. package/dist/internal.d.ts +9 -5
  180. package/dist/internal.d.ts.map +1 -1
  181. package/dist/internal.js +8 -4
  182. package/dist/internal.js.map +1 -1
  183. package/dist/issue/diagnostics.d.ts.map +1 -1
  184. package/dist/issue/diagnostics.js +2 -1
  185. package/dist/issue/diagnostics.js.map +1 -1
  186. package/dist/lib/concurrency.d.ts +25 -0
  187. package/dist/lib/concurrency.d.ts.map +1 -0
  188. package/dist/lib/concurrency.js +43 -0
  189. package/dist/lib/concurrency.js.map +1 -0
  190. package/dist/lib/index.d.ts +1 -0
  191. package/dist/lib/index.d.ts.map +1 -1
  192. package/dist/lib/index.js +1 -0
  193. package/dist/lib/index.js.map +1 -1
  194. package/dist/lifecycle/engine-ops.d.ts.map +1 -1
  195. package/dist/lifecycle/engine-ops.js +4 -2
  196. package/dist/lifecycle/engine-ops.js.map +1 -1
  197. package/dist/lifecycle/stage-guidance.d.ts +5 -0
  198. package/dist/lifecycle/stage-guidance.d.ts.map +1 -1
  199. package/dist/lifecycle/stage-guidance.js +14 -4
  200. package/dist/lifecycle/stage-guidance.js.map +1 -1
  201. package/dist/llm/catalog-cache.d.ts +3 -2
  202. package/dist/llm/catalog-cache.d.ts.map +1 -1
  203. package/dist/llm/catalog-cache.js +5 -5
  204. package/dist/llm/catalog-cache.js.map +1 -1
  205. package/dist/llm/credentials.d.ts +4 -4
  206. package/dist/llm/credentials.js +6 -6
  207. package/dist/llm/credentials.js.map +1 -1
  208. package/dist/llm/plugin-facade.js +4312 -1798
  209. package/dist/llm/plugin-facade.js.map +3 -3
  210. package/dist/llm/stable-device-id.d.ts.map +1 -1
  211. package/dist/llm/stable-device-id.js +3 -5
  212. package/dist/llm/stable-device-id.js.map +1 -1
  213. package/dist/memory/attention.d.ts.map +1 -1
  214. package/dist/memory/attention.js +30 -30
  215. package/dist/memory/attention.js.map +1 -1
  216. package/dist/memory/decision-contradiction.d.ts +152 -0
  217. package/dist/memory/decision-contradiction.d.ts.map +1 -0
  218. package/dist/memory/decision-contradiction.js +273 -0
  219. package/dist/memory/decision-contradiction.js.map +1 -0
  220. package/dist/memory/decisions.d.ts +42 -11
  221. package/dist/memory/decisions.d.ts.map +1 -1
  222. package/dist/memory/decisions.js +153 -56
  223. package/dist/memory/decisions.js.map +1 -1
  224. package/dist/memory/dialectic-evaluator.d.ts +7 -0
  225. package/dist/memory/dialectic-evaluator.d.ts.map +1 -1
  226. package/dist/memory/dialectic-evaluator.js +23 -8
  227. package/dist/memory/dialectic-evaluator.js.map +1 -1
  228. package/dist/memory/dispatch-trace-format.d.ts +39 -0
  229. package/dist/memory/dispatch-trace-format.d.ts.map +1 -0
  230. package/dist/memory/dispatch-trace-format.js +54 -0
  231. package/dist/memory/dispatch-trace-format.js.map +1 -0
  232. package/dist/memory/dispatch-trace.d.ts.map +1 -1
  233. package/dist/memory/dispatch-trace.js +1 -27
  234. package/dist/memory/dispatch-trace.js.map +1 -1
  235. package/dist/memory/engine-compat.d.ts +2 -0
  236. package/dist/memory/engine-compat.d.ts.map +1 -1
  237. package/dist/memory/engine-compat.js +1 -0
  238. package/dist/memory/engine-compat.js.map +1 -1
  239. package/dist/memory/import-from-provider.js +3 -3
  240. package/dist/memory/import-from-provider.js.map +1 -1
  241. package/dist/memory/learnings.d.ts +2 -2
  242. package/dist/memory/observation-type-decision.d.ts +96 -0
  243. package/dist/memory/observation-type-decision.d.ts.map +1 -0
  244. package/dist/memory/observation-type-decision.js +218 -0
  245. package/dist/memory/observation-type-decision.js.map +1 -0
  246. package/dist/memory/redaction.js +2 -0
  247. package/dist/memory/redaction.js.map +1 -1
  248. package/dist/memory/retrieval/build-retrieval-bundle.d.ts +9 -2
  249. package/dist/memory/retrieval/build-retrieval-bundle.d.ts.map +1 -1
  250. package/dist/memory/retrieval/build-retrieval-bundle.js +42 -11
  251. package/dist/memory/retrieval/build-retrieval-bundle.js.map +1 -1
  252. package/dist/memory/retrieval/get-current-session-id.d.ts +5 -0
  253. package/dist/memory/retrieval/get-current-session-id.d.ts.map +1 -1
  254. package/dist/memory/retrieval/get-current-session-id.js +7 -2
  255. package/dist/memory/retrieval/get-current-session-id.js.map +1 -1
  256. package/dist/memory/retrieval/observe.d.ts.map +1 -1
  257. package/dist/memory/retrieval/observe.js +30 -29
  258. package/dist/memory/retrieval/observe.js.map +1 -1
  259. package/dist/nexus/analyze-orchestrator.d.ts.map +1 -1
  260. package/dist/nexus/analyze-orchestrator.js +42 -29
  261. package/dist/nexus/analyze-orchestrator.js.map +1 -1
  262. package/dist/nexus/assessment-projection.d.ts +76 -0
  263. package/dist/nexus/assessment-projection.d.ts.map +1 -0
  264. package/dist/nexus/assessment-projection.js +143 -0
  265. package/dist/nexus/assessment-projection.js.map +1 -0
  266. package/dist/nexus/assessment-store.d.ts +14 -2
  267. package/dist/nexus/assessment-store.d.ts.map +1 -1
  268. package/dist/nexus/assessment-store.js +21 -3
  269. package/dist/nexus/assessment-store.js.map +1 -1
  270. package/dist/nexus/checkout-nonce.d.ts +48 -0
  271. package/dist/nexus/checkout-nonce.d.ts.map +1 -0
  272. package/dist/nexus/checkout-nonce.js +84 -0
  273. package/dist/nexus/checkout-nonce.js.map +1 -0
  274. package/dist/nexus/devices.d.ts +93 -0
  275. package/dist/nexus/devices.d.ts.map +1 -0
  276. package/dist/nexus/devices.js +141 -0
  277. package/dist/nexus/devices.js.map +1 -0
  278. package/dist/nexus/freshness.d.ts.map +1 -1
  279. package/dist/nexus/freshness.js +25 -6
  280. package/dist/nexus/freshness.js.map +1 -1
  281. package/dist/nexus/git-state.d.ts +225 -0
  282. package/dist/nexus/git-state.d.ts.map +1 -0
  283. package/dist/nexus/git-state.js +677 -0
  284. package/dist/nexus/git-state.js.map +1 -0
  285. package/dist/nexus/graph-manifest.d.ts +20 -4
  286. package/dist/nexus/graph-manifest.d.ts.map +1 -1
  287. package/dist/nexus/graph-manifest.js +40 -5
  288. package/dist/nexus/graph-manifest.js.map +1 -1
  289. package/dist/nexus/identity.d.ts +68 -23
  290. package/dist/nexus/identity.d.ts.map +1 -1
  291. package/dist/nexus/identity.js +99 -22
  292. package/dist/nexus/identity.js.map +1 -1
  293. package/dist/nexus/index.d.ts +5 -2
  294. package/dist/nexus/index.d.ts.map +1 -1
  295. package/dist/nexus/index.js +10 -4
  296. package/dist/nexus/index.js.map +1 -1
  297. package/dist/nexus/knowledge.d.ts +5 -0
  298. package/dist/nexus/knowledge.d.ts.map +1 -1
  299. package/dist/nexus/knowledge.js +26 -2
  300. package/dist/nexus/knowledge.js.map +1 -1
  301. package/dist/nexus/legacy-alias.d.ts +60 -0
  302. package/dist/nexus/legacy-alias.d.ts.map +1 -0
  303. package/dist/nexus/legacy-alias.js +68 -0
  304. package/dist/nexus/legacy-alias.js.map +1 -0
  305. package/dist/nexus/migrate-json-to-sqlite.js +2 -2
  306. package/dist/nexus/migrate-json-to-sqlite.js.map +1 -1
  307. package/dist/nexus/ops.d.ts +1 -0
  308. package/dist/nexus/ops.d.ts.map +1 -1
  309. package/dist/nexus/path-map.d.ts +264 -20
  310. package/dist/nexus/path-map.d.ts.map +1 -1
  311. package/dist/nexus/path-map.js +580 -44
  312. package/dist/nexus/path-map.js.map +1 -1
  313. package/dist/nexus/projects-clean.d.ts +9 -1
  314. package/dist/nexus/projects-clean.d.ts.map +1 -1
  315. package/dist/nexus/projects-clean.js +126 -14
  316. package/dist/nexus/projects-clean.js.map +1 -1
  317. package/dist/nexus/projects-scan.d.ts +56 -1
  318. package/dist/nexus/projects-scan.d.ts.map +1 -1
  319. package/dist/nexus/projects-scan.js +144 -3
  320. package/dist/nexus/projects-scan.js.map +1 -1
  321. package/dist/nexus/registry-hygiene.d.ts +18 -2
  322. package/dist/nexus/registry-hygiene.d.ts.map +1 -1
  323. package/dist/nexus/registry-hygiene.js +37 -11
  324. package/dist/nexus/registry-hygiene.js.map +1 -1
  325. package/dist/nexus/registry-roots.d.ts +47 -0
  326. package/dist/nexus/registry-roots.d.ts.map +1 -0
  327. package/dist/nexus/registry-roots.js +79 -0
  328. package/dist/nexus/registry-roots.js.map +1 -0
  329. package/dist/nexus/registry.d.ts +50 -9
  330. package/dist/nexus/registry.d.ts.map +1 -1
  331. package/dist/nexus/registry.js +311 -156
  332. package/dist/nexus/registry.js.map +1 -1
  333. package/dist/nexus/stored-roots.d.ts +106 -0
  334. package/dist/nexus/stored-roots.d.ts.map +1 -0
  335. package/dist/nexus/stored-roots.js +180 -0
  336. package/dist/nexus/stored-roots.js.map +1 -0
  337. package/dist/nexus/transfer.d.ts.map +1 -1
  338. package/dist/nexus/transfer.js +3 -0
  339. package/dist/nexus/transfer.js.map +1 -1
  340. package/dist/nexus/user-profile-hygiene.d.ts +168 -0
  341. package/dist/nexus/user-profile-hygiene.d.ts.map +1 -0
  342. package/dist/nexus/user-profile-hygiene.js +305 -0
  343. package/dist/nexus/user-profile-hygiene.js.map +1 -0
  344. package/dist/nexus/user-profile.d.ts +53 -2
  345. package/dist/nexus/user-profile.d.ts.map +1 -1
  346. package/dist/nexus/user-profile.js +106 -4
  347. package/dist/nexus/user-profile.js.map +1 -1
  348. package/dist/orchestrate/pivot.d.ts.map +1 -1
  349. package/dist/orchestrate/pivot.js +5 -1
  350. package/dist/orchestrate/pivot.js.map +1 -1
  351. package/dist/orchestrate/spawn-ops.d.ts +12 -0
  352. package/dist/orchestrate/spawn-ops.d.ts.map +1 -1
  353. package/dist/orchestrate/spawn-ops.js +152 -57
  354. package/dist/orchestrate/spawn-ops.js.map +1 -1
  355. package/dist/orchestration/classify-readiness.d.ts +37 -1
  356. package/dist/orchestration/classify-readiness.d.ts.map +1 -1
  357. package/dist/orchestration/classify-readiness.js +48 -19
  358. package/dist/orchestration/classify-readiness.js.map +1 -1
  359. package/dist/orchestration/index.d.ts +3 -2
  360. package/dist/orchestration/index.d.ts.map +1 -1
  361. package/dist/orchestration/index.js +2 -1
  362. package/dist/orchestration/index.js.map +1 -1
  363. package/dist/orchestration/owner-decision-readiness.d.ts +88 -0
  364. package/dist/orchestration/owner-decision-readiness.d.ts.map +1 -0
  365. package/dist/orchestration/owner-decision-readiness.js +152 -0
  366. package/dist/orchestration/owner-decision-readiness.js.map +1 -0
  367. package/dist/orchestration/psyche-memory-budget.d.ts +105 -0
  368. package/dist/orchestration/psyche-memory-budget.d.ts.map +1 -0
  369. package/dist/orchestration/psyche-memory-budget.js +383 -0
  370. package/dist/orchestration/psyche-memory-budget.js.map +1 -0
  371. package/dist/orchestration/spawn-prompt.d.ts +12 -1
  372. package/dist/orchestration/spawn-prompt.d.ts.map +1 -1
  373. package/dist/orchestration/spawn-prompt.js +141 -179
  374. package/dist/orchestration/spawn-prompt.js.map +1 -1
  375. package/dist/orchestration/spawn.d.ts +7 -0
  376. package/dist/orchestration/spawn.d.ts.map +1 -1
  377. package/dist/orchestration/spawn.js +1 -0
  378. package/dist/orchestration/spawn.js.map +1 -1
  379. package/dist/paths.d.ts +24 -8
  380. package/dist/paths.d.ts.map +1 -1
  381. package/dist/paths.js +135 -71
  382. package/dist/paths.js.map +1 -1
  383. package/dist/platform.d.ts +6 -1
  384. package/dist/platform.d.ts.map +1 -1
  385. package/dist/platform.js +8 -19
  386. package/dist/platform.js.map +1 -1
  387. package/dist/project-info.d.ts +18 -0
  388. package/dist/project-info.d.ts.map +1 -1
  389. package/dist/project-info.js +29 -1
  390. package/dist/project-info.js.map +1 -1
  391. package/dist/project-lifecycle.d.ts +91 -32
  392. package/dist/project-lifecycle.d.ts.map +1 -1
  393. package/dist/project-lifecycle.js +639 -75
  394. package/dist/project-lifecycle.js.map +1 -1
  395. package/dist/project-scope.d.ts +86 -4
  396. package/dist/project-scope.d.ts.map +1 -1
  397. package/dist/project-scope.js +226 -13
  398. package/dist/project-scope.js.map +1 -1
  399. package/dist/project-tombstone.d.ts +111 -0
  400. package/dist/project-tombstone.d.ts.map +1 -0
  401. package/dist/project-tombstone.js +220 -0
  402. package/dist/project-tombstone.js.map +1 -0
  403. package/dist/release/artifacts.d.ts.map +1 -1
  404. package/dist/release/artifacts.js +14 -9
  405. package/dist/release/artifacts.js.map +1 -1
  406. package/dist/release/ci-evidence.d.ts +223 -0
  407. package/dist/release/ci-evidence.d.ts.map +1 -0
  408. package/dist/release/ci-evidence.js +652 -0
  409. package/dist/release/ci-evidence.js.map +1 -0
  410. package/dist/release/engine-ops.d.ts.map +1 -1
  411. package/dist/release/engine-ops.js +3 -3
  412. package/dist/release/engine-ops.js.map +1 -1
  413. package/dist/release/plan.d.ts.map +1 -1
  414. package/dist/release/plan.js +7 -6
  415. package/dist/release/plan.js.map +1 -1
  416. package/dist/release/pr-evidence.d.ts +38 -0
  417. package/dist/release/pr-evidence.d.ts.map +1 -1
  418. package/dist/release/pr-evidence.js +67 -11
  419. package/dist/release/pr-evidence.js.map +1 -1
  420. package/dist/release/reconcile.d.ts.map +1 -1
  421. package/dist/release/reconcile.js +3 -2
  422. package/dist/release/reconcile.js.map +1 -1
  423. package/dist/release/release-manifest.d.ts +2 -1
  424. package/dist/release/release-manifest.d.ts.map +1 -1
  425. package/dist/release/release-manifest.js +6 -5
  426. package/dist/release/release-manifest.js.map +1 -1
  427. package/dist/render/nexus/graph/index.d.ts +4 -0
  428. package/dist/render/nexus/graph/index.d.ts.map +1 -1
  429. package/dist/render/nexus/graph/index.js +57 -0
  430. package/dist/render/nexus/graph/index.js.map +1 -1
  431. package/dist/resources/spawn-wrapper.d.ts +8 -1
  432. package/dist/resources/spawn-wrapper.d.ts.map +1 -1
  433. package/dist/resources/spawn-wrapper.js +42 -5
  434. package/dist/resources/spawn-wrapper.js.map +1 -1
  435. package/dist/routing/build-command-groups.d.ts.map +1 -1
  436. package/dist/routing/build-command-groups.js +1 -0
  437. package/dist/routing/build-command-groups.js.map +1 -1
  438. package/dist/sagas/index.d.ts +1 -1
  439. package/dist/sagas/index.d.ts.map +1 -1
  440. package/dist/sagas/index.js +1 -1
  441. package/dist/sagas/index.js.map +1 -1
  442. package/dist/sagas/reconcile.d.ts +24 -4
  443. package/dist/sagas/reconcile.d.ts.map +1 -1
  444. package/dist/sagas/reconcile.js +226 -100
  445. package/dist/sagas/reconcile.js.map +1 -1
  446. package/dist/scaffold/ensure-config.d.ts.map +1 -1
  447. package/dist/scaffold/ensure-config.js +31 -1
  448. package/dist/scaffold/ensure-config.js.map +1 -1
  449. package/dist/scaffold/project-identity.d.ts +2 -8
  450. package/dist/scaffold/project-identity.d.ts.map +1 -1
  451. package/dist/scaffold/project-identity.js +5 -21
  452. package/dist/scaffold/project-identity.js.map +1 -1
  453. package/dist/security/input-sanitization.d.ts +9 -1
  454. package/dist/security/input-sanitization.d.ts.map +1 -1
  455. package/dist/security/input-sanitization.js +12 -2
  456. package/dist/security/input-sanitization.js.map +1 -1
  457. package/dist/selfimprove/fix-gen.js +4321 -1807
  458. package/dist/selfimprove/fix-gen.js.map +3 -3
  459. package/dist/sentient/propose-tick.d.ts.map +1 -1
  460. package/dist/sentient/propose-tick.js +4 -2
  461. package/dist/sentient/propose-tick.js.map +1 -1
  462. package/dist/sequence/index.d.ts.map +1 -1
  463. package/dist/sequence/index.js +6 -23
  464. package/dist/sequence/index.js.map +1 -1
  465. package/dist/session/engine-ops.d.ts +9 -0
  466. package/dist/session/engine-ops.d.ts.map +1 -1
  467. package/dist/session/engine-ops.js +110 -18
  468. package/dist/session/engine-ops.js.map +1 -1
  469. package/dist/sessions/assumptions.d.ts.map +1 -1
  470. package/dist/sessions/assumptions.js +7 -4
  471. package/dist/sessions/assumptions.js.map +1 -1
  472. package/dist/sessions/briefing.d.ts +6 -0
  473. package/dist/sessions/briefing.d.ts.map +1 -1
  474. package/dist/sessions/briefing.js +12 -1
  475. package/dist/sessions/briefing.js.map +1 -1
  476. package/dist/sessions/decisions.d.ts +38 -3
  477. package/dist/sessions/decisions.d.ts.map +1 -1
  478. package/dist/sessions/decisions.js +235 -21
  479. package/dist/sessions/decisions.js.map +1 -1
  480. package/dist/sessions/drift-watchdog.d.ts.map +1 -1
  481. package/dist/sessions/drift-watchdog.js +4 -2
  482. package/dist/sessions/drift-watchdog.js.map +1 -1
  483. package/dist/sessions/index.d.ts.map +1 -1
  484. package/dist/sessions/index.js +33 -4
  485. package/dist/sessions/index.js.map +1 -1
  486. package/dist/sessions/session-end-snapshot-entry.d.ts +26 -0
  487. package/dist/sessions/session-end-snapshot-entry.d.ts.map +1 -0
  488. package/dist/sessions/session-end-snapshot-entry.js +59 -0
  489. package/dist/sessions/session-end-snapshot-entry.js.map +1 -0
  490. package/dist/sessions/session-end-snapshot.d.ts +114 -0
  491. package/dist/sessions/session-end-snapshot.d.ts.map +1 -0
  492. package/dist/sessions/session-end-snapshot.js +446 -0
  493. package/dist/sessions/session-end-snapshot.js.map +1 -0
  494. package/dist/sessions/session-manifest-mirror.d.ts.map +1 -1
  495. package/dist/sessions/session-manifest-mirror.js +10 -17
  496. package/dist/sessions/session-manifest-mirror.js.map +1 -1
  497. package/dist/sessions/session-switch.d.ts.map +1 -1
  498. package/dist/sessions/session-switch.js +5 -3
  499. package/dist/sessions/session-switch.js.map +1 -1
  500. package/dist/sessions/snapshot.d.ts.map +1 -1
  501. package/dist/sessions/snapshot.js +5 -4
  502. package/dist/sessions/snapshot.js.map +1 -1
  503. package/dist/sessions/terminal-identity.d.ts +169 -0
  504. package/dist/sessions/terminal-identity.d.ts.map +1 -0
  505. package/dist/sessions/terminal-identity.js +273 -0
  506. package/dist/sessions/terminal-identity.js.map +1 -0
  507. package/dist/setup/sections/verification.d.ts +1 -1
  508. package/dist/setup/sections/verification.d.ts.map +1 -1
  509. package/dist/setup/sections/verification.js +11 -15
  510. package/dist/setup/sections/verification.js.map +1 -1
  511. package/dist/skills/discovery.d.ts +8 -2
  512. package/dist/skills/discovery.d.ts.map +1 -1
  513. package/dist/skills/discovery.js +15 -3
  514. package/dist/skills/discovery.js.map +1 -1
  515. package/dist/skills/dispatch.d.ts +5 -2
  516. package/dist/skills/dispatch.d.ts.map +1 -1
  517. package/dist/skills/dispatch.js +5 -2
  518. package/dist/skills/dispatch.js.map +1 -1
  519. package/dist/skills/doctor.d.ts.map +1 -1
  520. package/dist/skills/doctor.js +5 -1
  521. package/dist/skills/doctor.js.map +1 -1
  522. package/dist/skills/federated-search.js +2 -2
  523. package/dist/skills/federated-search.js.map +1 -1
  524. package/dist/skills/index.d.ts +3 -3
  525. package/dist/skills/index.d.ts.map +1 -1
  526. package/dist/skills/index.js +1 -1
  527. package/dist/skills/index.js.map +1 -1
  528. package/dist/skills/migration.d.ts +5 -5
  529. package/dist/skills/migration.d.ts.map +1 -1
  530. package/dist/skills/migration.js +7 -9
  531. package/dist/skills/migration.js.map +1 -1
  532. package/dist/skills/orchestrator/startup.d.ts.map +1 -1
  533. package/dist/skills/orchestrator/startup.js +4 -2
  534. package/dist/skills/orchestrator/startup.js.map +1 -1
  535. package/dist/skills/skill-paths.d.ts +30 -3
  536. package/dist/skills/skill-paths.d.ts.map +1 -1
  537. package/dist/skills/skill-paths.js +28 -9
  538. package/dist/skills/skill-paths.js.map +1 -1
  539. package/dist/skills/skill-root.d.ts +60 -11
  540. package/dist/skills/skill-root.d.ts.map +1 -1
  541. package/dist/skills/skill-root.js +100 -14
  542. package/dist/skills/skill-root.js.map +1 -1
  543. package/dist/skills/skills-guard.js +5 -5
  544. package/dist/skills/skills-guard.js.map +1 -1
  545. package/dist/skills/types.d.ts +23 -0
  546. package/dist/skills/types.d.ts.map +1 -1
  547. package/dist/skills/types.js.map +1 -1
  548. package/dist/spawn/agent-identity.d.ts +51 -2
  549. package/dist/spawn/agent-identity.d.ts.map +1 -1
  550. package/dist/spawn/agent-identity.js +89 -26
  551. package/dist/spawn/agent-identity.js.map +1 -1
  552. package/dist/spawn/branch-lock.d.ts.map +1 -1
  553. package/dist/spawn/branch-lock.js +56 -79
  554. package/dist/spawn/branch-lock.js.map +1 -1
  555. package/dist/spawn/index.d.ts +2 -2
  556. package/dist/spawn/index.d.ts.map +1 -1
  557. package/dist/spawn/index.js +1 -1
  558. package/dist/spawn/index.js.map +1 -1
  559. package/dist/spawn/worktree-lock-holder.d.ts +37 -0
  560. package/dist/spawn/worktree-lock-holder.d.ts.map +1 -0
  561. package/dist/spawn/worktree-lock-holder.js +41 -0
  562. package/dist/spawn/worktree-lock-holder.js.map +1 -0
  563. package/dist/sticky/convert.d.ts.map +1 -1
  564. package/dist/sticky/convert.js +16 -4
  565. package/dist/sticky/convert.js.map +1 -1
  566. package/dist/store/attachment-store.d.ts +14 -0
  567. package/dist/store/attachment-store.d.ts.map +1 -1
  568. package/dist/store/attachment-store.js +11 -1
  569. package/dist/store/attachment-store.js.map +1 -1
  570. package/dist/store/backup-inspect.d.ts +7 -3
  571. package/dist/store/backup-inspect.d.ts.map +1 -1
  572. package/dist/store/backup-inspect.js +18 -7
  573. package/dist/store/backup-inspect.js.map +1 -1
  574. package/dist/store/backup-sidecar.d.ts +60 -0
  575. package/dist/store/backup-sidecar.d.ts.map +1 -0
  576. package/dist/store/backup-sidecar.js +96 -0
  577. package/dist/store/backup-sidecar.js.map +1 -0
  578. package/dist/store/data-accessor.d.ts +1 -1
  579. package/dist/store/data-accessor.d.ts.map +1 -1
  580. package/dist/store/data-accessor.js +6 -6
  581. package/dist/store/data-accessor.js.map +1 -1
  582. package/dist/store/data-safety-central.d.ts +13 -4
  583. package/dist/store/data-safety-central.d.ts.map +1 -1
  584. package/dist/store/data-safety-central.js +31 -4
  585. package/dist/store/data-safety-central.js.map +1 -1
  586. package/dist/store/dual-scope-db.d.ts.map +1 -1
  587. package/dist/store/dual-scope-db.js +10 -0
  588. package/dist/store/dual-scope-db.js.map +1 -1
  589. package/dist/store/exodus/__fixtures__/representative-fixture.d.ts.map +1 -1
  590. package/dist/store/exodus/__fixtures__/representative-fixture.js +4 -0
  591. package/dist/store/exodus/__fixtures__/representative-fixture.js.map +1 -1
  592. package/dist/store/exodus/migrate.js +1 -1
  593. package/dist/store/exodus/migrate.js.map +1 -1
  594. package/dist/store/exodus/reconcile.js +1 -1
  595. package/dist/store/exodus/reconcile.js.map +1 -1
  596. package/dist/store/exodus/runtime-targets.d.ts +4 -3
  597. package/dist/store/exodus/runtime-targets.d.ts.map +1 -1
  598. package/dist/store/exodus/runtime-targets.js +4 -3
  599. package/dist/store/exodus/runtime-targets.js.map +1 -1
  600. package/dist/store/import-logging.d.ts.map +1 -1
  601. package/dist/store/import-logging.js +5 -4
  602. package/dist/store/import-logging.js.map +1 -1
  603. package/dist/store/legacy-tasks-lineage.d.ts.map +1 -1
  604. package/dist/store/legacy-tasks-lineage.js +2 -7
  605. package/dist/store/legacy-tasks-lineage.js.map +1 -1
  606. package/dist/store/lock.d.ts +1 -0
  607. package/dist/store/lock.d.ts.map +1 -1
  608. package/dist/store/lock.js +1 -0
  609. package/dist/store/lock.js.map +1 -1
  610. package/dist/store/memory-accessor.d.ts +8 -3
  611. package/dist/store/memory-accessor.d.ts.map +1 -1
  612. package/dist/store/memory-accessor.js +29 -13
  613. package/dist/store/memory-accessor.js.map +1 -1
  614. package/dist/store/memory-sqlite.d.ts.map +1 -1
  615. package/dist/store/memory-sqlite.js +5 -0
  616. package/dist/store/memory-sqlite.js.map +1 -1
  617. package/dist/store/open-cleo-db.d.ts.map +1 -1
  618. package/dist/store/open-cleo-db.js +11 -6
  619. package/dist/store/open-cleo-db.js.map +1 -1
  620. package/dist/store/portable-bundle-relocate.d.ts +13 -8
  621. package/dist/store/portable-bundle-relocate.d.ts.map +1 -1
  622. package/dist/store/portable-bundle-relocate.js +75 -31
  623. package/dist/store/portable-bundle-relocate.js.map +1 -1
  624. package/dist/store/portable-bundle-scan.d.ts.map +1 -1
  625. package/dist/store/portable-bundle-scan.js +2 -1
  626. package/dist/store/portable-bundle-scan.js.map +1 -1
  627. package/dist/store/portable-bundle.d.ts.map +1 -1
  628. package/dist/store/portable-bundle.js +7 -3
  629. package/dist/store/portable-bundle.js.map +1 -1
  630. package/dist/store/pre-repair-snapshot.d.ts +77 -0
  631. package/dist/store/pre-repair-snapshot.d.ts.map +1 -0
  632. package/dist/store/pre-repair-snapshot.js +122 -0
  633. package/dist/store/pre-repair-snapshot.js.map +1 -0
  634. package/dist/store/regenerators.d.ts +2 -1
  635. package/dist/store/regenerators.d.ts.map +1 -1
  636. package/dist/store/regenerators.js +17 -3
  637. package/dist/store/regenerators.js.map +1 -1
  638. package/dist/store/relocated-store-guard.d.ts +71 -0
  639. package/dist/store/relocated-store-guard.d.ts.map +1 -0
  640. package/dist/store/relocated-store-guard.js +138 -0
  641. package/dist/store/relocated-store-guard.js.map +1 -0
  642. package/dist/store/safety-data-accessor.d.ts +2 -2
  643. package/dist/store/safety-data-accessor.d.ts.map +1 -1
  644. package/dist/store/safety-data-accessor.js +2 -2
  645. package/dist/store/safety-data-accessor.js.map +1 -1
  646. package/dist/store/schema/attachments.d.ts +2 -2
  647. package/dist/store/schema/audit.d.ts +5 -5
  648. package/dist/store/schema/background-jobs.d.ts +1 -1
  649. package/dist/store/schema/cleo-global/models-catalog.d.ts +1 -1
  650. package/dist/store/schema/cleo-global/nexus.d.ts +686 -0
  651. package/dist/store/schema/cleo-global/nexus.d.ts.map +1 -1
  652. package/dist/store/schema/cleo-global/nexus.js +175 -7
  653. package/dist/store/schema/cleo-global/nexus.js.map +1 -1
  654. package/dist/store/schema/cleo-global/skills.d.ts +2 -2
  655. package/dist/store/schema/cleo-project/audit.d.ts +5 -5
  656. package/dist/store/schema/cleo-project/docs.d.ts +3 -3
  657. package/dist/store/schema/cleo-project/lifecycle.d.ts +3 -3
  658. package/dist/store/schema/cleo-project/provenance-commits.d.ts +2 -2
  659. package/dist/store/schema/cleo-project/provenance-rest.d.ts +3 -3
  660. package/dist/store/schema/cleo-project/tasks-core-batch2.d.ts +1 -1
  661. package/dist/store/schema/cleo-project/tasks-core.d.ts +6 -6
  662. package/dist/store/schema/cleo-shared/brain.d.ts +26 -26
  663. package/dist/store/schema/lifecycle.d.ts +3 -3
  664. package/dist/store/schema/manifest.d.ts +1 -1
  665. package/dist/store/schema/memory-schema.d.ts +26 -26
  666. package/dist/store/schema/nexus-schema.d.ts +696 -2
  667. package/dist/store/schema/nexus-schema.d.ts.map +1 -1
  668. package/dist/store/schema/nexus-schema.js +175 -19
  669. package/dist/store/schema/nexus-schema.js.map +1 -1
  670. package/dist/store/schema/provenance/releases.d.ts +1 -1
  671. package/dist/store/schema/skills-schema.d.ts +2 -2
  672. package/dist/store/schema/tasks.d.ts +5 -5
  673. package/dist/store/session-binding-schema.d.ts +132 -0
  674. package/dist/store/session-binding-schema.d.ts.map +1 -0
  675. package/dist/store/session-binding-schema.js +53 -0
  676. package/dist/store/session-binding-schema.js.map +1 -0
  677. package/dist/store/session-store.d.ts +262 -4
  678. package/dist/store/session-store.d.ts.map +1 -1
  679. package/dist/store/session-store.js +486 -26
  680. package/dist/store/session-store.js.map +1 -1
  681. package/dist/store/snapshot-gate.d.ts +239 -0
  682. package/dist/store/snapshot-gate.d.ts.map +1 -0
  683. package/dist/store/snapshot-gate.js +398 -0
  684. package/dist/store/snapshot-gate.js.map +1 -0
  685. package/dist/store/split-brain-import.js +2 -2
  686. package/dist/store/split-brain-import.js.map +1 -1
  687. package/dist/store/sqlite-backup.d.ts +55 -13
  688. package/dist/store/sqlite-backup.d.ts.map +1 -1
  689. package/dist/store/sqlite-backup.js +414 -106
  690. package/dist/store/sqlite-backup.js.map +1 -1
  691. package/dist/store/sqlite-data-accessor.d.ts +15 -0
  692. package/dist/store/sqlite-data-accessor.d.ts.map +1 -1
  693. package/dist/store/sqlite-data-accessor.js +63 -4
  694. package/dist/store/sqlite-data-accessor.js.map +1 -1
  695. package/dist/store/sqlite.d.ts +1 -1
  696. package/dist/store/sqlite.d.ts.map +1 -1
  697. package/dist/store/sqlite.js +53 -5
  698. package/dist/store/sqlite.js.map +1 -1
  699. package/dist/store/store-write-guard.d.ts +28 -0
  700. package/dist/store/store-write-guard.d.ts.map +1 -0
  701. package/dist/store/store-write-guard.js +50 -0
  702. package/dist/store/store-write-guard.js.map +1 -0
  703. package/dist/store/table-classification.d.ts +143 -0
  704. package/dist/store/table-classification.d.ts.map +1 -0
  705. package/dist/store/table-classification.js +1866 -0
  706. package/dist/store/table-classification.js.map +1 -0
  707. package/dist/store/task-version.d.ts +72 -0
  708. package/dist/store/task-version.d.ts.map +1 -0
  709. package/dist/store/task-version.js +93 -0
  710. package/dist/store/task-version.js.map +1 -0
  711. package/dist/store/tasks-schema.d.ts +1 -0
  712. package/dist/store/tasks-schema.d.ts.map +1 -1
  713. package/dist/store/tasks-schema.js +13 -0
  714. package/dist/store/tasks-schema.js.map +1 -1
  715. package/dist/store/tasks-sqlite.d.ts +10 -3
  716. package/dist/store/tasks-sqlite.d.ts.map +1 -1
  717. package/dist/store/tasks-sqlite.js +12 -3
  718. package/dist/store/tasks-sqlite.js.map +1 -1
  719. package/dist/store/twin-collapse.d.ts +275 -0
  720. package/dist/store/twin-collapse.d.ts.map +1 -0
  721. package/dist/store/twin-collapse.js +805 -0
  722. package/dist/store/twin-collapse.js.map +1 -0
  723. package/dist/store/umbrella-data-accessor.d.ts +2 -2
  724. package/dist/store/umbrella-data-accessor.d.ts.map +1 -1
  725. package/dist/store/umbrella-data-accessor.js +2 -2
  726. package/dist/store/umbrella-data-accessor.js.map +1 -1
  727. package/dist/store/validation-schemas.d.ts +63 -63
  728. package/dist/store/worktree-isolation-guard.d.ts +46 -24
  729. package/dist/store/worktree-isolation-guard.d.ts.map +1 -1
  730. package/dist/store/worktree-isolation-guard.js +69 -50
  731. package/dist/store/worktree-isolation-guard.js.map +1 -1
  732. package/dist/system/backup.d.ts.map +1 -1
  733. package/dist/system/backup.js +3 -67
  734. package/dist/system/backup.js.map +1 -1
  735. package/dist/system/cleo-link.d.ts +163 -0
  736. package/dist/system/cleo-link.d.ts.map +1 -0
  737. package/dist/system/cleo-link.js +246 -0
  738. package/dist/system/cleo-link.js.map +1 -0
  739. package/dist/system/dependencies.d.ts.map +1 -1
  740. package/dist/system/dependencies.js +3 -6
  741. package/dist/system/dependencies.js.map +1 -1
  742. package/dist/system/global-delivery.d.ts +205 -0
  743. package/dist/system/global-delivery.d.ts.map +1 -0
  744. package/dist/system/global-delivery.js +320 -0
  745. package/dist/system/global-delivery.js.map +1 -0
  746. package/dist/system/health.d.ts.map +1 -1
  747. package/dist/system/health.js +29 -6
  748. package/dist/system/health.js.map +1 -1
  749. package/dist/system/project-health.d.ts.map +1 -1
  750. package/dist/system/project-health.js +1 -20
  751. package/dist/system/project-health.js.map +1 -1
  752. package/dist/system/runtime.d.ts.map +1 -1
  753. package/dist/system/runtime.js +2 -12
  754. package/dist/system/runtime.js.map +1 -1
  755. package/dist/system/safestop.d.ts.map +1 -1
  756. package/dist/system/safestop.js +5 -2
  757. package/dist/system/safestop.js.map +1 -1
  758. package/dist/system/skill-fixtures.d.ts +200 -0
  759. package/dist/system/skill-fixtures.d.ts.map +1 -0
  760. package/dist/system/skill-fixtures.js +331 -0
  761. package/dist/system/skill-fixtures.js.map +1 -0
  762. package/dist/tasks/add.d.ts.map +1 -1
  763. package/dist/tasks/add.js +4 -2
  764. package/dist/tasks/add.js.map +1 -1
  765. package/dist/tasks/affected-packages.d.ts +173 -0
  766. package/dist/tasks/affected-packages.d.ts.map +1 -0
  767. package/dist/tasks/affected-packages.js +389 -0
  768. package/dist/tasks/affected-packages.js.map +1 -0
  769. package/dist/tasks/change-set.d.ts +151 -0
  770. package/dist/tasks/change-set.d.ts.map +1 -0
  771. package/dist/tasks/change-set.js +634 -0
  772. package/dist/tasks/change-set.js.map +1 -0
  773. package/dist/tasks/complete.d.ts.map +1 -1
  774. package/dist/tasks/complete.js +10 -5
  775. package/dist/tasks/complete.js.map +1 -1
  776. package/dist/tasks/done-plan.d.ts +103 -0
  777. package/dist/tasks/done-plan.d.ts.map +1 -0
  778. package/dist/tasks/done-plan.js +541 -0
  779. package/dist/tasks/done-plan.js.map +1 -0
  780. package/dist/tasks/done-record.d.ts +127 -0
  781. package/dist/tasks/done-record.d.ts.map +1 -0
  782. package/dist/tasks/done-record.js +373 -0
  783. package/dist/tasks/done-record.js.map +1 -0
  784. package/dist/tasks/duplicate-detector.d.ts +68 -11
  785. package/dist/tasks/duplicate-detector.d.ts.map +1 -1
  786. package/dist/tasks/duplicate-detector.js +314 -58
  787. package/dist/tasks/duplicate-detector.js.map +1 -1
  788. package/dist/tasks/evidence-paths.d.ts +78 -0
  789. package/dist/tasks/evidence-paths.d.ts.map +1 -0
  790. package/dist/tasks/evidence-paths.js +150 -0
  791. package/dist/tasks/evidence-paths.js.map +1 -0
  792. package/dist/tasks/evidence.d.ts +55 -0
  793. package/dist/tasks/evidence.d.ts.map +1 -1
  794. package/dist/tasks/evidence.js +223 -26
  795. package/dist/tasks/evidence.js.map +1 -1
  796. package/dist/tasks/gate-preview.d.ts +5 -0
  797. package/dist/tasks/gate-preview.d.ts.map +1 -1
  798. package/dist/tasks/gate-preview.js +25 -21
  799. package/dist/tasks/gate-preview.js.map +1 -1
  800. package/dist/tasks/gate-result-cache.d.ts +205 -0
  801. package/dist/tasks/gate-result-cache.d.ts.map +1 -0
  802. package/dist/tasks/gate-result-cache.js +285 -0
  803. package/dist/tasks/gate-result-cache.js.map +1 -0
  804. package/dist/tasks/gate-runner.d.ts +126 -3
  805. package/dist/tasks/gate-runner.d.ts.map +1 -1
  806. package/dist/tasks/gate-runner.js +483 -28
  807. package/dist/tasks/gate-runner.js.map +1 -1
  808. package/dist/tasks/session-scope.d.ts +3 -1
  809. package/dist/tasks/session-scope.d.ts.map +1 -1
  810. package/dist/tasks/session-scope.js +18 -3
  811. package/dist/tasks/session-scope.js.map +1 -1
  812. package/dist/tasks/tool-resolver.d.ts +23 -6
  813. package/dist/tasks/tool-resolver.d.ts.map +1 -1
  814. package/dist/tasks/tool-resolver.js +146 -8
  815. package/dist/tasks/tool-resolver.js.map +1 -1
  816. package/dist/tasks/tool-vacuity.d.ts +71 -0
  817. package/dist/tasks/tool-vacuity.d.ts.map +1 -0
  818. package/dist/tasks/tool-vacuity.js +334 -0
  819. package/dist/tasks/tool-vacuity.js.map +1 -0
  820. package/dist/tasks/update.d.ts +8 -0
  821. package/dist/tasks/update.d.ts.map +1 -1
  822. package/dist/tasks/update.js +159 -78
  823. package/dist/tasks/update.js.map +1 -1
  824. package/dist/upgrade.d.ts.map +1 -1
  825. package/dist/upgrade.js +18 -2
  826. package/dist/upgrade.js.map +1 -1
  827. package/dist/validation/doctor/checks.d.ts +20 -2
  828. package/dist/validation/doctor/checks.d.ts.map +1 -1
  829. package/dist/validation/doctor/checks.js +64 -4
  830. package/dist/validation/doctor/checks.js.map +1 -1
  831. package/dist/validation/doctor/index.d.ts +1 -1
  832. package/dist/validation/doctor/index.d.ts.map +1 -1
  833. package/dist/validation/doctor/index.js +1 -1
  834. package/dist/validation/doctor/index.js.map +1 -1
  835. package/dist/validation/doctor/utils.d.ts.map +1 -1
  836. package/dist/validation/doctor/utils.js +7 -2
  837. package/dist/validation/doctor/utils.js.map +1 -1
  838. package/dist/validation/engine-ops.d.ts +15 -0
  839. package/dist/validation/engine-ops.d.ts.map +1 -1
  840. package/dist/validation/engine-ops.js +249 -98
  841. package/dist/validation/engine-ops.js.map +1 -1
  842. package/migrations/drizzle-cleo-global/20260927000000_t12469-project-locations/migration.sql +121 -0
  843. package/migrations/drizzle-cleo-global/20260927010000_t12543-user-profile-project-scope/migration.sql +23 -0
  844. package/migrations/drizzle-cleo-global/20260928010000_t12470-location-candidates/migration.sql +72 -0
  845. package/migrations/drizzle-cleo-global/20260928020000_t12510-nexus-devices/migration.sql +34 -0
  846. package/migrations/drizzle-cleo-global/20260928030000_t12511-project-git-state/migration.sql +62 -0
  847. package/migrations/drizzle-cleo-project/20260927000000_t12499-session-terminal-bindings/migration.sql +34 -0
  848. package/migrations/drizzle-cleo-project/20260928000000_t12500-binding-provider-flag/migration.sql +15 -0
  849. package/package.json +23 -13
  850. package/schemas/config.schema.json +54 -0
  851. package/schemas/project-context.schema.json +51 -3
  852. package/schemas/project-info.schema.json +28 -1
  853. package/templates/CLEO-INJECTION.md +40 -332
  854. package/templates/CLEO-REFERENCE.md +303 -0
  855. package/templates/workflows/README.md +36 -14
@@ -0,0 +1,1075 @@
1
+ /**
2
+ * `cleo doctor projects` — machine-wide integrity check of the project
3
+ * registry (T12471 · ADR-094).
4
+ *
5
+ * The registry is keyed by project id, never by path. A path is a per-device
6
+ * hint that goes stale when a project moves, is deleted, or is re-initialized
7
+ * under a new id. This module walks every registry row, probes its path, scans
8
+ * the directories projects live in for `.cleo/` declarations, and classifies
9
+ * each row (see `NexusRegistryFindingKind`):
10
+ *
11
+ * - {@link inspectProjectRegistry} is read-only and returns every non-`ok`
12
+ * row with its exact remedy.
13
+ * - {@link applyProjectRegistryRepair} applies only the safe classes: a
14
+ * `moved` row is rebound to the one path that declares its id, a
15
+ * `missing` row's location is recorded as `missing`, and an alias row whose
16
+ * truncated legacy key several projects claim is removed (T12589). No
17
+ * registry row is deleted. The rows the changes touch are imaged before and
18
+ * after, and both images are written to `nexus_audit_log` in the same
19
+ * transaction as the changes.
20
+ * - {@link rollbackProjectRegistryRepair} restores the before image, guarded
21
+ * on the current rows still matching the after image.
22
+ *
23
+ * Split identities, ambiguous moves, temp rows and home/root rows are owner
24
+ * decisions: they are reported with a remedy and never changed.
25
+ *
26
+ * Probes are bounded: at most `concurrency` filesystem reads at once, each
27
+ * with a `timeoutMs` budget. Only ENOENT/ENOTDIR prove a path gone; EACCES,
28
+ * EPERM or a timeout leave the row `unreadable` and untouched (#1606).
29
+ *
30
+ * @task T12471
31
+ * @epic T12468
32
+ * @see ADR-094 — write-once portable project identity
33
+ */
34
+ import { randomUUID } from 'node:crypto';
35
+ import { existsSync } from 'node:fs';
36
+ import { readdir } from 'node:fs/promises';
37
+ import { homedir } from 'node:os';
38
+ import { basename, dirname, join, parse, resolve, sep } from 'node:path';
39
+ import { legacyAliasClaimants, readDeclaredProjectIdentity } from '@cleocode/paths';
40
+ import { eq, inArray, or } from 'drizzle-orm';
41
+ import { runWithConcurrency } from '../lib/concurrency.js';
42
+ import { readCheckoutNonce } from '../nexus/checkout-nonce.js';
43
+ import { collectCheckoutEvidence } from '../nexus/identity.js';
44
+ import { registryAliasClaimants } from '../nexus/legacy-alias.js';
45
+ import { confirmProjectLocation, currentDeviceId, demoteProjectLocation, holdingKey, isSupersededRegistryPath, LOCAL_DEVICE_SENTINEL, probeProjectHolding, } from '../nexus/path-map.js';
46
+ import { walkForCleoBounded, withinBudget } from '../nexus/projects-scan.js';
47
+ import { isEphemeralPath } from '../nexus/registry-hygiene.js';
48
+ import { getCleoHome } from '../paths.js';
49
+ import { isStrictlyInsideDir, readValidProjectTombstone } from '../project-tombstone.js';
50
+ /** Command that reports registry integrity; the prefix of the remedies below. */
51
+ const DOCTOR_COMMAND = 'cleo doctor projects';
52
+ /** Audit-log `action` of an applied repair. */
53
+ const APPLY_ACTION = 'doctor.projects.apply';
54
+ /** Audit-log `action` of a rollback. */
55
+ const ROLLBACK_ACTION = 'doctor.projects.rollback';
56
+ /** Default filesystem reads in flight at once. */
57
+ const DEFAULT_CONCURRENCY = 16;
58
+ /** Default budget for one filesystem probe. */
59
+ const DEFAULT_TIMEOUT_MS = 2000;
60
+ /** Default scan depth below each root. */
61
+ const DEFAULT_MAX_DEPTH = 2;
62
+ /** SQLite bound-parameter headroom per `IN (...)` list. */
63
+ const CHUNK = 400;
64
+ /** Every kind, so `counts` always carries all of them. */
65
+ const FINDING_KINDS = [
66
+ 'ok',
67
+ 'moved',
68
+ 'ambiguous',
69
+ 'split',
70
+ 'possible-split',
71
+ 'missing',
72
+ 'temp',
73
+ 'root',
74
+ 'unreadable',
75
+ 'other-device',
76
+ ];
77
+ /** Error with a stable code, raised when a rollback is refused. */
78
+ export class RegistryRepairError extends Error {
79
+ code;
80
+ /** @override */
81
+ name = 'RegistryRepairError';
82
+ /**
83
+ * @param code - Stable error code (`E_NOT_FOUND`, `E_ROLLBACK_CONFLICT`).
84
+ * @param message - Explanation with the remedy.
85
+ */
86
+ constructor(code, message) {
87
+ super(message);
88
+ this.code = code;
89
+ }
90
+ }
91
+ /**
92
+ * Whether a registry path is a temp row: under a temp directory while the
93
+ * registry itself is persistent. A sandboxed registry (temp `CLEO_HOME`) keeps
94
+ * its temp fixtures legitimately, as encounter registration does.
95
+ *
96
+ * @param projectPath - Registered path.
97
+ * @param cleoHome - Home of the registry being checked.
98
+ * @returns `true` for a temp row.
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * isTempRegistryPath('/tmp/fixture', '/home/me/.local/share/cleo'); // true
103
+ * ```
104
+ */
105
+ export function isTempRegistryPath(projectPath, cleoHome) {
106
+ return isEphemeralPath(projectPath) && !isEphemeralPath(cleoHome);
107
+ }
108
+ /**
109
+ * Whether a registry path is the home directory or a filesystem root: a
110
+ * project registered there claims every directory below it.
111
+ *
112
+ * @param projectPath - Registered path.
113
+ * @param home - Home directory (defaults to the current user's).
114
+ * @returns `true` for a home or root row.
115
+ *
116
+ * @example
117
+ * ```ts
118
+ * isHomeOrRootPath('/'); // true
119
+ * ```
120
+ */
121
+ export function isHomeOrRootPath(projectPath, home = homedir()) {
122
+ const resolved = resolve(projectPath);
123
+ return resolved === parse(resolved).root || resolved === resolve(home);
124
+ }
125
+ /** Split `items` into slices of at most {@link CHUNK}. */
126
+ function chunks(items) {
127
+ const out = [];
128
+ for (let i = 0; i < items.length; i += CHUNK)
129
+ out.push(items.slice(i, i + CHUNK));
130
+ return out;
131
+ }
132
+ /** Open the global registry store and its schema. */
133
+ async function openRegistry(cleoHome) {
134
+ const { getNexusRegistryDb, getNexusRegistryDbPath } = await import('../store/nexus-sqlite.js');
135
+ const schema = await import('../store/schema/nexus-schema.js');
136
+ return {
137
+ db: await getNexusRegistryDb(cleoHome),
138
+ storePath: getNexusRegistryDbPath(cleoHome),
139
+ ...schema,
140
+ };
141
+ }
142
+ /**
143
+ * Scan `roots` and read the id each found project declares. Every `.cleo/`
144
+ * is first read asynchronously within the budget; only a directory that
145
+ * answered in time is read synchronously, so one hung mount cannot stall the
146
+ * scan. Projects in a trash directory are never candidates.
147
+ */
148
+ async function scanRoots(roots, opts) {
149
+ const walked = await walkForCleoBounded(roots.filter((root) => !isTrashPath(root)), opts);
150
+ const timedOut = [...walked.timedOut];
151
+ const unreadable = [...walked.unreadable];
152
+ const found = walked.found.filter((path) => !isTrashPath(path));
153
+ const answered = await runWithConcurrency(found, opts.concurrency, async (path) => {
154
+ const read = await withinBudget(readdir(join(path, '.cleo')).then(() => 'ok', () => 'unreadable'), opts.timeoutMs);
155
+ if (read === 'timeout')
156
+ timedOut.push(path);
157
+ else if (read === 'unreadable')
158
+ unreadable.push(path);
159
+ return read === 'ok';
160
+ });
161
+ const projects = [];
162
+ found.forEach((path, index) => {
163
+ if (!answered[index])
164
+ return;
165
+ const declared = readDeclaredProjectIdentity(path);
166
+ if (!declared)
167
+ return;
168
+ projects.push({ path, projectId: declared.projectId, nonce: readCheckoutNonce(path) });
169
+ });
170
+ return { projects, timedOut: timedOut.sort(), unreadable: unreadable.sort() };
171
+ }
172
+ /** Path segments that mark a trash directory (macOS, Windows, freedesktop). */
173
+ const TRASH_SEGMENTS = new Set(['.trash', '.trashes', '$recycle.bin']);
174
+ /**
175
+ * Whether `path` lies in a trash directory. A project the owner trashed is
176
+ * never a move target, and its registry row may be cleaned.
177
+ *
178
+ * @param path - Absolute path.
179
+ * @returns `true` inside `.Trash`, `.Trashes`, `$RECYCLE.BIN` or `.local/share/Trash`.
180
+ *
181
+ * @example
182
+ * ```ts
183
+ * isTrashPath('/Users/me/.Trash/app'); // true
184
+ * ```
185
+ */
186
+ export function isTrashPath(path) {
187
+ const segments = resolve(path)
188
+ .split(sep)
189
+ .map((segment) => segment.toLowerCase());
190
+ return segments.some((segment, i) => TRASH_SEGMENTS.has(segment) ||
191
+ (segment === 'trash' && segments[i - 1] === 'share' && segments[i - 2] === '.local'));
192
+ }
193
+ /** `true` when `inner` is strictly inside `outer`, compared lexically. */
194
+ function isLexicallyInside(outer, inner) {
195
+ const o = resolve(outer);
196
+ return resolve(inner).startsWith(o.endsWith(sep) ? o : o + sep);
197
+ }
198
+ /**
199
+ * Directories to scan: the parent of every registered path, existing or not
200
+ * (a project is most often moved or renamed next to where it was), plus the
201
+ * operator's `--roots`. A filesystem root is never scanned by default; a
202
+ * parent that no longer exists is skipped by the walker.
203
+ */
204
+ function scanRootsFor(paths, extra) {
205
+ const roots = new Set(extra.map((root) => resolve(root)));
206
+ for (const projectPath of paths) {
207
+ if (isSupersededRegistryPath(projectPath))
208
+ continue;
209
+ const parent = dirname(resolve(projectPath));
210
+ if (parent !== parse(parent).root)
211
+ roots.add(parent);
212
+ }
213
+ return [...roots].sort();
214
+ }
215
+ /** The remedy for a split proven by shared repository evidence. */
216
+ function splitRemedy(row, peer) {
217
+ const keepPeer = `to keep ${peer.projectId}: review \`cleo nexus show ${row.projectId}\`, then \`cleo nexus unregister ${row.projectId}\``;
218
+ const keepRow = `to keep ${row.projectId}: in "${peer.projectPath}" restore .cleo/project-id to ${row.projectId} ` +
219
+ `(\`git log -p -- .cleo/project-id\`)${peer.registered ? `, run \`cleo nexus unregister ${peer.projectId}\`` : ''}, ` +
220
+ 'then run `cleo doctor project-identity --resolve` there';
221
+ return `Owner decision — CLEO never merges ids. ${keepPeer}; or ${keepRow}; or keep both: a fork is a distinct project.`;
222
+ }
223
+ /** Why a scanned path that declares a row's id is not a rebind target, or `null`. */
224
+ function ineligibleTarget(target, mine, cleoHome) {
225
+ if (isTrashPath(target.path))
226
+ return 'it is in the trash';
227
+ if (isLexicallyInside(cleoHome, target.path))
228
+ return 'it is inside the CLEO home (a worktree)';
229
+ if (readValidProjectTombstone(target.path))
230
+ return 'it holds a valid reroot tombstone (the project was rerooted away from it)';
231
+ // #1606 reroot geometry: the location was demoted to missing while a
232
+ // location strictly inside it became the project's home.
233
+ const here = mine.find((l) => l.path === target.path);
234
+ if (here?.state === 'missing' &&
235
+ mine.some((l) => l.path !== target.path && isStrictlyInsideDir(target.path, l.path)))
236
+ return 'the project was rerooted away from it (its location is recorded missing)';
237
+ return null;
238
+ }
239
+ /**
240
+ * Classify one row whose registered path provably no longer holds it.
241
+ *
242
+ * A row is rebound only to a path whose untracked checkout NONCE equals one
243
+ * recorded for the id (T12470 · #1606): the committed id alone is forgeable
244
+ * (a clone, a copied `.cleo/project-id`), so an id-only match is reported
245
+ * with the explicit `project-identity --resolve` remedy and never applied.
246
+ */
247
+ async function classifyGone(row, ctx) {
248
+ const base = { projectId: row.projectId, name: row.name, projectPath: row.projectPath };
249
+ const recordedNonces = new Set(ctx.mine.map((l) => l.checkoutNonce).filter((n) => n !== null));
250
+ const elsewhere = (ctx.byId.get(row.projectId) ?? []).filter((p) => p.path !== row.projectPath);
251
+ const excluded = elsewhere
252
+ .map((p) => ({ p, reason: ineligibleTarget(p, ctx.mine, ctx.cleoHome) }))
253
+ .filter((x) => x.reason !== null);
254
+ const eligible = elsewhere.filter((p) => !excluded.some((x) => x.p === p));
255
+ const proven = eligible.filter((p) => p.nonce !== null && recordedNonces.has(p.nonce));
256
+ const foundAt = elsewhere.map((p) => p.path);
257
+ const target = proven.length === 1 ? proven[0] : undefined;
258
+ if (target) {
259
+ return {
260
+ finding: {
261
+ ...base,
262
+ kind: 'moved',
263
+ message: `${row.projectId} is no longer at ${row.projectPath}; ${target.path} declares it and its checkout nonce matches (a real move).`,
264
+ remedy: `${DOCTOR_COMMAND} --apply (rebinds the row to ${target.path}; permissions stay with the row)`,
265
+ applicable: true,
266
+ foundAt,
267
+ proof: 'nonce',
268
+ },
269
+ rebindTo: { path: target.path, nonce: target.nonce },
270
+ };
271
+ }
272
+ if (proven.length > 1) {
273
+ return {
274
+ finding: {
275
+ ...base,
276
+ kind: 'ambiguous',
277
+ message: `${row.projectId} is no longer at ${row.projectPath}; ${proven.length} paths carry its checkout nonce (copies of one checkout).`,
278
+ remedy: `cd into the checkout to keep and run \`cleo doctor project-identity --resolve\` (candidates: ${proven.map((p) => `"${p.path}"`).join(', ')})`,
279
+ applicable: false,
280
+ foundAt,
281
+ proof: 'nonce',
282
+ },
283
+ };
284
+ }
285
+ if (elsewhere.length > 0) {
286
+ const why = excluded.map((x) => `${x.p.path}: ${x.reason}`).join('; ');
287
+ const candidates = eligible.map((p) => `"${p.path}"`).join(', ');
288
+ return {
289
+ finding: {
290
+ ...base,
291
+ kind: 'moved',
292
+ message: `${row.projectId} is no longer at ${row.projectPath}. ` +
293
+ (eligible.length > 0
294
+ ? `${candidates} declare${eligible.length === 1 ? 's' : ''} the id, but no checkout nonce proves a move (a clone or a copied .cleo/project-id declares it too).`
295
+ : 'Every path that declares the id is excluded.') +
296
+ (why ? ` Not a target — ${why}.` : ''),
297
+ remedy: eligible.length > 0
298
+ ? `If it IS the project, confirm explicitly: cd into it and run \`cleo doctor project-identity --resolve\` (${candidates}). Never applied automatically.`
299
+ : `Inspect the paths listed; if the project left this device, the row can stay (\`cleo nexus unregister ${row.projectId}\` only when it is gone for good).`,
300
+ applicable: false,
301
+ foundAt,
302
+ proof: 'id-only',
303
+ },
304
+ };
305
+ }
306
+ const split = await splitPeers(row, ctx);
307
+ const localState = ctx.mine.find((l) => l.path === row.projectPath)?.state ?? null;
308
+ const markMissing = ctx.gone !== 'sentinel' && localState !== 'missing';
309
+ const byEvidence = split.filter((peer) => peer.matchedBy !== 'name');
310
+ const peer = byEvidence[0];
311
+ if (peer) {
312
+ return {
313
+ finding: {
314
+ ...base,
315
+ kind: 'split',
316
+ message: `Split identity: ${row.projectId} is gone from ${row.projectPath}, and ${peer.projectPath} holds the same repository (${peer.matchedBy === 'remote' ? 'git remote' : 'root commit'}) under ${peer.projectId} (a re-minted id).`,
317
+ remedy: splitRemedy(row, peer),
318
+ applicable: false,
319
+ splitWith: byEvidence,
320
+ },
321
+ };
322
+ }
323
+ const named = split[0];
324
+ if (named) {
325
+ return {
326
+ finding: {
327
+ ...base,
328
+ kind: 'possible-split',
329
+ message: `${row.projectId} is gone from ${row.projectPath}; ${named.projectPath} has the same name under ${named.projectId}, but no repository evidence ties them — it may be an unrelated project.`,
330
+ remedy: `Inspect only — compare \`cleo nexus show ${row.projectId}\` with "${named.projectPath}" (its git log and .cleo/project-id). ` +
331
+ "Never change the other project's id or registry row on a name match alone; " +
332
+ 'keep both if they differ: a fork is a distinct project.' +
333
+ (markMissing
334
+ ? ` \`${DOCTOR_COMMAND} --apply\` records ${row.projectId}'s location as missing and keeps its row.`
335
+ : ''),
336
+ applicable: markMissing,
337
+ splitWith: split,
338
+ },
339
+ markMissing,
340
+ };
341
+ }
342
+ if (ctx.gone === 'path' && isTempRegistryPath(row.projectPath, ctx.cleoHome)) {
343
+ return {
344
+ finding: {
345
+ ...base,
346
+ kind: 'temp',
347
+ message: `${row.projectPath} is a temp directory that no longer exists (a test fixture or scratch project).`,
348
+ remedy: 'cleo nexus projects clean --include-temp --dry-run then without --dry-run',
349
+ applicable: false,
350
+ },
351
+ };
352
+ }
353
+ const where = ctx.gone === 'sentinel'
354
+ ? `${row.projectId} has no live location on this device`
355
+ : ctx.gone === 'path'
356
+ ? `${row.projectPath} no longer exists`
357
+ : ctx.gone === 'cleo'
358
+ ? `${row.projectPath} exists but has no .cleo/ (the project was moved out of it)`
359
+ : `${row.projectPath}/.cleo now declares ${ctx.gone.declares}, not ${row.projectId}`;
360
+ return {
361
+ finding: {
362
+ ...base,
363
+ kind: 'missing',
364
+ message: `${where}, and no scanned path declares ${row.projectId}.`,
365
+ remedy: `If it moved, scan its new parent: \`${DOCTOR_COMMAND} --roots <dir>\`. ` +
366
+ `If it is gone for good: \`cleo nexus unregister ${row.projectId}\`.` +
367
+ (markMissing
368
+ ? ` \`${DOCTOR_COMMAND} --apply\` records the location as missing and keeps the row.`
369
+ : ''),
370
+ applicable: markMissing,
371
+ },
372
+ markMissing,
373
+ };
374
+ }
375
+ /**
376
+ * Scanned projects with the row's name under another id. A peer whose
377
+ * repository evidence (git remote or root commit, recorded for the row and
378
+ * read from the peer) matches is tagged by that evidence; otherwise `name`.
379
+ */
380
+ async function splitPeers(row, ctx) {
381
+ const name = isSupersededRegistryPath(row.projectPath) ? row.name : basename(row.projectPath);
382
+ const remotes = new Set(ctx.mine.map((l) => l.gitRemote).filter((v) => !!v));
383
+ const commits = new Set(ctx.mine.map((l) => l.gitRootCommit).filter((v) => !!v));
384
+ const peers = [];
385
+ for (const p of ctx.scanned) {
386
+ if (p.projectId === row.projectId || ctx.linked(p.projectId, row.projectId))
387
+ continue;
388
+ if (basename(p.path) !== name && basename(p.path) !== row.name)
389
+ continue;
390
+ const evidence = remotes.size > 0 || commits.size > 0 ? await ctx.evidenceOf(p.path) : null;
391
+ // A shared root commit with a DIFFERENT remote is a fork or a second
392
+ // clone of one template — a distinct project, never proof of a split.
393
+ const remotesDiffer = !!evidence?.gitRemote && remotes.size > 0 && !remotes.has(evidence.gitRemote);
394
+ const matchedBy = evidence?.gitRemote && remotes.has(evidence.gitRemote)
395
+ ? 'remote'
396
+ : evidence?.gitRootCommit && commits.has(evidence.gitRootCommit) && !remotesDiffer
397
+ ? 'root-commit'
398
+ : 'name';
399
+ peers.push({
400
+ projectId: p.projectId,
401
+ projectPath: p.path,
402
+ registered: ctx.registered.has(p.projectId),
403
+ matchedBy,
404
+ });
405
+ }
406
+ return peers;
407
+ }
408
+ /** Classify one row whose registered path still holds it. */
409
+ function classifyHeld(row, remotePeers, cleoHome) {
410
+ const base = { projectId: row.projectId, name: row.name, projectPath: row.projectPath };
411
+ if (isHomeOrRootPath(row.projectPath)) {
412
+ return {
413
+ finding: {
414
+ ...base,
415
+ kind: 'root',
416
+ message: `${row.projectPath} is the home directory or a filesystem root; a project there claims every directory below it.`,
417
+ remedy: `If a real project lives below it, run \`cleo project reroot <dir>\` from ${row.projectPath}; otherwise \`cleo nexus unregister ${row.projectId}\``,
418
+ applicable: false,
419
+ },
420
+ };
421
+ }
422
+ if (isTempRegistryPath(row.projectPath, cleoHome)) {
423
+ return {
424
+ finding: {
425
+ ...base,
426
+ kind: 'temp',
427
+ message: `${row.projectPath} is under a temp directory (a test fixture or scratch project in the persistent registry).`,
428
+ remedy: 'cleo nexus projects clean --include-temp --dry-run then without --dry-run',
429
+ applicable: false,
430
+ },
431
+ };
432
+ }
433
+ const peer = remotePeers[0];
434
+ if (peer) {
435
+ return {
436
+ finding: {
437
+ ...base,
438
+ kind: 'split',
439
+ message: `Split identity: ${row.projectId} at ${row.projectPath} and ${peer.projectId} at ${peer.projectPath} share a git remote.`,
440
+ remedy: splitRemedy(row, peer),
441
+ applicable: false,
442
+ splitWith: remotePeers,
443
+ },
444
+ };
445
+ }
446
+ return {
447
+ finding: {
448
+ ...base,
449
+ kind: 'ok',
450
+ message: 'The registered path holds the project.',
451
+ remedy: null,
452
+ applicable: false,
453
+ },
454
+ };
455
+ }
456
+ /** Gather registry state, probe, scan and classify. Read-only. */
457
+ async function inspect(opts) {
458
+ const cleoHome = opts.cleoHome ?? getCleoHome();
459
+ const budget = scanBudget(opts);
460
+ const { db, storePath, projectRegistry, projectLocations, projectIdAliases } = await openRegistry(cleoHome);
461
+ const rows = db
462
+ .select({
463
+ projectId: projectRegistry.projectId,
464
+ projectPath: projectRegistry.projectPath,
465
+ name: projectRegistry.name,
466
+ })
467
+ .from(projectRegistry)
468
+ .all();
469
+ const locations = db
470
+ .select({
471
+ projectId: projectLocations.projectId,
472
+ deviceId: projectLocations.deviceId,
473
+ path: projectLocations.path,
474
+ state: projectLocations.state,
475
+ checkoutNonce: projectLocations.checkoutNonce,
476
+ gitRemote: projectLocations.gitRemote,
477
+ gitRootCommit: projectLocations.gitRootCommit,
478
+ })
479
+ .from(projectLocations)
480
+ .all();
481
+ const aliases = db
482
+ .select({ legacyId: projectIdAliases.legacyId, canonicalId: projectIdAliases.canonicalId })
483
+ .from(projectIdAliases)
484
+ .all();
485
+ const deviceId = currentDeviceId();
486
+ const local = (l) => l.deviceId === deviceId || l.deviceId === LOCAL_DEVICE_SENTINEL;
487
+ const aliasPairs = new Set(aliases.map((a) => `${a.legacyId}\u0000${a.canonicalId}`));
488
+ const linked = (a, b) => aliasPairs.has(`${a}\u0000${b}`) || aliasPairs.has(`${b}\u0000${a}`);
489
+ const registered = new Map(rows.map((row) => [row.projectId, row]));
490
+ const localById = new Map();
491
+ for (const l of locations) {
492
+ if (!local(l))
493
+ continue;
494
+ const list = localById.get(l.projectId) ?? [];
495
+ list.push(l);
496
+ localById.set(l.projectId, list);
497
+ }
498
+ const roots = scanRootsFor(rows.map((row) => row.projectPath), opts.roots ?? []);
499
+ const scan = await scanRoots(roots, budget);
500
+ const byId = new Map();
501
+ for (const project of scan.projects) {
502
+ const list = byId.get(project.projectId) ?? [];
503
+ list.push(project);
504
+ byId.set(project.projectId, list);
505
+ }
506
+ // Registered rows sharing one git remote on this device are one project
507
+ // under two ids — unless one is nested inside another registered project
508
+ // (a fixture project inside a repo shares the repo's remote).
509
+ const realPaths = rows.filter((r) => !isSupersededRegistryPath(r.projectPath));
510
+ const nested = new Set(realPaths
511
+ .filter((r) => realPaths.some((o) => o.projectId !== r.projectId && isLexicallyInside(o.projectPath, r.projectPath)))
512
+ .map((r) => r.projectId));
513
+ const remoteOf = new Map();
514
+ for (const l of locations) {
515
+ const row = registered.get(l.projectId);
516
+ if (l.gitRemote && row && l.path === row.projectPath && local(l) && !nested.has(l.projectId))
517
+ remoteOf.set(l.projectId, l.gitRemote);
518
+ }
519
+ const holdings = await runWithConcurrency(rows, budget.concurrency, async (row) => {
520
+ if (isSupersededRegistryPath(row.projectPath))
521
+ return 'no';
522
+ const here = locations.filter((l) => l.projectId === row.projectId && l.path === row.projectPath);
523
+ if (here.length > 0 && !here.some(local))
524
+ return 'other-device';
525
+ return probeProjectHolding(row.projectPath, row.projectId, budget.timeoutMs);
526
+ });
527
+ const evidence = new Map();
528
+ const evidenceOf = (path) => {
529
+ const cached = evidence.get(path);
530
+ if (cached)
531
+ return cached;
532
+ const pending = withinBudget(collectCheckoutEvidence(path).catch(() => null), budget.timeoutMs).then((settled) => (settled === 'timeout' ? null : settled));
533
+ evidence.set(path, pending);
534
+ return pending;
535
+ };
536
+ const planned = await runWithConcurrency(rows, budget.concurrency, async (row, index) => {
537
+ const holding = holdings[index];
538
+ const base = { projectId: row.projectId, name: row.name, projectPath: row.projectPath };
539
+ if (holding === 'other-device') {
540
+ return {
541
+ finding: {
542
+ ...base,
543
+ kind: 'other-device',
544
+ message: `${row.projectPath} was recorded on another device; it is not probed here.`,
545
+ remedy: null,
546
+ applicable: false,
547
+ },
548
+ };
549
+ }
550
+ if (holding === 'unknown') {
551
+ return {
552
+ finding: {
553
+ ...base,
554
+ kind: 'unreadable',
555
+ message: `${row.projectPath}/.cleo could not be read (permission denied or timed out). Only a missing path proves a project gone, so the row is kept as is.`,
556
+ remedy: `Check access to "${row.projectPath}" (\`ls -ld "${row.projectPath}/.cleo"\`), then re-run \`${DOCTOR_COMMAND}\`; or \`cleo nexus unregister ${row.projectId}\` if you no longer need it`,
557
+ applicable: false,
558
+ },
559
+ };
560
+ }
561
+ if (holding === 'yes') {
562
+ const remote = remoteOf.get(row.projectId);
563
+ const peers = remote
564
+ ? rows
565
+ .filter((other) => other.projectId !== row.projectId &&
566
+ remoteOf.get(other.projectId) === remote &&
567
+ !linked(other.projectId, row.projectId))
568
+ .map((other) => ({
569
+ projectId: other.projectId,
570
+ projectPath: other.projectPath,
571
+ registered: true,
572
+ matchedBy: 'remote',
573
+ }))
574
+ : [];
575
+ return classifyHeld(row, peers, cleoHome);
576
+ }
577
+ return classifyGone(row, {
578
+ byId,
579
+ scanned: scan.projects,
580
+ registered,
581
+ linked,
582
+ mine: localById.get(row.projectId) ?? [],
583
+ gone: goneShape(row),
584
+ cleoHome,
585
+ evidenceOf,
586
+ });
587
+ });
588
+ // T12589: a truncated legacy key several projects claim names none of them.
589
+ const recorded = rows.map((row) => ({ projectId: row.projectId, path: row.projectPath }));
590
+ const ambiguousAliases = aliases.flatMap((alias) => {
591
+ const claimants = legacyAliasClaimants(alias.legacyId, alias.canonicalId, recorded);
592
+ if (claimants.length < 2)
593
+ return [];
594
+ return [
595
+ {
596
+ legacyId: alias.legacyId,
597
+ canonicalId: alias.canonicalId,
598
+ claimants,
599
+ message: `Alias ${alias.legacyId} is claimed by ${claimants.length} projects (${claimants.join(', ')}); it resolves to none of them.`,
600
+ remedy: `${DOCTOR_COMMAND} --apply`,
601
+ },
602
+ ];
603
+ });
604
+ const counts = Object.fromEntries(FINDING_KINDS.map((kind) => [kind, 0]));
605
+ for (const p of planned)
606
+ counts[p.finding.kind]++;
607
+ return {
608
+ report: {
609
+ dryRun: true,
610
+ storePath,
611
+ roots,
612
+ scan: {
613
+ projectsFound: scan.projects.length,
614
+ timedOut: scan.timedOut,
615
+ unreadable: scan.unreadable,
616
+ },
617
+ counts,
618
+ findings: planned.filter((p) => p.finding.kind !== 'ok').map((p) => p.finding),
619
+ ambiguousAliases,
620
+ },
621
+ planned,
622
+ };
623
+ }
624
+ /**
625
+ * What a registered path that provably no longer holds its project holds
626
+ * now. Read only after the bounded probe answered `no`, so these reads hit a
627
+ * directory that already responded.
628
+ */
629
+ function goneShape(row) {
630
+ if (isSupersededRegistryPath(row.projectPath))
631
+ return 'sentinel';
632
+ const declared = readDeclaredProjectIdentity(row.projectPath);
633
+ if (declared && declared.projectId !== row.projectId)
634
+ return { declares: declared.projectId };
635
+ return existsSync(join(row.projectPath, '.cleo')) || existsSync(row.projectPath)
636
+ ? 'cleo'
637
+ : 'path';
638
+ }
639
+ /** Resolve the scan budget from options. */
640
+ function scanBudget(opts) {
641
+ return {
642
+ maxDepth: Math.max(0, Math.min(opts.maxDepth ?? DEFAULT_MAX_DEPTH, 20)),
643
+ concurrency: Math.max(1, opts.concurrency ?? DEFAULT_CONCURRENCY),
644
+ timeoutMs: Math.max(1, opts.timeoutMs ?? DEFAULT_TIMEOUT_MS),
645
+ };
646
+ }
647
+ /**
648
+ * Classify every registry row, read-only.
649
+ *
650
+ * @param opts - Extra scan roots, depth, concurrency, per-probe budget, registry home.
651
+ * @returns Counts per kind and every non-`ok` row with its exact remedy.
652
+ *
653
+ * @example
654
+ * ```ts
655
+ * const report = await inspectProjectRegistry({ roots: ['/home/me/src'] });
656
+ * for (const f of report.findings) console.log(f.kind, f.projectPath, f.remedy);
657
+ * ```
658
+ */
659
+ export async function inspectProjectRegistry(opts = {}) {
660
+ return (await inspect(opts)).report;
661
+ }
662
+ /** Stable text of a row, independent of key order. */
663
+ function rowKey(row) {
664
+ return JSON.stringify(Object.entries(row).sort(([a], [b]) => a.localeCompare(b)));
665
+ }
666
+ /** Sort image rows so two images compare by content. */
667
+ function normalizeImage(image) {
668
+ const sort = (rows) => [...rows].sort((a, b) => rowKey(a).localeCompare(rowKey(b)));
669
+ return {
670
+ registry: sort(image.registry),
671
+ locations: sort(image.locations),
672
+ paths: sort(image.paths),
673
+ ...(image.aliases ? { aliases: sort(image.aliases) } : {}),
674
+ };
675
+ }
676
+ /** Read every row of the three tables that names one of `ids` or `paths`. */
677
+ function captureImage(db, schema, ids, paths, aliasKeys) {
678
+ const { projectRegistry, projectLocations, projectPaths, projectIdAliases } = schema;
679
+ const aliasRows = [];
680
+ for (const slice of chunks(aliasKeys ?? []))
681
+ for (const row of db
682
+ .select()
683
+ .from(projectIdAliases)
684
+ .where(inArray(projectIdAliases.legacyId, slice))
685
+ .all())
686
+ aliasRows.push({ ...row });
687
+ const registry = new Map();
688
+ const locations = new Map();
689
+ const pathRows = new Map();
690
+ for (const idSlice of chunks(ids.length > 0 ? ids : [''])) {
691
+ for (const pathSlice of chunks(paths.length > 0 ? paths : [''])) {
692
+ for (const row of db
693
+ .select()
694
+ .from(projectRegistry)
695
+ .where(or(inArray(projectRegistry.projectId, idSlice), inArray(projectRegistry.projectPath, pathSlice)))
696
+ .all())
697
+ registry.set(row.projectId, { ...row });
698
+ for (const row of db
699
+ .select()
700
+ .from(projectLocations)
701
+ .where(or(inArray(projectLocations.projectId, idSlice), inArray(projectLocations.path, pathSlice)))
702
+ .all())
703
+ locations.set(`${row.projectId}\u0000${row.deviceId}\u0000${row.path}`, { ...row });
704
+ for (const row of db
705
+ .select()
706
+ .from(projectPaths)
707
+ .where(or(inArray(projectPaths.projectId, idSlice), inArray(projectPaths.projectPath, pathSlice)))
708
+ .all())
709
+ pathRows.set(row.projectPath, { ...row });
710
+ }
711
+ }
712
+ return normalizeImage({
713
+ registry: [...registry.values()],
714
+ locations: [...locations.values()],
715
+ paths: [...pathRows.values()],
716
+ ...(aliasKeys ? { aliases: aliasRows } : {}),
717
+ });
718
+ }
719
+ /** Delete every row of the three tables in scope. */
720
+ function deleteScope(db, schema, ids, paths, aliasKeys = []) {
721
+ const { projectRegistry, projectLocations, projectPaths, projectIdAliases } = schema;
722
+ for (const slice of chunks(aliasKeys))
723
+ db.delete(projectIdAliases).where(inArray(projectIdAliases.legacyId, slice)).run();
724
+ for (const idSlice of chunks(ids.length > 0 ? ids : [''])) {
725
+ for (const pathSlice of chunks(paths.length > 0 ? paths : [''])) {
726
+ db.delete(projectRegistry)
727
+ .where(or(inArray(projectRegistry.projectId, idSlice), inArray(projectRegistry.projectPath, pathSlice)))
728
+ .run();
729
+ db.delete(projectLocations)
730
+ .where(or(inArray(projectLocations.projectId, idSlice), inArray(projectLocations.path, pathSlice)))
731
+ .run();
732
+ db.delete(projectPaths)
733
+ .where(or(inArray(projectPaths.projectId, idSlice), inArray(projectPaths.projectPath, pathSlice)))
734
+ .run();
735
+ }
736
+ }
737
+ }
738
+ /**
739
+ * The rows a repair can change, fixed BEFORE it runs so the before and after
740
+ * images cover the same rows: every touched id, every touched path, and the id
741
+ * of any other project registered at a touched path (a rebind re-homes that
742
+ * row). A rollback deletes exactly this scope and re-inserts the before image,
743
+ * so a row outside it is never touched and a row inside it is always restored.
744
+ */
745
+ function repairScope(db, schema, touchedIds, touchedPaths) {
746
+ const ids = new Set(touchedIds);
747
+ for (const slice of chunks([...new Set(touchedPaths)]))
748
+ for (const row of db
749
+ .select({ projectId: schema.projectRegistry.projectId })
750
+ .from(schema.projectRegistry)
751
+ .where(inArray(schema.projectRegistry.projectPath, slice))
752
+ .all())
753
+ ids.add(row.projectId);
754
+ // Every registry writer re-keys migration-backfilled (`local`) location rows
755
+ // to this device first (adoptLocalDeviceRows). Those rows belong to ANY
756
+ // project, so their ids join the scope: the before image keeps them as they
757
+ // were and a rollback restores them exactly.
758
+ for (const row of db
759
+ .select({ projectId: schema.projectLocations.projectId })
760
+ .from(schema.projectLocations)
761
+ .where(eq(schema.projectLocations.deviceId, LOCAL_DEVICE_SENTINEL))
762
+ .all())
763
+ ids.add(row.projectId);
764
+ return { ids: [...ids].sort(), paths: [...new Set(touchedPaths)].sort() };
765
+ }
766
+ /**
767
+ * Probe, before the write transaction, every local location the writers
768
+ * would otherwise probe synchronously inside it (T12471): the live locations
769
+ * of each id in scope. Keyed by {@link holdingKey}.
770
+ */
771
+ async function preProbe(db, schema, ids, budget) {
772
+ const deviceId = currentDeviceId();
773
+ const targets = [];
774
+ for (const slice of chunks(ids))
775
+ for (const l of db
776
+ .select({
777
+ projectId: schema.projectLocations.projectId,
778
+ path: schema.projectLocations.path,
779
+ deviceId: schema.projectLocations.deviceId,
780
+ state: schema.projectLocations.state,
781
+ })
782
+ .from(schema.projectLocations)
783
+ .where(inArray(schema.projectLocations.projectId, slice))
784
+ .all())
785
+ if (l.state === 'live' && (l.deviceId === deviceId || l.deviceId === LOCAL_DEVICE_SENTINEL))
786
+ targets.push(l);
787
+ const holdings = await runWithConcurrency(targets, budget.concurrency, (t) => probeProjectHolding(t.path, t.projectId, budget.timeoutMs));
788
+ return new Map(targets.map((t, i) => [holdingKey(t.projectId, t.path), holdings[i] ?? 'unknown']));
789
+ }
790
+ /**
791
+ * Inspect, then apply the safe repairs: rebind every `moved` row to the path
792
+ * that declares its id, record every `missing` row's location as
793
+ * `missing`, and remove every alias row whose key several projects claim
794
+ * (T12589). One immediate transaction makes the changes and writes the
795
+ * receipt (before and after images of every touched row, alias rows
796
+ * included) to `nexus_audit_log`. No registry row is deleted, and nothing
797
+ * outside the registry is written. A row that changed since inspection is
798
+ * skipped.
799
+ *
800
+ * @param opts - Same options as {@link inspectProjectRegistry}.
801
+ * @returns The report, with `dryRun: false` and a receipt when anything applied.
802
+ *
803
+ * @example
804
+ * ```ts
805
+ * const result = await applyProjectRegistryRepair();
806
+ * if (result.receipt) console.log(result.receipt.rollback);
807
+ * ```
808
+ */
809
+ export async function applyProjectRegistryRepair(opts = {}) {
810
+ const { report, planned } = await inspect(opts);
811
+ // A row registered at another row's rebind target moves first, so the later
812
+ // rebind does not displace it to the `superseded:` sentinel.
813
+ const targets = new Set(planned.flatMap((p) => (p.rebindTo ? [p.rebindTo.path] : [])));
814
+ const work = planned
815
+ .filter((p) => p.rebindTo || p.markMissing)
816
+ .sort((a, b) => Number(targets.has(b.finding.projectPath)) - Number(targets.has(a.finding.projectPath)));
817
+ const aliasKeys = report.ambiguousAliases.map((a) => a.legacyId);
818
+ if (work.length === 0 && aliasKeys.length === 0)
819
+ return { ...report, dryRun: false };
820
+ const cleoHome = opts.cleoHome ?? getCleoHome();
821
+ const schema = await openRegistry(cleoHome);
822
+ const { db, storePath, projectRegistry, projectIdAliases, nexusAuditLog } = schema;
823
+ const receiptId = randomUUID();
824
+ const now = new Date().toISOString();
825
+ const touchedIds = work.map((p) => p.finding.projectId);
826
+ const touchedPaths = work.flatMap((p) => p.rebindTo ? [p.finding.projectPath, p.rebindTo.path] : [p.finding.projectPath]);
827
+ // Probe first, write after: no filesystem access inside the IMMEDIATE tx.
828
+ const probed = await preProbe(db, schema, repairScope(db, schema, touchedIds, touchedPaths).ids, scanBudget(opts));
829
+ const receipt = db.transaction((tx) => {
830
+ const rowsBefore = tx
831
+ .select({ id: projectRegistry.projectId })
832
+ .from(projectRegistry)
833
+ .all().length;
834
+ const scope = { ...repairScope(tx, schema, touchedIds, touchedPaths), aliases: aliasKeys };
835
+ const before = captureImage(tx, schema, scope.ids, scope.paths, scope.aliases);
836
+ const actions = [];
837
+ for (const p of work) {
838
+ const { projectId, projectPath } = p.finding;
839
+ const current = tx
840
+ .select({ projectPath: projectRegistry.projectPath })
841
+ .from(projectRegistry)
842
+ .where(eq(projectRegistry.projectId, projectId))
843
+ .get();
844
+ const unchanged = current?.projectPath === projectPath;
845
+ if (p.rebindTo) {
846
+ if (unchanged)
847
+ confirmProjectLocation(tx, {
848
+ projectId,
849
+ projectPath: p.rebindTo.path,
850
+ now,
851
+ checkoutNonce: p.rebindTo.nonce,
852
+ probed,
853
+ });
854
+ actions.push({
855
+ action: 'rebind',
856
+ projectId,
857
+ from: projectPath,
858
+ to: p.rebindTo.path,
859
+ outcome: unchanged ? 'applied' : 'skipped',
860
+ });
861
+ }
862
+ else {
863
+ if (unchanged)
864
+ demoteProjectLocation(tx, { projectId, projectPath, now }, 'missing');
865
+ actions.push({
866
+ action: 'mark-missing',
867
+ projectId,
868
+ from: projectPath,
869
+ outcome: unchanged ? 'applied' : 'skipped',
870
+ });
871
+ }
872
+ }
873
+ // T12589: remove each alias row whose key several projects still claim,
874
+ // re-checked inside the transaction (after any rebind above).
875
+ for (const ambiguous of report.ambiguousAliases) {
876
+ const row = tx
877
+ .select({ canonicalId: projectIdAliases.canonicalId })
878
+ .from(projectIdAliases)
879
+ .where(eq(projectIdAliases.legacyId, ambiguous.legacyId))
880
+ .get();
881
+ const still = row?.canonicalId === ambiguous.canonicalId &&
882
+ registryAliasClaimants(tx, ambiguous.legacyId, row.canonicalId).length > 1;
883
+ if (still)
884
+ tx.delete(projectIdAliases)
885
+ .where(eq(projectIdAliases.legacyId, ambiguous.legacyId))
886
+ .run();
887
+ const owner = tx
888
+ .select({ projectPath: projectRegistry.projectPath })
889
+ .from(projectRegistry)
890
+ .where(eq(projectRegistry.projectId, ambiguous.canonicalId))
891
+ .get();
892
+ actions.push({
893
+ action: 'drop-ambiguous-alias',
894
+ projectId: ambiguous.canonicalId,
895
+ from: owner?.projectPath ?? '',
896
+ alias: ambiguous.legacyId,
897
+ outcome: still ? 'applied' : 'skipped',
898
+ });
899
+ }
900
+ const after = captureImage(tx, schema, scope.ids, scope.paths, scope.aliases);
901
+ const result = {
902
+ receiptId,
903
+ storePath,
904
+ appliedAt: now,
905
+ actions,
906
+ registryRows: {
907
+ before: rowsBefore,
908
+ after: tx.select({ id: projectRegistry.projectId }).from(projectRegistry).all().length,
909
+ },
910
+ rollback: `${DOCTOR_COMMAND} --rollback ${receiptId}`,
911
+ };
912
+ const stored = {
913
+ receipt: result,
914
+ scope,
915
+ before,
916
+ after,
917
+ };
918
+ tx.insert(nexusAuditLog)
919
+ .values({
920
+ id: receiptId,
921
+ action: APPLY_ACTION,
922
+ domain: 'doctor',
923
+ operation: APPLY_ACTION,
924
+ success: 1,
925
+ detailsJson: JSON.stringify(stored),
926
+ })
927
+ .run();
928
+ return result;
929
+ }, { behavior: 'immediate' });
930
+ return { ...report, dryRun: false, receipt };
931
+ }
932
+ /** Read and parse a stored repair; `null` when the id is not an applied repair. */
933
+ function readStoredRepair(db, schema, receiptId) {
934
+ const row = db
935
+ .select({ action: schema.nexusAuditLog.action, detailsJson: schema.nexusAuditLog.detailsJson })
936
+ .from(schema.nexusAuditLog)
937
+ .where(eq(schema.nexusAuditLog.id, receiptId))
938
+ .get();
939
+ if (!row || row.action !== APPLY_ACTION || !row.detailsJson)
940
+ return null;
941
+ return JSON.parse(row.detailsJson);
942
+ }
943
+ /**
944
+ * Restore the rows an applied repair changed. Refused when the receipt is
945
+ * unknown, was already rolled back, or when any row in its scope changed
946
+ * since the repair (the current rows must equal the receipt's after image) —
947
+ * a later edit is never overwritten. The rollback writes its own receipt.
948
+ *
949
+ * @param receiptId - `receiptId` from {@link applyProjectRegistryRepair}.
950
+ * @param opts - Registry home.
951
+ * @returns Rows restored per table and the rollback's own receipt id.
952
+ * @throws RegistryRepairError (`E_NOT_FOUND`, `E_ROLLBACK_CONFLICT`).
953
+ *
954
+ * @example
955
+ * ```ts
956
+ * await rollbackProjectRegistryRepair(receipt.receiptId);
957
+ * ```
958
+ */
959
+ export async function rollbackProjectRegistryRepair(receiptId, opts = {}) {
960
+ const schema = await openRegistry(opts.cleoHome ?? getCleoHome());
961
+ const { db, projectRegistry, projectLocations, projectPaths, projectIdAliases, nexusAuditLog } = schema;
962
+ const stored = readStoredRepair(db, schema, receiptId);
963
+ if (!stored)
964
+ throw new RegistryRepairError('E_NOT_FOUND', `No applied \`${DOCTOR_COMMAND}\` repair has receipt ${receiptId}. List receipts in nexus_audit_log (action ${APPLY_ACTION}).`);
965
+ const rollbackReceiptId = randomUUID();
966
+ return db.transaction((tx) => {
967
+ const prior = tx
968
+ .select({ detailsJson: nexusAuditLog.detailsJson })
969
+ .from(nexusAuditLog)
970
+ .where(eq(nexusAuditLog.action, ROLLBACK_ACTION))
971
+ .all()
972
+ .some((row) => {
973
+ const details = JSON.parse(row.detailsJson ?? '{}');
974
+ return details.rolledBack === receiptId;
975
+ });
976
+ if (prior)
977
+ throw new RegistryRepairError('E_ROLLBACK_CONFLICT', `Receipt ${receiptId} was already rolled back.`);
978
+ const current = captureImage(tx, schema, stored.scope.ids, stored.scope.paths, stored.scope.aliases);
979
+ if (JSON.stringify(current) !== JSON.stringify(normalizeImage(stored.after)))
980
+ throw new RegistryRepairError('E_ROLLBACK_CONFLICT', `Rows in the scope of receipt ${receiptId} changed after the repair; rolling back would overwrite that change. ` +
981
+ `Inspect with \`${DOCTOR_COMMAND}\` and repair forward instead.`);
982
+ deleteScope(tx, schema, stored.scope.ids, stored.scope.paths, stored.scope.aliases);
983
+ for (const row of stored.before.registry)
984
+ tx.insert(projectRegistry).values(row).run();
985
+ for (const row of stored.before.locations)
986
+ tx.insert(projectLocations).values(row).run();
987
+ for (const row of stored.before.paths)
988
+ tx.insert(projectPaths).values(row).run();
989
+ for (const row of stored.before.aliases ?? [])
990
+ tx.insert(projectIdAliases).values(row).run();
991
+ const restored = {
992
+ registry: stored.before.registry.length,
993
+ locations: stored.before.locations.length,
994
+ paths: stored.before.paths.length,
995
+ ...(stored.before.aliases ? { aliases: stored.before.aliases.length } : {}),
996
+ };
997
+ tx.insert(nexusAuditLog)
998
+ .values({
999
+ id: rollbackReceiptId,
1000
+ action: ROLLBACK_ACTION,
1001
+ domain: 'doctor',
1002
+ operation: ROLLBACK_ACTION,
1003
+ success: 1,
1004
+ detailsJson: JSON.stringify({ rolledBack: receiptId, restored, replaced: current }),
1005
+ })
1006
+ .run();
1007
+ return { receiptId, rollbackReceiptId, restored };
1008
+ }, { behavior: 'immediate' });
1009
+ }
1010
+ /**
1011
+ * For each id, the paths on this device other than `exclude` that provably
1012
+ * hold it now: recorded locations of the id that still declare it, plus
1013
+ * directories found by scanning `roots`. Used by `nexus projects clean` to
1014
+ * refuse deleting a row whose project merely moved (T12471).
1015
+ *
1016
+ * @param ids - Project ids to look for, with the registered path to exclude.
1017
+ * @param opts - Scan roots and budgets; roots default to the parents of the given paths.
1018
+ * @returns Map from id to the paths that declare it (ids found nowhere are absent).
1019
+ *
1020
+ * @example
1021
+ * ```ts
1022
+ * const found = await locateProjectsElsewhere([{ projectId, projectPath }]);
1023
+ * ```
1024
+ */
1025
+ export async function locateProjectsElsewhere(ids, opts = {}) {
1026
+ const out = new Map();
1027
+ if (ids.length === 0)
1028
+ return out;
1029
+ const budget = scanBudget(opts);
1030
+ const { db, projectRegistry, projectLocations } = await openRegistry(opts.cleoHome ?? getCleoHome());
1031
+ const wanted = new Map(ids.map((i) => [i.projectId, i.projectPath]));
1032
+ const add = (id, path) => {
1033
+ // A trashed checkout does not keep a row alive.
1034
+ if (wanted.get(id) === path || isTrashPath(path))
1035
+ return;
1036
+ const list = out.get(id) ?? [];
1037
+ if (!list.includes(path))
1038
+ list.push(path);
1039
+ out.set(id, list);
1040
+ };
1041
+ const deviceId = currentDeviceId();
1042
+ const recorded = [];
1043
+ for (const slice of chunks([...wanted.keys()]))
1044
+ for (const l of db
1045
+ .select({
1046
+ projectId: projectLocations.projectId,
1047
+ path: projectLocations.path,
1048
+ deviceId: projectLocations.deviceId,
1049
+ state: projectLocations.state,
1050
+ })
1051
+ .from(projectLocations)
1052
+ .where(inArray(projectLocations.projectId, slice))
1053
+ .all())
1054
+ if ((l.deviceId === deviceId || l.deviceId === LOCAL_DEVICE_SENTINEL) &&
1055
+ l.state !== 'superseded')
1056
+ recorded.push(l);
1057
+ const holds = await runWithConcurrency(recorded, budget.concurrency, (l) => wanted.get(l.projectId) === l.path
1058
+ ? Promise.resolve('no')
1059
+ : probeProjectHolding(l.path, l.projectId, budget.timeoutMs));
1060
+ recorded.forEach((l, i) => {
1061
+ if (holds[i] === 'yes' && readDeclaredProjectIdentity(l.path)?.projectId === l.projectId)
1062
+ add(l.projectId, l.path);
1063
+ });
1064
+ const allPaths = db
1065
+ .select({ projectPath: projectRegistry.projectPath })
1066
+ .from(projectRegistry)
1067
+ .all();
1068
+ const roots = scanRootsFor(allPaths.map((r) => r.projectPath), opts.roots ?? []);
1069
+ const scan = await scanRoots(roots, budget);
1070
+ for (const project of scan.projects)
1071
+ if (wanted.has(project.projectId))
1072
+ add(project.projectId, project.path);
1073
+ return out;
1074
+ }
1075
+ //# sourceMappingURL=projects.js.map