@cleocode/core 2026.9.23 → 2026.9.24

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 (651) hide show
  1. package/dist/admin/import-tasks.d.ts.map +1 -1
  2. package/dist/admin/import-tasks.js +35 -6
  3. package/dist/admin/import-tasks.js.map +1 -1
  4. package/dist/admin/import.d.ts.map +1 -1
  5. package/dist/admin/import.js +25 -6
  6. package/dist/admin/import.js.map +1 -1
  7. package/dist/build-provenance.json +2 -2
  8. package/dist/cloud/http.d.ts +8 -0
  9. package/dist/cloud/http.d.ts.map +1 -1
  10. package/dist/cloud/http.js +8 -1
  11. package/dist/cloud/http.js.map +1 -1
  12. package/dist/cloud/index.d.ts +6 -1
  13. package/dist/cloud/index.d.ts.map +1 -1
  14. package/dist/cloud/index.js +6 -1
  15. package/dist/cloud/index.js.map +1 -1
  16. package/dist/cloud/nexus-auth.d.ts +169 -0
  17. package/dist/cloud/nexus-auth.d.ts.map +1 -0
  18. package/dist/cloud/nexus-auth.js +372 -0
  19. package/dist/cloud/nexus-auth.js.map +1 -0
  20. package/dist/cloud/nexus-credentials.d.ts +188 -0
  21. package/dist/cloud/nexus-credentials.d.ts.map +1 -0
  22. package/dist/cloud/nexus-credentials.js +323 -0
  23. package/dist/cloud/nexus-credentials.js.map +1 -0
  24. package/dist/cloud/nexus-link.d.ts +85 -0
  25. package/dist/cloud/nexus-link.d.ts.map +1 -0
  26. package/dist/cloud/nexus-link.js +226 -0
  27. package/dist/cloud/nexus-link.js.map +1 -0
  28. package/dist/cloud/presence.d.ts +88 -0
  29. package/dist/cloud/presence.d.ts.map +1 -0
  30. package/dist/cloud/presence.js +137 -0
  31. package/dist/cloud/presence.js.map +1 -0
  32. package/dist/cloud/projects.d.ts +3 -1
  33. package/dist/cloud/projects.d.ts.map +1 -1
  34. package/dist/cloud/projects.js +4 -2
  35. package/dist/cloud/projects.js.map +1 -1
  36. package/dist/crypto/credentials.d.ts +14 -4
  37. package/dist/crypto/credentials.d.ts.map +1 -1
  38. package/dist/crypto/credentials.js +15 -1
  39. package/dist/crypto/credentials.js.map +1 -1
  40. package/dist/decide/bench/dataset.d.ts +125 -0
  41. package/dist/decide/bench/dataset.d.ts.map +1 -0
  42. package/dist/decide/bench/dataset.js +457 -0
  43. package/dist/decide/bench/dataset.js.map +1 -0
  44. package/dist/decide/bench/index.d.ts +23 -0
  45. package/dist/decide/bench/index.d.ts.map +1 -0
  46. package/dist/decide/bench/index.js +23 -0
  47. package/dist/decide/bench/index.js.map +1 -0
  48. package/dist/decide/bench/metrics.d.ts +55 -0
  49. package/dist/decide/bench/metrics.d.ts.map +1 -0
  50. package/dist/decide/bench/metrics.js +100 -0
  51. package/dist/decide/bench/metrics.js.map +1 -0
  52. package/dist/decide/bench/operation.d.ts +119 -0
  53. package/dist/decide/bench/operation.d.ts.map +1 -0
  54. package/dist/decide/bench/operation.js +171 -0
  55. package/dist/decide/bench/operation.js.map +1 -0
  56. package/dist/decide/bench/profiles.d.ts +74 -0
  57. package/dist/decide/bench/profiles.d.ts.map +1 -0
  58. package/dist/decide/bench/profiles.js +131 -0
  59. package/dist/decide/bench/profiles.js.map +1 -0
  60. package/dist/decide/bench/questions.d.ts +40 -0
  61. package/dist/decide/bench/questions.d.ts.map +1 -0
  62. package/dist/decide/bench/questions.js +107 -0
  63. package/dist/decide/bench/questions.js.map +1 -0
  64. package/dist/decide/bench/report.d.ts +21 -0
  65. package/dist/decide/bench/report.d.ts.map +1 -0
  66. package/dist/decide/bench/report.js +120 -0
  67. package/dist/decide/bench/report.js.map +1 -0
  68. package/dist/decide/bench/runner.d.ts +203 -0
  69. package/dist/decide/bench/runner.d.ts.map +1 -0
  70. package/dist/decide/bench/runner.js +335 -0
  71. package/dist/decide/bench/runner.js.map +1 -0
  72. package/dist/decide/bench/source.d.ts +21 -0
  73. package/dist/decide/bench/source.d.ts.map +1 -0
  74. package/dist/decide/bench/source.js +67 -0
  75. package/dist/decide/bench/source.js.map +1 -0
  76. package/dist/decide/bench/spot-check.d.ts +117 -0
  77. package/dist/decide/bench/spot-check.d.ts.map +1 -0
  78. package/dist/decide/bench/spot-check.js +221 -0
  79. package/dist/decide/bench/spot-check.js.map +1 -0
  80. package/dist/decide/bench/stats.d.ts +57 -0
  81. package/dist/decide/bench/stats.d.ts.map +1 -0
  82. package/dist/decide/bench/stats.js +82 -0
  83. package/dist/decide/bench/stats.js.map +1 -0
  84. package/dist/decide/bench/types.d.ts +216 -0
  85. package/dist/decide/bench/types.d.ts.map +1 -0
  86. package/dist/decide/bench/types.js +104 -0
  87. package/dist/decide/bench/types.js.map +1 -0
  88. package/dist/decide/client.d.ts +35 -1
  89. package/dist/decide/client.d.ts.map +1 -1
  90. package/dist/decide/client.js +124 -30
  91. package/dist/decide/client.js.map +1 -1
  92. package/dist/decide/credentials.d.ts +177 -17
  93. package/dist/decide/credentials.d.ts.map +1 -1
  94. package/dist/decide/credentials.js +505 -41
  95. package/dist/decide/credentials.js.map +1 -1
  96. package/dist/decide/index.d.ts +8 -8
  97. package/dist/decide/index.d.ts.map +1 -1
  98. package/dist/decide/index.js +8 -8
  99. package/dist/decide/index.js.map +1 -1
  100. package/dist/decide/jev-wire.d.ts +25 -19
  101. package/dist/decide/jev-wire.d.ts.map +1 -1
  102. package/dist/decide/jev-wire.js +56 -31
  103. package/dist/decide/jev-wire.js.map +1 -1
  104. package/dist/decide/operations.d.ts +63 -12
  105. package/dist/decide/operations.d.ts.map +1 -1
  106. package/dist/decide/operations.js +116 -58
  107. package/dist/decide/operations.js.map +1 -1
  108. package/dist/decide/provider-state.d.ts +85 -12
  109. package/dist/decide/provider-state.d.ts.map +1 -1
  110. package/dist/decide/provider-state.js +154 -21
  111. package/dist/decide/provider-state.js.map +1 -1
  112. package/dist/decide/provider.d.ts +5 -0
  113. package/dist/decide/provider.d.ts.map +1 -1
  114. package/dist/decide/provider.js.map +1 -1
  115. package/dist/decide/providers.d.ts +15 -2
  116. package/dist/decide/providers.d.ts.map +1 -1
  117. package/dist/decide/providers.js +22 -6
  118. package/dist/decide/providers.js.map +1 -1
  119. package/dist/decide/sites/registry.d.ts +22 -0
  120. package/dist/decide/sites/registry.d.ts.map +1 -1
  121. package/dist/decide/sites/registry.js +23 -0
  122. package/dist/decide/sites/registry.js.map +1 -1
  123. package/dist/decide/wizard.d.ts +12 -4
  124. package/dist/decide/wizard.d.ts.map +1 -1
  125. package/dist/decide/wizard.js +79 -26
  126. package/dist/decide/wizard.js.map +1 -1
  127. package/dist/dispatch/mvi-projection.d.ts +15 -2
  128. package/dist/dispatch/mvi-projection.d.ts.map +1 -1
  129. package/dist/dispatch/mvi-projection.js +73 -12
  130. package/dist/dispatch/mvi-projection.js.map +1 -1
  131. package/dist/docs/adr-allocator.d.ts +2 -2
  132. package/dist/docs/adr-allocator.d.ts.map +1 -1
  133. package/dist/docs/adr-allocator.js +17 -4
  134. package/dist/docs/adr-allocator.js.map +1 -1
  135. package/dist/docs/blob-consolidation.js +1 -1
  136. package/dist/docs/blob-consolidation.js.map +1 -1
  137. package/dist/docs/display-alias.d.ts +1 -1
  138. package/dist/docs/display-alias.d.ts.map +1 -1
  139. package/dist/docs/display-alias.js +7 -4
  140. package/dist/docs/display-alias.js.map +1 -1
  141. package/dist/docs/docs-inconsistency-detector.js +1 -1
  142. package/dist/docs/docs-inconsistency-detector.js.map +1 -1
  143. package/dist/docs/docs-update.d.ts.map +1 -1
  144. package/dist/docs/docs-update.js +6 -1
  145. package/dist/docs/docs-update.js.map +1 -1
  146. package/dist/docs/export-document.js +2928 -1053
  147. package/dist/docs/export-document.js.map +3 -3
  148. package/dist/docs/numbering.js +1 -1
  149. package/dist/docs/numbering.js.map +1 -1
  150. package/dist/docs/supersede.d.ts.map +1 -1
  151. package/dist/docs/supersede.js +6 -3
  152. package/dist/docs/supersede.js.map +1 -1
  153. package/dist/docs/version-ssot.js +1 -1
  154. package/dist/docs/version-ssot.js.map +1 -1
  155. package/dist/doctor/db-substrate.d.ts.map +1 -1
  156. package/dist/doctor/db-substrate.js +39 -24
  157. package/dist/doctor/db-substrate.js.map +1 -1
  158. package/dist/doctor/knowledge-summary.d.ts +10 -3
  159. package/dist/doctor/knowledge-summary.d.ts.map +1 -1
  160. package/dist/doctor/knowledge-summary.js +10 -3
  161. package/dist/doctor/knowledge-summary.js.map +1 -1
  162. package/dist/doctor/project-identity.d.ts +128 -37
  163. package/dist/doctor/project-identity.d.ts.map +1 -1
  164. package/dist/doctor/project-identity.js +476 -63
  165. package/dist/doctor/project-identity.js.map +1 -1
  166. package/dist/doctor/twin-collapse.d.ts +52 -2
  167. package/dist/doctor/twin-collapse.d.ts.map +1 -1
  168. package/dist/doctor/twin-collapse.js +108 -4
  169. package/dist/doctor/twin-collapse.js.map +1 -1
  170. package/dist/error-catalog.js +2 -2
  171. package/dist/error-catalog.js.map +1 -1
  172. package/dist/error-registry.js +1 -1
  173. package/dist/error-registry.js.map +1 -1
  174. package/dist/gateway-client/generated/types.gen.d.ts +22 -2
  175. package/dist/gateway-client/generated/types.gen.d.ts.map +1 -1
  176. package/dist/index.d.ts +1 -1
  177. package/dist/index.d.ts.map +1 -1
  178. package/dist/index.js +1 -1
  179. package/dist/index.js.map +1 -1
  180. package/dist/init.d.ts.map +1 -1
  181. package/dist/init.js +20 -11
  182. package/dist/init.js.map +1 -1
  183. package/dist/inject/index.d.ts.map +1 -1
  184. package/dist/inject/index.js +2 -3
  185. package/dist/inject/index.js.map +1 -1
  186. package/dist/internal.d.ts +5 -4
  187. package/dist/internal.d.ts.map +1 -1
  188. package/dist/internal.js +6 -4
  189. package/dist/internal.js.map +1 -1
  190. package/dist/llm/credential-remove-entry.d.ts +59 -0
  191. package/dist/llm/credential-remove-entry.d.ts.map +1 -0
  192. package/dist/llm/credential-remove-entry.js +101 -0
  193. package/dist/llm/credential-remove-entry.js.map +1 -0
  194. package/dist/llm/oauth/device-code.d.ts +26 -6
  195. package/dist/llm/oauth/device-code.d.ts.map +1 -1
  196. package/dist/llm/oauth/device-code.js +44 -12
  197. package/dist/llm/oauth/device-code.js.map +1 -1
  198. package/dist/llm/plugin-facade.js +3017 -1142
  199. package/dist/llm/plugin-facade.js.map +3 -3
  200. package/dist/memory/attention.d.ts.map +1 -1
  201. package/dist/memory/attention.js +5 -3
  202. package/dist/memory/attention.js.map +1 -1
  203. package/dist/memory/decisions.d.ts +48 -3
  204. package/dist/memory/decisions.d.ts.map +1 -1
  205. package/dist/memory/decisions.js +98 -14
  206. package/dist/memory/decisions.js.map +1 -1
  207. package/dist/memory/learnings.d.ts +6 -6
  208. package/dist/memory/patterns.d.ts +4 -4
  209. package/dist/migration/manual/T10165-backfill-adr-index.d.ts.map +1 -1
  210. package/dist/migration/manual/T10165-backfill-adr-index.js +5 -1
  211. package/dist/migration/manual/T10165-backfill-adr-index.js.map +1 -1
  212. package/dist/nexus/fleet-status.d.ts +83 -0
  213. package/dist/nexus/fleet-status.d.ts.map +1 -0
  214. package/dist/nexus/fleet-status.js +414 -0
  215. package/dist/nexus/fleet-status.js.map +1 -0
  216. package/dist/nexus/git-state.d.ts +15 -0
  217. package/dist/nexus/git-state.d.ts.map +1 -1
  218. package/dist/nexus/git-state.js +67 -2
  219. package/dist/nexus/git-state.js.map +1 -1
  220. package/dist/nexus/identity.d.ts.map +1 -1
  221. package/dist/nexus/identity.js +4 -0
  222. package/dist/nexus/identity.js.map +1 -1
  223. package/dist/nexus/index.d.ts +4 -1
  224. package/dist/nexus/index.d.ts.map +1 -1
  225. package/dist/nexus/index.js +7 -1
  226. package/dist/nexus/index.js.map +1 -1
  227. package/dist/nexus/migrate-json-to-sqlite.d.ts +3 -2
  228. package/dist/nexus/migrate-json-to-sqlite.d.ts.map +1 -1
  229. package/dist/nexus/migrate-json-to-sqlite.js +9 -17
  230. package/dist/nexus/migrate-json-to-sqlite.js.map +1 -1
  231. package/dist/nexus/ops.d.ts +1 -0
  232. package/dist/nexus/ops.d.ts.map +1 -1
  233. package/dist/nexus/project-activity.d.ts +104 -0
  234. package/dist/nexus/project-activity.d.ts.map +1 -0
  235. package/dist/nexus/project-activity.js +150 -0
  236. package/dist/nexus/project-activity.js.map +1 -0
  237. package/dist/nexus/query.d.ts +2 -2
  238. package/dist/nexus/query.d.ts.map +1 -1
  239. package/dist/nexus/query.js +4 -19
  240. package/dist/nexus/query.js.map +1 -1
  241. package/dist/nexus/registry-errors.d.ts +61 -0
  242. package/dist/nexus/registry-errors.d.ts.map +1 -0
  243. package/dist/nexus/registry-errors.js +85 -0
  244. package/dist/nexus/registry-errors.js.map +1 -0
  245. package/dist/nexus/registry.d.ts +30 -5
  246. package/dist/nexus/registry.d.ts.map +1 -1
  247. package/dist/nexus/registry.js +115 -70
  248. package/dist/nexus/registry.js.map +1 -1
  249. package/dist/orchestrate/pivot.d.ts +4 -0
  250. package/dist/orchestrate/pivot.d.ts.map +1 -1
  251. package/dist/orchestrate/pivot.js +75 -12
  252. package/dist/orchestrate/pivot.js.map +1 -1
  253. package/dist/orchestrate/query-ops.d.ts.map +1 -1
  254. package/dist/orchestrate/query-ops.js +29 -27
  255. package/dist/orchestrate/query-ops.js.map +1 -1
  256. package/dist/orchestrate/spawn-ops.d.ts.map +1 -1
  257. package/dist/orchestrate/spawn-ops.js +29 -3
  258. package/dist/orchestrate/spawn-ops.js.map +1 -1
  259. package/dist/orchestration/bootstrap.d.ts.map +1 -1
  260. package/dist/orchestration/bootstrap.js +20 -14
  261. package/dist/orchestration/bootstrap.js.map +1 -1
  262. package/dist/orchestration/index.d.ts +15 -1
  263. package/dist/orchestration/index.d.ts.map +1 -1
  264. package/dist/orchestration/index.js +25 -6
  265. package/dist/orchestration/index.js.map +1 -1
  266. package/dist/orchestration/lead-rollup.d.ts +7 -1
  267. package/dist/orchestration/lead-rollup.d.ts.map +1 -1
  268. package/dist/orchestration/lead-rollup.js +6 -3
  269. package/dist/orchestration/lead-rollup.js.map +1 -1
  270. package/dist/orchestration/spawn-prompt.js +1 -1
  271. package/dist/orchestration/spawn-prompt.js.map +1 -1
  272. package/dist/orchestration/waves.d.ts +21 -10
  273. package/dist/orchestration/waves.d.ts.map +1 -1
  274. package/dist/orchestration/waves.js +38 -35
  275. package/dist/orchestration/waves.js.map +1 -1
  276. package/dist/paths.d.ts +9 -2
  277. package/dist/paths.d.ts.map +1 -1
  278. package/dist/paths.js +30 -13
  279. package/dist/paths.js.map +1 -1
  280. package/dist/phases/index.d.ts +1 -1
  281. package/dist/phases/index.d.ts.map +1 -1
  282. package/dist/phases/index.js +3 -4
  283. package/dist/phases/index.js.map +1 -1
  284. package/dist/project-info.d.ts +40 -3
  285. package/dist/project-info.d.ts.map +1 -1
  286. package/dist/project-info.js +74 -6
  287. package/dist/project-info.js.map +1 -1
  288. package/dist/project-lifecycle.d.ts +28 -7
  289. package/dist/project-lifecycle.d.ts.map +1 -1
  290. package/dist/project-lifecycle.js +114 -47
  291. package/dist/project-lifecycle.js.map +1 -1
  292. package/dist/project-scope.d.ts +70 -3
  293. package/dist/project-scope.d.ts.map +1 -1
  294. package/dist/project-scope.js +154 -15
  295. package/dist/project-scope.js.map +1 -1
  296. package/dist/release/open.d.ts +12 -2
  297. package/dist/release/open.d.ts.map +1 -1
  298. package/dist/release/open.js +32 -3
  299. package/dist/release/open.js.map +1 -1
  300. package/dist/release/preflight-skip.d.ts +73 -0
  301. package/dist/release/preflight-skip.d.ts.map +1 -0
  302. package/dist/release/preflight-skip.js +254 -0
  303. package/dist/release/preflight-skip.js.map +1 -0
  304. package/dist/render/decide/index.d.ts +51 -0
  305. package/dist/render/decide/index.d.ts.map +1 -0
  306. package/dist/render/decide/index.js +289 -0
  307. package/dist/render/decide/index.js.map +1 -0
  308. package/dist/render/index.d.ts +1 -0
  309. package/dist/render/index.d.ts.map +1 -1
  310. package/dist/render/index.js +1 -0
  311. package/dist/render/index.js.map +1 -1
  312. package/dist/render/nexus/graph/index.d.ts +5 -0
  313. package/dist/render/nexus/graph/index.d.ts.map +1 -1
  314. package/dist/render/nexus/graph/index.js +53 -0
  315. package/dist/render/nexus/graph/index.js.map +1 -1
  316. package/dist/scaffold/ensure-config.d.ts +14 -6
  317. package/dist/scaffold/ensure-config.d.ts.map +1 -1
  318. package/dist/scaffold/ensure-config.js +39 -17
  319. package/dist/scaffold/ensure-config.js.map +1 -1
  320. package/dist/scaffold/project-identity.d.ts +115 -23
  321. package/dist/scaffold/project-identity.d.ts.map +1 -1
  322. package/dist/scaffold/project-identity.js +196 -44
  323. package/dist/scaffold/project-identity.js.map +1 -1
  324. package/dist/selfimprove/fix-gen.js +3027 -1152
  325. package/dist/selfimprove/fix-gen.js.map +3 -3
  326. package/dist/sentient/cross-project-hygiene.d.ts +12 -6
  327. package/dist/sentient/cross-project-hygiene.d.ts.map +1 -1
  328. package/dist/sentient/cross-project-hygiene.js +15 -10
  329. package/dist/sentient/cross-project-hygiene.js.map +1 -1
  330. package/dist/session/engine-ops.d.ts +13 -4
  331. package/dist/session/engine-ops.d.ts.map +1 -1
  332. package/dist/session/engine-ops.js +38 -45
  333. package/dist/session/engine-ops.js.map +1 -1
  334. package/dist/sessions/briefing.d.ts +7 -3
  335. package/dist/sessions/briefing.d.ts.map +1 -1
  336. package/dist/sessions/briefing.js +10 -10
  337. package/dist/sessions/briefing.js.map +1 -1
  338. package/dist/sessions/drift-watchdog.d.ts.map +1 -1
  339. package/dist/sessions/drift-watchdog.js +11 -19
  340. package/dist/sessions/drift-watchdog.js.map +1 -1
  341. package/dist/sessions/focus-state-store.d.ts +103 -26
  342. package/dist/sessions/focus-state-store.d.ts.map +1 -1
  343. package/dist/sessions/focus-state-store.js +166 -37
  344. package/dist/sessions/focus-state-store.js.map +1 -1
  345. package/dist/sessions/handoff.d.ts.map +1 -1
  346. package/dist/sessions/handoff.js +9 -18
  347. package/dist/sessions/handoff.js.map +1 -1
  348. package/dist/sessions/index.d.ts +4 -2
  349. package/dist/sessions/index.d.ts.map +1 -1
  350. package/dist/sessions/index.js +35 -6
  351. package/dist/sessions/index.js.map +1 -1
  352. package/dist/sessions/session-drift.d.ts.map +1 -1
  353. package/dist/sessions/session-drift.js +4 -2
  354. package/dist/sessions/session-drift.js.map +1 -1
  355. package/dist/sessions/session-view.d.ts +8 -1
  356. package/dist/sessions/session-view.d.ts.map +1 -1
  357. package/dist/sessions/session-view.js +9 -2
  358. package/dist/sessions/session-view.js.map +1 -1
  359. package/dist/setup/config-json-merge.d.ts.map +1 -1
  360. package/dist/setup/config-json-merge.js +1 -0
  361. package/dist/setup/config-json-merge.js.map +1 -1
  362. package/dist/setup/index.d.ts +4 -2
  363. package/dist/setup/index.d.ts.map +1 -1
  364. package/dist/setup/index.js +6 -2
  365. package/dist/setup/index.js.map +1 -1
  366. package/dist/setup/sections/nexus-account.d.ts +32 -0
  367. package/dist/setup/sections/nexus-account.d.ts.map +1 -0
  368. package/dist/setup/sections/nexus-account.js +64 -0
  369. package/dist/setup/sections/nexus-account.js.map +1 -0
  370. package/dist/setup/wizard.d.ts +1 -1
  371. package/dist/setup/wizard.d.ts.map +1 -1
  372. package/dist/setup/wizard.js.map +1 -1
  373. package/dist/skills/orchestrator/startup.d.ts.map +1 -1
  374. package/dist/skills/orchestrator/startup.js +18 -31
  375. package/dist/skills/orchestrator/startup.js.map +1 -1
  376. package/dist/snapshot/index.d.ts.map +1 -1
  377. package/dist/snapshot/index.js +60 -53
  378. package/dist/snapshot/index.js.map +1 -1
  379. package/dist/spawn/agent-identity.d.ts +33 -1
  380. package/dist/spawn/agent-identity.d.ts.map +1 -1
  381. package/dist/spawn/agent-identity.js +53 -1
  382. package/dist/spawn/agent-identity.js.map +1 -1
  383. package/dist/spawn/index.d.ts +1 -1
  384. package/dist/spawn/index.d.ts.map +1 -1
  385. package/dist/spawn/index.js +1 -1
  386. package/dist/spawn/index.js.map +1 -1
  387. package/dist/stats/index.d.ts.map +1 -1
  388. package/dist/stats/index.js +3 -4
  389. package/dist/stats/index.js.map +1 -1
  390. package/dist/status/index.d.ts +12 -3
  391. package/dist/status/index.d.ts.map +1 -1
  392. package/dist/status/index.js +47 -5
  393. package/dist/status/index.js.map +1 -1
  394. package/dist/store/attachment-repair.d.ts.map +1 -1
  395. package/dist/store/attachment-repair.js +42 -3
  396. package/dist/store/attachment-repair.js.map +1 -1
  397. package/dist/store/attachment-store.d.ts.map +1 -1
  398. package/dist/store/attachment-store.js +30 -46
  399. package/dist/store/attachment-store.js.map +1 -1
  400. package/dist/store/backup-recover-brain.d.ts +4 -0
  401. package/dist/store/backup-recover-brain.d.ts.map +1 -1
  402. package/dist/store/backup-recover-brain.js +2 -0
  403. package/dist/store/backup-recover-brain.js.map +1 -1
  404. package/dist/store/backup-recover.d.ts +9 -0
  405. package/dist/store/backup-recover.d.ts.map +1 -1
  406. package/dist/store/backup-recover.js +28 -1
  407. package/dist/store/backup-recover.js.map +1 -1
  408. package/dist/store/backup-sidecar.d.ts +30 -0
  409. package/dist/store/backup-sidecar.d.ts.map +1 -1
  410. package/dist/store/backup-sidecar.js +90 -2
  411. package/dist/store/backup-sidecar.js.map +1 -1
  412. package/dist/store/blob-keep.d.ts +135 -0
  413. package/dist/store/blob-keep.d.ts.map +1 -0
  414. package/dist/store/blob-keep.js +283 -0
  415. package/dist/store/blob-keep.js.map +1 -0
  416. package/dist/store/converters.d.ts.map +1 -1
  417. package/dist/store/converters.js +11 -0
  418. package/dist/store/converters.js.map +1 -1
  419. package/dist/store/credential-transfer.d.ts +37 -5
  420. package/dist/store/credential-transfer.d.ts.map +1 -1
  421. package/dist/store/credential-transfer.js +73 -18
  422. package/dist/store/credential-transfer.js.map +1 -1
  423. package/dist/store/data-accessor.d.ts +1 -1
  424. package/dist/store/data-accessor.d.ts.map +1 -1
  425. package/dist/store/data-accessor.js.map +1 -1
  426. package/dist/store/db-helpers.d.ts +18 -0
  427. package/dist/store/db-helpers.d.ts.map +1 -1
  428. package/dist/store/db-helpers.js +46 -0
  429. package/dist/store/db-helpers.js.map +1 -1
  430. package/dist/store/exodus/on-open.d.ts.map +1 -1
  431. package/dist/store/exodus/on-open.js +35 -0
  432. package/dist/store/exodus/on-open.js.map +1 -1
  433. package/dist/store/exodus/runtime-targets.d.ts +3 -3
  434. package/dist/store/exodus/runtime-targets.js +3 -3
  435. package/dist/store/import-remap.d.ts +12 -1
  436. package/dist/store/import-remap.d.ts.map +1 -1
  437. package/dist/store/import-remap.js +14 -0
  438. package/dist/store/import-remap.js.map +1 -1
  439. package/dist/store/legacy-tasks-lineage.d.ts +3 -1
  440. package/dist/store/legacy-tasks-lineage.d.ts.map +1 -1
  441. package/dist/store/legacy-tasks-lineage.js +3 -1
  442. package/dist/store/legacy-tasks-lineage.js.map +1 -1
  443. package/dist/store/memory-accessor.js +1 -1
  444. package/dist/store/memory-accessor.js.map +1 -1
  445. package/dist/store/migration-manager.d.ts +61 -0
  446. package/dist/store/migration-manager.d.ts.map +1 -1
  447. package/dist/store/migration-manager.js +102 -4
  448. package/dist/store/migration-manager.js.map +1 -1
  449. package/dist/store/nexus-validation-schemas.d.ts +64 -0
  450. package/dist/store/nexus-validation-schemas.d.ts.map +1 -1
  451. package/dist/store/portable-bundle-import.d.ts +8 -0
  452. package/dist/store/portable-bundle-import.d.ts.map +1 -1
  453. package/dist/store/portable-bundle-import.js +10 -0
  454. package/dist/store/portable-bundle-import.js.map +1 -1
  455. package/dist/store/portable-bundle-scan.d.ts.map +1 -1
  456. package/dist/store/portable-bundle-scan.js +1 -0
  457. package/dist/store/portable-bundle-scan.js.map +1 -1
  458. package/dist/store/portable-bundle.d.ts.map +1 -1
  459. package/dist/store/portable-bundle.js +15 -14
  460. package/dist/store/portable-bundle.js.map +1 -1
  461. package/dist/store/pre-repair-snapshot.d.ts +4 -1
  462. package/dist/store/pre-repair-snapshot.d.ts.map +1 -1
  463. package/dist/store/pre-repair-snapshot.js +5 -1
  464. package/dist/store/pre-repair-snapshot.js.map +1 -1
  465. package/dist/store/regenerators.d.ts +4 -2
  466. package/dist/store/regenerators.d.ts.map +1 -1
  467. package/dist/store/regenerators.js +7 -3
  468. package/dist/store/regenerators.js.map +1 -1
  469. package/dist/store/repair-malformed-dbs.d.ts +4 -0
  470. package/dist/store/repair-malformed-dbs.d.ts.map +1 -1
  471. package/dist/store/repair-malformed-dbs.js +3 -0
  472. package/dist/store/repair-malformed-dbs.js.map +1 -1
  473. package/dist/store/safety-data-accessor.d.ts +8 -3
  474. package/dist/store/safety-data-accessor.d.ts.map +1 -1
  475. package/dist/store/safety-data-accessor.js +11 -4
  476. package/dist/store/safety-data-accessor.js.map +1 -1
  477. package/dist/store/schema/cleo-global/nexus.d.ts +48 -0
  478. package/dist/store/schema/cleo-global/nexus.d.ts.map +1 -1
  479. package/dist/store/schema/cleo-global/nexus.js +17 -0
  480. package/dist/store/schema/cleo-global/nexus.js.map +1 -1
  481. package/dist/store/schema/cleo-project/docs.d.ts +16 -0
  482. package/dist/store/schema/cleo-project/docs.d.ts.map +1 -1
  483. package/dist/store/schema/cleo-project/docs.js +10 -0
  484. package/dist/store/schema/cleo-project/docs.js.map +1 -1
  485. package/dist/store/schema/cleo-project/tasks-core.d.ts +80 -0
  486. package/dist/store/schema/cleo-project/tasks-core.d.ts.map +1 -1
  487. package/dist/store/schema/cleo-project/tasks-core.js +25 -1
  488. package/dist/store/schema/cleo-project/tasks-core.js.map +1 -1
  489. package/dist/store/schema/cleo-shared/brain.d.ts +1 -1
  490. package/dist/store/schema/memory-schema.d.ts +1 -1
  491. package/dist/store/schema/nexus-schema.d.ts +48 -0
  492. package/dist/store/schema/nexus-schema.d.ts.map +1 -1
  493. package/dist/store/schema/nexus-schema.js +28 -7
  494. package/dist/store/schema/nexus-schema.js.map +1 -1
  495. package/dist/store/session-store.d.ts +65 -0
  496. package/dist/store/session-store.d.ts.map +1 -1
  497. package/dist/store/session-store.js +108 -7
  498. package/dist/store/session-store.js.map +1 -1
  499. package/dist/store/sqlite-data-accessor.d.ts.map +1 -1
  500. package/dist/store/sqlite-data-accessor.js +245 -47
  501. package/dist/store/sqlite-data-accessor.js.map +1 -1
  502. package/dist/store/sqlite.d.ts +15 -3
  503. package/dist/store/sqlite.d.ts.map +1 -1
  504. package/dist/store/sqlite.js +52 -20
  505. package/dist/store/sqlite.js.map +1 -1
  506. package/dist/store/store-write-guard.d.ts +9 -3
  507. package/dist/store/store-write-guard.d.ts.map +1 -1
  508. package/dist/store/store-write-guard.js +25 -4
  509. package/dist/store/store-write-guard.js.map +1 -1
  510. package/dist/store/table-classification.d.ts +1 -1
  511. package/dist/store/table-classification.d.ts.map +1 -1
  512. package/dist/store/table-classification.js +34 -8
  513. package/dist/store/table-classification.js.map +1 -1
  514. package/dist/store/task-claim.d.ts +144 -0
  515. package/dist/store/task-claim.d.ts.map +1 -0
  516. package/dist/store/task-claim.js +195 -0
  517. package/dist/store/task-claim.js.map +1 -0
  518. package/dist/store/task-version.d.ts +43 -5
  519. package/dist/store/task-version.d.ts.map +1 -1
  520. package/dist/store/task-version.js +113 -17
  521. package/dist/store/task-version.js.map +1 -1
  522. package/dist/store/tasks-schema.d.ts +2 -0
  523. package/dist/store/tasks-schema.d.ts.map +1 -1
  524. package/dist/store/tasks-schema.js +1 -0
  525. package/dist/store/tasks-schema.js.map +1 -1
  526. package/dist/store/tasks-sqlite.js +1 -1
  527. package/dist/store/tasks-sqlite.js.map +1 -1
  528. package/dist/store/twin-collapse.d.ts +339 -22
  529. package/dist/store/twin-collapse.d.ts.map +1 -1
  530. package/dist/store/twin-collapse.js +1323 -48
  531. package/dist/store/twin-collapse.js.map +1 -1
  532. package/dist/store/umbrella-data-accessor.d.ts +8 -3
  533. package/dist/store/umbrella-data-accessor.d.ts.map +1 -1
  534. package/dist/store/umbrella-data-accessor.js +10 -4
  535. package/dist/store/umbrella-data-accessor.js.map +1 -1
  536. package/dist/store/validation-schemas.d.ts +161 -1
  537. package/dist/store/validation-schemas.d.ts.map +1 -1
  538. package/dist/store/with-retry.d.ts +36 -0
  539. package/dist/store/with-retry.d.ts.map +1 -1
  540. package/dist/store/with-retry.js +51 -0
  541. package/dist/store/with-retry.js.map +1 -1
  542. package/dist/store/worktree-build-guard.d.ts +13 -4
  543. package/dist/store/worktree-build-guard.d.ts.map +1 -1
  544. package/dist/store/worktree-build-guard.js +21 -10
  545. package/dist/store/worktree-build-guard.js.map +1 -1
  546. package/dist/store/worktree-isolation-guard.d.ts +121 -10
  547. package/dist/store/worktree-isolation-guard.d.ts.map +1 -1
  548. package/dist/store/worktree-isolation-guard.js +155 -34
  549. package/dist/store/worktree-isolation-guard.js.map +1 -1
  550. package/dist/system/backup.d.ts +7 -0
  551. package/dist/system/backup.d.ts.map +1 -1
  552. package/dist/system/backup.js +5 -0
  553. package/dist/system/backup.js.map +1 -1
  554. package/dist/system/health.d.ts.map +1 -1
  555. package/dist/system/health.js +3 -1
  556. package/dist/system/health.js.map +1 -1
  557. package/dist/system/inject-generate.d.ts.map +1 -1
  558. package/dist/system/inject-generate.js +23 -10
  559. package/dist/system/inject-generate.js.map +1 -1
  560. package/dist/system/project-health.d.ts +8 -0
  561. package/dist/system/project-health.d.ts.map +1 -1
  562. package/dist/system/project-health.js +13 -5
  563. package/dist/system/project-health.js.map +1 -1
  564. package/dist/task-tools/index.d.ts +1 -1
  565. package/dist/task-tools/index.d.ts.map +1 -1
  566. package/dist/task-tools/index.js +1 -1
  567. package/dist/task-tools/index.js.map +1 -1
  568. package/dist/task-tools/score-task-priority.d.ts +38 -0
  569. package/dist/task-tools/score-task-priority.d.ts.map +1 -1
  570. package/dist/task-tools/score-task-priority.js +51 -0
  571. package/dist/task-tools/score-task-priority.js.map +1 -1
  572. package/dist/task-work/claims.d.ts +215 -0
  573. package/dist/task-work/claims.d.ts.map +1 -0
  574. package/dist/task-work/claims.js +272 -0
  575. package/dist/task-work/claims.js.map +1 -0
  576. package/dist/task-work/index.d.ts +47 -3
  577. package/dist/task-work/index.d.ts.map +1 -1
  578. package/dist/task-work/index.js +74 -41
  579. package/dist/task-work/index.js.map +1 -1
  580. package/dist/tasks/add.d.ts.map +1 -1
  581. package/dist/tasks/add.js +3 -2
  582. package/dist/tasks/add.js.map +1 -1
  583. package/dist/tasks/analyze.d.ts.map +1 -1
  584. package/dist/tasks/analyze.js +3 -4
  585. package/dist/tasks/analyze.js.map +1 -1
  586. package/dist/tasks/change-set.d.ts +5 -0
  587. package/dist/tasks/change-set.d.ts.map +1 -1
  588. package/dist/tasks/change-set.js +59 -3
  589. package/dist/tasks/change-set.js.map +1 -1
  590. package/dist/tasks/complete.d.ts +16 -0
  591. package/dist/tasks/complete.d.ts.map +1 -1
  592. package/dist/tasks/complete.js +27 -7
  593. package/dist/tasks/complete.js.map +1 -1
  594. package/dist/tasks/component-pr.d.ts +162 -1
  595. package/dist/tasks/component-pr.d.ts.map +1 -1
  596. package/dist/tasks/component-pr.js +560 -59
  597. package/dist/tasks/component-pr.js.map +1 -1
  598. package/dist/tasks/done-record.d.ts +14 -1
  599. package/dist/tasks/done-record.d.ts.map +1 -1
  600. package/dist/tasks/done-record.js +37 -0
  601. package/dist/tasks/done-record.js.map +1 -1
  602. package/dist/tasks/duplicate-detector.d.ts +20 -1
  603. package/dist/tasks/duplicate-detector.d.ts.map +1 -1
  604. package/dist/tasks/duplicate-detector.js +10 -2
  605. package/dist/tasks/duplicate-detector.js.map +1 -1
  606. package/dist/tasks/engine-wrap-ops.d.ts +19 -10
  607. package/dist/tasks/engine-wrap-ops.d.ts.map +1 -1
  608. package/dist/tasks/engine-wrap-ops.js +38 -10
  609. package/dist/tasks/engine-wrap-ops.js.map +1 -1
  610. package/dist/tasks/graph-ops.d.ts +9 -2
  611. package/dist/tasks/graph-ops.d.ts.map +1 -1
  612. package/dist/tasks/graph-ops.js +24 -32
  613. package/dist/tasks/graph-ops.js.map +1 -1
  614. package/dist/tasks/plan.d.ts.map +1 -1
  615. package/dist/tasks/plan.js +16 -60
  616. package/dist/tasks/plan.js.map +1 -1
  617. package/dist/tasks/task-context.d.ts.map +1 -1
  618. package/dist/tasks/task-context.js +13 -0
  619. package/dist/tasks/task-context.js.map +1 -1
  620. package/dist/tasks/task-import.d.ts.map +1 -1
  621. package/dist/tasks/task-import.js +21 -12
  622. package/dist/tasks/task-import.js.map +1 -1
  623. package/dist/tasks/task-next.d.ts +25 -2
  624. package/dist/tasks/task-next.d.ts.map +1 -1
  625. package/dist/tasks/task-next.js +32 -13
  626. package/dist/tasks/task-next.js.map +1 -1
  627. package/dist/tasks/update.d.ts.map +1 -1
  628. package/dist/tasks/update.js +7 -5
  629. package/dist/tasks/update.js.map +1 -1
  630. package/dist/upgrade.d.ts +1 -1
  631. package/dist/upgrade.d.ts.map +1 -1
  632. package/dist/upgrade.js +48 -9
  633. package/dist/upgrade.js.map +1 -1
  634. package/dist/validation/doctor/checks.d.ts +20 -6
  635. package/dist/validation/doctor/checks.d.ts.map +1 -1
  636. package/dist/validation/doctor/checks.js +51 -8
  637. package/dist/validation/doctor/checks.js.map +1 -1
  638. package/dist/validation/validate-ops.d.ts.map +1 -1
  639. package/dist/validation/validate-ops.js +2 -3
  640. package/dist/validation/validate-ops.js.map +1 -1
  641. package/migrations/drizzle-cleo-global/20260929010000_t12512-registry-probed-opened/migration.sql +35 -0
  642. package/migrations/drizzle-cleo-global/20260929020000_t12721-git-state-head-committed-at/migration.sql +25 -0
  643. package/migrations/drizzle-cleo-project/20260929000000_t12535-docs-attachments-union-shape/migration.sql +21 -0
  644. package/migrations/drizzle-cleo-project/20260929120000_t12502-task-claim-leases/migration.sql +77 -0
  645. package/migrations/drizzle-cleo-project/20260929130000_t12736-claim-lease-iso-repair/migration.sql +37 -0
  646. package/package.json +23 -13
  647. package/schemas/project-info.schema.json +10 -4
  648. package/templates/CLEO-INJECTION.md +1 -1
  649. package/templates/CLEO-REFERENCE.md +3 -3
  650. package/templates/cleo-gitignore +11 -0
  651. package/templates/workflows/release-prepare.yml.tmpl +156 -10
@@ -1,23 +1,30 @@
1
1
  /**
2
- * Inspect and resolve the portable project identity (T12353 · ADR-094).
2
+ * Inspect and resolve the portable project identity (T12353 · ADR-094 · T12716).
3
3
  *
4
- * `.cleo/project-id` is tracked and write-once. `project-info.json` holds the
5
- * id that local state is keyed by, and it is not tracked. T12325 made
6
- * `cleo init` report a disagreement between the two and keep the local id,
7
- * but left the operator without a way to see or fix it. This module is that
8
- * way:
4
+ * The tracked identity is `.cleo/project.json` `{schemaVersion, id, name}`
5
+ * (T12716), with the ADR-094 `.cleo/project-id` kept as a legacy mirror.
6
+ * `project-info.json` is untracked and device-local; its `projectId` is a
7
+ * cache of the tracked id. The tracked id always wins at runtime. This module
8
+ * is the ONLY place that migrates or re-keys identity files — nothing does it
9
+ * on open, init or upgrade:
9
10
  *
10
- * - {@link inspectProjectIdentity} is read-only. It classifies the pair, checks
11
- * whether git actually tracks the file, and returns the exact remedy command.
12
- * - {@link resolveProjectIdentity} applies the remedy. For a conflict it
13
- * re-keys the LOCAL side to the tracked id. The registry row's primary key
14
- * and every alias pointing at the old id are updated in one registry
15
- * transaction, and the old id is itself recorded as an alias. Nothing is
16
- * deleted: rows elsewhere that still carry the old id (audit log, session
17
- * manifest, background jobs) keep resolving through the alias table.
11
+ * - {@link inspectProjectIdentity} is read-only. It classifies the three
12
+ * sources (project.json / project-id / project-info.json), checks whether
13
+ * git actually tracks the canonical file, and returns the exact remedy.
14
+ * - {@link inspectProjectNameDrift} is read-only. It compares the registry's
15
+ * label with the declared display name.
16
+ * - {@link resolveProjectIdentity} applies the remedy. A legacy project gets
17
+ * `project.json` written from its `project-id` and `project-info.json` name
18
+ * (no id ever changes). For a conflict it re-keys the LOCAL side to the
19
+ * tracked id: the registry row's primary key and every alias pointing at the
20
+ * old id are updated in one registry transaction, and the old id is itself
21
+ * recorded as an alias. Nothing is deleted: rows elsewhere that still carry
22
+ * the old id (audit log, session manifest, background jobs) keep resolving
23
+ * through the alias table, and credentials sealed under it are re-wrapped on
24
+ * their next open (`previousProjectIds`).
18
25
  *
19
- * The tracked file is never modified here. It is write-once, and a malformed
20
- * one is restored from version control, never regenerated.
26
+ * The id in a tracked file is never modified here. It is write-once, and a
27
+ * malformed file is restored from version control, never regenerated.
21
28
  *
22
29
  * T12557: `projectRoot` is a path fact, derived from the real root at runtime.
23
30
  * A persisted copy in `project-info.json` or `project-context.json` goes stale
@@ -29,13 +36,16 @@
29
36
  *
30
37
  * @task T12353
31
38
  * @task T12557
39
+ * @task T12716
32
40
  * @see ADR-094 — write-once portable project identity (amends ADR-013 §9)
41
+ * @see ADR-096 — one committed `.cleo/project.json` {id, name} (amends ADR-094)
33
42
  */
34
43
  import { execFileSync } from 'node:child_process';
44
+ import { randomUUID } from 'node:crypto';
35
45
  import { existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
36
46
  import { join } from 'node:path';
37
- import { canonicalizePath, getCleoHome, isValidPortableProjectId, PORTABLE_PROJECT_ID_FILE, readDeclaredProjectIdentity, readPortableProjectId, } from '@cleocode/paths';
38
- import { ensurePortableProjectId } from '../scaffold/project-identity.js';
47
+ import { canonicalizePath, getCleoHome, isValidPortableProjectId, isValidProjectDisplayName, PORTABLE_PROJECT_ID_FILE, PROJECT_MANIFEST_FILE, readDeclaredProjectIdentity, readPortableProjectId, readProjectIdFile, readProjectManifest, } from '@cleocode/paths';
48
+ import { createProjectIdMirror, createProjectManifest, defaultProjectDisplayName, ensurePortableProjectId, } from '../scaffold/project-identity.js';
39
49
  import { detectRelocatedRoot } from '../store/relocated-store-guard.js';
40
50
  /** Command that reports identity state; the prefix of every remedy below. */
41
51
  const IDENTITY_COMMAND = 'cleo doctor project-identity';
@@ -95,9 +105,9 @@ function findDerivedFields(projectRoot) {
95
105
  }
96
106
  return found;
97
107
  }
98
- /** Git's view of the tracked file: `null` when this is not a git work tree. */
99
- function gitFileState(projectRoot) {
100
- const rel = `.cleo/${PORTABLE_PROJECT_ID_FILE}`;
108
+ /** Git's view of a tracked identity file: `null` when this is not a git work tree. */
109
+ function gitFileState(projectRoot, file = PROJECT_MANIFEST_FILE) {
110
+ const rel = `.cleo/${file}`;
101
111
  const run = (args) => {
102
112
  try {
103
113
  execFileSync('git', args, { cwd: projectRoot, stdio: 'pipe', timeout: GIT_PROBE_TIMEOUT_MS });
@@ -116,7 +126,20 @@ function gitFileState(projectRoot) {
116
126
  return 'untracked';
117
127
  }
118
128
  /**
119
- * Classify the identity pair of a project, read-only.
129
+ * The name a migrated `project.json` records: the cached `displayName` (a
130
+ * legacy rename), else the cached `name`, when valid; else the basename.
131
+ */
132
+ function migrationName(projectRoot, info) {
133
+ if (info && info !== 'unparseable')
134
+ for (const field of ['displayName', 'name', 'projectName']) {
135
+ const name = info.data[field];
136
+ if (typeof name === 'string' && isValidProjectDisplayName(name))
137
+ return name;
138
+ }
139
+ return defaultProjectDisplayName(projectRoot);
140
+ }
141
+ /**
142
+ * Classify the identity sources of a project, read-only.
120
143
  *
121
144
  * @param projectRoot - Absolute project root.
122
145
  * @returns The inspection with the exact remedy command.
@@ -128,26 +151,51 @@ function gitFileState(projectRoot) {
128
151
  * ```
129
152
  */
130
153
  export function inspectProjectIdentity(projectRoot) {
154
+ const manifest = readProjectManifest(projectRoot);
155
+ const legacy = readProjectIdFile(projectRoot);
131
156
  const tracked = readPortableProjectId(projectRoot);
132
157
  const info = readInfo(projectRoot);
133
158
  const trackedId = tracked.status === 'valid' ? tracked.projectId : null;
134
159
  const localId = info && info !== 'unparseable' ? info.id : null;
135
- const base = { projectRoot, trackedId, localId, derivedFields: findDerivedFields(projectRoot) };
160
+ const manifestId = manifest.status === 'valid' ? manifest.manifest.id : null;
161
+ const legacyId = legacy.status === 'valid' ? legacy.projectId : null;
162
+ const base = {
163
+ projectRoot,
164
+ trackedId,
165
+ manifestId,
166
+ legacyId,
167
+ declaredName: manifest.status === 'valid' ? manifest.manifest.name : null,
168
+ localId,
169
+ derivedFields: findDerivedFields(projectRoot),
170
+ };
136
171
  // A CLEO root need not be a git work tree; git remedies apply only inside one.
137
- let git;
138
- const gitState = () => {
139
- if (git === undefined)
140
- git = gitFileState(projectRoot);
141
- return git;
172
+ const gitStates = new Map();
173
+ const gitState = (file = PROJECT_MANIFEST_FILE) => {
174
+ if (!gitStates.has(file))
175
+ gitStates.set(file, gitFileState(projectRoot, file));
176
+ return gitStates.get(file) ?? null;
142
177
  };
143
178
  if (tracked.status === 'invalid') {
179
+ const rel = `.cleo/${tracked.file ?? PORTABLE_PROJECT_ID_FILE}`;
144
180
  return {
145
181
  ...base,
146
182
  state: 'invalid',
147
- message: `.cleo/project-id is unusable (${tracked.reason}). CLEO never regenerates it.`,
183
+ message: `${rel} is unusable (${tracked.reason}). CLEO never regenerates it.`,
184
+ remedy: gitState() === null
185
+ ? `restore ${rel} from a backup of this project (the CLEO root is not a git work tree)`
186
+ : `git checkout -- ${rel} (restore the committed file; inspect with \`git log -p -- ${rel}\`)`,
187
+ };
188
+ }
189
+ if (manifestId && legacy.status !== 'absent' && legacyId !== manifestId) {
190
+ return {
191
+ ...base,
192
+ state: 'mirror-conflict',
193
+ message: legacy.status === 'invalid'
194
+ ? `.cleo/project-id is unusable (${legacy.reason}), while .cleo/project.json declares ${manifestId}. CLEO uses ${manifestId}; builds that read only .cleo/project-id cannot.`
195
+ : `.cleo/project.json declares ${manifestId} but .cleo/project-id declares ${legacyId}. CLEO uses ${manifestId}; builds that read only .cleo/project-id use ${legacyId}.`,
148
196
  remedy: gitState() === null
149
- ? 'restore .cleo/project-id from a backup of this project (the CLEO root is not a git work tree)'
150
- : 'git checkout -- .cleo/project-id (restore the committed id; inspect with `git log -p -- .cleo/project-id`)',
197
+ ? 'restore both files from a backup of this project; CLEO never rewrites a tracked id'
198
+ : 'inspect `git log -p -- .cleo/project.json .cleo/project-id`, then restore the edited file with `git checkout <commit> -- <file>` (CLEO never rewrites a tracked id)',
151
199
  };
152
200
  }
153
201
  if (info === undefined) {
@@ -159,7 +207,7 @@ export function inspectProjectIdentity(projectRoot) {
159
207
  return {
160
208
  ...base,
161
209
  state: 'not-adopted',
162
- message: `.cleo/project-id declares ${relocated.projectId}, which was rerooted from here to ${relocated.movedTo} (${relocated.via}).`,
210
+ message: `The tracked identity declares ${relocated.projectId}, which was rerooted from here to ${relocated.movedTo} (${relocated.via}).`,
163
211
  remedy: `cd "${relocated.movedTo}" (the live project). A DIFFERENT project here needs a new id: \`cleo init --here --new-identity\``,
164
212
  };
165
213
  }
@@ -167,13 +215,13 @@ export function inspectProjectIdentity(projectRoot) {
167
215
  ? {
168
216
  ...base,
169
217
  state: 'not-adopted',
170
- message: `.cleo/project-id declares ${trackedId}, but this checkout has not been initialized.`,
218
+ message: `The tracked identity declares ${trackedId}, but this checkout has not been initialized.`,
171
219
  remedy: 'cleo init (adopts the tracked id; it never mints a new one)',
172
220
  }
173
221
  : {
174
222
  ...base,
175
223
  state: 'uninitialized',
176
- message: 'No project identity: neither .cleo/project-info.json nor .cleo/project-id exists.',
224
+ message: 'No project identity: none of .cleo/project.json, .cleo/project-id or .cleo/project-info.json exists.',
177
225
  remedy: 'cleo init',
178
226
  };
179
227
  }
@@ -189,40 +237,60 @@ export function inspectProjectIdentity(projectRoot) {
189
237
  return {
190
238
  ...base,
191
239
  state: 'missing',
192
- message: `.cleo/project-id is missing. The local id is ${localId}.`,
240
+ message: `No tracked identity: .cleo/project.json is missing. The local id is ${localId}.`,
193
241
  remedy: gitState() === null
194
- ? `${IDENTITY_COMMAND} --resolve (the CLEO root is not a git work tree; keep .cleo/project-id with the project)`
195
- : `${IDENTITY_COMMAND} --resolve && git add .cleo/project-id && git commit -m "chore: track CLEO project id"`,
242
+ ? `${IDENTITY_COMMAND} --resolve (the CLEO root is not a git work tree; keep .cleo/project.json with the project)`
243
+ : `${IDENTITY_COMMAND} --resolve && git add .cleo/project.json .cleo/project-id && git commit -m "chore: track CLEO project identity"`,
196
244
  };
197
245
  }
198
246
  if (trackedId !== localId) {
199
247
  return {
200
248
  ...base,
201
249
  state: 'conflict',
202
- message: `Identity conflict: .cleo/project-id is ${trackedId}, but project-info.json is ${localId}. Two lineages; local state is still keyed by ${localId}.`,
250
+ message: `Identity conflict: the tracked id is ${trackedId}, but the project-info.json cache is ${localId}. Two lineages; CLEO uses ${trackedId}, while local state (registry row, aliases) is still keyed by ${localId}.`,
203
251
  remedy: `${IDENTITY_COMMAND} --resolve --dry-run then ${IDENTITY_COMMAND} --resolve (re-keys local state to ${trackedId}; ${localId} stays resolvable as an alias)`,
204
252
  };
205
253
  }
254
+ if (!manifestId) {
255
+ return {
256
+ ...base,
257
+ state: 'legacy',
258
+ message: `Only the legacy .cleo/project-id records ${trackedId}; .cleo/project.json (id + name) does not exist yet.`,
259
+ remedy: gitState(PORTABLE_PROJECT_ID_FILE) === null
260
+ ? `${IDENTITY_COMMAND} --resolve --dry-run then ${IDENTITY_COMMAND} --resolve (writes .cleo/project.json; no id changes)`
261
+ : `${IDENTITY_COMMAND} --resolve --dry-run then ${IDENTITY_COMMAND} --resolve && git add .cleo/project.json .cleo/.gitignore && git commit -m "chore: track CLEO project.json" (no id changes)`,
262
+ };
263
+ }
264
+ if (!legacyId) {
265
+ return {
266
+ ...base,
267
+ state: 'mirror-missing',
268
+ message: `.cleo/project.json records ${trackedId}, but the legacy .cleo/project-id mirror is missing, so builds that read only that file cannot resolve the id.`,
269
+ remedy: gitState() === null
270
+ ? `${IDENTITY_COMMAND} --resolve (writes the mirror)`
271
+ : `${IDENTITY_COMMAND} --resolve && git add .cleo/project-id && git commit -m "chore: track CLEO project-id mirror"`,
272
+ };
273
+ }
206
274
  if (gitState() === 'ignored') {
207
275
  return {
208
276
  ...base,
209
277
  state: 'ignored',
210
- message: '.cleo/project-id is gitignored, so clones cannot inherit it. The .cleo/.gitignore predates ADR-094.',
211
- remedy: 'cleo upgrade (refreshes .cleo/.gitignore with `!project-id`), then git add .cleo/project-id',
278
+ message: '.cleo/project.json is gitignored, so clones cannot inherit it. The .cleo/.gitignore predates T12716.',
279
+ remedy: `${IDENTITY_COMMAND} --resolve (adds \`!project.json\` to .cleo/.gitignore; \`cleo upgrade\` refreshes the whole file), then git add .cleo/.gitignore .cleo/project.json .cleo/project-id`,
212
280
  };
213
281
  }
214
- if (gitState() === 'untracked') {
282
+ if (gitState() === 'untracked' || gitState(PORTABLE_PROJECT_ID_FILE) === 'untracked') {
215
283
  return {
216
284
  ...base,
217
285
  state: 'untracked',
218
- message: '.cleo/project-id is not committed yet, so clones cannot inherit it.',
219
- remedy: 'git add .cleo/project-id && git commit -m "chore: track CLEO project id"',
286
+ message: 'The tracked identity files are not committed yet, so clones cannot inherit them.',
287
+ remedy: 'git add .cleo/project.json .cleo/project-id && git commit -m "chore: track CLEO project identity"',
220
288
  };
221
289
  }
222
290
  return {
223
291
  ...base,
224
292
  state: 'ok',
225
- message: `Project identity ${trackedId} is tracked and consistent.`,
293
+ message: `Project identity ${trackedId} ("${base.declaredName}") is tracked and consistent.`,
226
294
  remedy: null,
227
295
  };
228
296
  }
@@ -347,20 +415,81 @@ async function confirmCandidateCheckout(candidate, cleoHome) {
347
415
  }, { behavior: 'immediate' });
348
416
  }
349
417
  /**
350
- * Resolve the identity state that {@link inspectProjectIdentity} reported.
418
+ * If `.cleo/.gitignore` (a CLEO-managed file older than T12716) ignores
419
+ * `.cleo/project.json`, the surgical repair: insert `!project.json` beside
420
+ * `!project-id` (or append it). Nothing else in the file changes; a rule from
421
+ * any other ignore file is reported, never edited.
351
422
  *
352
- * - `missing` writes `.cleo/project-id` from the local id (create-only).
423
+ * @returns The step to report, or `null` when the file is not ignored.
424
+ */
425
+ function allowManifestInGitignore(projectRoot, dryRun) {
426
+ const rel = `.cleo/${PROJECT_MANIFEST_FILE}`;
427
+ let source;
428
+ try {
429
+ source = execFileSync('git', ['check-ignore', '-v', '--no-index', rel], {
430
+ cwd: projectRoot,
431
+ stdio: ['ignore', 'pipe', 'ignore'],
432
+ timeout: GIT_PROBE_TIMEOUT_MS,
433
+ encoding: 'utf-8',
434
+ });
435
+ }
436
+ catch {
437
+ return null; // not ignored (exit 1), or not a git work tree
438
+ }
439
+ // `<source>:<line>:<pattern>\t<path>`; a matching `!pattern` means NOT ignored.
440
+ const [ignoreFile = '', , pattern = ''] = (source.split('\t')[0] ?? '').split(':');
441
+ if (pattern.startsWith('!'))
442
+ return null;
443
+ const cleoIgnore = join(projectRoot, '.cleo', '.gitignore');
444
+ if (ignoreFile !== '.cleo/.gitignore' || !existsSync(cleoIgnore)) {
445
+ return {
446
+ refused: `${rel} is ignored by ${ignoreFile || 'a gitignore rule'} (${source.trim()}). Allow it there (\`!${rel}\`), then git add ${rel}.`,
447
+ };
448
+ }
449
+ const step = {
450
+ action: 'allow-project-json',
451
+ detail: `add \`!${PROJECT_MANIFEST_FILE}\` to .cleo/.gitignore (it predates T12716 and ignores ${rel}); commit it with ${rel}`,
452
+ };
453
+ if (!dryRun) {
454
+ const lines = readFileSync(cleoIgnore, 'utf-8').split('\n');
455
+ const at = lines.findIndex((line) => line.trim() === `!${PORTABLE_PROJECT_ID_FILE}`);
456
+ if (at >= 0)
457
+ lines.splice(at, 0, `!${PROJECT_MANIFEST_FILE}`);
458
+ else
459
+ lines.splice(lines.at(-1) === '' ? lines.length - 1 : lines.length, 0, `!${PROJECT_MANIFEST_FILE}`);
460
+ writeFileSync(cleoIgnore, lines.join('\n'));
461
+ }
462
+ return step;
463
+ }
464
+ /**
465
+ * Resolve the identity state that {@link inspectProjectIdentity} reported —
466
+ * the ONLY migration path from the legacy files to `.cleo/project.json`
467
+ * (T12716); nothing migrates on open, init or upgrade.
468
+ *
469
+ * - `legacy` writes `.cleo/project.json` (create-only) from the legacy
470
+ * `.cleo/project-id` id and the `project-info.json` name (else the
471
+ * basename). `project-id` stays as the legacy mirror; no id changes.
472
+ * - `missing` writes `.cleo/project.json` and the `project-id` mirror from the
473
+ * local id (create-only).
474
+ * - `mirror-missing` writes the legacy `.cleo/project-id` mirror (create-only).
475
+ * - Whenever `project.json` is (or is about to be) ignored by a pre-T12716
476
+ * `.cleo/.gitignore` — including the `ignored` state — `!project.json` is
477
+ * inserted there, so the file can be committed; nothing else in it changes.
353
478
  * - `info-invalid` re-runs the scaffold decision (tracked id or registry re-link).
354
479
  * - `conflict` re-keys local state to the tracked id. One registry transaction
355
480
  * renames the row's primary key, re-points aliases and records the old id as
356
481
  * an alias. Then `project-info.json` is rewritten, keeping the old id under
357
- * `previousProjectIds`.
482
+ * `previousProjectIds`, and `project.json` is written when only the legacy
483
+ * file existed.
358
484
  * - In every state, persisted `projectRoot` fields are stripped first
359
485
  * (T12557). The removed values are kept in `project-info.json`
360
486
  * `strippedFields`. `projectHash` is never touched.
487
+ * - Last, a registry label that drifted from the declared name is synced
488
+ * (see {@link inspectProjectNameDrift}), unless another project holds it.
361
489
  *
362
- * Every other state is refused with its remedy. The operation is idempotent:
363
- * re-running after a partial apply completes the remaining steps.
490
+ * Every other state (`invalid`, `mirror-conflict`, `untracked`, …) is refused
491
+ * with its remedy. The operation is idempotent: re-running after a partial
492
+ * apply completes the remaining steps.
364
493
  *
365
494
  * @param projectRoot - Absolute project root.
366
495
  * @param options - `dryRun` and registry home.
@@ -373,6 +502,42 @@ async function confirmCandidateCheckout(candidate, cleoHome) {
373
502
  * ```
374
503
  */
375
504
  export async function resolveProjectIdentity(projectRoot, options = {}) {
505
+ const resolution = await resolveIdentityFiles(projectRoot, options);
506
+ // A refusal with a remedy (invalid, mirror-conflict, …) stops here.
507
+ if (resolution.refused && resolution.refused !== NOTHING_TO_RESOLVE)
508
+ return resolution;
509
+ // Compare against the name project.json holds after the file steps, so a
510
+ // dry-run plans exactly what an apply does.
511
+ const writesManifest = resolution.steps.some((step) => step.action === 'write-project-json' || step.action === 'write-tracked-id');
512
+ // A dry-run conflict has not re-keyed yet: its row is still keyed by the
513
+ // cached id, while the apply compares after the re-key. Look the row up by
514
+ // the id it holds NOW, so both see the same row (review finding 2).
515
+ const rekeyPending = options.dryRun === true &&
516
+ resolution.steps.some((step) => step.action === 'rekey-registry-row');
517
+ const drift = await inspectProjectNameDrift(projectRoot, options.cleoHome, writesManifest ? migrationName(projectRoot, readInfo(projectRoot)) : undefined, rekeyPending ? (resolution.before.localId ?? undefined) : undefined);
518
+ if (drift.state !== 'drift' || drift.projectId === null || drift.declaredName === null)
519
+ return resolution;
520
+ const steps = [
521
+ ...resolution.steps,
522
+ {
523
+ action: 'sync-registry-name',
524
+ detail: `registry label '${drift.registryName}' -> '${drift.declaredName}' (the declared name)`,
525
+ },
526
+ ];
527
+ if (options.dryRun !== true) {
528
+ const { nexusRenameProject } = await import('../nexus/registry.js');
529
+ const { generateProjectHash } = await import('../nexus/hash.js');
530
+ const { worktreeScope } = await import('../project-scope.js');
531
+ const { projectId, declaredName } = drift;
532
+ // Scoped to the explicit root, so no ambient project owns the write.
533
+ await worktreeScope.run({ worktreeRoot: projectRoot, projectHash: generateProjectHash(projectRoot) }, () => nexusRenameProject(projectId, declaredName));
534
+ }
535
+ return { ...resolution, steps, refused: null };
536
+ }
537
+ /** Refusal text when nothing needs resolving. */
538
+ const NOTHING_TO_RESOLVE = 'Nothing to resolve.';
539
+ /** The file steps of {@link resolveProjectIdentity}, before the name sync. */
540
+ async function resolveIdentityFiles(projectRoot, options) {
376
541
  const dryRun = options.dryRun === true;
377
542
  const before = inspectProjectIdentity(projectRoot);
378
543
  const steps = [];
@@ -407,20 +572,71 @@ export async function resolveProjectIdentity(projectRoot, options = {}) {
407
572
  if (!dryRun && !stripDerivedFields(projectRoot, before.derivedFields, new Date().toISOString()))
408
573
  return result('Cannot record a strip receipt: .cleo/project-info.json is missing or unreadable.');
409
574
  }
575
+ const name = migrationName(projectRoot, readInfo(projectRoot));
576
+ // A migrated or ignored project.json must be committable (T12716).
577
+ const allowStep = () => {
578
+ const allowed = allowManifestInGitignore(projectRoot, dryRun);
579
+ if (allowed === null)
580
+ return null;
581
+ if ('refused' in allowed)
582
+ return allowed.refused;
583
+ steps.push(allowed);
584
+ return null;
585
+ };
586
+ // T12716: the migration. Create-only; the id is copied, never changed.
587
+ const writeManifestStep = async (projectId) => {
588
+ steps.push({
589
+ action: 'write-project-json',
590
+ detail: `create .cleo/project.json = {id: ${projectId}, name: ${JSON.stringify(name)}} (.cleo/project-id stays as the legacy mirror; commit .cleo/project.json)`,
591
+ });
592
+ if (dryRun)
593
+ return null;
594
+ const outcome = await createProjectManifest(projectRoot, projectId, name);
595
+ return outcome === 'written' || outcome === 'present'
596
+ ? null
597
+ : `Cannot write .cleo/project.json (${outcome}).`;
598
+ };
410
599
  if (before.state === 'missing' && before.localId) {
411
600
  steps.push({
412
601
  action: 'write-tracked-id',
413
- detail: `create .cleo/project-id = ${before.localId}`,
602
+ detail: `create .cleo/project.json = {id: ${before.localId}, name: ${JSON.stringify(name)}} and the .cleo/project-id mirror`,
414
603
  });
415
604
  if (!dryRun)
416
- await ensurePortableProjectId(projectRoot, before.localId);
605
+ await ensurePortableProjectId(projectRoot, before.localId, name);
606
+ const ignored = allowStep();
607
+ await confirmCandidateStep();
608
+ return result(ignored);
609
+ }
610
+ if (before.state === 'legacy' && before.legacyId) {
611
+ const failed = await writeManifestStep(before.legacyId);
612
+ if (failed)
613
+ return result(failed);
614
+ const ignored = allowStep();
615
+ await confirmCandidateStep();
616
+ return result(ignored);
617
+ }
618
+ if (before.state === 'ignored') {
619
+ const ignored = allowStep();
620
+ await confirmCandidateStep();
621
+ return result(ignored);
622
+ }
623
+ if (before.state === 'mirror-missing' && before.manifestId) {
624
+ steps.push({
625
+ action: 'write-legacy-mirror',
626
+ detail: `create .cleo/project-id = ${before.manifestId} (for builds that read only that file; commit it)`,
627
+ });
628
+ if (!dryRun) {
629
+ const outcome = await createProjectIdMirror(projectRoot, before.manifestId);
630
+ if (outcome !== 'written' && outcome !== 'present')
631
+ return result(`Cannot write .cleo/project-id (${outcome}).`);
632
+ }
417
633
  await confirmCandidateStep();
418
634
  return result(null);
419
635
  }
420
636
  if (before.state === 'info-invalid') {
421
637
  steps.push({
422
638
  action: 'rewrite-project-info',
423
- detail: 'regenerate the project-info.json id from .cleo/project-id or the registry',
639
+ detail: 'regenerate the project-info.json id from the tracked identity or the registry',
424
640
  });
425
641
  if (!dryRun) {
426
642
  const { ensureProjectInfo } = await import('../scaffold/ensure-config.js');
@@ -434,13 +650,14 @@ export async function resolveProjectIdentity(projectRoot, options = {}) {
434
650
  if (before.state !== 'conflict' || !before.trackedId || !before.localId) {
435
651
  if (before.remedy)
436
652
  return result(`${before.message} Remedy: ${before.remedy}`);
437
- return result(steps.length > 0 ? null : 'Nothing to resolve.');
653
+ return result(steps.length > 0 ? null : NOTHING_TO_RESOLVE);
438
654
  }
439
655
  const oldId = before.localId;
440
656
  const newId = before.trackedId;
441
657
  const { getNexusRegistryDb } = await import('../store/nexus-sqlite.js');
442
- const { projectIdAliases, projectRegistry } = await import('../store/schema/nexus-schema.js');
443
- const { eq } = await import('drizzle-orm');
658
+ const { nexusAuditLog, projectGitState, projectIdAliases, projectLocations, projectRegistry } = await import('../store/schema/nexus-schema.js');
659
+ const { and, eq } = await import('drizzle-orm');
660
+ const { isSupersededRegistryPath, projectHolding } = await import('../nexus/path-map.js');
444
661
  const db = await getNexusRegistryDb(options.cleoHome ?? getCleoHome());
445
662
  const countRows = () => db.select().from(projectRegistry).all().length;
446
663
  const rowsBefore = countRows();
@@ -454,10 +671,25 @@ export async function resolveProjectIdentity(projectRoot, options = {}) {
454
671
  .from(projectRegistry)
455
672
  .where(eq(projectRegistry.projectId, newId))
456
673
  .get();
674
+ // Both ids registered: the cached id was registered before the tracked file
675
+ // arrived, and a later encounter registered the tracked id (review finding
676
+ // 5). When the cached-id row is THIS checkout, or its path no longer holds
677
+ // the cached id, the rows are one project: fold the cached-id row into the
678
+ // tracked row. Its content is kept whole in a nexus audit receipt, its
679
+ // locations move to the tracked id, and the cached id stays resolvable
680
+ // through the alias. A cached-id row still live elsewhere is a second
681
+ // lineage and is refused, never unregistered.
682
+ const merge = Boolean(oldRow && newRow);
457
683
  if (oldRow && newRow) {
458
- return result(`Both ids are registered on this device: ${oldId} at ${oldRow.projectPath} and ${newId} at ${newRow.projectPath}. ` +
459
- 'One registry row cannot hold two paths. ' +
460
- `Remedy: if ${newRow.projectPath} is gone or is a stale checkout, run \`cleo nexus unregister ${newId}\` and then re-run \`${IDENTITY_COMMAND} --resolve\`.`, { before: rowsBefore, after: rowsBefore });
684
+ const here = canonicalizePath(projectRoot);
685
+ const elsewhereLive = oldRow.projectPath !== here &&
686
+ !isSupersededRegistryPath(oldRow.projectPath) &&
687
+ projectHolding(oldRow.projectPath, oldId) !== 'no';
688
+ if (elsewhereLive) {
689
+ return result(`Both ids are registered on this device, and ${oldId} is still declared by ${oldRow.projectPath}. ` +
690
+ 'That is a second checkout of the old lineage; nothing was changed. ' +
691
+ `Remedy: run \`${IDENTITY_COMMAND} --resolve --dry-run\` in "${oldRow.projectPath}" first (it adopts the tracked id there), then re-run it here.`, { before: rowsBefore, after: rowsBefore });
692
+ }
461
693
  }
462
694
  const newIdAlias = db
463
695
  .select()
@@ -473,7 +705,12 @@ export async function resolveProjectIdentity(projectRoot, options = {}) {
473
705
  .from(projectIdAliases)
474
706
  .where(eq(projectIdAliases.canonicalId, oldId))
475
707
  .all();
476
- if (oldRow)
708
+ if (merge && oldRow && newRow)
709
+ steps.push({
710
+ action: 'merge-registry-row',
711
+ detail: `fold registry row ${oldId} (path ${oldRow.projectPath}, permissions ${oldRow.permissions}) into ${newId} (path ${newRow.projectPath} kept; stronger permissions and earlier registeredAt carried over); the whole row is kept in a nexus audit receipt and ${oldId} resolves through the alias`,
712
+ });
713
+ else if (oldRow)
477
714
  steps.push({
478
715
  action: 'rekey-registry-row',
479
716
  detail: `registry row ${oldId} -> ${newId} (path ${oldRow.projectPath} kept)`,
@@ -492,15 +729,80 @@ export async function resolveProjectIdentity(projectRoot, options = {}) {
492
729
  action: 'rewrite-project-info',
493
730
  detail: `project-info.json projectId ${oldId} -> ${newId} (previousProjectIds keeps ${oldId})`,
494
731
  });
495
- if (dryRun)
496
- return result(null, { before: rowsBefore, after: rowsBefore });
732
+ // Credentials sealed under the old id are re-wrapped eagerly, not on their
733
+ // next open (review finding 4). The step is the receipt: every row named.
734
+ const rewrapStep = async () => {
735
+ const { migrateProjectCredentialsAtRoot } = await import('../store/credential-transfer.js');
736
+ const plan = await migrateProjectCredentialsAtRoot(projectRoot, {
737
+ dryRun,
738
+ ...(options.cleoHome !== undefined && { cleoHome: options.cleoHome }),
739
+ });
740
+ if (plan.migrated.length === 0 && plan.reentry.length === 0)
741
+ return;
742
+ steps.push({
743
+ action: 'rewrap-credentials',
744
+ detail: `re-wrap ${plan.migrated.length} credential(s) under ${newId}: ${plan.migrated.map((c) => `${c.store}:${c.id}`).join(', ') || 'none'}${plan.reentry.length > 0 ? `; ${plan.reentry.length} cannot be opened and must be re-entered: ${plan.reentry.map((r) => `${r.store}:${r.id}`).join(', ')}` : ''}`,
745
+ });
746
+ };
747
+ if (dryRun) {
748
+ await rewrapStep();
749
+ if (!before.manifestId)
750
+ await writeManifestStep(newId);
751
+ return result(before.manifestId ? null : allowStep(), {
752
+ before: rowsBefore,
753
+ after: rowsBefore,
754
+ });
755
+ }
497
756
  const now = new Date().toISOString();
757
+ const foldedLocations = [projectLocations, projectGitState].flatMap((table) => db.select().from(table).where(eq(table.projectId, oldId)).all());
758
+ const rank = { read: 0, write: 1, execute: 2 };
759
+ const strongest = (a, b) => (rank[a] ?? 0) >= (rank[b] ?? 0) ? a : b;
498
760
  db.transaction((tx) => {
499
- if (oldRow)
761
+ if (merge && oldRow && newRow) {
762
+ tx.insert(nexusAuditLog)
763
+ .values({
764
+ id: randomUUID(),
765
+ action: 'merge-identity',
766
+ projectHash: newRow.projectHash,
767
+ projectId: newId,
768
+ domain: 'nexus',
769
+ operation: 'doctor.project-identity.resolve',
770
+ source: 'cleo doctor project-identity --resolve',
771
+ success: 1,
772
+ detailsJson: JSON.stringify({ foldedRow: oldRow, foldedLocations, into: newId }),
773
+ })
774
+ .run();
775
+ tx.update(projectRegistry)
776
+ .set({
777
+ permissions: strongest(newRow.permissions, oldRow.permissions),
778
+ registeredAt: oldRow.registeredAt < newRow.registeredAt ? oldRow.registeredAt : newRow.registeredAt,
779
+ })
780
+ .where(eq(projectRegistry.projectId, newId))
781
+ .run();
782
+ tx.delete(projectRegistry).where(eq(projectRegistry.projectId, oldId)).run();
783
+ }
784
+ else if (oldRow)
500
785
  tx.update(projectRegistry)
501
786
  .set({ projectId: newId })
502
787
  .where(eq(projectRegistry.projectId, oldId))
503
788
  .run();
789
+ // Per-checkout rows follow the id. A checkout the tracked id already
790
+ // records keeps the tracked row (earliest firstSeen carried over); the
791
+ // duplicate is in the receipt, so nothing is lost.
792
+ for (const table of [projectLocations, projectGitState]) {
793
+ for (const row of tx.select().from(table).where(eq(table.projectId, oldId)).all()) {
794
+ const at = (id) => and(eq(table.projectId, id), eq(table.deviceId, row.deviceId), eq(table.path, row.path));
795
+ const taken = tx.select().from(table).where(at(newId)).get();
796
+ if (!taken) {
797
+ tx.update(table).set({ projectId: newId }).where(at(oldId)).run();
798
+ continue;
799
+ }
800
+ if (table === projectLocations && 'firstSeen' in row && 'firstSeen' in taken)
801
+ if (row.firstSeen < taken.firstSeen)
802
+ tx.update(projectLocations).set({ firstSeen: row.firstSeen }).where(at(newId)).run();
803
+ tx.delete(table).where(at(oldId)).run();
804
+ }
805
+ }
504
806
  if (newIdAlias?.canonicalId === oldId)
505
807
  tx.delete(projectIdAliases).where(eq(projectIdAliases.legacyId, newId)).run();
506
808
  tx.update(projectIdAliases)
@@ -529,6 +831,117 @@ export async function resolveProjectIdentity(projectRoot, options = {}) {
529
831
  lastUpdated: now,
530
832
  });
531
833
  }
532
- return result(null, { before: rowsBefore, after: countRows() });
834
+ await rewrapStep();
835
+ const manifestFailure = before.manifestId
836
+ ? null
837
+ : ((await writeManifestStep(newId)) ?? allowStep());
838
+ return result(manifestFailure, { before: rowsBefore, after: countRows() });
839
+ }
840
+ /**
841
+ * Compare the global registry's label for this project with the name the
842
+ * project declares in its committed `.cleo/project.json` (T12716), read-only.
843
+ * Only a committed name counts: a legacy project's cached name or basename
844
+ * differs per checkout, so it is never a drift source.
845
+ *
846
+ * @param projectRoot - Absolute project root.
847
+ * @param cleoHome - Global CLEO home whose registry is read.
848
+ * @param plannedName - The name `project.json` will hold once a planned
849
+ * migration writes it (used by `--resolve` so its dry-run matches the apply).
850
+ * @param rowId - The id the registry row is keyed by right now, when it
851
+ * differs from the declared id (a conflict whose re-key is only planned).
852
+ * @returns The drift report; an unreadable registry is `unavailable`, never `ok`.
853
+ *
854
+ * @example
855
+ * ```ts
856
+ * const drift = await inspectProjectNameDrift('/repo');
857
+ * if (drift.state === 'drift') console.log(drift.remedy);
858
+ * ```
859
+ * @task T12716
860
+ */
861
+ export async function inspectProjectNameDrift(projectRoot, cleoHome, plannedName, rowId) {
862
+ const declared = readDeclaredProjectIdentity(projectRoot);
863
+ const declaredName = declared?.name ?? plannedName ?? null;
864
+ if (!declared) {
865
+ return {
866
+ state: 'undeclared',
867
+ projectId: null,
868
+ declaredName,
869
+ registryName: null,
870
+ message: 'The project declares no identity, so no registry row can be compared.',
871
+ remedy: 'cleo init',
872
+ };
873
+ }
874
+ try {
875
+ const { getNexusRegistryDb } = await import('../store/nexus-sqlite.js');
876
+ const { projectRegistry } = await import('../store/schema/nexus-schema.js');
877
+ const { eq } = await import('drizzle-orm');
878
+ if (declaredName === null) {
879
+ return {
880
+ state: 'no-declared-name',
881
+ projectId: declared.projectId,
882
+ declaredName,
883
+ registryName: null,
884
+ message: 'No .cleo/project.json yet, so the project declares no committed name.',
885
+ remedy: null,
886
+ };
887
+ }
888
+ const db = await getNexusRegistryDb(cleoHome ?? getCleoHome());
889
+ const row = db
890
+ .select({ name: projectRegistry.name })
891
+ .from(projectRegistry)
892
+ .where(eq(projectRegistry.projectId, rowId ?? declared.projectId))
893
+ .get();
894
+ const base = { projectId: declared.projectId, declaredName };
895
+ if (!row) {
896
+ return {
897
+ ...base,
898
+ state: 'not-registered',
899
+ registryName: null,
900
+ message: `No registry row for ${declared.projectId} on this device.`,
901
+ remedy: null,
902
+ };
903
+ }
904
+ if (row.name === declaredName) {
905
+ return {
906
+ ...base,
907
+ state: 'ok',
908
+ registryName: row.name,
909
+ message: `The registry label matches the declared name "${declaredName}".`,
910
+ remedy: null,
911
+ };
912
+ }
913
+ const holder = db
914
+ .select({ projectId: projectRegistry.projectId })
915
+ .from(projectRegistry)
916
+ .where(eq(projectRegistry.name, declaredName))
917
+ .all()
918
+ .find((other) => other.projectId !== (rowId ?? declared.projectId));
919
+ if (holder) {
920
+ return {
921
+ ...base,
922
+ state: 'taken',
923
+ registryName: row.name,
924
+ message: `The registry labels this project "${row.name}", but it declares "${declaredName}", which project ${holder.projectId} already uses on this device.`,
925
+ remedy: `cleo project rename <a-unique-name> (here, or in the project ${holder.projectId})`,
926
+ };
927
+ }
928
+ return {
929
+ ...base,
930
+ state: 'drift',
931
+ registryName: row.name,
932
+ message: `The registry labels this project "${row.name}", but it declares "${declaredName}".`,
933
+ remedy: `${IDENTITY_COMMAND} --resolve (syncs the registry label to the declared name)`,
934
+ };
935
+ }
936
+ catch (error) {
937
+ return {
938
+ state: 'unavailable',
939
+ projectId: declared.projectId,
940
+ declaredName,
941
+ registryName: null,
942
+ message: `Registry unavailable, name drift not checked: ${error instanceof Error ? error.message : String(error)}`,
943
+ remedy: null,
944
+ };
945
+ }
533
946
  }
534
947
  //# sourceMappingURL=project-identity.js.map