@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
@@ -4,13 +4,15 @@ import { createHash } from 'node:crypto';
4
4
  import { defineLeaf } from '../../core/command.js';
5
5
  import { notFound, usage, general, network } from '../../core/errors.js';
6
6
  import { findMarketplaceByName, findPluginByName, listAllPlugins } from '../../core/resolver.js';
7
- import { pluginsDir, ensureProjectScopeRoot, userScopeRoot, resolveScopeArg, projectScopeRoot, requireScopeRoot, } from '../../core/scope.js';
7
+ import { NEUTRAL_PROJECT_MEMORY, pluginsDir, ensureProjectScopeRoot, userScopeRoot, resolveScopeArg, projectScopeRoot, requireScopeRoot, } from '../../core/scope.js';
8
8
  import { updateConfig, updateState, ensureScopeInitialized, invalidPluginKindsReasons, invalidPluginBinReasons, invalidPluginRequiresReasons, normalizeRequires } from '../../core/config.js';
9
9
  import { invalidPageComponentsReasons } from '../../core/human/page-catalog.js';
10
- import { pathExists, ensureDir, removePath, nowIso, linkOrCopy, isSymlink, readSymlinkTarget, atomicWriteJson, readText, walkFiles } from '../../core/fs-utils.js';
10
+ import { pathExists, ensureDir, removePath, nowIso, linkOrCopy, isSymlink, readSymlinkTarget, atomicWriteJson, readText, realpathOrSelf, walkFiles } from '../../core/fs-utils.js';
11
11
  import { clone, deriveNameFromUrl, currentSha, gitSync, isGitRepo } from '../../core/git.js';
12
12
  import { readMarketplaceManifest, readPluginManifest, validatePluginManifestMemoryExtensions } from '../../core/manifest.js';
13
13
  import { memoryExtensionValidationCatalog, validateMemoryExtensionValues } from '../../core/memory/extensions.js';
14
+ import { lintMemoryStore, MEMORY_LINT_IDENTITY_RULES } from '../../core/memory/lint.js';
15
+ import { buildCorpusSnapshot, loadCorpusSnapshots, parseFailureBlockers, runCorpusMigrations } from '../../migrations/corpus.js';
14
16
  import { lintSubstrateFrontmatter } from '../../core/substrate/frontmatter-validation.js';
15
17
  import { parseFrontmatterGeneric } from '../../core/frontmatter.js';
16
18
  import { validatePluginCommands, validateEffectiveCommandPlugins } from '../../core/command-plugins/discovery.js';
@@ -21,7 +23,7 @@ import { compileHookRegistry, discoverCandidateHookRegistry, discoverCandidateHo
21
23
  import { hasDeclaredHooks, hookReport } from '../../core/command-hooks/report.js';
22
24
  import { fetchHttpPluginBundle } from '../../core/command-plugins/transport/http-fetch.js';
23
25
  import { SUBTREE_NAMES, coreCommandPaths, coreHookCatalog } from '../../build-root.js';
24
- import { isGitUrl, setPluginEnabled, resolveInstallScope } from './shared.js';
26
+ import { isGitUrl, resolvePluginConfigTarget, setPluginEnabled, resolveInstallScope } from './shared.js';
25
27
  /** Validate a just-installed/updated plugin's command manifest, if it declares
26
28
  * one. Returns undefined for plugins without a `commands` pointer. Never
27
29
  * executes the plugin binary — validation is fully static. */
@@ -158,9 +160,71 @@ async function assertManifestHooksValid(manifest, root, scope, scopeRootPath, wh
158
160
  if (issues.length > 0)
159
161
  throw invalidBundleError(`plugin hook behavior at ${where} is invalid`, issues);
160
162
  }
163
+ function pluginCandidateStore(name, scope, storeRoot) {
164
+ return { scope, storeRoot, namespace: name, plugin: name, mountStatus: 'ready', projectMemory: NEUTRAL_PROJECT_MEMORY };
165
+ }
166
+ function storeRelative(storeRoot, path) {
167
+ const rel = relative(storeRoot, path);
168
+ return rel === '' ? storeRoot : rel;
169
+ }
170
+ function corpusBlockedError(name, where, storeRoot, blockers, wrote) {
171
+ const lines = blockers.map((b) => `- ${b.kind}${b.path === undefined ? '' : ` at ${storeRelative(storeRoot, b.path)}`}: ${b.message}`);
172
+ return general(`plugin "${name}" memory corpus at ${where} cannot be migrated to exact canonical identity:\n${lines.join('\n')}`, {
173
+ blockers,
174
+ next: wrote === 0
175
+ ? `Correct the package's memory corpus, then retry — nothing was written and "${name}" was not activated.`
176
+ : `Correct the package's memory corpus, then re-run — the rewrite is on disk but failed verification, and "${name}" was not activated.`,
177
+ });
178
+ }
179
+ /** Converge a candidate package's memory corpus onto exact canonical identity
180
+ * through the same corpus planner `crtr sys migrate` runs, then refuse the
181
+ * candidate on any identity finding that would leave it unmountable. The
182
+ * package is one store no target mounts, so its links and routes resolve
183
+ * against itself; a blocked plan writes nothing at all. */
184
+ function migratePluginMemory(manifest, root, scope, where, ownership) {
185
+ const storeRoot = join(root, 'memory');
186
+ if (!pathExists(storeRoot))
187
+ return undefined;
188
+ const discovery = {
189
+ roots: [{ reason: 'plugin', root: storeRoot, boundary: 'store', detail: manifest.name }],
190
+ stores: [{
191
+ key: realpathOrSelf(storeRoot),
192
+ descriptor: pluginCandidateStore(manifest.name, scope, storeRoot),
193
+ discovery: [{ reason: 'plugin', root: storeRoot }],
194
+ targets: [],
195
+ writable: true,
196
+ }],
197
+ targets: [],
198
+ blockers: [],
199
+ nodeMetadata: { available: true, nodes: 0 },
200
+ };
201
+ const load = () => {
202
+ const { snapshots, skipped } = loadCorpusSnapshots(discovery);
203
+ return { corpus: buildCorpusSnapshot(discovery, snapshots), blockers: parseFailureBlockers(skipped) };
204
+ };
205
+ const initial = load();
206
+ const run = runCorpusMigrations(initial.corpus, { blockers: initial.blockers, rebuild: () => load().corpus });
207
+ const wrote = run.wrote.consumers.length + run.wrote.identities.length;
208
+ if (run.blocked)
209
+ throw corpusBlockedError(manifest.name, where, storeRoot, run.blockers, wrote);
210
+ // Reference checks are deliberately skipped (no `resolvable`): a package's
211
+ // links reach documents that only exist in the corpus it will be mounted
212
+ // into, which this isolated store cannot see.
213
+ const lint = lintMemoryStore(pluginCandidateStore(manifest.name, scope, storeRoot));
214
+ const identityErrors = lint.errors.filter((finding) => MEMORY_LINT_IDENTITY_RULES.has(finding.rule));
215
+ if (identityErrors.length > 0) {
216
+ const lines = identityErrors.map((f) => `- ${f.rule} at ${storeRelative(storeRoot, f.path)}: ${f.message}`);
217
+ throw general(`plugin "${manifest.name}" memory corpus at ${where} is incompatible with exact canonical identity:\n${lines.join('\n')}`, { issues: identityErrors, next: `Correct the package's memory documents, then retry — "${manifest.name}" was not activated.` });
218
+ }
219
+ return {
220
+ docs: lint.documents,
221
+ migrated: [...new Set([...run.wrote.consumers, ...run.wrote.identities])].map((path) => storeRelative(storeRoot, path)).sort(),
222
+ ...(ownership === 'linked' ? { linkedSource: realpathOrSelf(storeRoot) } : {}),
223
+ };
224
+ }
161
225
  /** Validate a package candidate before it gains an active plugin path. The
162
226
  * candidate namespace shadows any installed declaration of the same name. */
163
- export async function assertPluginCandidateValid(manifest, root, scope, scopeRootPath, where, candidates) {
227
+ export async function assertPluginCandidateValid(manifest, root, scope, scopeRootPath, where, candidates, ownership = 'package') {
164
228
  try {
165
229
  validatePluginManifestMemoryExtensions(manifest);
166
230
  }
@@ -195,6 +259,7 @@ export async function assertPluginCandidateValid(manifest, root, scope, scopeRoo
195
259
  throw general(`plugin memory document ${relative(root, file)} is invalid: ${issue.path}: ${issue.message}`);
196
260
  }
197
261
  }
262
+ return migratePluginMemory(manifest, root, scope, where, ownership);
198
263
  }
199
264
  function invocationTransport(bundle) {
200
265
  return { kind: 'http', endpoint: new URL(bundle.endpoint).origin, ...(bundle.authEnv !== undefined ? { authEnv: bundle.authEnv } : {}) };
@@ -218,12 +283,13 @@ function writeStagedBundle(stagingRoot, manifest, bundle) {
218
283
  }
219
284
  }
220
285
  async function validateStagedBundle(name, scope, root, scopeRootPath, manifest) {
221
- await assertPluginCandidateValid(manifest, root, scope, scopeRootPath, `${root}/.crouter-plugin/plugin.json`);
286
+ const memory = await assertPluginCandidateValid(manifest, root, scope, scopeRootPath, `${root}/.crouter-plugin/plugin.json`);
222
287
  const staged = { name, scope, root, manifest, enabled: true };
223
288
  const validation = validatePluginCommands(staged, new Set(SUBTREE_NAMES), await coreCommandPaths());
224
289
  if (validation.issues.length > 0) {
225
290
  throw invalidBundleError(`bundle staged package for "${name}" is invalid`, validation.issues);
226
291
  }
292
+ return memory;
227
293
  }
228
294
  function isSourceInstalled(plugin) {
229
295
  return plugin.sourceMarketplace !== undefined || isGitRepo(plugin.root) || isSymlink(plugin.root);
@@ -287,10 +353,11 @@ async function replaceBundlePlugin(name, bundleSource, scope, options) {
287
353
  ensureDir(tmpRoot);
288
354
  ensureDir(join(scopeRootPath, 'plugins'));
289
355
  removePath(staging);
356
+ let memory;
290
357
  try {
291
358
  ensureDir(staging);
292
359
  writeStagedBundle(staging, manifest, validated.bundle);
293
- await validateStagedBundle(name, scope, staging, scopeRootPath, manifest);
360
+ memory = await validateStagedBundle(name, scope, staging, scopeRootPath, manifest);
294
361
  let movedPrevious = false;
295
362
  if (pathExists(root)) {
296
363
  renameSync(root, previous);
@@ -322,7 +389,7 @@ async function replaceBundlePlugin(name, bundleSource, scope, options) {
322
389
  const commands = await commandReport(name, scope);
323
390
  if (commands === undefined)
324
391
  throw general(`bundle plugin "${name}" has no staged command report`);
325
- return { name, scope, path: root, transport: 'http', version, docs: validated.bundle.memory.length, kinds: Object.keys(validated.bundle.kinds ?? {}).length, pageComponents: (validated.bundle.page_components ?? []).length, commands };
392
+ return { name, scope, path: root, transport: 'http', version, docs: validated.bundle.memory.length, kinds: Object.keys(validated.bundle.kinds ?? {}).length, pageComponents: (validated.bundle.page_components ?? []).length, commands, ...(memory !== undefined ? { memory } : {}) };
326
393
  }
327
394
  async function installHttpPlugin(name, endpoint, authEnv, scope) {
328
395
  const source = validateHttpInstall(name, endpoint, authEnv);
@@ -451,7 +518,7 @@ async function installFromGit(source, ref, scope) {
451
518
  const manifest = readPluginManifest(staging);
452
519
  if (manifest === null)
453
520
  throw general(`cloned repo does not contain a valid .crouter-plugin/plugin.json: ${source}`);
454
- await assertPluginCandidateValid(manifest, staging, scope, scopeRootPath, `${source}/.crouter-plugin/plugin.json`);
521
+ const memory = await assertPluginCandidateValid(manifest, staging, scope, scopeRootPath, `${source}/.crouter-plugin/plugin.json`);
455
522
  const finalDir = join(pDir, manifest.name);
456
523
  if (pathExists(finalDir))
457
524
  throw general(`plugin "${manifest.name}" is already installed at ${finalDir}`);
@@ -460,7 +527,7 @@ async function installFromGit(source, ref, scope) {
460
527
  updateConfig(scope, (cfg) => {
461
528
  cfg.plugins[manifest.name] = { enabled: true, version: manifest.version };
462
529
  });
463
- return { name: manifest.name, scope, path: finalDir };
530
+ return { name: manifest.name, scope, path: finalDir, ...(memory !== undefined ? { memory } : {}) };
464
531
  }
465
532
  finally {
466
533
  removePath(staging);
@@ -473,7 +540,9 @@ async function installFromLocal(source, scope) {
473
540
  throw notFound(`plugin manifest not found at ${sourcePath}/.crouter-plugin/plugin.json`);
474
541
  }
475
542
  const scopeRootPath = scope === 'project' ? ensureProjectScopeRoot() : userScopeRoot();
476
- await assertPluginCandidateValid(manifest, sourcePath, scope, scopeRootPath, `${sourcePath}/.crouter-plugin/plugin.json`);
543
+ // The install keeps `sourcePath` as a live link, so the caller's own
544
+ // directory is the installed physical corpus and is converged in place.
545
+ const memory = await assertPluginCandidateValid(manifest, sourcePath, scope, scopeRootPath, `${sourcePath}/.crouter-plugin/plugin.json`, undefined, 'linked');
477
546
  ensureScopeInitialized(scope, scopeRootPath);
478
547
  const destDir = join(scopeRootPath, 'plugins', manifest.name);
479
548
  if (pathExists(destDir)) {
@@ -486,7 +555,7 @@ async function installFromLocal(source, scope) {
486
555
  version: manifest.version,
487
556
  };
488
557
  });
489
- return { name: manifest.name, scope, path: destDir };
558
+ return { name: manifest.name, scope, path: destDir, ...(memory !== undefined ? { memory } : {}) };
490
559
  }
491
560
  /** Install one marketplace plugin into a scope. Exported so `sys setup` can
492
561
  * install the default plugin set through the same path `pkg plugin install`
@@ -505,6 +574,7 @@ export async function installFromMarketplace(ref, scope) {
505
574
  if (pathExists(destPluginDir))
506
575
  throw general(`plugin "${ref.plugin}" is already installed at ${destPluginDir}`);
507
576
  let pluginManifest;
577
+ let memory;
508
578
  if (isMarketplaceRelativeSource(entry.source)) {
509
579
  const sourcePath = join(refreshed.marketplace.root, entry.source);
510
580
  if (!pathExists(sourcePath))
@@ -512,7 +582,9 @@ export async function installFromMarketplace(ref, scope) {
512
582
  const candidateManifest = readPluginManifest(sourcePath);
513
583
  if (candidateManifest === null)
514
584
  throw notFound(`plugin manifest not found at ${sourcePath}/.crouter-plugin/plugin.json`);
515
- await assertPluginCandidateValid(candidateManifest, sourcePath, scope, destRoot, `${sourcePath}/.crouter-plugin/plugin.json`);
585
+ // Linked to the marketplace checkout: that directory IS the installed
586
+ // corpus, so it is converged where it lies.
587
+ memory = await assertPluginCandidateValid(candidateManifest, sourcePath, scope, destRoot, `${sourcePath}/.crouter-plugin/plugin.json`, undefined, 'linked');
516
588
  linkOrCopy(sourcePath, destPluginDir);
517
589
  pluginManifest = candidateManifest;
518
590
  }
@@ -524,7 +596,7 @@ export async function installFromMarketplace(ref, scope) {
524
596
  const candidateManifest = readPluginManifest(staging);
525
597
  if (candidateManifest === null)
526
598
  throw notFound(`plugin manifest not found at ${staging}/.crouter-plugin/plugin.json`);
527
- await assertPluginCandidateValid(candidateManifest, staging, scope, destRoot, `${staging}/.crouter-plugin/plugin.json`);
599
+ memory = await assertPluginCandidateValid(candidateManifest, staging, scope, destRoot, `${staging}/.crouter-plugin/plugin.json`);
528
600
  ensureDir(join(destRoot, 'plugins'));
529
601
  renameSync(staging, destPluginDir);
530
602
  pluginManifest = candidateManifest;
@@ -543,7 +615,7 @@ export async function installFromMarketplace(ref, scope) {
543
615
  pluginCfg.version = version;
544
616
  cfg.plugins[ref.plugin] = pluginCfg;
545
617
  });
546
- return { name: ref.plugin, scope, path: destPluginDir };
618
+ return { name: ref.plugin, scope, path: destPluginDir, ...(memory !== undefined ? { memory } : {}) };
547
619
  }
548
620
  async function updateGitPluginCandidate(plugin) {
549
621
  if (isSymlink(plugin.root)) {
@@ -551,16 +623,16 @@ async function updateGitPluginCandidate(plugin) {
551
623
  if (manifest === null)
552
624
  throw notFound(`plugin manifest not found at ${plugin.root}/.crouter-plugin/plugin.json`);
553
625
  const scopeRootPath = plugin.scope === 'project' ? requireScopeRoot('project') : userScopeRoot();
554
- await assertPluginCandidateValid(manifest, plugin.root, plugin.scope, scopeRootPath, `${plugin.root}/.crouter-plugin/plugin.json`);
555
- return { updated: false, sha: currentSha(plugin.root) ?? '', manifest };
626
+ const memory = await assertPluginCandidateValid(manifest, plugin.root, plugin.scope, scopeRootPath, `${plugin.root}/.crouter-plugin/plugin.json`, undefined, 'linked');
627
+ return { updated: false, sha: currentSha(plugin.root) ?? '', manifest, ...(memory !== undefined ? { memory } : {}) };
556
628
  }
557
629
  if (!isGitRepo(plugin.root)) {
558
630
  const manifest = readPluginManifest(plugin.root);
559
631
  if (manifest === null)
560
632
  throw notFound(`plugin manifest not found at ${plugin.root}/.crouter-plugin/plugin.json`);
561
633
  const scopeRootPath = plugin.scope === 'project' ? requireScopeRoot('project') : userScopeRoot();
562
- await assertPluginCandidateValid(manifest, plugin.root, plugin.scope, scopeRootPath, `${plugin.root}/.crouter-plugin/plugin.json`);
563
- return { updated: false, sha: '', manifest };
634
+ const memory = await assertPluginCandidateValid(manifest, plugin.root, plugin.scope, scopeRootPath, `${plugin.root}/.crouter-plugin/plugin.json`);
635
+ return { updated: false, sha: '', manifest, ...(memory !== undefined ? { memory } : {}) };
564
636
  }
565
637
  const remote = gitSync(['config', '--get', 'remote.origin.url'], plugin.root).stdout.trim();
566
638
  if (remote === '')
@@ -578,7 +650,7 @@ async function updateGitPluginCandidate(plugin) {
578
650
  const manifest = readPluginManifest(staging);
579
651
  if (manifest === null)
580
652
  throw notFound(`plugin manifest not found in update candidate: ${staging}/.crouter-plugin/plugin.json`);
581
- await assertPluginCandidateValid(manifest, staging, plugin.scope, scopeRootPath, `${staging}/.crouter-plugin/plugin.json`);
653
+ const memory = await assertPluginCandidateValid(manifest, staging, plugin.scope, scopeRootPath, `${staging}/.crouter-plugin/plugin.json`);
582
654
  renameSync(plugin.root, previous);
583
655
  try {
584
656
  renameSync(staging, plugin.root);
@@ -589,7 +661,7 @@ async function updateGitPluginCandidate(plugin) {
589
661
  }
590
662
  removePath(previous);
591
663
  const sha = currentSha(plugin.root) ?? '';
592
- return { updated: before !== sha, sha, manifest };
664
+ return { updated: before !== sha, sha, manifest, ...(memory !== undefined ? { memory } : {}) };
593
665
  }
594
666
  finally {
595
667
  removePath(staging);
@@ -601,7 +673,7 @@ async function updateOnePlugin(plugin, marketplaceCache, opts) {
601
673
  const sourceInstalled = isSourceInstalled(plugin);
602
674
  if (!sourceInstalled && plugin.manifest.bundle !== undefined) {
603
675
  const replaced = await replaceBundlePlugin(plugin.name, plugin.manifest.bundle, plugin.scope, { enable: plugin.enabled });
604
- return { name: plugin.name, transport: 'http', updated: true, version: replaced.version, docs: replaced.docs, kinds: replaced.kinds, pageComponents: replaced.pageComponents, commands: replaced.commands };
676
+ return { name: plugin.name, transport: 'http', updated: true, version: replaced.version, docs: replaced.docs, kinds: replaced.kinds, pageComponents: replaced.pageComponents, commands: replaced.commands, ...(replaced.memory !== undefined ? { memory: replaced.memory } : {}) };
605
677
  }
606
678
  if (plugin.manifest.transport?.kind === 'http' && !sourceInstalled)
607
679
  rejectLegacyHttpPlugin(plugin.name);
@@ -627,6 +699,7 @@ async function updateOnePlugin(plugin, marketplaceCache, opts) {
627
699
  sha: candidate.sha,
628
700
  ...(commands !== undefined ? { commands } : {}),
629
701
  ...(hooks !== undefined ? { hooks } : {}),
702
+ ...(candidate.memory !== undefined ? { memory: candidate.memory } : {}),
630
703
  ...(warnings.length > 0 ? { warnings } : {}),
631
704
  };
632
705
  }
@@ -657,6 +730,7 @@ export const pluginInstall = defineLeaf({
657
730
  { name: 'commands', type: 'object', required: false, constraint: 'Present only when the plugin declares a command manifest. {mounts: string[] (accepted top-level command names now live for the next invocation), issues: object[] (typed validation issues that rejected a contribution — {code, path?, message, received, expected, next})}. Validated statically; commands are never executed.' },
658
731
  { name: 'hooks', type: 'object', required: false, constraint: 'Present only when the source plugin declares hooks. {manifestPath?, executablePath?, declarations: {target, phase, op, description, effects}[], issues: object[], trust: string}. Declarations, eligible targets, and replacement collisions are validated statically; hook executables are never run.' },
659
732
  { name: 'warnings', type: 'string[]', required: false, constraint: 'Advisory missing PATH executable requirements declared by the installed plugin. The install still succeeds and leaves the plugin enabled.' },
733
+ { name: 'memory', type: 'object', required: false, constraint: 'Present only when the plugin ships a memory store. {docs: integer (documents checked), migrated: string[] (store-relative documents rewritten to reach exact canonical identity), linkedSource?: string (the caller-owned directory converged in place, present only when the install keeps that directory as a live link)}.' },
660
734
  ],
661
735
  outputKind: 'object',
662
736
  effects: [
@@ -665,6 +739,7 @@ export const pluginInstall = defineLeaf({
665
739
  'If the plugin declares a command manifest, its commands go live on the next crtr invocation. Exec commands run trusted local code only when explicitly invoked; HTTP commands call their declared endpoint only when explicitly invoked.',
666
740
  'Source plugin hooks go live on the next crtr invocation. They run implicitly with caller authority, may receive normalized inputs and after results, may block or replace declared targets, and may transmit received data. Hook executables are never run during install or update validation.',
667
741
  'A plugin manifest `requires` declaration is checked against the PATH a node bash receives after install. Missing executables emit a warning with the declared install hint but never fail the install, disable the plugin, or remove its bare-binary contributions.',
742
+ 'Before activation the candidate\u2019s memory corpus is migrated to exact canonical identity and refused if it cannot mount. A staged candidate is migrated in staging and rolls back with the package; a local path or marketplace-relative source stays a live link, so its own directory is rewritten in place, keeps that rewrite whether or not the install completes, and is named as `memory.linkedSource`. A refused corpus writes nothing and installs nothing.',
668
743
  ],
669
744
  },
670
745
  run: async (input) => {
@@ -775,11 +850,26 @@ export const pluginEnable = defineLeaf({
775
850
  { name: 'name', type: 'string', required: true, constraint: 'Plugin name.' },
776
851
  { name: 'scope', type: 'string', required: true, constraint: 'Scope the change was applied in.' },
777
852
  { name: 'enabled', type: 'boolean', required: true, constraint: 'Always true.' },
853
+ { name: 'memory', type: 'object', required: false, constraint: 'Present only when the plugin ships a memory store. {docs: integer (documents checked), migrated: string[] (store-relative documents rewritten to reach exact canonical identity), linkedSource?: string (the caller-owned directory converged in place, present only for a symlinked install)}.' },
778
854
  ],
779
855
  outputKind: 'object',
780
- effects: ['Sets plugin enabled=true in config.json.'],
856
+ effects: [
857
+ 'Validates the installed package before any config change: its memory corpus is migrated to exact canonical identity and refused if it cannot mount. A refusal leaves the plugin disabled.',
858
+ 'Sets plugin enabled=true in config.json.',
859
+ ],
860
+ },
861
+ run: async (input) => {
862
+ const name = input['name'];
863
+ const target = resolvePluginConfigTarget(name, input['scope']);
864
+ if (target === null)
865
+ throw notFound(`plugin not found: ${name}`);
866
+ const manifest = readPluginManifest(target.root);
867
+ if (manifest === null)
868
+ throw notFound(`plugin manifest not found at ${target.root}/.crouter-plugin/plugin.json`);
869
+ const scopeRootPath = target.scope === 'project' ? requireScopeRoot('project') : userScopeRoot();
870
+ const memory = await assertPluginCandidateValid(manifest, target.root, target.scope, scopeRootPath, `${target.root}/.crouter-plugin/plugin.json`, undefined, isSymlink(target.root) ? 'linked' : 'package');
871
+ return { ...(await setPluginEnabled(input, true)), ...(memory !== undefined ? { memory } : {}) };
781
872
  },
782
- run: async (input) => setPluginEnabled(input, true),
783
873
  });
784
874
  export const pluginDisable = defineLeaf({
785
875
  name: 'disable',
@@ -814,10 +904,13 @@ export const pluginUpdate = defineLeaf({
814
904
  { kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Narrows resolution.' },
815
905
  ],
816
906
  output: [
817
- { name: 'updated', type: 'object[]', required: true, constraint: 'One entry per plugin processed: {name, transport?, updated, sha?, version?, docs?, kinds?, pageComponents?, commands?, hooks?, warnings?, error?}. Archive plugins report transport=http and replace their complete directory after an authenticated refetch; version is the archive content identifier, docs is the written memory-document count, kinds is the declared kind-registry entry count, and pageComponents is the declared page-component registration count. sha is present for git updates. A bulk update reports and skips a failed archive plugin. commands is present for validated command plugins: {mounts: string[], issues: object[]}. hooks is present for source hook plugins: {manifestPath?, executablePath?, declarations: {target, phase, op, description, effects}[], issues: object[], trust: string}; it is static and never executes hooks. warnings lists advisory PATH requirements that remain absent after update.' },
907
+ { name: 'updated', type: 'object[]', required: true, constraint: 'One entry per plugin processed: {name, transport?, updated, sha?, version?, docs?, kinds?, pageComponents?, commands?, hooks?, memory?, warnings?, error?}. memory is present for a plugin shipping a memory store and reports the same corpus migration install performs: {docs, migrated, linkedSource?}. Archive plugins report transport=http and replace their complete directory after an authenticated refetch; version is the archive content identifier, docs is the written memory-document count, kinds is the declared kind-registry entry count, and pageComponents is the declared page-component registration count. sha is present for git updates. A bulk update reports and skips a failed archive plugin. commands is present for validated command plugins: {mounts: string[], issues: object[]}. hooks is present for source hook plugins: {manifestPath?, executablePath?, declarations: {target, phase, op, description, effects}[], issues: object[], trust: string}; it is static and never executes hooks. warnings lists advisory PATH requirements that remain absent after update.' },
818
908
  ],
819
909
  outputKind: 'object',
820
- effects: ['Archive plugins unconditionally re-fetch, validate, and replace their complete package; a named failure exits nonzero and preserves the prior package. Bulk update reports a failed archive plugin and continues. Source-installed plugins retain their source update, version, and last_updated behavior.'],
910
+ effects: [
911
+ 'Archive plugins unconditionally re-fetch, validate, and replace their complete package; a named failure exits nonzero and preserves the prior package. Bulk update reports a failed archive plugin and continues. Source-installed plugins retain their source update, version, and last_updated behavior.',
912
+ 'Each candidate passes the same pre-activation memory-corpus migration install performs, with the same linked-source rewrite semantics for a symlinked install.',
913
+ ],
821
914
  },
822
915
  run: async (input) => {
823
916
  const name = input['name'];
@@ -2,4 +2,12 @@ import type { Scope } from '../../types.js';
2
2
  export declare const GIT_URL_RE: RegExp;
3
3
  export declare function isGitUrl(arg: string): boolean;
4
4
  export declare function resolveInstallScope(scopeInput: string | undefined): Scope;
5
+ /** The one installed copy an enable/disable acts on: the first scope in
6
+ * precedence whose plugins dir holds `name`, disabled copies included. Enable
7
+ * validates that exact copy before flipping its config, so both share this
8
+ * resolution rather than each walking the scopes. */
9
+ export declare function resolvePluginConfigTarget(name: string, scopeInput: string | undefined): {
10
+ scope: Scope;
11
+ root: string;
12
+ } | null;
5
13
  export declare function setPluginEnabled(input: Record<string, unknown>, enabled: boolean): Promise<Record<string, unknown>>;
@@ -21,9 +21,11 @@ export function resolveInstallScope(scopeInput) {
21
21
  // Default: project if available, else user
22
22
  return projectScopeRoot() !== null ? 'project' : 'user';
23
23
  }
24
- export async function setPluginEnabled(input, enabled) {
25
- const name = input['name'];
26
- const scopeInput = input['scope'];
24
+ /** The one installed copy an enable/disable acts on: the first scope in
25
+ * precedence whose plugins dir holds `name`, disabled copies included. Enable
26
+ * validates that exact copy before flipping its config, so both share this
27
+ * resolution rather than each walking the scopes. */
28
+ export function resolvePluginConfigTarget(name, scopeInput) {
27
29
  let scopes;
28
30
  if (scopeInput !== undefined) {
29
31
  const resolved = resolveScopeArg(scopeInput);
@@ -34,28 +36,29 @@ export async function setPluginEnabled(input, enabled) {
34
36
  else {
35
37
  scopes = ['project', 'user'].filter((s) => s !== 'project' || projectScopeRoot() !== null);
36
38
  }
37
- let actedScope;
38
39
  for (const scope of scopes) {
39
40
  const pDir = pluginsDir(scope);
40
41
  if (pDir === null)
41
42
  continue;
42
43
  const pluginDir = join(pDir, name);
43
- if (!pathExists(pluginDir))
44
- continue;
45
- updateConfig(scope, (cfg) => {
46
- const entry = cfg.plugins[name];
47
- if (entry !== undefined) {
48
- entry.enabled = enabled;
49
- }
50
- else {
51
- cfg.plugins[name] = { enabled };
52
- }
53
- });
54
- actedScope = scope;
55
- break; // only act on first found scope
44
+ if (pathExists(pluginDir))
45
+ return { scope, root: pluginDir };
56
46
  }
57
- if (actedScope === undefined) {
47
+ return null;
48
+ }
49
+ export async function setPluginEnabled(input, enabled) {
50
+ const name = input['name'];
51
+ const target = resolvePluginConfigTarget(name, input['scope']);
52
+ if (target === null)
58
53
  throw notFound(`plugin not found: ${name}`);
59
- }
60
- return { name, scope: actedScope, enabled };
54
+ updateConfig(target.scope, (cfg) => {
55
+ const entry = cfg.plugins[name];
56
+ if (entry !== undefined) {
57
+ entry.enabled = enabled;
58
+ }
59
+ else {
60
+ cfg.plugins[name] = { enabled };
61
+ }
62
+ });
63
+ return { name, scope: target.scope, enabled };
61
64
  }
@@ -1,2 +1,7 @@
1
1
  import type { LeafDef } from '../core/command.js';
2
+ /** The node a `crtr surface attach to <id>` command line targets. Panes opened
3
+ * by a respawn path (the Markdown file-open bridge, the review companion) carry
4
+ * a shell-quoted id, so the raw command line is not comparable to the canonical
5
+ * one — the target node is. */
6
+ export declare function attachTargetNode(command: string): string | undefined;
2
7
  export declare const surfaceReloadLeaf: LeafDef;
@@ -48,6 +48,14 @@ function paneTreeHasViewer(rootPid, nodeId) {
48
48
  }
49
49
  return false;
50
50
  }
51
+ /** The node a `crtr surface attach to <id>` command line targets. Panes opened
52
+ * by a respawn path (the Markdown file-open bridge, the review companion) carry
53
+ * a shell-quoted id, so the raw command line is not comparable to the canonical
54
+ * one — the target node is. */
55
+ export function attachTargetNode(command) {
56
+ const match = /(?:^|\s)surface attach to\s+(?:'([^']*)'|"([^"]*)"|(\S+))/.exec(command);
57
+ return match?.[1] ?? match?.[2] ?? match?.[3];
58
+ }
51
59
  export const surfaceReloadLeaf = defineLeaf({
52
60
  name: 'reload',
53
61
  description: 'respawn a viewer pane in place onto the current runtime generation',
@@ -94,7 +102,7 @@ export const surfaceReloadLeaf = defineLeaf({
94
102
  if (startCommand !== undefined && startCommand.startsWith('crtr surface attach ')) {
95
103
  // The start command must target the TAGGED node — a mismatch means the
96
104
  // tag is stale or the viewer belongs to another node; don't touch it.
97
- if (startCommand !== viewerCommand) {
105
+ if (attachTargetNode(startCommand) !== nodeId) {
98
106
  displayMessage('crtr: viewer/node mismatch in pane', pane);
99
107
  throw new InputError({ error: 'no_viewer', message: `this pane's viewer command (${startCommand}) does not match its node tag (${nodeId})`, next: 'Pass --pane <pane-id> for a pane running `crtr surface attach to <node>`.' });
100
108
  }