@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
@@ -1,8 +1,23 @@
1
+ import { join } from 'node:path';
1
2
  import { defineLeaf } from '../../core/command.js';
2
3
  import { usage } from '../../core/errors.js';
3
- import { writeText, pathExists } from '../../core/fs-utils.js';
4
+ import { ensureDir, realpathOrSelf, walkFiles, writeText } from '../../core/fs-utils.js';
5
+ import { associateRepository } from '../../core/memory/repository-association.js';
4
6
  import { appendHistoryRecord, buildHistoryRecord, historyLogPathFor, } from '../../core/memory/history.js';
5
- import { DOC_RATIONALE_CONSTRAINT, GUIDE_DOC_LINKS, GUIDE_PREDICATE_VOCABULARY, GUIDE_ROUTING_LINE, GUIDE_SURFACES, MEMORY_SCOPES, resolveWriteTarget, memoryFilePath, buildOrigin, coerceGate, coerceSurface, memoryExtensionCatalogForDoc, memoryExtensionFieldCatalogHelp, overlayParam, parseRequestedExtensionChanges, applyExtensionChanges, serializeMemoryDoc, } from './shared.js';
7
+ import { DOC_RATIONALE_CONSTRAINT, GUIDE_CANONICAL_NAMES, GUIDE_DOC_LINKS, GUIDE_PREDICATE_VOCABULARY, GUIDE_ROUTING_LINE, GUIDE_SURFACES, planNewDocumentPlacement, resolveWriteSelector, requireMountedStore, buildOrigin, coerceGate, coerceSurface, memoryExtensionCatalogForDoc, memoryExtensionFieldCatalogHelp, overlayParam, requireCanonicalName, selectorParam, parseRequestedExtensionChanges, applyExtensionChanges, serializeMemoryDoc, } from './shared.js';
8
+ function emptyMemoryStore(memoryDir) {
9
+ return walkFiles(memoryDir, (name) => name.endsWith('.md') && name !== 'SKILL.md', (name) => name === '.crouter').length === 0;
10
+ }
11
+ function hasWorkspaceFrontDoor(surfaces) {
12
+ const workspaceOpen = surfaces.some((entry) => entry['on'] === 'workspace-open' && entry['at'] === 'content');
13
+ const readEntry = surfaces.some((entry) => {
14
+ if (entry['on'] !== 'read' || entry['at'] !== 'content')
15
+ return false;
16
+ const match = entry['match'];
17
+ return match === './**' || (Array.isArray(match) && match.includes('./**'));
18
+ });
19
+ return workspaceOpen && readEntry;
20
+ }
6
21
  // A topic's memory set stays usable only when authors treat cohesion as a creation
7
22
  // gate; otherwise small corrections accumulate as overlapping leaves whose routing
8
23
  // lines cannot tell a future reader which one owns the topic.
@@ -12,24 +27,25 @@ export const writeLeaf = defineLeaf({
12
27
  whenToUse: 'you are recording a new knowledge document or preference that does not exist yet. Creation only — a name already taken at the resolved scope is an error, because revising a stored document goes through `crtr memory edit`, which requires a rationale and records the revision.',
13
28
  help: {
14
29
  name: 'memory write',
15
- summary: 'create memory/<name>.md at the resolved scope from frontmatter flags + a stdin body',
30
+ summary: 'create a memory document at the resolved store from frontmatter flags + a stdin body',
16
31
  guide: 'Every frontmatter flag decides who sees this doc, when, and at what context cost. Each rung up is paid by every future agent at every boot or read, so default each rung down.\n\n' +
17
32
  'Pick the kind. knowledge is consulted for facts or procedures; preference directs behavior. The kind choice is about how the doc is used, not about how long it is.\n\n' +
18
33
  'Store reusable current truth, not session notes. Useful memories are non-obvious procedures, gotchas, durable preferences, and cross-repo conventions. Do not store chat summaries, implementation history, or facts already recorded in the repo.\n\n' +
19
34
  GUIDE_SURFACES + '\n\n' +
20
35
  'Choose the scope. `project` is for facts any agent in one repo needs. `user` is for person-wide facts and preferences that should follow the user everywhere. `profile` is for the profile’s bundle of dirs: cross-repo conventions, how the pieces relate, or the user’s stance toward that body of work. `node` is scratch memory only this running node sees; it rides into this node’s knowledge block and dies with the node. When unsure, choose the narrowest scope that will still reach the next agent who needs it.\n\n' +
21
- 'A workspace front door is an ordinary doc carrying the entry pair {on: workspace-open, at: content} plus {on: read, match: "./**", at: content}: it enters first-message context when cwd/profile mounts its project store and fires on any file read beneath the store’s owning dir. Target the exact project with --dir. Keep only the project constraints, key commands, architecture orientation, and conventions that differ from defaults. `crtr memory lint` enforces exactly one such doc per project store managed by the selected profile.\n\n' +
36
+ 'A workspace front door is an ordinary doc carrying the entry pair {on: workspace-open, at: content} plus {on: read, match: "./**", at: content}: it enters first-message context when cwd/profile mounts its project store and fires on any file read beneath the store’s owning dir. Target the exact project with --dir. Keep only the project constraints, key commands, architecture orientation, and conventions that differ from defaults. `crtr memory lint` requires this front door on the repository-root guide for a repository managed by the selected profile; nested stores may carry ordinary guides without a missing-door finding.\n\n' +
22
37
  'Choose the hook — boot vs read. A boot entry rides the boot catalog every agent sees; a read entry fires only when a matching file is actually read. Put code-specific knowledge in the owning project store, give it the narrowest real file glob, and keep it off boot when the file read is the useful trigger. Knowledge about a person or process usually has no file boundary, so skip read entries and route it through boot instead.\n\n' +
23
38
  'Write the routing line (--when-and-why-to-read) first, before storing anything. ' + GUIDE_ROUTING_LINE + '\n\n' +
24
39
  GUIDE_PREDICATE_VOCABULARY + '\n\n' +
40
+ GUIDE_CANONICAL_NAMES + '\n\n' +
25
41
  GUIDE_DOC_LINKS + '\n\n' +
26
- 'When a doc grows long or information-rich, nest it into a graph instead of letting it become a scroll. The main doc at the topic’s path keeps the high-level, most load-bearing information, most important first; depth splits into reference docs under the topic’s directory (`area/topic/...`), each pointed at with a `[[link]]`. Split by subject: a leaf earns its link by covering a different subject a task might need on its own; a leaf of offloaded “further evidence”, examples, or references is never followed, so supporting material either sits in the main doc next to the point it supports or gets cut. The main doc is the entry point a reader can act from alone; a reference leaf is loaded only when the task needs that depth. Give reference leaves no surfaces at all — the directory listing and the link from the main doc are how they are found, so any routing entry just double-charges every boot or read for depth the graph already routes. Keep every doc as short as its job allows; `crtr memory lint` caps body length by delivery rung and its findings carry the split guidance.\n\n' +
42
+ 'When a doc grows long or information-rich, nest it into a graph instead of letting it become a scroll. The main doc at the topic’s path keeps the high-level, most load-bearing information, most important first; depth splits into reference docs under the topic’s directory (`area/topic/...`), each pointed at with a `[[link]]`. Split by subject: a leaf earns its link by covering a different subject a task might need on its own; a leaf of offloaded “further evidence”, examples, or references is never followed, so supporting material either sits in the main doc next to the point it supports or gets cut. The main doc is the entry point a reader can act from alone; a reference leaf is loaded only when the task needs that depth. Give reference leaves no surfaces at all — the directory listing and the link from the main doc are how they are found, so any routing entry just double-charges every boot or read for depth the graph already routes. State each fact once. Keep facts that affect action or judgment, including constraints, exceptions, numbers, and exact names. Remove known context, inferable conclusions, filler transitions, and examples that resolve no ambiguity. `crtr memory lint` caps body length by delivery rung and its findings carry the split guidance.\n\n' +
27
43
  'A directory needs no index doc: reading a directory name returns its listing — each member’s routing line — so never author a doc that merely lists, fronts, or paraphrases its siblings. Write a directory-level doc only for synthesis: an operating guide or the cluster’s mechanics, ordering, conditions, and relationships, content no single member can carry. Guided entrance into a topic is an ordinary member doc, found through the listing like any other.\n\n' +
28
44
  'Find before write. Prefer slightly expanding an existing document with `crtr memory edit`, nesting genuinely separate depth under its topic, and updating the existing `when-and-why-to-read` (plus its INDEX router when present) over creating another similar memory. A new document earns its own identity only when it has a distinct read trigger and a coherent body whose merge into the existing document would make it harder to route or use. Group related docs with path names (area/topic). Provenance is stamped here and preserved by every later revision. Run `crtr memory lint` after authoring.\n\n' +
29
45
  '--rationale is the gap this doc exists to close — the observed agent failure that prompted it, captured from user signal (a correction, a mistake you watched happen) rather than inferred from the doc’s own content. If the rationale is guessable from reading the doc, it is not the real one — a guessable gap is one agents do not actually fall into. Omit the flag when you have no observed gap to record.\n\n' +
30
46
  'Revise an existing doc with `crtr memory edit`.',
31
47
  params: [
32
- { kind: 'positional', name: 'name', required: true, constraint: 'Path-derived identity: one segment, or several joined with `/` to nest the document under a directory and group it with related docs → memory/<name>.md at the resolved scope. The name must be free at that scope: an existing one is an error naming `crtr memory edit`.' },
48
+ { kind: 'positional', name: 'name', required: true, constraint: 'Full canonical identity. Project writes require `<namespace>/<local name>`; writing the namespace itself targets the repository-root document. A trailing `/INDEX` is invalid because directory documents answer at the directory name. The identity must be free in the selected exact store.' },
33
49
  overlayParam('kind', { required: true }),
34
50
  overlayParam('when-and-why-to-read', { required: true }),
35
51
  overlayParam('short-form'),
@@ -39,13 +55,13 @@ export const writeLeaf = defineLeaf({
39
55
  overlayParam('slash'),
40
56
  { kind: 'flag', name: 'rationale', type: 'string', required: false, constraint: DOC_RATIONALE_CONSTRAINT },
41
57
  { kind: 'flag', name: 'extension', type: 'string', required: false, repeatable: true, constraint: 'Set one declared plugin field as `extensions.<plugin>.<field>=VALUE`. The path is required in full; booleans accept only true or false, numbers require finite numeric syntax, and strings/enums preserve the literal text after the first =. Every requested field validates before the document is written.' },
42
- { kind: 'flag', name: 'scope', type: 'enum', choices: [...MEMORY_SCOPES], required: false, constraint: 'Target scope. Default: project when inside a project, else user. `project` resolves to the NEAREST ancestor `.crouter/` walking up from cwd — in a nested workspace that can be a parent’s store, not the dir you are standing in; pass --dir to pin the exact project directory. `profile` requires a selected profile (CRTR_PROFILE_ID) or an explicit --profile. `node` writes to the this-node store (`nodes/<CRTR_NODE_ID>/context/memory/`) — the nearest scope, seen only by this running node, requires a node context.' },
43
- { kind: 'flag', name: 'dir', type: 'string', required: false, constraint: 'Exact project directory to write under — targets `<dir>/.crouter/memory/` regardless of cwd or ancestor stores, scaffolding `.crouter/` there if absent. THE way to place a doc in a specific project’s store (e.g. another project in your profile’s purview) without cd’ing there, and the only way to target a dir shadowed by an ancestor store. Implies --scope project; rejects --scope user/profile.' },
44
- { kind: 'flag', name: 'profile', type: 'string', required: false, constraint: 'Profile id or name to write under, for --scope profile. Default: the process CRTR_PROFILE_ID (the node\u2019s selected profile). Resolved through the same profile lookup as `crtr profile show`. Rejected (usage error) unless --scope profile is also selected \u2014 never silently ignored for another scope.' },
58
+ selectorParam('scope-write'),
59
+ selectorParam('dir'),
60
+ selectorParam('profile'),
45
61
  { kind: 'stdin', name: 'body', required: true, constraint: 'Document body (markdown, no frontmatter). Piped on stdin only — this leaf already claims the one positional for NAME, so a second bare argv token is rejected, not silently accepted as the body.' },
46
62
  ],
47
63
  output: [
48
- { name: 'name', type: 'string', required: true, constraint: 'The path-derived document name written.' },
64
+ { name: 'name', type: 'string', required: true, constraint: 'The full canonical document name written.' },
49
65
  { name: 'kind', type: 'string', required: true, constraint: 'Kind recorded in frontmatter.' },
50
66
  { name: 'scope', type: 'string', required: true, constraint: 'Scope the document was written to: user, project, profile, or node.' },
51
67
  { name: 'path', type: 'string', required: true, constraint: 'Absolute path to the written document.' },
@@ -57,7 +73,7 @@ export const writeLeaf = defineLeaf({
57
73
  outputKind: 'object',
58
74
  dynamicState: () => memoryExtensionFieldCatalogHelp('write'),
59
75
  effects: [
60
- 'Creates memory/<name>.md at the resolved scope with the given frontmatter fields + stdin body, and opens memory/.history/<name>.jsonl with a `create` record.',
76
+ 'Creates the selected physical memory document with the given frontmatter fields + stdin body, and opens its mirrored `.history` sidecar with a `create` record. An empty repository-root store is initialized at its root namespace only when the ordinary workspace front door surfaces are supplied.'
61
77
  ],
62
78
  },
63
79
  run: async (input) => {
@@ -67,17 +83,41 @@ export const writeLeaf = defineLeaf({
67
83
  const profileArg = input['profile'];
68
84
  const body = input['body'] ?? '';
69
85
  const dirArg = input['dir'];
70
- const { scope, memoryDir } = resolveWriteTarget(scopeArg, profileArg, dirArg);
71
- const path = memoryFilePath(memoryDir, name);
72
- const extensionChanges = parseRequestedExtensionChanges(input['extension'], undefined, memoryExtensionCatalogForDoc({ scope, path }));
73
- // Create-only. Revision goes through `edit`, which requires a rationale and
74
- // records the change — a write that still overwrote would be a standing
75
- // bypass of the audit trail. The error is the migration path.
76
- if (pathExists(path)) {
77
- throw usage(`${name} already exists at ${scope} scope — revise it with \`crtr memory edit ${name} --rationale "<why this revision>"\`, which records the change. \`write\` only creates.`, { memory: name, scope, path });
86
+ const selector = resolveWriteSelector({ scope: scopeArg, profile: profileArg, dir: dirArg });
87
+ const { scope, memoryDir, store } = selector;
88
+ let initializing = false;
89
+ if (scope === 'project' && store.mountStatus !== 'ready' && dirArg !== undefined && emptyMemoryStore(memoryDir)) {
90
+ const association = associateRepository(realpathOrSelf(dirArg));
91
+ if (association.ok) {
92
+ if (association.association.ownerRelativePath !== '') {
93
+ throw usage(`${dirArg} is a nested store in the repository rooted at ${association.association.repositoryRoot}; only the repository root declares a namespace`, {
94
+ next: 'crtr memory write <namespace> --dir <repositoryRoot>, or crtr sys migrate --dir <repositoryRoot>',
95
+ });
96
+ }
97
+ initializing = true;
98
+ }
99
+ else if (association.code === 'no-project-root') {
100
+ initializing = true;
101
+ }
102
+ }
103
+ if (scope === 'project' && store.mountStatus !== 'ready' && !initializing)
104
+ requireMountedStore(store);
105
+ let canonicalName;
106
+ let path;
107
+ if (initializing) {
108
+ canonicalName = requireCanonicalName(name);
109
+ path = join(memoryDir, 'INDEX.md');
110
+ }
111
+ else {
112
+ const placement = planNewDocumentPlacement(store, name);
113
+ canonicalName = placement.canonicalName;
114
+ path = placement.path;
78
115
  }
116
+ const extensionChanges = parseRequestedExtensionChanges(input['extension'], undefined, memoryExtensionCatalogForDoc({ scope, path }));
79
117
  const frontmatter = {};
80
118
  frontmatter['kind'] = kind;
119
+ if (initializing)
120
+ frontmatter['namespace'] = canonicalName;
81
121
  // Provenance is runtime-stamped ONCE, here — who/when/where authored this
82
122
  // doc. Every later revision preserves it, so it always points at the
83
123
  // conversation that created the doc, not the last one to touch it.
@@ -112,7 +152,17 @@ export const writeLeaf = defineLeaf({
112
152
  surfacesVal.some((e) => e !== null && typeof e === 'object' && e['on'] === 'workspace-open')) {
113
153
  throw usage('a workspace-open surfaces entry is a project-store mount route; user, profile, and node memory cannot carry one — drop the entry or write to a project store');
114
154
  }
155
+ if (initializing) {
156
+ const surfaces = Array.isArray(surfacesVal) ? surfacesVal.filter((entry) => entry !== null && typeof entry === 'object') : [];
157
+ if (!hasWorkspaceFrontDoor(surfaces)) {
158
+ throw usage('empty project initialization requires the ordinary workspace front door: --surface \'{on: workspace-open, at: content}\' and --surface \'{on: read, match: "./**", at: content}\'', {
159
+ memory: canonicalName,
160
+ next: `Pass both front-door surfaces when initializing ${canonicalName} with --dir.`,
161
+ });
162
+ }
163
+ }
115
164
  const after = serializeMemoryDoc(frontmatter, body);
165
+ ensureDir(memoryDir);
116
166
  writeText(path, after);
117
167
  const logPath = historyLogPathFor(memoryDir, path);
118
168
  appendHistoryRecord(logPath, buildHistoryRecord({ op: 'create', before: '', after }));
@@ -121,6 +171,7 @@ export const writeLeaf = defineLeaf({
121
171
  // result or its collapsed viewer preview.
122
172
  const selectedKeys = [
123
173
  'kind',
174
+ ...(initializing ? ['namespace'] : []),
124
175
  ...(input['whenAndWhyToRead'] !== undefined ? ['when-and-why-to-read'] : []),
125
176
  ...(input['shortForm'] !== undefined ? ['short-form'] : []),
126
177
  ...(input['unlisted'] === true ? ['unlisted'] : []),
@@ -132,14 +183,14 @@ export const writeLeaf = defineLeaf({
132
183
  ];
133
184
  const selectedFrontmatter = selectedKeys.map((key) => ({ key, value: frontmatter[key] }));
134
185
  return {
135
- name,
186
+ name: canonicalName,
136
187
  kind,
137
188
  scope,
138
189
  path,
139
190
  created: true,
140
191
  frontmatter: selectedFrontmatter,
141
192
  log_path: logPath,
142
- follow_up: `Read it back with \`crtr memory read ${name}\`, revise it later with \`crtr memory edit ${name} --rationale "<why>"\`, or browse the inventory with \`crtr memory list\`.`,
193
+ follow_up: `Read it back with \`crtr memory read ${canonicalName}\`, revise it later with \`crtr memory edit ${canonicalName} --rationale "<why>"\`, or browse the inventory with \`crtr memory list\`.`,
143
194
  };
144
195
  },
145
196
  });
@@ -18,12 +18,12 @@ export function registerMemory() {
18
18
  rootEntry: {
19
19
  concept: 'a memory document you read on demand — knowledge or a preference',
20
20
  desc: 'list, read, search, and write memory documents',
21
- useWhen: 'durable knowledge and preferences shared across nodes and sessions — use when prior guidance may apply to the current task, or when a non-obvious reusable truth should outlive this conversation. Names are path-derived crtr identifiers, not file paths — read docs through the command surface, never cat or find the markdown off disk.',
21
+ useWhen: 'durable knowledge and preferences shared across nodes and sessions — use when prior guidance may apply to the current task, or when a non-obvious reusable truth should outlive this conversation. Every document has one canonical name, a crtr identifier rather than a file path — read docs through the command surface, never cat or find the markdown off disk.',
22
22
  },
23
23
  help: {
24
24
  name: 'memory',
25
25
  summary: 'list, read, search, and write memory documents — knowledge and preferences',
26
- model: 'Documents have path-derived identities and resolve across layered scopes in precedence order: node > project stack > profile > user > builtin. Browse the inventory with `list` to see what is stored; address a document directly with `read` once you know its name — a directory name is a valid read target answering with its listing. One verb per operation: `write` creates, `edit` revises (every revision carries a rationale and is recorded), `move` relocates or renames a doc and rewrites inbound `[[refs]]` corpus-wide, `delete` removes, and `history` reads how a document reached its current state. Beyond listings, delivery is explicit `surfaces` routing: each entry names the event that fires it (boot, workspace-open, read, memory-read, command) and how much delivers (name, preview, content). A selected profile widens the project stack to every project in its purview, and each of those relationships caps what its stores deliver to the boot and workspace-open events; `read`, find, listings, and file- and command-routed delivery see the full corpus regardless.',
26
+ model: 'One address per document: its canonical name, the store’s namespace composed with its store-local name. A repository declares that namespace once, as `namespace:` on the root `INDEX.md` of `<repository root>/.crouter/memory/`, and a store nested inside the repository composes under that namespace plus its own path from the repository root; user, profile, node, and builtin names carry no namespace, and an installed plugin’s documents mount under the plugin’s own name. Resolution is exact — a file path, a bare leaf, a `<scope>/<name>` spelling, and a trailing `/INDEX` all fail — so `find` is the discovery surface when you do not know a name and `list` browses the whole inventory. A trailing `INDEX` is never part of an address, so a directory may carry its own document: reading the directory name returns that body together with the directory’s immediate listing. Precedence — node > project stack > profile > user > builtin — picks the winner among documents sharing ONE canonical name; `--paths` reports every physical candidate behind a name, and `--dir <project>` reaches one exact store. One verb per operation: `write` creates, `edit` revises (every revision carries a rationale and is recorded), `move` relocates or renames a doc and rewrites inbound `[[refs]]` corpus-wide, `delete` removes, and `history` reads how a document reached its current state. Beyond listings, delivery is explicit `surfaces` routing: each entry names the event that fires it (boot, workspace-open, read, memory-read, command) and how much delivers (name, preview, content). A selected profile widens the project stack to every project in its purview, and each of those relationships caps what its stores deliver to the boot and workspace-open events; `read`, find, listings, and file- and command-routed delivery see the full corpus regardless.',
27
27
  },
28
28
  children: [listLeaf, readLeaf, findLeaf, writeLeaf, editLeaf, moveLeaf, historyLeaf, deleteLeaf, originLeaf, lintLeaf],
29
29
  });
@@ -12,7 +12,7 @@ export declare function assertKind(kind: string): void;
12
12
  *
13
13
  * Keep this block cheap and caller-agnostic: help should not depend on the
14
14
  * running node's current persona env, which can lag after polymorph. Sub-kinds
15
- * (e.g. `plan/reviewers/security`) are OMITTED here — `KindConfig.availableTo`
15
+ * (e.g. `audit/security`) are OMITTED here — `KindConfig.availableTo`
16
16
  * defaults to the sub-kind's own top-level ancestor, and this generic block has
17
17
  * no single ancestor to check against, so only an explicit `availableTo: ['*']`
18
18
  * sub-kind (universally available) earns a place in this list. A direct launch
@@ -40,7 +40,7 @@ export function assertKind(kind) {
40
40
  *
41
41
  * Keep this block cheap and caller-agnostic: help should not depend on the
42
42
  * running node's current persona env, which can lag after polymorph. Sub-kinds
43
- * (e.g. `plan/reviewers/security`) are OMITTED here — `KindConfig.availableTo`
43
+ * (e.g. `audit/security`) are OMITTED here — `KindConfig.availableTo`
44
44
  * defaults to the sub-kind's own top-level ancestor, and this generic block has
45
45
  * no single ancestor to check against, so only an explicit `availableTo: ['*']`
46
46
  * sub-kind (universally available) earns a place in this list. A direct launch
@@ -23,7 +23,7 @@ const nodeList = defineLeaf({
23
23
  summary: 'list nodes on the canvas, filtered by any combination of attribute slices and a graph-scope (--under) that AND together',
24
24
  params: [
25
25
  { kind: 'flag', name: 'status', type: 'string', required: false, constraint: 'Filter status (comma-separated for several; accepted values: active, idle, done, dead, canceled). NOTE: `active` means the engine process is live (never closed), NOT that the node is generating right now — an active node is usually dormant between turns. To tell working-vs-idle, check the pi session-file mtime or CPU, not status.' },
26
- { kind: 'flag', name: 'kind', type: 'string', required: false, constraint: 'Filter by persona kind (comma-separated for several). The <kinds> list below names every top-level installable kind; a sub-kind (e.g. plan/reviewers/security) filters too even though it is not listed.' },
26
+ { kind: 'flag', name: 'kind', type: 'string', required: false, constraint: 'Filter by persona kind (comma-separated for several). The <kinds> list below names every top-level installable kind; a registered sub-kind filters too by exact path even though it is not listed.' },
27
27
  { kind: 'flag', name: 'mode', type: 'string', required: false, constraint: 'Filter by mode (comma-separated for several; accepted values: base, orchestrator).' },
28
28
  { kind: 'flag', name: 'lifecycle', type: 'string', required: false, constraint: 'Filter by lifecycle (comma-separated for several; accepted values: terminal, resident).' },
29
29
  { kind: 'flag', name: 'profile', type: 'string', required: false, constraint: 'Filter by profile. On the local canvas, accepts an exact profile id or a unique manifest name from the <profiles> list below. Against a remote canvas, pass the exact profile id carried by its node rows.' },
@@ -131,7 +131,7 @@ export const nodeConfig = defineLeaf({
131
131
  { kind: 'flag', name: 'pane', type: 'string', required: false, constraint: 'tmux pane id to resolve the node from. Defaults to $TMUX_PANE.' },
132
132
  { kind: 'flag', name: 'model', type: 'string', required: false, constraint: 'Model spec: exact provider/id, capability tier (ultra|strong|medium|light), family alias (opus|sonnet|haiku), or a free-text substring when the broker is live. Dormant nodes require a provider/id, tier, or alias.' },
133
133
  { kind: 'flag', name: 'lifecycle', type: 'enum', choices: ['terminal', 'resident'], required: false, constraint: 'Set lifecycle headlessly without closing the viewer. terminal owes a final and reaps when done; resident stays interactable while attached or awaiting something live.' },
134
- { kind: 'flag', name: 'kind', type: 'string', required: false, constraint: 'Persona kind. The <kinds> list below names every top-level installable kind and when to use each; a sub-kind (e.g. plan/reviewers/security) is valid too by exact path but not listed here.' },
134
+ { kind: 'flag', name: 'kind', type: 'string', required: false, constraint: 'Persona kind. The <kinds> list below names every top-level installable kind and when to use each; a registered sub-kind is valid too by exact path but not listed here.' },
135
135
  { kind: 'flag', name: 'mode', type: 'enum', choices: ['base', 'orchestrator'], required: false, constraint: 'Set persona mode headlessly. base is hands-on; orchestrator holds a roadmap and delegates. orchestrator seeds a roadmap scaffold if absent.' },
136
136
  { kind: 'flag', name: 'name', type: 'string', required: false, constraint: 'Rename the node; if the node has a live window, also rename that viewer window.' },
137
137
  ],
@@ -241,7 +241,7 @@ export const nodePromote = defineLeaf({
241
241
  name: 'node promote',
242
242
  summary: 'promote yourself to an orchestrator when worthwhile parallel delegation should make coordination your primary job; task duration, sequential phases, or one helper do not qualify. Mode only — lifecycle stays as-is unless you pass --resident',
243
243
  params: [
244
- { kind: 'flag', name: 'kind', type: 'string', required: false, constraint: 'Specialize as this kind of orchestrator. The <kinds> list below names every top-level installable kind and when to use each; a sub-kind (e.g. plan/reviewers/security) is valid too by exact path but not listed here. For an exact composed persona list, use `crtr sys prompt-review --list --kind <kind>` with the target kind. Defaults to your current kind. Promoting from a generic kind? CHOOSE a concrete one — it sets the orchestrator persona you revive into.' },
244
+ { kind: 'flag', name: 'kind', type: 'string', required: false, constraint: 'Specialize as this kind of orchestrator. The <kinds> list below names every top-level installable kind and when to use each; a registered sub-kind is valid too by exact path but not listed here. For an exact composed persona list, use `crtr sys prompt-review --list --kind <kind>` with the target kind. Defaults to your current kind. Promoting from a generic kind? CHOOSE a concrete one — it sets the orchestrator persona you revive into.' },
245
245
  { kind: 'flag', name: 'resident', type: 'bool', required: false, constraint: 'ALSO flip lifecycle→resident: make the node interactable — it is never forced to submit a final, stays wakeable while attached or awaiting something live, and completes after the unattended grace otherwise. Omit to stay terminal/orchestrator (delegates + holds a roadmap, but still owes a final up the spine and reaps when done).' },
246
246
  { kind: 'flag', name: 'model', type: 'enum', choices: ['ultra', 'strong'], required: false, constraint: 'Change the model you run on, by capability tier: `ultra` (frontier — reserve for high-taste judgment or enormous work: speccing, planning something large, e2e-testing something hard to test) or `strong` (opus — regular dev work). Omit to keep your current model. An orchestrator steering work is never weaker than opus, so these are the only two choices; the change is durable across future revives.' },
247
247
  { kind: 'flag', name: 'node', type: 'string', required: false, constraint: 'Node to promote. Defaults to the caller (CRTR_NODE_ID).' },
@@ -258,7 +258,7 @@ export const nodePromote = defineLeaf({
258
258
  ],
259
259
  dynamicState: () => kindsStateBlock(),
260
260
  outputKind: 'object',
261
- effects: ['Flips mode→orchestrator + kind→chosen (lifecycle unchanged unless --resident, which also flips lifecycle→resident); --model durably pins the model tier; rewrites the launch spec to that kind\'s orchestrator persona; seeds context/roadmap.md scaffold if absent.', 'Your new-role guidance is injected automatically at the turn boundary by the persona injector — the command no longer returns it.'],
261
+ effects: ['Flips mode→orchestrator + kind→chosen (lifecycle unchanged unless --resident, which also flips lifecycle→resident); --model durably pins the model tier; rewrites the launch spec to that kind\'s orchestrator persona; seeds context/roadmap.md scaffold if absent.', 'The next model request is gated on appending and persisting your new-role guidance. If a fresh context starts first, its rebuilt baseline carries the new role.'],
262
262
  },
263
263
  run: async (input) => {
264
264
  const id = input['node'] ?? process.env['CRTR_NODE_ID'];
@@ -327,7 +327,7 @@ const nodeDemote = defineLeaf({
327
327
  { name: 'detached', type: 'boolean', required: false, constraint: 'True when --detach closed the agent\'s viewer pane (broker keeps running).' },
328
328
  ],
329
329
  outputKind: 'object',
330
- effects: ['Flips the node\'s lifecycle→terminal and rebuilds its launch spec so a future revive boots terminal — it stays focused and running, now owing a final up the spine.', 'The persona injector delivers the transition guidance at the next turn boundary (or on the node\'s next revive if it is dormant).', 'With --detach: closes the agent\'s viewer pane WITHOUT touching the broker engine — it keeps generating off-screen, reconnectable by a later `focus`.'],
330
+ effects: ['Flips the node\'s lifecycle→terminal and rebuilds its launch spec so a future revive boots terminal — it stays focused and running, now owing a final up the spine.', 'The next model request is gated on appending and persisting the role transition. If a fresh context starts first, its rebuilt baseline carries the new lifecycle.', 'With --detach: closes the agent\'s viewer pane WITHOUT touching the broker engine — it keeps generating off-screen, reconnectable by a later `focus`.'],
331
331
  },
332
332
  run: async (input) => {
333
333
  const pane = input['pane'] ?? process.env['TMUX_PANE'];
@@ -373,7 +373,7 @@ export const nodeYield = defineLeaf({
373
373
  guide: 'Persist anything needed after refresh and send any pending human question before invoking; in-flight asks survive the refresh.',
374
374
  params: [
375
375
  { kind: 'flag', name: 'promote', type: 'bool', required: false, constraint: 'Also become an orchestrator as you refresh. Pass only when (1) the remaining work contains independent units that can run in parallel and (2) the task is large enough that parallel execution will materially improve intelligence, productivity, or elapsed throughput after coordination and synthesis. Task length, context exhaustion, sequential phases, or one helper are not enough; keep hands-on work in base mode and yield again as needed. Promote when coordinating and integrating children becomes your primary job. Already an orchestrator? This is not an error: your mode stays orchestrator and a missing roadmap is seeded.' },
376
- { kind: 'flag', name: 'kind', type: 'string', required: false, constraint: 'Respecialize as you refresh. The <kinds> list below names every top-level available kind and when to choose it; a sub-kind (e.g. plan/reviewers/security) is valid too by exact path but not listed here. Defaults to your current kind. With --promote it sets the orchestrator kind you become; without --promote it changes the kind while preserving your current mode.' },
376
+ { kind: 'flag', name: 'kind', type: 'string', required: false, constraint: 'Respecialize as you refresh. The <kinds> list below names every top-level available kind and when to choose it; a registered sub-kind is valid too by exact path but not listed here. Defaults to your current kind. With --promote it sets the orchestrator kind you become; without --promote it changes the kind while preserving your current mode.' },
377
377
  { kind: 'flag', name: 'model', type: 'enum', choices: ['ultra', 'strong'], required: false, constraint: 'Change the model your fresh revive runs on, by capability tier: `ultra` (frontier — reserve for high-taste judgment or enormous work: speccing, planning something large, e2e-testing something hard to test) or `strong` (opus — regular dev work). Omit to keep your current model. A node steering work is never weaker than opus, so these are the only two choices; the change is durable across future revives.' },
378
378
  // Deliberate fail-first contract: builtin orchestration guidance shows bare
379
379
  // `crtr node yield`; that invocation fails fast and surfaces yield's full help just in
@@ -147,11 +147,17 @@ export const marketRemove = defineLeaf({
147
147
  return { name, removed: true };
148
148
  },
149
149
  });
150
+ /** Stage a fresh checkout beside the active root. A clone that fails throws
151
+ * the git error: reporting the active revision as current would present a
152
+ * failed refresh as an up-to-date one, which is how a checkout stays pinned
153
+ * to a stale revision with nothing on screen to say so. */
150
154
  function stageGitRoot(url, ref, active, candidate) {
151
- const result = gitSync(['clone', '--depth', '1', '--branch', ref, url, candidate]);
152
- if (result.status !== 0) {
155
+ try {
156
+ clone(url, candidate, { depth: 1, ref });
157
+ }
158
+ catch (error) {
153
159
  removePath(candidate);
154
- return undefined;
160
+ throw error;
155
161
  }
156
162
  return { active, candidate, previous: `${candidate}.previous` };
157
163
  }
@@ -195,21 +201,17 @@ async function updateMarketplaceTarget(target) {
195
201
  if (isGitRepo(target.root)) {
196
202
  const candidate = join(dirname(target.root), `.${target.name}.${process.pid}.${Date.now()}.candidate`);
197
203
  marketplaceCandidate = stageGitRoot(target.url, target.ref, target.root, candidate);
198
- if (marketplaceCandidate !== undefined) {
199
- cleanupRoots.push(candidate);
200
- const candidateManifest = readMarketplaceManifest(candidate);
201
- if (candidateManifest === null)
202
- throw notFound(`marketplace manifest not found in candidate checkout: ${candidate}/.crouter-marketplace/marketplace.json`);
203
- manifest = candidateManifest;
204
- if (currentSha(candidate) !== activeSha)
205
- stagedRoots.push(marketplaceCandidate);
206
- }
207
- else {
208
- const activeManifest = readMarketplaceManifest(target.root);
209
- if (activeManifest === null)
210
- throw notFound(`marketplace manifest not found after refresh: ${target.root}/.crouter-marketplace/marketplace.json`);
211
- manifest = activeManifest;
212
- }
204
+ cleanupRoots.push(candidate);
205
+ const candidateManifest = readMarketplaceManifest(candidate);
206
+ if (candidateManifest === null)
207
+ throw notFound(`marketplace manifest not found in candidate checkout: ${candidate}/.crouter-marketplace/marketplace.json`);
208
+ manifest = candidateManifest;
209
+ // The staged checkout replaces the active root even at an unchanged
210
+ // revision. The tree is package-owned content, not a working copy: an
211
+ // in-place edit to it (a memory migration reaching a plugin through its
212
+ // symlink, a hand edit) otherwise survives every update, and a revision
213
+ // comparison would call that drifted tree current.
214
+ stagedRoots.push(marketplaceCandidate);
213
215
  }
214
216
  else {
215
217
  const activeManifest = readMarketplaceManifest(target.root);
@@ -221,6 +223,8 @@ async function updateMarketplaceTarget(target) {
221
223
  if (isSymlink(plugin.root)) {
222
224
  if (marketplaceCandidate === undefined)
223
225
  continue;
226
+ // A symlinked plugin resolves through the marketplace root, so the root
227
+ // swap above already replaced its content.
224
228
  const entry = manifest.plugins.find((candidate) => candidate.name === plugin.name);
225
229
  if (entry === undefined || !isMarketplaceRelativeSource(entry.source)) {
226
230
  throw general(`marketplace candidate removes relative-source plugin "${plugin.name}" while it is installed`);
@@ -251,15 +255,12 @@ async function updateMarketplaceTarget(target) {
251
255
  const candidate = join(scopeRoot, 'tmp', `${plugin.name}.${process.pid}.${Date.now()}.candidate`);
252
256
  ensureDir(join(scopeRoot, 'tmp'));
253
257
  const staged = stageGitRoot(remote, branch, plugin.root, candidate);
254
- if (staged === undefined)
255
- continue;
256
258
  cleanupRoots.push(candidate);
257
259
  const candidateManifest = readPluginManifest(candidate);
258
260
  if (candidateManifest === null)
259
261
  throw notFound(`plugin manifest not found in update candidate: ${candidate}/.crouter-plugin/plugin.json`);
260
262
  candidates.push(candidatePlugin(plugin, candidate, candidateManifest));
261
- if (currentSha(candidate) !== currentSha(plugin.root))
262
- stagedRoots.push(staged);
263
+ stagedRoots.push(staged);
263
264
  }
264
265
  for (const candidate of candidates) {
265
266
  await assertPluginCandidateValid(candidate.plugin.manifest, candidate.plugin.root, candidate.plugin.scope, candidate.scopeRoot, `${candidate.plugin.root}/.crouter-plugin/plugin.json`, candidates);
@@ -294,7 +295,7 @@ async function updateMarketplaceTarget(target) {
294
295
  }
295
296
  export const marketUpdate = defineLeaf({
296
297
  name: 'update',
297
- description: 'pull latest marketplace source',
298
+ description: 'replace marketplace source with its latest revision',
298
299
  whenToUse: 'refreshing one named marketplace source from git, or all registered marketplace sources when no name is given; also updates installed plugins sourced from refreshed marketplaces',
299
300
  help: {
300
301
  name: 'pkg market update',
@@ -307,7 +308,7 @@ export const marketUpdate = defineLeaf({
307
308
  { name: 'updated', type: 'object[]', required: true, constraint: 'One entry per marketplace processed: {name, updated, sha}.' },
308
309
  ],
309
310
  outputKind: 'object',
310
- effects: ['Stages and validates marketplace and sourced-plugin revisions before atomically activating them. Updates installed plugin content/version for plugins sourced from refreshed marketplaces.'],
311
+ effects: ['Stages and validates marketplace and sourced-plugin revisions before atomically activating them, replacing each checkout even at an unchanged revision — local modifications inside those directories are discarded. `updated` reports whether the revision moved, not whether the checkout was replaced. Updates installed plugin content/version for plugins sourced from refreshed marketplaces.'],
311
312
  },
312
313
  run: async (input) => {
313
314
  const name = input['name'];
@@ -4,18 +4,34 @@ interface MarketplaceRef {
4
4
  marketplace: string;
5
5
  plugin: string;
6
6
  }
7
+ interface InstallResult {
8
+ name: string;
9
+ scope: Scope;
10
+ path: string;
11
+ memory?: PluginMemoryReport;
12
+ }
13
+ /** Who owns the physical memory corpus a candidate gate converges. A `package`
14
+ * corpus is bytes this command owns — staged, or installed under the scope's
15
+ * plugins dir — so a failed activation still rolls the package back. A `linked`
16
+ * corpus is the caller's own directory the install keeps as a live link: the
17
+ * rewrite lands there, outlives a rejected install, and is never rolled back. */
18
+ type PluginMemoryOwnership = 'package' | 'linked';
19
+ interface PluginMemoryReport extends Record<string, unknown> {
20
+ /** Markdown documents the candidate's memory store holds. */
21
+ docs: number;
22
+ /** Store-relative documents this gate rewrote to reach exact canonical identity. */
23
+ migrated: string[];
24
+ /** The caller-owned directory converged in place, absent for a package corpus. */
25
+ linkedSource?: string;
26
+ }
7
27
  /** Validate a package candidate before it gains an active plugin path. The
8
28
  * candidate namespace shadows any installed declaration of the same name. */
9
- export declare function assertPluginCandidateValid(manifest: PluginManifest, root: string, scope: Scope, scopeRootPath: string, where: string, candidates?: readonly HookPluginCandidate[]): Promise<void>;
29
+ export declare function assertPluginCandidateValid(manifest: PluginManifest, root: string, scope: Scope, scopeRootPath: string, where: string, candidates?: readonly HookPluginCandidate[], ownership?: PluginMemoryOwnership): Promise<PluginMemoryReport | undefined>;
10
30
  export declare function isMarketplaceRelativeSource(source: string): boolean;
11
31
  /** Install one marketplace plugin into a scope. Exported so `sys setup` can
12
32
  * install the default plugin set through the same path `pkg plugin install`
13
33
  * uses — refresh the source, resolve the entry, link/clone, register. */
14
- export declare function installFromMarketplace(ref: MarketplaceRef, scope: Scope): Promise<{
15
- name: string;
16
- scope: Scope;
17
- path: string;
18
- }>;
34
+ export declare function installFromMarketplace(ref: MarketplaceRef, scope: Scope): Promise<InstallResult>;
19
35
  export declare const pluginInstall: import("../../core/command.js").LeafDef;
20
36
  export declare const pluginRemove: import("../../core/command.js").LeafDef;
21
37
  export declare const pluginEnable: import("../../core/command.js").LeafDef;