@north-light/crouter 0.3.227 → 0.3.229

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 (312) hide show
  1. package/dist/api/client.d.ts +5 -5
  2. package/dist/api/client.js +3 -3
  3. package/dist/api/dto/broker-ops.d.ts +8 -0
  4. package/dist/api/dto/memory.d.ts +8 -7
  5. package/dist/api/dto/memory.js +2 -2
  6. package/dist/builtin-memory/04-base-worker.md +1 -1
  7. package/dist/builtin-memory/insights/init.md +5 -5
  8. package/dist/builtin-memory/internal/INDEX.md +1 -1
  9. package/dist/builtin-memory/internal/agent-shaping.md +11 -11
  10. package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
  11. package/dist/builtin-memory/internal/memory-loading.md +6 -6
  12. package/dist/builtin-memory/internal/plugins.md +1 -1
  13. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +50 -37
  14. package/dist/clients/attach/__tests__/pane-tag-successor.test.js +56 -0
  15. package/dist/clients/attach/__tests__/profile-files.test.js +21 -0
  16. package/dist/clients/attach/chrome/inbox-strip.js +11 -7
  17. package/dist/clients/attach/session/pane-tag.d.ts +28 -2
  18. package/dist/clients/attach/session/pane-tag.js +48 -22
  19. package/dist/clients/attach/session/profile-files.d.ts +6 -0
  20. package/dist/clients/attach/session/profile-files.js +44 -11
  21. package/dist/clients/attach/slash/dispatch.js +4 -7
  22. package/dist/clients/attach/viewer.js +588 -596
  23. package/dist/commands/__tests__/surface-reload-target.test.js +21 -0
  24. package/dist/commands/api-client.d.ts +3 -7
  25. package/dist/commands/api-client.js +7 -12
  26. package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +292 -0
  27. package/dist/commands/memory/__tests__/repository-root-lint.test.js +146 -0
  28. package/dist/commands/memory/delete.js +51 -24
  29. package/dist/commands/memory/edit.js +50 -10
  30. package/dist/commands/memory/find.js +105 -56
  31. package/dist/commands/memory/history.js +51 -31
  32. package/dist/commands/memory/lint.d.ts +0 -9
  33. package/dist/commands/memory/lint.js +78 -322
  34. package/dist/commands/memory/list.d.ts +12 -4
  35. package/dist/commands/memory/list.js +64 -25
  36. package/dist/commands/memory/move.js +103 -73
  37. package/dist/commands/memory/origin.js +35 -5
  38. package/dist/commands/memory/read.d.ts +4 -0
  39. package/dist/commands/memory/read.js +132 -80
  40. package/dist/commands/memory/shared.d.ts +115 -25
  41. package/dist/commands/memory/shared.js +331 -74
  42. package/dist/commands/memory/write.js +72 -21
  43. package/dist/commands/memory.js +2 -2
  44. package/dist/commands/node/create.d.ts +1 -1
  45. package/dist/commands/node/create.js +1 -1
  46. package/dist/commands/node/inspect.js +1 -1
  47. package/dist/commands/node/lifecycle.js +5 -5
  48. package/dist/commands/pkg/market-manage.js +25 -24
  49. package/dist/commands/pkg/plugin-manage.d.ts +22 -6
  50. package/dist/commands/pkg/plugin-manage.js +118 -25
  51. package/dist/commands/pkg/shared.d.ts +8 -0
  52. package/dist/commands/pkg/shared.js +23 -20
  53. package/dist/commands/surface-reload.d.ts +5 -0
  54. package/dist/commands/surface-reload.js +9 -1
  55. package/dist/commands/sys/__tests__/migrate.test.js +1140 -22
  56. package/dist/commands/sys/__tests__/sync-project-guidance.test.js +218 -0
  57. package/dist/commands/sys/migrate.js +126 -140
  58. package/dist/commands/sys/panels/profiles-panel.d.ts +66 -0
  59. package/dist/commands/sys/panels/profiles-panel.js +599 -0
  60. package/dist/commands/sys/settings-shell.d.ts +4 -2
  61. package/dist/commands/sys/settings-shell.js +35 -4
  62. package/dist/commands/sys/settings.js +21 -5
  63. package/dist/commands/sys/sync-deps.d.ts +2 -0
  64. package/dist/commands/sys/sync-deps.js +26 -15
  65. package/dist/commands/sys/sync-project-guidance.js +228 -145
  66. package/dist/commands/sys/sync-skills.js +28 -17
  67. package/dist/commands/sys/update.js +11 -3
  68. package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +15 -2
  69. package/dist/core/__tests__/config-change-delta.test.js +106 -0
  70. package/dist/core/__tests__/context-intro.test.js +13 -9
  71. package/dist/core/__tests__/daemon-boot.test.js +22 -16
  72. package/dist/core/__tests__/fixtures/memory-slash-live-probe.js +5 -1
  73. package/dist/core/__tests__/human-deliver.test.js +19 -0
  74. package/dist/core/__tests__/inline-memory-refs.test.js +50 -46
  75. package/dist/core/__tests__/{serial → integration}/broker-fork-seam.test.js +1 -1
  76. package/dist/core/__tests__/{serial → integration}/broker-sdk-wiring.test.js +1 -1
  77. package/dist/core/__tests__/{serial → integration}/broker-snapshot-history.test.js +1 -1
  78. package/dist/core/__tests__/{serial → integration}/command-plugins.test.js +94 -3
  79. package/dist/core/__tests__/{serial → integration}/deferred-no-wake.test.js +1 -1
  80. package/dist/core/__tests__/{serial → integration}/flagship-lifecycle.test.js +13 -27
  81. package/dist/core/__tests__/{serial → integration}/host-teardown-process-group.test.js +1 -1
  82. package/dist/core/__tests__/{serial → integration}/human-deliver-e2e.test.js +1 -1
  83. package/dist/core/__tests__/{live-mutation-verbs.test.js → integration/live-mutation-verbs.test.js} +22 -33
  84. package/dist/core/__tests__/{serial → integration}/live-mutation.test.js +21 -41
  85. package/dist/core/__tests__/integration/refresh-stall-recycle.test.d.ts +1 -0
  86. package/dist/core/__tests__/{serial → integration}/refresh-stall-recycle.test.js +1 -1
  87. package/dist/core/__tests__/integration/revive.test.d.ts +1 -0
  88. package/dist/core/__tests__/{serial → integration}/revive.test.js +38 -19
  89. package/dist/core/__tests__/integration/spawn-root.test.d.ts +1 -0
  90. package/dist/core/__tests__/{serial → integration}/spawn-root.test.js +104 -1
  91. package/dist/core/__tests__/integration/subscription-delivery.test.d.ts +1 -0
  92. package/dist/core/__tests__/{serial → integration}/subscription-delivery.test.js +1 -1
  93. package/dist/core/__tests__/integration/tmux-surface.test.d.ts +1 -0
  94. package/dist/core/__tests__/{serial → integration}/tmux-surface.test.js +1 -1
  95. package/dist/core/__tests__/integration/worktree-land.test.d.ts +1 -0
  96. package/dist/core/__tests__/integration/worktree-land.test.js +400 -0
  97. package/dist/core/__tests__/integration/worktree-reap.test.d.ts +1 -0
  98. package/dist/core/__tests__/{serial/worktree.test.js → integration/worktree-reap.test.js} +6 -338
  99. package/dist/core/__tests__/kickoff.test.js +16 -5
  100. package/dist/core/__tests__/memory-resolver-precedence.test.js +122 -91
  101. package/dist/core/__tests__/nested-store-discovery.test.js +5 -3
  102. package/dist/core/__tests__/on-read-crouter-home-fence.test.js +2 -1
  103. package/dist/core/__tests__/on-read-dedup-resume.test.js +39 -27
  104. package/dist/core/__tests__/on-read-nested-store.test.js +19 -13
  105. package/dist/core/__tests__/profile-project-memory-delivery.test.js +138 -44
  106. package/dist/core/__tests__/repository-association.test.d.ts +1 -0
  107. package/dist/core/__tests__/repository-association.test.js +153 -0
  108. package/dist/core/__tests__/repository-root-identity.test.d.ts +1 -0
  109. package/dist/core/__tests__/repository-root-identity.test.js +219 -0
  110. package/dist/core/__tests__/review-model-floor.test.js +12 -4
  111. package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.js +27 -11
  112. package/dist/core/__tests__/warm-claim-preference-snapshot.test.d.ts +1 -0
  113. package/dist/core/__tests__/warm-claim-preference-snapshot.test.js +40 -0
  114. package/dist/core/canvas/canvas.d.ts +3 -0
  115. package/dist/core/canvas/canvas.js +30 -10
  116. package/dist/core/canvas/db.js +28 -1
  117. package/dist/core/canvas/paths.d.ts +4 -6
  118. package/dist/core/canvas/paths.js +8 -6
  119. package/dist/core/canvas/render-source.js +2 -2
  120. package/dist/core/canvas/types.d.ts +9 -13
  121. package/dist/core/config.d.ts +3 -3
  122. package/dist/core/config.js +3 -3
  123. package/dist/core/exclusive-lock.d.ts +2 -0
  124. package/dist/core/exclusive-lock.js +21 -0
  125. package/dist/core/git.d.ts +0 -1
  126. package/dist/core/git.js +0 -3
  127. package/dist/core/human/__tests__/integration/inbox-core.test.d.ts +1 -0
  128. package/dist/core/human/feedback-companion.js +3 -0
  129. package/dist/core/human/scan.js +8 -1
  130. package/dist/core/keybindings/catalog.d.ts +2 -2
  131. package/dist/core/keybindings/catalog.js +2 -1
  132. package/dist/core/memory/doc-link-grammar.js +2 -2
  133. package/dist/core/memory/history.d.ts +24 -0
  134. package/dist/core/memory/history.js +66 -1
  135. package/dist/core/memory/identity.d.ts +65 -0
  136. package/dist/core/memory/identity.js +185 -0
  137. package/dist/core/memory/inline-ref-guidance.d.ts +1 -1
  138. package/dist/core/memory/inline-ref-guidance.js +1 -1
  139. package/dist/core/memory/inline-ref-inventory.d.ts +3 -10
  140. package/dist/core/memory/inline-ref-inventory.js +30 -62
  141. package/dist/core/memory/lint.d.ts +129 -0
  142. package/dist/core/memory/lint.js +517 -0
  143. package/dist/core/memory/project-namespace.d.ts +31 -0
  144. package/dist/core/memory/project-namespace.js +85 -0
  145. package/dist/core/memory/repository-association.d.ts +31 -0
  146. package/dist/core/memory/repository-association.js +129 -0
  147. package/dist/core/memory/tree.d.ts +39 -0
  148. package/dist/core/memory/tree.js +93 -0
  149. package/dist/core/memory-resolver.d.ts +193 -90
  150. package/dist/core/memory-resolver.js +461 -375
  151. package/dist/core/nested-stores.js +6 -13
  152. package/dist/core/profiles/select.d.ts +4 -1
  153. package/dist/core/profiles/select.js +108 -48
  154. package/dist/core/review/__tests__/capture-origin.test.js +2 -2
  155. package/dist/core/review/__tests__/stage-identity.test.js +2 -2
  156. package/dist/core/review/companion.js +11 -2
  157. package/dist/core/runtime/bearings.d.ts +2 -2
  158. package/dist/core/runtime/bearings.js +3 -3
  159. package/dist/core/runtime/broker/daemon-ops.d.ts +2 -2
  160. package/dist/core/runtime/broker/rebind.js +5 -0
  161. package/dist/core/runtime/broker-extension-render.d.ts +7 -3
  162. package/dist/core/runtime/broker-extension-render.js +10 -5
  163. package/dist/core/runtime/broker-persona-guidance.d.ts +19 -5
  164. package/dist/core/runtime/broker-persona-guidance.js +122 -27
  165. package/dist/core/runtime/deliver-live.d.ts +16 -4
  166. package/dist/core/runtime/deliver-live.js +29 -15
  167. package/dist/core/runtime/kickoff.d.ts +3 -3
  168. package/dist/core/runtime/kickoff.js +8 -16
  169. package/dist/core/runtime/launch.js +1 -1
  170. package/dist/core/runtime/lifecycle.js +2 -3
  171. package/dist/core/runtime/nodes.d.ts +3 -4
  172. package/dist/core/runtime/nodes.js +3 -4
  173. package/dist/core/runtime/persona.d.ts +8 -12
  174. package/dist/core/runtime/persona.js +24 -96
  175. package/dist/core/runtime/promote.d.ts +2 -2
  176. package/dist/core/runtime/promote.js +8 -21
  177. package/dist/core/runtime/revive.js +20 -17
  178. package/dist/core/runtime/spawn.js +18 -7
  179. package/dist/core/runtime/tmux-bindings.js +2 -3
  180. package/dist/core/runtime/warm-pool.js +3 -4
  181. package/dist/core/scope.js +2 -0
  182. package/dist/core/self-update.d.ts +0 -2
  183. package/dist/core/self-update.js +2 -35
  184. package/dist/core/substrate/__tests__/surface-match-memory-read.test.d.ts +1 -0
  185. package/dist/core/substrate/__tests__/surface-match-memory-read.test.js +28 -0
  186. package/dist/core/substrate/index.d.ts +2 -2
  187. package/dist/core/substrate/index.js +1 -1
  188. package/dist/core/substrate/injected-store.d.ts +43 -27
  189. package/dist/core/substrate/injected-store.js +208 -104
  190. package/dist/core/substrate/listings.d.ts +19 -12
  191. package/dist/core/substrate/listings.js +75 -52
  192. package/dist/core/substrate/on-read-node.d.ts +4 -7
  193. package/dist/core/substrate/on-read-node.js +6 -8
  194. package/dist/core/substrate/on-read.d.ts +22 -25
  195. package/dist/core/substrate/on-read.js +103 -147
  196. package/dist/core/substrate/render-node.d.ts +4 -7
  197. package/dist/core/substrate/render-node.js +5 -7
  198. package/dist/core/substrate/render.d.ts +21 -3
  199. package/dist/core/substrate/render.js +291 -223
  200. package/dist/core/substrate/schema.d.ts +1 -13
  201. package/dist/core/substrate/schema.js +5 -40
  202. package/dist/core/substrate/session-cache.d.ts +14 -4
  203. package/dist/core/substrate/session-cache.js +40 -22
  204. package/dist/core/substrate/surface-match.d.ts +9 -7
  205. package/dist/core/substrate/surface-match.js +26 -25
  206. package/dist/daemon/__tests__/helpers/source-daemon.d.ts +30 -0
  207. package/dist/daemon/__tests__/helpers/source-daemon.js +174 -0
  208. package/dist/daemon/__tests__/integration/migration-startup.test.d.ts +1 -0
  209. package/dist/daemon/__tests__/integration/migration-startup.test.js +97 -0
  210. package/dist/daemon/api/__tests__/bridge-heartbeat.test.js +21 -5
  211. package/dist/daemon/api/handlers/broker-ops.js +21 -16
  212. package/dist/daemon/api/handlers/memory.js +2 -0
  213. package/dist/daemon/crtrd.js +2 -0
  214. package/dist/daemon/human/finish.js +8 -1
  215. package/dist/daemon/manage.d.ts +0 -1
  216. package/dist/daemon/manage.js +7 -16
  217. package/dist/daemon/startup-policy.d.ts +1 -0
  218. package/dist/daemon/startup-policy.js +1 -0
  219. package/dist/migrations/001-surfaces-frontmatter.js +21 -109
  220. package/dist/migrations/002-profile-project-memory.js +1 -0
  221. package/dist/migrations/003-repository-root-memory-identity/front-door.d.ts +26 -0
  222. package/dist/migrations/003-repository-root-memory-identity/front-door.js +231 -0
  223. package/dist/migrations/003-repository-root-memory-identity/index.d.ts +2 -0
  224. package/dist/migrations/003-repository-root-memory-identity/index.js +513 -0
  225. package/dist/migrations/003-repository-root-memory-identity/references.d.ts +95 -0
  226. package/dist/migrations/003-repository-root-memory-identity/references.js +469 -0
  227. package/dist/migrations/003-repository-root-memory-identity/repository-facts.d.ts +39 -0
  228. package/dist/migrations/003-repository-root-memory-identity/repository-facts.js +349 -0
  229. package/dist/migrations/__tests__/activation-concurrency.test.d.ts +1 -0
  230. package/dist/migrations/__tests__/activation-concurrency.test.js +145 -0
  231. package/dist/migrations/__tests__/activation.test.d.ts +1 -0
  232. package/dist/migrations/__tests__/activation.test.js +149 -0
  233. package/dist/migrations/__tests__/deletion-and-root-declaration.test.d.ts +1 -0
  234. package/dist/migrations/__tests__/deletion-and-root-declaration.test.js +149 -0
  235. package/dist/migrations/activation.d.ts +16 -0
  236. package/dist/migrations/activation.js +78 -0
  237. package/dist/migrations/convergent.d.ts +14 -3
  238. package/dist/migrations/convergent.js +21 -10
  239. package/dist/migrations/corpus.d.ts +78 -0
  240. package/dist/migrations/corpus.js +497 -0
  241. package/dist/migrations/frontmatter-splice.d.ts +15 -0
  242. package/dist/migrations/frontmatter-splice.js +176 -0
  243. package/dist/migrations/registry.d.ts +6 -1
  244. package/dist/migrations/registry.js +7 -2
  245. package/dist/migrations/runner.d.ts +41 -0
  246. package/dist/migrations/runner.js +81 -0
  247. package/dist/migrations/types.d.ts +148 -9
  248. package/dist/migrations/types.js +9 -2
  249. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +225 -17
  250. package/dist/pi-extensions/__tests__/canvas-goal-capture-envelope.test.js +11 -3
  251. package/dist/pi-extensions/canvas-context-intro.d.ts +3 -5
  252. package/dist/pi-extensions/canvas-context-intro.js +50 -46
  253. package/dist/pi-extensions/canvas-doc-substrate.d.ts +1 -8
  254. package/dist/pi-extensions/canvas-doc-substrate.js +55 -122
  255. package/dist/pi-extensions/canvas-stophook.js +6 -13
  256. package/dist/shared/generated-context.d.ts +0 -3
  257. package/dist/shared/generated-context.js +0 -57
  258. package/dist/shared/tool-groups.js +2 -3
  259. package/dist/types.d.ts +8 -11
  260. package/dist/types.js +4 -31
  261. package/package.json +5 -4
  262. package/runtime.lock.json +2 -2
  263. package/dist/builtin-memory/05-kinds/design/00-base.md +0 -17
  264. package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +0 -15
  265. package/dist/builtin-memory/05-kinds/design/design-contract.md +0 -19
  266. package/dist/builtin-memory/05-kinds/developer/00-base.md +0 -17
  267. package/dist/builtin-memory/05-kinds/developer/01-orchestrator.md +0 -15
  268. package/dist/builtin-memory/05-kinds/plan/00-base.md +0 -17
  269. package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +0 -17
  270. package/dist/builtin-memory/05-kinds/plan/plan-contract.md +0 -28
  271. package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +0 -15
  272. package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +0 -15
  273. package/dist/builtin-memory/05-kinds/plan/reviewers/lens-contract.md +0 -13
  274. package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +0 -17
  275. package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +0 -17
  276. package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +0 -17
  277. package/dist/builtin-memory/05-kinds/spec/00-base.md +0 -17
  278. package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +0 -15
  279. package/dist/builtin-memory/05-kinds/spec/requirements.md +0 -15
  280. package/dist/builtin-memory/design/guide.md +0 -35
  281. package/dist/builtin-memory/design/roadmap.md +0 -21
  282. package/dist/builtin-memory/development.md +0 -113
  283. package/dist/builtin-memory/plan/roadmap.md +0 -25
  284. package/dist/builtin-memory/spec/guide.md +0 -53
  285. package/dist/builtin-memory/spec/requirements.md +0 -29
  286. package/dist/builtin-memory/spec/roadmap.md +0 -36
  287. package/dist/builtin-memory/testing.md +0 -39
  288. /package/dist/api/__tests__/{serial → integration}/client.test.d.ts +0 -0
  289. /package/dist/api/__tests__/{serial → integration}/client.test.js +0 -0
  290. /package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/{serial → integration}/provider-rotation.test.ts +0 -0
  291. /package/dist/clients/{inbox/__tests__/serial/inbox-controller.test.d.ts → attach/__tests__/pane-tag-successor.test.d.ts} +0 -0
  292. /package/dist/clients/{inbox/__tests__/serial/mount-panel.test.d.ts → attach/__tests__/profile-files.test.d.ts} +0 -0
  293. /package/dist/{core/__tests__/live-mutation-verbs.test.d.ts → clients/inbox/__tests__/integration/inbox-controller.test.d.ts} +0 -0
  294. /package/dist/clients/inbox/__tests__/{serial → integration}/inbox-controller.test.js +0 -0
  295. /package/dist/{core/__tests__/serial/broker-fork-seam.test.d.ts → clients/inbox/__tests__/integration/mount-panel.test.d.ts} +0 -0
  296. /package/dist/clients/inbox/__tests__/{serial → integration}/mount-panel.test.js +0 -0
  297. /package/dist/{core/__tests__/serial/broker-sdk-wiring.test.d.ts → commands/__tests__/surface-reload-target.test.d.ts} +0 -0
  298. /package/dist/{core/__tests__/serial/broker-snapshot-history.test.d.ts → commands/memory/__tests__/command-selector-and-mutation-guards.test.d.ts} +0 -0
  299. /package/dist/{core/__tests__/serial/command-plugins.test.d.ts → commands/memory/__tests__/repository-root-lint.test.d.ts} +0 -0
  300. /package/dist/{core/__tests__/serial/deferred-no-wake.test.d.ts → commands/sys/__tests__/sync-project-guidance.test.d.ts} +0 -0
  301. /package/dist/core/__tests__/{serial/flagship-lifecycle.test.d.ts → config-change-delta.test.d.ts} +0 -0
  302. /package/dist/core/__tests__/{serial/host-teardown-process-group.test.d.ts → integration/broker-fork-seam.test.d.ts} +0 -0
  303. /package/dist/core/__tests__/{serial/human-deliver-e2e.test.d.ts → integration/broker-sdk-wiring.test.d.ts} +0 -0
  304. /package/dist/core/__tests__/{serial/live-mutation.test.d.ts → integration/broker-snapshot-history.test.d.ts} +0 -0
  305. /package/dist/core/__tests__/{serial/refresh-stall-recycle.test.d.ts → integration/command-plugins.test.d.ts} +0 -0
  306. /package/dist/core/__tests__/{serial/revive.test.d.ts → integration/deferred-no-wake.test.d.ts} +0 -0
  307. /package/dist/core/__tests__/{serial/spawn-root.test.d.ts → integration/flagship-lifecycle.test.d.ts} +0 -0
  308. /package/dist/core/__tests__/{serial/subscription-delivery.test.d.ts → integration/host-teardown-process-group.test.d.ts} +0 -0
  309. /package/dist/core/__tests__/{serial/tmux-surface.test.d.ts → integration/human-deliver-e2e.test.d.ts} +0 -0
  310. /package/dist/core/__tests__/{serial/worktree.test.d.ts → integration/live-mutation-verbs.test.d.ts} +0 -0
  311. /package/dist/core/{human/__tests__/serial/inbox-core.test.d.ts → __tests__/integration/live-mutation.test.d.ts} +0 -0
  312. /package/dist/core/human/__tests__/{serial → integration}/inbox-core.test.js +0 -0
@@ -0,0 +1,218 @@
1
+ import assert from 'node:assert/strict';
2
+ import { spawnSync } from 'node:child_process';
3
+ import { existsSync, mkdtempSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { dirname, join } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import test from 'node:test';
8
+ import { lintMemoryStore } from '../../../core/memory/lint.js';
9
+ import { openProjectMemoryStore } from '../../../core/memory-resolver.js';
10
+ const HERE = dirname(fileURLToPath(import.meta.url));
11
+ const CLI = join(HERE, '../../../cli.ts');
12
+ const TSX_LOADER = import.meta.resolve('tsx/esm');
13
+ function write(path, content) {
14
+ mkdirSync(dirname(path), { recursive: true });
15
+ writeFileSync(path, content);
16
+ }
17
+ /** Store keys and target ids are realpaths, so the fixture must be one too:
18
+ * macOS hands `mkdtemp` a `/var/folders` path whose realpath is `/private/...`. */
19
+ function fixtureHome(prefix) {
20
+ return realpathSync(mkdtempSync(join(tmpdir(), prefix)));
21
+ }
22
+ /** A home the ambient node's own CRTR_* environment cannot leak into. */
23
+ function syncEnv(home) {
24
+ const env = { ...process.env, HOME: home };
25
+ for (const key of Object.keys(env)) {
26
+ if (key.startsWith('CRTR_'))
27
+ delete env[key];
28
+ }
29
+ env['CRTR_NO_AUTO_INIT'] = '1';
30
+ env['CRTR_NO_AUTO_UPDATE'] = '1';
31
+ return env;
32
+ }
33
+ function runSyncRaw(home, opts = {}) {
34
+ const env = syncEnv(home);
35
+ if (opts.profileId !== undefined)
36
+ env['CRTR_PROFILE_ID'] = opts.profileId;
37
+ return spawnSync(process.execPath, [
38
+ '--conditions=crtr-src',
39
+ '--import',
40
+ TSX_LOADER,
41
+ CLI,
42
+ 'sys',
43
+ 'sync',
44
+ 'project-guidance',
45
+ ...(opts.source === undefined ? [] : ['--source', opts.source]),
46
+ '--json',
47
+ ...(opts.overwrite === true ? ['--overwrite'] : []),
48
+ ], { cwd: opts.cwd ?? home, env, encoding: 'utf8' });
49
+ }
50
+ function runSync(home, opts = {}) {
51
+ const result = runSyncRaw(home, opts);
52
+ assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
53
+ return JSON.parse(result.stdout);
54
+ }
55
+ function initializeRepository(path) {
56
+ mkdirSync(path, { recursive: true });
57
+ const result = spawnSync('git', ['init', '--quiet', path], { encoding: 'utf8' });
58
+ assert.equal(result.status, 0, result.stderr);
59
+ }
60
+ function writeProfile(home, profileId, project) {
61
+ write(join(home, '.crouter', 'profiles', profileId, 'profile.json'), JSON.stringify({
62
+ schema_version: 4,
63
+ name: 'sync fixture',
64
+ projects: [{ path: realpathSync(project), memory: 'content' }],
65
+ home: realpathSync(project),
66
+ paused_at: null,
67
+ created_at: '2026-01-01T00:00:00.000Z',
68
+ last_used_at: null,
69
+ }));
70
+ }
71
+ test('sync project-guidance --overwrite preserves an authored root and still imports rules', () => {
72
+ const home = fixtureHome('crtr-sync-project-guidance-');
73
+ const project = join(home, 'my-project');
74
+ initializeRepository(project);
75
+ const authoredRoot = `---
76
+ kind: knowledge
77
+ namespace: my-project
78
+ when-and-why-to-read: When working in my-project, this knowledge should be read because it holds hand-authored operating guidance.
79
+ short-form: Hand-authored project guide, not migrated.
80
+ surfaces:
81
+ - on: workspace-open
82
+ at: content
83
+ - on: read
84
+ match: ./**
85
+ at: content
86
+ ---
87
+ # My Project
88
+
89
+ This is the real, hand-written operating guide. It must survive a rules-only sync.
90
+ `;
91
+ write(join(project, '.crouter', 'memory', 'INDEX.md'), authoredRoot);
92
+ write(join(project, '.claude', 'rules', 'lint.md'), `---
93
+ description: Lint rules
94
+ paths: "src/**/*.ts"
95
+ ---
96
+ # Lint
97
+
98
+ Run the linter before committing.
99
+ `);
100
+ const before = readFileSync(join(project, '.crouter', 'memory', 'INDEX.md'), 'utf8');
101
+ const output = runSync(home, { source: project, overwrite: true });
102
+ const after = readFileSync(join(project, '.crouter', 'memory', 'INDEX.md'), 'utf8');
103
+ assert.equal(after, before, 'authored root must not be replaced by the synthetic placeholder');
104
+ const rootRow = output.results.find((r) => r.target.endsWith(join('.crouter', 'memory', 'INDEX.md')));
105
+ assert.equal(rootRow, undefined, 'an already-authored root must not appear as an import/skip row');
106
+ const ruleRow = output.results.find((r) => r.source.endsWith(join('.claude', 'rules', 'lint.md')));
107
+ assert.ok(ruleRow, 'the rule must still be imported');
108
+ assert.equal(ruleRow?.status, 'imported');
109
+ const ruleDoc = readFileSync(ruleRow.target, 'utf8');
110
+ assert.match(ruleDoc, /Run the linter before committing\./);
111
+ });
112
+ test('sync project-guidance writes one repository-root declaration for each repository', () => {
113
+ const home = fixtureHome('crtr-sync-project-guidance-');
114
+ const workspace = join(home, 'workspace');
115
+ const alpha = join(workspace, 'alpha-repository');
116
+ const beta = join(workspace, 'beta-repository');
117
+ initializeRepository(alpha);
118
+ initializeRepository(beta);
119
+ write(join(alpha, 'CLAUDE.md'), '# Alpha Repository\n');
120
+ write(join(beta, 'AGENTS.md'), '# Beta Repository\n');
121
+ const output = runSync(home, { source: workspace });
122
+ for (const [repository, namespace] of [[alpha, 'alpha-repository'], [beta, 'beta-repository']]) {
123
+ const declaration = join(repository, '.crouter', 'memory', 'INDEX.md');
124
+ assert.equal(existsSync(declaration), true, `the repository root owns ${namespace}'s declaration`);
125
+ assert.match(readFileSync(declaration, 'utf8'), new RegExp(`namespace: ${namespace}`));
126
+ assert.ok(output.results.some((row) => row.target === declaration && row.name === namespace));
127
+ }
128
+ assert.equal(output.results.filter((row) => row.target.endsWith(join('.crouter', 'memory', 'INDEX.md'))).length, 2);
129
+ });
130
+ test('sync project-guidance converts a nested guide without repository-root metadata', () => {
131
+ const home = fixtureHome('crtr-sync-project-guidance-');
132
+ const repository = join(home, 'nested-guide-repository');
133
+ const nested = join(repository, 'packages', 'tool');
134
+ initializeRepository(repository);
135
+ write(join(repository, 'CLAUDE.md'), '# Repository Guide\n');
136
+ write(join(nested, 'AGENTS.md'), `---
137
+ surfaces:
138
+ - on: workspace-open
139
+ at: content
140
+ - on: read
141
+ match: ./**
142
+ at: content
143
+ - on: read
144
+ match: ./packages/tool/**/*.ts
145
+ at: content
146
+ ---
147
+ # Nested Guide
148
+
149
+ Nested operating guidance.
150
+ `);
151
+ runSync(home, { source: repository });
152
+ const nestedIndex = readFileSync(join(nested, '.crouter', 'memory', 'INDEX.md'), 'utf8');
153
+ assert.match(nestedIndex, /kind: knowledge/);
154
+ assert.doesNotMatch(nestedIndex, /^namespace:/m);
155
+ assert.doesNotMatch(nestedIndex, /^name:/m);
156
+ assert.doesNotMatch(nestedIndex, /on: workspace-open/);
157
+ assert.match(nestedIndex, /when-and-why-to-read:/);
158
+ assert.match(nestedIndex, /on: read/);
159
+ assert.match(nestedIndex, /packages\/tool\/\*\*\/\*\.ts/);
160
+ assert.match(nestedIndex, /Nested operating guidance\./);
161
+ const lint = lintMemoryStore(openProjectMemoryStore(nested));
162
+ assert.equal(lint.ok, true, JSON.stringify(lint.findings));
163
+ });
164
+ test('sync project-guidance imports nested rules as leaves without a synthetic nested index', () => {
165
+ const home = fixtureHome('crtr-sync-project-guidance-');
166
+ const repository = join(home, 'nested-rules-repository');
167
+ const nested = join(repository, 'packages', 'tool');
168
+ initializeRepository(repository);
169
+ write(join(nested, '.claude', 'rules', 'lint.md'), `---
170
+ description: Nested lint rules
171
+ paths: "src/**/*.ts"
172
+ ---
173
+ # Lint
174
+
175
+ Run nested linting.
176
+ `);
177
+ const output = runSync(home, { source: repository });
178
+ const nestedStore = join(nested, '.crouter', 'memory');
179
+ const rule = join(nestedStore, 'lint.md');
180
+ assert.equal(existsSync(join(repository, '.crouter', 'memory', 'INDEX.md')), true, 'the repository declaration is synthesized');
181
+ assert.equal(existsSync(rule), true);
182
+ assert.equal(existsSync(join(nestedStore, 'INDEX.md')), false, 'rules never synthesize a nested store root');
183
+ assert.ok(output.results.some((row) => row.target === rule && row.name === 'nested-rules-repository/packages/tool/lint'));
184
+ });
185
+ test('sync project-guidance rejects a nested authored namespace at the repository declaration path', () => {
186
+ const home = fixtureHome('crtr-sync-project-guidance-');
187
+ const repository = join(home, 'namespace-rejection-repository');
188
+ const nestedGuide = join(repository, 'packages', 'tool', 'AGENTS.md');
189
+ initializeRepository(repository);
190
+ write(join(repository, 'CLAUDE.md'), '# Repository Guide\n');
191
+ write(nestedGuide, '---\nnamespace: forbidden\n---\n# Nested Guide\n');
192
+ const result = runSyncRaw(home, { source: repository });
193
+ assert.notEqual(result.status, 0);
194
+ assert.match(result.stdout, new RegExp(`project guidance at ${nestedGuide} declares a namespace`));
195
+ assert.match(result.stdout, new RegExp(`${join(repository, '.crouter', 'memory', 'INDEX.md')} is the only declaration for this repository`));
196
+ });
197
+ test('sync project-guidance keeps canonical names stable across cwd, profile widening, and explicit source discovery', () => {
198
+ const names = [];
199
+ for (const context of ['cwd', 'profile', 'source']) {
200
+ const home = fixtureHome(`crtr-sync-project-guidance-${context}-`);
201
+ const repository = join(home, 'canonical-repository');
202
+ initializeRepository(repository);
203
+ write(join(repository, 'CLAUDE.md'), '# Canonical Repository\n');
204
+ const output = context === 'cwd'
205
+ ? runSync(home, { cwd: repository })
206
+ : context === 'profile'
207
+ ? (() => {
208
+ const profileId = 'sync-fixture-12345678';
209
+ writeProfile(home, profileId, repository);
210
+ return runSync(home, { profileId });
211
+ })()
212
+ : runSync(home, { source: repository });
213
+ const root = output.results.find((row) => row.target === join(repository, '.crouter', 'memory', 'INDEX.md'));
214
+ assert.ok(root, `${context} discovers the repository root guide`);
215
+ names.push(root.name);
216
+ }
217
+ assert.deepEqual(names, ['canonical-repository', 'canonical-repository', 'canonical-repository']);
218
+ });
@@ -1,172 +1,158 @@
1
- // `crtr sys migrate` — the on-disk state-migration runner: converge every
2
- // stored profile manifest to the current manifest format, then every writable
3
- // memory store's documents to the current document format. (Journaled
4
- // canvas-db migrations run in the db's own ordered chain when the daemon opens
5
- // it; this leaf owns only the on-disk state.)
6
- //
7
- // Manifests come FIRST and are read as raw bytes under the profile lock: the
8
- // current reader treats a pre-migration manifest exactly like corrupt JSON, so
9
- // `listProfiles()` sees nothing until this lane has run. Store discovery below
10
- // therefore works from the lane's post-migration snapshots — which are equally
11
- // coherent under `--dry-run`, where the same fold ran and nothing was written.
12
- import { dirname, join } from 'node:path';
1
+ // `crtr sys migrate` formats the shared on-disk migration runner's result.
2
+ // Journaled canvas-db migrations remain in the database's ordered chain.
13
3
  import { defineLeaf } from '../../core/command.js';
14
- import { pathExists, realpathOrSelf } from '../../core/fs-utils.js';
15
- import { createMemoryDocSnapshot } from '../../core/memory-resolver.js';
16
- import { listInstalledPlugins, listInstalledPluginsInRoot } from '../../core/resolver.js';
17
- import { pluginMemoryDir, projectScopeRoots, scopeMemoryDir } from '../../core/scope.js';
18
- import { profileMemoryDir, profilesRoot, withProfileManifestLock } from '../../core/profiles/manifest.js';
19
- import { descendantStoreRootsExhaustive } from '../../core/nested-stores.js';
20
- import { memoryDir as nodeMemoryDir } from '../../core/runtime/memory.js';
21
- import { runConvergentMigrations } from '../../migrations/convergent.js';
22
- import { runProfileManifestMigrations } from '../../migrations/profile-manifests.js';
23
- function targetKey(target) {
24
- return `${realpathOrSelf(target.cwd)}\u0000${target.profileId ?? ''}\u0000${target.nodeId ?? ''}`;
25
- }
26
- /** Every writable memory store — the lint corpus minus builtin (package-owned,
27
- * regenerated on every install, so migrating it would fight the package).
28
- * Physical stores dedupe once while retaining every owning target corpus. */
29
- function migratableStores(profiles) {
30
- const stores = new Map();
31
- const add = (dir, target) => {
32
- if (dir === null || dir === '' || !pathExists(dir))
33
- return;
34
- const real = realpathOrSelf(dir);
35
- let store = stores.get(real);
36
- if (store === undefined) {
37
- store = { dir: real, targets: [] };
38
- stores.set(real, store);
39
- }
40
- const key = targetKey(target);
41
- if (!store.targets.some((candidate) => targetKey(candidate) === key))
42
- store.targets.push(target);
43
- };
44
- const addProjectRoots = (roots, profileId) => {
45
- const addRoot = (projectRoot) => {
46
- const target = { cwd: dirname(projectRoot), profileId, nodeId: null };
47
- add(join(projectRoot, 'memory'), target);
48
- for (const plugin of listInstalledPluginsInRoot('project', projectRoot)) {
49
- if (plugin.enabled)
50
- add(pluginMemoryDir(plugin), target);
51
- }
52
- };
53
- for (const root of roots)
54
- addRoot(root);
55
- for (const descendant of descendantStoreRootsExhaustive(roots))
56
- addRoot(descendant);
57
- };
58
- const selectedProfileId = process.env['CRTR_PROFILE_ID'] || null;
59
- addProjectRoots(projectScopeRoots(process.cwd(), null), selectedProfileId);
60
- const profileTargets = [];
61
- for (const { profileId, manifest } of profiles) {
62
- const home = manifest.home !== null && manifest.home !== '' ? manifest.home : null;
63
- const profileTarget = {
64
- cwd: home ?? manifest.projects[0]?.path ?? process.cwd(),
65
- profileId,
66
- nodeId: null,
67
- };
68
- profileTargets.push(profileTarget);
69
- add(profileMemoryDir(profileId), profileTarget);
70
- const roots = [
71
- ...new Set(manifest.projects.flatMap((project) => [join(project.path, '.crouter'), ...projectScopeRoots(project.path, null)])),
72
- ];
73
- addProjectRoots(roots, profileId);
74
- }
75
- const ambientTarget = { cwd: process.cwd(), profileId: selectedProfileId, nodeId: null };
76
- const globalTargets = [ambientTarget, ...profileTargets];
77
- const userDir = scopeMemoryDir('user');
78
- for (const target of globalTargets)
79
- add(userDir, target);
80
- for (const plugin of listInstalledPlugins('user')) {
81
- if (!plugin.enabled)
82
- continue;
83
- for (const target of globalTargets)
84
- add(pluginMemoryDir(plugin), target);
85
- }
86
- const nodeId = process.env['CRTR_NODE_ID'] ?? '';
87
- if (nodeId !== '')
88
- add(nodeMemoryDir(nodeId), { cwd: process.cwd(), profileId: selectedProfileId, nodeId });
89
- return [...stores.values()];
90
- }
4
+ import { general, usage } from '../../core/errors.js';
5
+ import { pathExists } from '../../core/fs-utils.js';
6
+ import { runForcedOnDiskMigrations } from '../../migrations/activation.js';
91
7
  export const sysMigrateLeaf = defineLeaf({
92
8
  name: 'migrate',
93
9
  description: 'converge every profile manifest and writable memory store to the current on-disk format',
94
10
  whenToUse: 'after a crtr update whose release notes name a state migration, when profiles stop resolving on a home that predates the update, or when old-format docs resurface (a restore, a sync, a hand-authored file). Convergent and idempotent: re-running over current state changes nothing, so use --dry-run first only when you want to review the rewrite.',
95
11
  help: {
96
12
  name: 'sys migrate',
97
- summary: 'run the registered state migrations over every profile manifest and writable memory store',
13
+ summary: 'run the registered state migrations over every profile manifest and discovered memory store',
14
+ guide: 'Discovery decides what is migrated: the profiles and the projects they name, the ancestors of this directory, every nested store below any project root it reaches (a node\'s recorded cwd included), every installed plugin (disabled included), the node stores holding documents, and builtin as read-only input. A store outside that frontier is invisible to the run — add it with --dir; discovery_roots reports the frontier the run actually searched. A migration that changes a canonical identity plans across the whole corpus at once and applies in two phases, every reference rewrite before any identity rewrite, so one blocker writes nothing anywhere.',
98
15
  params: [
99
16
  { kind: 'flag', name: 'dry-run', type: 'bool', required: false, constraint: 'Plan and report every rewrite without writing any file.' },
17
+ { kind: 'flag', name: 'dir', type: 'path', required: false, repeatable: true, constraint: 'Project directory (the one containing `.crouter/`, not the memory dir) to add to discovery, once per occurrence. For a store no profile names and this directory does not sit under. The path must exist.' },
100
18
  ],
101
19
  output: [
102
20
  { name: 'profiles', type: 'number', required: true, constraint: 'Profile manifests scanned.' },
103
- { name: 'stores', type: 'number', required: true, constraint: 'Memory stores scanned.' },
104
- { name: 'changed', type: 'number', required: true, constraint: 'Files rewritten (or, under --dry-run, that would be) — manifests and documents together.' },
105
- { name: 'applied', type: 'object[]', required: true, constraint: 'One row per migration that changed files: {store, lane, migration, files}, lane one of profile-manifest, convergent. Empty when the state is already current.' },
21
+ { name: 'stores', type: 'number', required: true, constraint: 'Memory stores discovered, deduped by realpath. Builtin is counted: it is read-only corpus input.' },
22
+ { name: 'changed', type: 'number', required: true, constraint: 'Files rewritten (or, under --dry-run, that would be) — manifests, per-store documents, and corpus phases together.' },
23
+ { name: 'applied', type: 'object[]', required: true, constraint: 'One row per migration that changed files: {store, lane, migration, files}, lane one of profile-manifest, convergent, corpus. Empty when the state is already current.' },
106
24
  { name: 'skipped', type: 'object[]', required: true, constraint: 'Files left untouched because they could not be parsed — a doc whose frontmatter is not valid YAML, or a manifest that is not valid JSON: {store, relPath, error}. Fix the file, then re-run.' },
25
+ { name: 'blockers', type: 'object[]', required: true, constraint: 'Why the corpus lane may not write: {kind, message, store?, target?, path?, name?, paths?}. kind is inaccessible-root, inaccessible-target, parse-failure, namespace, association, front-door, divergent-reference, unsafe-route, collision, package-owned-corpus, or verification. A non-empty list with a corpus migration registered fails the run, and `received` carries this whole report plus {corpus_phase, wrote} — corpus_phase is not-started (the corpus is untouched), planned (--dry-run), or written (both phases landed and verification then failed, so `wrote` is the inventory on disk).' },
26
+ { name: 'discovery_roots', type: 'object[]', required: true, constraint: 'The frontier searched: {reason, root, boundary, detail?}. reason is user, profile, profile-project, cwd-ancestor, descendant, node, plugin, builtin, or explicit-dir; boundary is store, ancestors, descendants, plugins, or nodes.' },
27
+ { name: 'discovered_stores', type: 'object[]', required: true, constraint: 'One row per physical store: {store, scope, namespace, mount_status, writable, reasons, targets, docs}. reasons holds every way the store was reached; targets the target ids that mount it; docs its parseable document count.' },
28
+ { name: 'discovered_targets', type: 'object[]', required: true, constraint: 'One row per resolution view: {id, kind, origin, cwd, profile_id, node_id, stores}. stores lists store keys in that target\'s precedence order.' },
29
+ { name: 'node_metadata', type: 'object', required: true, constraint: '{available, nodes, error?} — whether cwd/profile metadata was readable from every durable meta.json whose node store holds documents. Missing or corrupt metadata blocks corpus writes.' },
30
+ { name: 'repository_namespaces', type: 'object[]', required: true, constraint: 'Repository-root namespace decisions this run makes: {repository_root, repository_key, declaration_path, namespace, source, action}. A nested store inherits this declaration; its reported effective prefix is not a declaration.' },
31
+ { name: 'store_prefixes', type: 'object[]', required: true, constraint: 'Effective prefix facts for each project store: {store_root, owner_dir, repository_root, owner_relative_path, old_prefix, old_rule, effective_prefix, nested_declaration_action, front_door_action}. A nonempty owner_relative_path always reports front_door_action `none`; a nested effective_prefix is derived, not a declaration.' },
32
+ { name: 'renames', type: 'object[]', required: true, constraint: 'Canonical identity changes: {store_root, path, from, to}.' },
33
+ { name: 'references', type: 'object[]', required: true, constraint: 'Rewritten pointers at a changed identity: {store_root, path, kind, from, to}, kind link or memory-read.' },
107
34
  { name: 'dryRun', type: 'boolean', required: true, constraint: 'True when nothing was written (preview only).' },
108
35
  ],
109
36
  outputKind: 'object',
110
37
  effects: [
111
38
  'Rewrites old-format `profile.json` manifests in place (atomic, under the same per-profile lock `profile project add/remove` take). A manifest whose structure cannot be converted fails the whole run — no project setting is ever guessed.',
112
- 'Rewrites old-format memory documents in place (atomic per-file writes) across the writable stores: every profile and every profile-owned project (plus nested descendant stores and installed plugin stores), the current project, user, and this node\'s store. Builtin docs are package-owned and never touched.',
39
+ 'Rewrites old-format memory documents in place (atomic per-file writes) across every writable discovered store: profiles, profile-owned projects, this directory\'s ancestors, nested descendant stores, installed plugin stores, and node stores. Builtin docs are package-owned and never touched.',
40
+ 'Applies corpus-wide identity changes in two ordered phases across every writable store, after the global plan comes back blocker-free; a post-write pass re-reads the corpus from disk and verifies it.',
113
41
  'Under --dry-run: read-only, writes nothing.',
114
42
  ],
115
43
  },
116
44
  run: async (input) => {
117
45
  const dryRun = input.dryRun === true;
118
- const applied = [];
119
- const skipped = [];
120
- const profilesDir = profilesRoot();
121
- const profileRun = runProfileManifestMigrations(profilesDir, { dryRun, withLock: withProfileManifestLock });
122
- for (const row of profileRun.applied) {
123
- applied.push({ store: profilesDir, lane: 'profile-manifest', migration: row.migration, files: [row.path] });
124
- }
125
- for (const row of profileRun.skipped) {
126
- skipped.push({ store: profilesDir, relPath: `${row.profileId}/profile.json`, error: row.error });
127
- }
128
- const stores = migratableStores(profileRun.snapshots);
129
- const snapshots = new Map();
130
- for (const store of stores) {
131
- const resolvers = store.targets.map((target) => (name) => {
132
- const key = targetKey(target);
133
- let snapshot = snapshots.get(key);
134
- if (snapshot === undefined) {
135
- snapshot = createMemoryDocSnapshot(target);
136
- snapshots.set(key, snapshot);
137
- }
138
- return snapshot.legacyDirectoryIndex(name);
139
- });
140
- const result = runConvergentMigrations(store.dir, {
141
- dryRun,
142
- context: {
143
- legacyDirectoryIndex: (name) => {
144
- let replacement = null;
145
- for (const resolve of resolvers) {
146
- const candidate = resolve(name);
147
- if (candidate === null)
148
- return null;
149
- if (replacement !== null && replacement !== candidate)
150
- return null;
151
- replacement = candidate;
152
- }
153
- return replacement;
154
- },
155
- },
46
+ const explicitDirs = input['dir'] ?? [];
47
+ const missingDirs = explicitDirs.filter((dir) => !pathExists(dir));
48
+ if (missingDirs.length > 0) {
49
+ throw usage(`--dir path does not exist: ${missingDirs.join(', ')}`, {
50
+ received: missingDirs,
51
+ field: 'dir',
52
+ next: 'Pass the project directory that contains `.crouter/`, or omit --dir to migrate the discovered frontier only.',
156
53
  });
157
- for (const row of result.applied)
158
- applied.push({ store: store.dir, lane: 'convergent', migration: row.migration, files: row.files });
159
- for (const s of result.skipped)
160
- skipped.push({ store: store.dir, relPath: s.relPath, error: s.error });
161
54
  }
162
- const changed = applied.reduce((n, row) => n + row.files.length, 0);
163
- return {
164
- profiles: profileRun.profiles,
165
- stores: stores.length,
55
+ const result = await runForcedOnDiskMigrations({ dryRun, explicitDirs });
56
+ const { applied, changed, discovery, skipped, snapshots } = result;
57
+ const blockers = result.blockers.map((blocker) => ({
58
+ kind: blocker.kind,
59
+ message: blocker.message,
60
+ ...(blocker.storeRoot === undefined ? {} : { store: blocker.storeRoot }),
61
+ ...(blocker.targetId === undefined ? {} : { target: blocker.targetId }),
62
+ ...(blocker.path === undefined ? {} : { path: blocker.path }),
63
+ ...(blocker.name === undefined ? {} : { name: blocker.name }),
64
+ ...(blocker.paths === undefined ? {} : { paths: blocker.paths }),
65
+ }));
66
+ // One report body, whether the run converged or blocked: a blocked planner
67
+ // stays as inspectable as a successful one, and the failure payload adds only
68
+ // the phase and the write inventory that a success cannot have.
69
+ const report = {
70
+ profiles: result.profiles,
71
+ stores: discovery.stores.length,
166
72
  changed,
167
73
  applied,
168
74
  skipped,
75
+ blockers,
76
+ discovery_roots: discovery.roots.map((row) => ({
77
+ reason: row.reason,
78
+ root: row.root,
79
+ boundary: row.boundary,
80
+ ...(row.detail === undefined ? {} : { detail: row.detail }),
81
+ })),
82
+ discovered_stores: discovery.stores.map((store) => ({
83
+ store: store.key,
84
+ scope: store.descriptor.scope,
85
+ namespace: store.descriptor.namespace,
86
+ mount_status: store.descriptor.mountStatus,
87
+ writable: store.writable,
88
+ reasons: [...new Set(store.discovery.map((row) => row.reason))],
89
+ targets: store.targets,
90
+ docs: snapshots.get(store.key)?.docs.length ?? 0,
91
+ })),
92
+ discovered_targets: discovery.targets.map((target) => ({
93
+ id: target.id,
94
+ kind: target.kind,
95
+ origin: target.origin,
96
+ cwd: target.target.cwd,
97
+ profile_id: target.target.profileId,
98
+ node_id: target.target.nodeId,
99
+ stores: target.storeKeys,
100
+ })),
101
+ node_metadata: discovery.nodeMetadata,
102
+ repository_namespaces: result.repositories.map((row) => ({
103
+ repository_root: row.repositoryRoot,
104
+ repository_key: row.repositoryKey,
105
+ declaration_path: row.declarationPath,
106
+ namespace: row.namespace,
107
+ source: row.source,
108
+ action: row.action,
109
+ })),
110
+ store_prefixes: result.storePrefixes.map((row) => ({
111
+ store_root: row.storeRoot,
112
+ owner_dir: row.ownerDir,
113
+ repository_root: row.repositoryRoot,
114
+ owner_relative_path: row.ownerRelativePath,
115
+ old_prefix: row.oldPrefix,
116
+ old_rule: row.oldRule,
117
+ effective_prefix: row.effectivePrefix,
118
+ nested_declaration_action: row.nestedDeclarationAction,
119
+ front_door_action: row.frontDoorAction,
120
+ })),
121
+ renames: result.renames.map((row) => ({ store_root: row.storeRoot, path: row.path, from: row.from, to: row.to })),
122
+ references: result.references.map((row) => ({
123
+ store_root: row.storeRoot,
124
+ path: row.path,
125
+ kind: row.kind,
126
+ from: row.from,
127
+ to: row.to,
128
+ })),
169
129
  dryRun,
130
+ };
131
+ if (result.blocked) {
132
+ const earlier = applied.filter((row) => row.lane !== 'corpus').flatMap((row) => row.files);
133
+ const corpusPhase = result.corpusPhase;
134
+ const wrote = {
135
+ profile_manifests: result.wrote.profileManifests,
136
+ documents: result.wrote.documents,
137
+ corpus_consumers: result.wrote.corpusConsumers,
138
+ corpus_identities: result.wrote.corpusIdentities,
139
+ };
140
+ const wroteSummary = dryRun
141
+ ? '— this was a --dry-run; nothing was written'
142
+ : corpusPhase === 'written'
143
+ ? `after both write phases — ${wrote.corpus_consumers.length} reference and ${wrote.corpus_identities.length} identity rewrite(s) are on disk and are NOT rolled back`
144
+ : earlier.length > 0
145
+ ? `before any corpus write — the corpus is unchanged, but ${earlier.length} earlier manifest/document rewrite(s) are already on disk`
146
+ : '— nothing was written';
147
+ throw general(`corpus migration ${corpusPhase === 'written' ? 'verification failed with' : 'blocked by'} ${blockers.length} finding(s) ${wroteSummary}`, {
148
+ received: { ...report, corpus_phase: corpusPhase, wrote },
149
+ next: corpusPhase === 'written'
150
+ ? 'Resolve every verification finding against the files listed in received.wrote.corpus_identities and .corpus_consumers, then re-run `crtr sys migrate` — the lane converges from file truth.'
151
+ : 'Resolve every blocker, then re-run `crtr sys migrate` — the earlier lanes are convergent and re-running them is a no-op.',
152
+ });
153
+ }
154
+ return {
155
+ ...report,
170
156
  follow_up: skipped.length > 0
171
157
  ? 'Some files could not be parsed and were left untouched — fix each named file, then re-run `crtr sys migrate`.'
172
158
  : changed === 0
@@ -0,0 +1,66 @@
1
+ import { type SetupPanel } from "../../../core/tui/panel.js";
2
+ export declare class ProfilesPanel implements SetupPanel {
3
+ private readonly cwd;
4
+ private readonly onChange;
5
+ private readonly nameInput;
6
+ private readonly dirInput;
7
+ private entries;
8
+ private listIndex;
9
+ private dirIndex;
10
+ private mode;
11
+ /** Profile the detail level / a confirm is about. */
12
+ private openId;
13
+ private notice;
14
+ private noticeTone;
15
+ private mutating;
16
+ constructor(cwd: string, onChange: () => void);
17
+ badge(): string;
18
+ /** A live text field or a confirm keeps EVERY key, so a name containing `q`
19
+ * can never close the page. The detail level claims only Escape and ← — they
20
+ * mean "back to the profile list" here rather than "jump to the settings
21
+ * rail" — and leaves the page's own navigation and cancel keys alone. */
22
+ ownsInput(data: string): boolean;
23
+ /** Profiles covering cwd first (most recently used within), then the rest by
24
+ * recency, with root last — the ones a startup pick is deciding between sit
25
+ * at the top, where the eye starts. */
26
+ private reload;
27
+ private selected;
28
+ private open;
29
+ private currentDir;
30
+ private pinnedHere;
31
+ private say;
32
+ /** Run a manifest mutation, turning its structured failure into the panel's
33
+ * notice line rather than tearing the page down. */
34
+ private attempt;
35
+ handleInput(data: string, matches?: (id: string, input: string) => boolean): boolean;
36
+ private route;
37
+ private routeText;
38
+ private routeConfirm;
39
+ private routeList;
40
+ private routeDetail;
41
+ /** Seed a field with the value the user most likely wants, cursor at its end
42
+ * (`\x05` — the field is an edit-or-accept, never a blank you must retype). */
43
+ private edit;
44
+ private leaveField;
45
+ private move;
46
+ private moveDir;
47
+ private addCwdTo;
48
+ private togglePin;
49
+ private submitName;
50
+ private submitDir;
51
+ private cycleMemory;
52
+ private setHome;
53
+ private askRemoveDir;
54
+ private removeDir;
55
+ private togglePause;
56
+ private askDelete;
57
+ private deleteOpen;
58
+ footerHint(): string;
59
+ render(width: number, height: number): string[];
60
+ private header;
61
+ private renderList;
62
+ private renderDetail;
63
+ /** A labelled single-line text field, used by every prompt in the panel. */
64
+ private field;
65
+ private deletePrompt;
66
+ }