@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
@@ -3,22 +3,41 @@ import { resolve as resolvePath } from 'node:path';
3
3
  import { defineLeaf } from '../../core/command.js';
4
4
  import { CrtrError, notFound, usage } from '../../core/errors.js';
5
5
  import { readText } from '../../core/fs-utils.js';
6
- import { resolveMemoryDoc } from '../../core/memory-resolver.js';
6
+ import { loadMemoryStoreView, resolveMemoryCandidates } from '../../core/memory-resolver.js';
7
7
  import { appendHistoryRecord, buildHistoryRecord, historyLogPathFor } from '../../core/memory/history.js';
8
- import { MEMORY_SCOPES, pruneEmptyParents } from './shared.js';
8
+ import { MEMORY_WRITE_SCOPES, pruneEmptyParents, requireMountedStore, selectMutationDoc, resolveReadSelector, selectorParam, } from './shared.js';
9
+ /** Every physical document owning that exact canonical identity — in the target
10
+ * view, or inside the one store `--dir` selected — or null when nothing
11
+ * answers to the name. */
12
+ function targetCandidates(rawName, scope, storeView) {
13
+ if (storeView !== null)
14
+ return storeView.candidates(rawName);
15
+ try {
16
+ return resolveMemoryCandidates(rawName, {
17
+ includeDescendants: true,
18
+ ...(scope !== undefined ? { scope } : {}),
19
+ });
20
+ }
21
+ catch (e) {
22
+ if (e instanceof CrtrError && e.code === 'not_found')
23
+ return null;
24
+ throw e; // usage propagates with its own recovery guidance
25
+ }
26
+ }
9
27
  export const deleteLeaf = defineLeaf({
10
28
  name: 'delete',
11
29
  description: 'delete a memory document by name',
12
- whenToUse: 'a stored document is wrong, obsolete, or superseded and should be removed — the sanctioned way to delete one, so you never rm the markdown off disk. Resolves the name exactly as `read` does (exact/direct matches by node > project stack > profile > user precedence, then leaf-name fallback) and removes that file: if you can read it, you can delete it. Refuses docs you do not own — builtin docs (shipped with the package) and installed-plugin docs (managed by `crtr pkg`) — and refuses an ambiguous leaf name rather than guessing which doc to destroy; pass the full `area/topic` name or `--scope` to pin one.',
30
+ whenToUse: 'a stored document is wrong, obsolete, or superseded and should be removed — the sanctioned way to delete one, so you never rm the markdown off disk. Resolves the full canonical name exactly as `read` does and removes that one file: if you can read it, you can delete it. Refuses docs you do not own — builtin docs (shipped with the package) and installed-plugin docs (managed by `crtr pkg`). If two physical files in the selected store share the name, it selects the first physical path in stable lexical order and recovers the collision.',
13
31
  help: {
14
32
  name: 'memory delete',
15
- summary: 'resolve a path-derived name and remove that document from its scope store',
33
+ summary: 'resolve an exact canonical name and remove that document from its store',
16
34
  params: [
17
- { kind: 'positional', name: 'name', required: true, constraint: 'Path-derived memory identifier (e.g. `topic` or `area/topic`), resolved as `read` resolves it: exact identity/direct path by precedence node > project stack > profile > user, then bare leaf fallback. A leaf matching multiple different docs in one source is rejected as ambiguous — pass the full name or --scope to disambiguate. May be scope-prefixed (`user/topic`) to pin the store directly.' },
18
- { kind: 'flag', name: 'scope', type: 'enum', choices: [...MEMORY_SCOPES], required: false, constraint: 'Restrict resolution to this scope before deleting. Use it to disambiguate a leaf name present at multiple scopes, or to assert which store you mean. builtin is not a choice — builtin docs ship with the package and cannot be deleted.' },
35
+ { kind: 'positional', name: 'name', required: true, constraint: 'Full canonical name of the document — a project document is `<namespace>/<local name>`. Resolution is exact, as `read` resolves it: a file path, a bare leaf, a `<scope>/<name>` spelling, and a trailing `/INDEX` all fail. A canonical directory’s own document is deleted at the directory name; its members are untouched and stay reachable. If two physical files in the selected store own the name, delete selects the first path in stable lexical order and reports it. Builtin and installed-plugin documents are read-only and refuse to delete, as does the repository-root document, which declares the immutable namespace for every document in the repository; a nested store’s root guide is ordinary and may be deleted.' },
36
+ selectorParam('scope-read', { choices: [...MEMORY_WRITE_SCOPES] }, 'builtin is not a choice — builtin documents ship with the package and cannot be deleted.'),
37
+ selectorParam('dir', {}, 'Deletes from that one store, including a non-winning duplicate no target view resolves to.'),
19
38
  ],
20
39
  output: [
21
- { name: 'name', type: 'string', required: true, constraint: 'Path-derived name of the deleted document.' },
40
+ { name: 'name', type: 'string', required: true, constraint: 'Canonical name of the deleted document.' },
22
41
  { name: 'scope', type: 'string', required: true, constraint: 'Scope the document was deleted from: node, user, project, or profile.' },
23
42
  { name: 'path', type: 'string', required: true, constraint: 'Absolute path of the file that was removed.' },
24
43
  { name: 'deleted', type: 'boolean', required: true, constraint: 'Always true on success — the file was removed. A missing document fails with not_found instead.' },
@@ -27,39 +46,47 @@ export const deleteLeaf = defineLeaf({
27
46
  ],
28
47
  outputKind: 'object',
29
48
  effects: [
30
- 'Permanently removes memory/<name>.md from the resolved scope store, and prunes any parent directories it leaves empty. Irreversible.',
31
- 'Appends a `delete` record carrying the document’s full final text to memory/.history/<name>.jsonl. The log itself is never removed.',
49
+ 'Permanently removes the resolved file from its store, and prunes any parent directories it leaves empty. Irreversible. Refuses the repository-root document: it declares the immutable `namespace:` every document in this repository composes under, so removing it would unmount the whole repository.',
50
+ 'Appends a `delete` record carrying the document’s full final text to `.history/<physical path>.jsonl` in the same store. The log itself is never removed.',
32
51
  ]
33
52
  },
34
53
  run: async (input) => {
35
54
  const nameRaw = input['name'];
36
55
  const scopeArg = input['scope'];
37
- let doc;
38
- try {
39
- doc = resolveMemoryDoc(nameRaw, { includeDescendants: true, ...(scopeArg !== undefined ? { scope: scopeArg } : {}) });
40
- }
41
- catch (e) {
42
- if (e instanceof CrtrError && e.code === 'not_found') {
43
- throw notFound(`memory document not found: ${nameRaw}`, {
44
- memory: nameRaw,
45
- next: 'Run `crtr memory find <query>` to locate it, or `crtr memory list` to browse the inventory. If it exists at another scope, pass --scope.',
46
- });
47
- }
48
- throw e; // ambiguous / usage propagate with their own recovery guidance
56
+ const dirArg = input['dir'];
57
+ const selector = resolveReadSelector({ scope: scopeArg, dir: dirArg });
58
+ const storeView = selector.store === null ? null : loadMemoryStoreView(requireMountedStore(selector.store), true);
59
+ const set = targetCandidates(nameRaw, selector.scope, storeView);
60
+ if (set === null) {
61
+ throw notFound(`memory document not found: ${nameRaw}`, {
62
+ memory: nameRaw,
63
+ next: 'A delete takes the full canonical name (a project document is <namespace>/<local name>), and a directory’s own document answers at the directory name. Run `crtr memory find <leaf>` to look it up, or pass --dir to reach one exact project store.',
64
+ });
49
65
  }
66
+ const selected = selectMutationDoc(set);
67
+ const doc = selected.doc;
50
68
  if (doc.plugin !== undefined) {
51
69
  throw usage(`${doc.name} belongs to the installed plugin "${doc.plugin}" — it is not a scope doc and cannot be deleted here. Manage plugin docs with \`crtr pkg\`. To override just this doc, write a same-named doc at a writable scope (\`crtr memory write ${doc.name} ...\`).`, { memory: doc.name, plugin: doc.plugin });
52
70
  }
53
71
  if (doc.scope === 'builtin') {
54
72
  throw usage(`${doc.name} is a builtin document shipped with the package (read-only) — it cannot be deleted. Override it with a same-named doc at a writable scope instead (\`crtr memory write ${doc.name} ...\`).`, { memory: doc.name, scope: 'builtin' });
55
73
  }
74
+ // Only the repository-root document declares the namespace and is
75
+ // immutable through the memory commands.
76
+ if (doc.localName === '' && doc.store.ownerRelativePath === '') {
77
+ throw usage(`${doc.name} is the repository-root document at ${doc.path}, which declares the namespace every document in this repository composes under — deleting it would unmount the whole repository, so it cannot be deleted.`, {
78
+ memory: doc.name,
79
+ path: doc.path,
80
+ next: `Delete the documents under \`${doc.name}/\` individually, or revise the repository-root document with \`crtr memory edit ${doc.name} --rationale "<why>"\`.`,
81
+ });
82
+ }
56
83
  const before = readText(doc.path);
57
84
  const logPath = historyLogPathFor(doc.root, doc.path);
58
85
  rmSync(doc.path);
59
- pruneEmptyParents(resolvePath(doc.path), doc.name.split('/').length);
86
+ pruneEmptyParents(resolvePath(doc.path), doc.physicalRelativePath.split('/').length);
60
87
  // The log is the audit trail and outlives the doc: the tombstone carries the
61
88
  // full final text, and a later `write` of the same name keeps appending to
62
- // this same log — one log per name over its whole life.
89
+ // this same log — one log per physical path over its whole life.
63
90
  appendHistoryRecord(logPath, buildHistoryRecord({ op: 'delete', before, after: '' }));
64
91
  return {
65
92
  name: doc.name,
@@ -67,7 +94,7 @@ export const deleteLeaf = defineLeaf({
67
94
  path: doc.path,
68
95
  deleted: true,
69
96
  log_path: logPath,
70
- follow_up: `Removed. Its revision history survives — read it with \`crtr memory history ${doc.name}\`, or browse what remains with \`crtr memory list\`.`,
97
+ follow_up: `Removed${selected.collisionPaths.length > 0 ? ` the first physical path in stable lexical order (${doc.path}) to recover a same-store collision.` : '.'} Its revision history survives — read it with \`crtr memory history ${doc.name}\`, or browse what remains with \`crtr memory list\`.`,
71
98
  };
72
99
  },
73
100
  });
@@ -2,9 +2,9 @@ import { defineLeaf } from '../../core/command.js';
2
2
  import { CrtrError, notFound, usage } from '../../core/errors.js';
3
3
  import { parseFrontmatterGeneric } from '../../core/frontmatter.js';
4
4
  import { readText, writeText } from '../../core/fs-utils.js';
5
- import { resolveMemoryDoc } from '../../core/memory-resolver.js';
5
+ import { loadMemoryStoreView, resolveMemoryCandidates, } from '../../core/memory-resolver.js';
6
6
  import { appendHistoryRecord, buildHistoryRecord, historyLogPathFor, readHistoryRecords, } from '../../core/memory/history.js';
7
- import { DOC_RATIONALE_CONSTRAINT, GUIDE_DOC_LINKS, GUIDE_PREDICATE_VOCABULARY, GUIDE_ROUTING_LINE, GUIDE_SURFACES, MEMORY_SCOPES, coerceGate, coerceSurface, memoryExtensionCatalogForDoc, memoryExtensionFieldCatalogHelp, overlayParam, parseRequestedExtensionChanges, applyExtensionChanges, literalBodySegment, serializeMemoryDocLiteral, } from './shared.js';
7
+ import { DOC_RATIONALE_CONSTRAINT, GUIDE_CANONICAL_NAMES, GUIDE_DOC_LINKS, GUIDE_PREDICATE_VOCABULARY, GUIDE_ROUTING_LINE, GUIDE_SURFACES, coerceGate, coerceSurface, memoryExtensionCatalogForDoc, memoryExtensionFieldCatalogHelp, overlayParam, parseRequestedExtensionChanges, applyExtensionChanges, literalBodySegment, serializeMemoryDocLiteral, selectorParam, requireLocalNameInStore, requireUnambiguousDoc, resolveReadSelector, } from './shared.js';
8
8
  /** Frontmatter fields an overlay flag cannot express removing. `kind` and the
9
9
  * routing line are the document's contract (a doc without them is malformed);
10
10
  * `origin` and `last-updated` are runtime provenance. */
@@ -25,10 +25,11 @@ export const editLeaf = defineLeaf({
25
25
  GUIDE_ROUTING_LINE + '\n\n' +
26
26
  GUIDE_SURFACES + '\n\n' +
27
27
  GUIDE_PREDICATE_VOCABULARY + '\n\n' +
28
+ GUIDE_CANONICAL_NAMES + '\n\n' +
28
29
  GUIDE_DOC_LINKS + '\n\n' +
29
30
  'Create a document that does not exist yet with `crtr memory write`. Read the revisions back with `crtr memory history`.',
30
31
  params: [
31
- { kind: 'positional', name: 'name', required: true, constraint: 'Path-derived memory identifier (e.g. `topic` or `area/topic`), resolved as `read` resolves it: exact identity/direct path by precedence node > project stack > profile > user, then bare leaf fallback. A leaf matching multiple documents in one source is rejected as ambiguous \u2014 pass the full name or --scope. Builtin and installed-plugin docs are read-only corpora and are refused.' },
32
+ { kind: 'positional', name: 'name', required: true, constraint: 'Full canonical identity, resolved exactly in the target view or exact project store. A project document requires its complete `<namespace>/<local name>`; a directory document answers at the directory name, never with a trailing `/INDEX`. Same-store physical collisions are refused with both paths; `--dir` selects one exact project store. Builtin and installed-plugin docs are read-only corpora and are refused.' },
32
33
  { kind: 'flag', name: 'rationale', type: 'string', required: true, constraint: 'Why THIS REVISION is happening \u2014 the audit-trail entry, required on every edit. Recorded in the document\u2019s revision history, NEVER written into the document. To change the doc\u2019s standing `rationale` frontmatter field instead, use --doc-rationale.' },
33
34
  { kind: 'flag', name: 'verbatim', type: 'bool', required: false, constraint: 'Declares the piped body a verbatim payload from the principal \u2014 their own text, relayed unchanged \u2014 rather than agent-authored prose. Pure provenance recorded in the history record; the save path is identical either way, because there is only one save path and it never transforms.' },
34
35
  overlayParam('kind'),
@@ -42,7 +43,9 @@ export const editLeaf = defineLeaf({
42
43
  { 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 revised.' },
43
44
  { kind: 'flag', name: 'unset-extension', type: 'string', required: false, repeatable: true, constraint: 'Remove one explicitly persisted declared field by its full `extensions.<plugin>.<field>` path. A removed explicit value lets its declaration default apply only in structured output.' },
44
45
  { kind: 'flag', name: 'unset', type: 'enum', choices: [...CLEARABLE_FIELDS], required: false, repeatable: true, constraint: 'Remove a frontmatter field an overlay flag cannot express removing. One field per occurrence. `kind`, `when-and-why-to-read`, `origin`, and `last-updated` are never clearable.' },
45
- { kind: 'flag', name: 'scope', type: 'enum', choices: [...MEMORY_SCOPES], required: false, constraint: 'Restrict resolution to this scope before editing. A disambiguation filter only \u2014 never a creation target, and never a way to move a doc between stores.' },
46
+ selectorParam('scope-read', {}, 'Edit resolves the address winner and refuses same-store physical collisions.'),
47
+ selectorParam('dir'),
48
+ selectorParam('profile'),
46
49
  { kind: 'stdin', name: 'body', required: false, constraint: 'Full replacement body (markdown, no frontmatter), saved byte for byte. Piped on stdin only \u2014 this leaf already claims the one positional for NAME. Absent or empty stdin leaves the existing body untouched, which is how a frontmatter-only revision is made.' },
47
50
  ],
48
51
  output: [
@@ -58,7 +61,7 @@ export const editLeaf = defineLeaf({
58
61
  outputKind: 'object',
59
62
  dynamicState: () => memoryExtensionFieldCatalogHelp('edit'),
60
63
  effects: [
61
- 'Rewrites memory/<name>.md at the resolved scope and appends one `edit` record to memory/.history/<name>.jsonl.',
64
+ 'Rewrites the selected physical memory document and appends one `edit` record to its mirrored `.history` sidecar.'
62
65
  ],
63
66
  },
64
67
  run: async (input) => {
@@ -67,12 +70,27 @@ export const editLeaf = defineLeaf({
67
70
  const rationale = input['rationale'];
68
71
  const verbatim = input['verbatim'] === true;
69
72
  const stdinBody = input['body'] ?? '';
73
+ const dirArg = input['dir'];
74
+ const profileArg = input['profile'];
75
+ const selector = resolveReadSelector({ scope: scopeArg, dir: dirArg, profile: profileArg });
70
76
  let doc;
71
77
  try {
72
- doc = resolveMemoryDoc(nameRaw, {
73
- includeDescendants: true,
74
- ...(scopeArg !== undefined ? { scope: scopeArg } : {}),
75
- });
78
+ const set = selector.store === null
79
+ ? resolveMemoryCandidates(nameRaw, {
80
+ includeDescendants: true,
81
+ ...(selector.scope === undefined ? {} : { scope: selector.scope }),
82
+ })
83
+ : (() => {
84
+ requireLocalNameInStore(selector.store, nameRaw);
85
+ return loadMemoryStoreView(selector.store, true).candidates(nameRaw);
86
+ })();
87
+ if (set === null) {
88
+ throw notFound(`memory document not found: ${nameRaw}`, {
89
+ memory: nameRaw,
90
+ next: `\`edit\` revises a document that already exists. Create it with \`crtr memory write ${nameRaw} ...\`, or run \`crtr memory find <query>\` if it exists under another name.`,
91
+ });
92
+ }
93
+ doc = requireUnambiguousDoc(set);
76
94
  }
77
95
  catch (e) {
78
96
  if (e instanceof CrtrError && e.code === 'not_found') {
@@ -81,7 +99,7 @@ export const editLeaf = defineLeaf({
81
99
  next: `\`edit\` revises a document that already exists. Create it with \`crtr memory write ${nameRaw} ...\`, or run \`crtr memory find <query>\` if it exists under another name.`,
82
100
  });
83
101
  }
84
- throw e; // ambiguous / usage propagate with their own recovery guidance
102
+ throw e;
85
103
  }
86
104
  if (doc.plugin !== undefined) {
87
105
  throw usage(`${doc.name} belongs to the installed plugin "${doc.plugin}" \u2014 a read-only corpus managed by \`crtr pkg\`, not editable here. To override just this doc, write a same-named doc at a writable scope (\`crtr memory write ${doc.name} ...\`).`, { memory: doc.name, plugin: doc.plugin });
@@ -94,6 +112,28 @@ export const editLeaf = defineLeaf({
94
112
  const parsed = parseFrontmatterGeneric(before);
95
113
  const previous = { ...(parsed.data ?? {}) };
96
114
  const frontmatter = { ...previous };
115
+ if (doc.scope === 'project' && doc.localName === '') {
116
+ if (doc.store.ownerRelativePath !== '' && Object.prototype.hasOwnProperty.call(previous, 'namespace')) {
117
+ throw usage(`a nested store's INDEX.md carries no namespace: — the repository at ${doc.store.repositoryRoot ?? doc.root} declares it`, {
118
+ memory: doc.name,
119
+ path: doc.path,
120
+ next: `Run \`crtr memory lint --dir ${doc.store.ownerDir ?? doc.root}\` or \`crtr sys migrate --dir ${doc.store.repositoryRoot ?? doc.root}\`.`,
121
+ });
122
+ }
123
+ if (doc.store.ownerRelativePath === '' && previous['namespace'] !== doc.store.repositoryNamespace) {
124
+ throw usage(`the repository namespace on ${doc.path} is immutable; it must remain \`${doc.store.repositoryNamespace}\``, {
125
+ memory: doc.name,
126
+ path: doc.path,
127
+ });
128
+ }
129
+ if (previous['name'] !== undefined) {
130
+ throw usage(`the project root INDEX at ${doc.path} cannot carry a \`name:\` field`, {
131
+ memory: doc.name,
132
+ path: doc.path,
133
+ next: `Run \`crtr sys migrate --dir ${doc.store.repositoryRoot ?? doc.store.ownerDir ?? doc.root}\`.`,
134
+ });
135
+ }
136
+ }
97
137
  const setIf = (key, value) => {
98
138
  if (value !== undefined)
99
139
  frontmatter[key] = value;
@@ -1,81 +1,99 @@
1
1
  import { defineLeaf } from '../../core/command.js';
2
2
  import { usage } from '../../core/errors.js';
3
- import { listAllMemoryDocs } from '../../core/memory-resolver.js';
3
+ import { listAllMemoryDocs, loadMemoryStoreView } from '../../core/memory-resolver.js';
4
4
  import { parseSubstrateDoc } from '../../core/substrate/schema.js';
5
5
  import { paginate } from '../../core/pagination.js';
6
- import { MEMORY_KINDS, MEMORY_SCOPES } from './shared.js';
7
- function substrateUnit(d) {
8
- return {
9
- name: d.name,
10
- kind: d.kind,
11
- scope: d.scope,
12
- path: d.path,
13
- routing: d.whenAndWhyToRead,
14
- shortForm: d.shortForm,
15
- loadBody: () => d.body,
16
- };
17
- }
18
- /** The candidate set: every substrate memory document (native + plugin, supplied
19
- * by listAllMemoryDocs), optionally narrowed to one kind and/or one scope.
20
- * Within that explicit narrowing find searches EVERYTHING — it never applies
21
- * gate or visibility-rung filtering (design §11#3); --kind/--scope are caller
22
- * intent, not a visibility policy.
6
+ import { candidateRow } from './read.js';
7
+ import { MEMORY_KINDS, registerMemoryListingExposure, requireMountedStore, resolveReadSelector, selectorParam, } from './shared.js';
8
+ /** The search space over one corpus: every substrate memory document, narrowed
9
+ * to one kind when asked. Within that narrowing find searches EVERYTHING — it
10
+ * never applies gate or visibility-rung filtering; --kind/--scope/--dir are
11
+ * caller intent, not a visibility policy.
23
12
  *
24
- * Dedup by (scope, name): native and plugin docs of the same scope can collide
25
- * on identity. The FIRST unit encountered for a given identity wins — native
26
- * before plugin, which mirrors the memory resolver's own precedence. */
27
- function candidates(kindFilter, scopeFilter) {
28
- const seen = new Set(); // "scope/name" → first wins
29
- const add = (u) => {
30
- const id = `${u.scope}/${u.name}`;
31
- if (seen.has(id))
32
- return;
33
- seen.add(id);
34
- units.push(u);
35
- };
13
+ * A target view is deduped by canonical name, first in source order wins, so a
14
+ * hit names the document a read of that name returns. An exact store (`--dir`)
15
+ * is not deduped: naming a store means seeing what it physically holds. */
16
+ function searchUnits(kindFilter, docs, dedup) {
17
+ const byName = new Map();
18
+ for (const doc of docs) {
19
+ const bucket = byName.get(doc.name);
20
+ if (bucket !== undefined)
21
+ bucket.push(doc);
22
+ else
23
+ byName.set(doc.name, [doc]);
24
+ }
25
+ const seen = new Set();
36
26
  const units = [];
37
- for (const doc of listAllMemoryDocs(scopeFilter, false, true)) {
27
+ for (const doc of docs) {
38
28
  const sub = parseSubstrateDoc(doc);
39
29
  if (sub === null)
40
30
  continue;
41
31
  if (kindFilter !== undefined && sub.kind !== kindFilter)
42
32
  continue;
43
- add(substrateUnit(sub));
33
+ if (dedup) {
34
+ if (seen.has(sub.name))
35
+ continue;
36
+ seen.add(sub.name);
37
+ }
38
+ units.push({
39
+ name: sub.name,
40
+ localName: doc.localName,
41
+ kind: sub.kind,
42
+ scope: sub.scope,
43
+ path: sub.path,
44
+ storeRoot: doc.root,
45
+ physicalRelativePath: doc.physicalRelativePath,
46
+ routing: sub.whenAndWhyToRead,
47
+ shortForm: sub.shortForm,
48
+ candidates: byName.get(doc.name) ?? [doc],
49
+ loadBody: () => sub.body,
50
+ });
44
51
  }
45
52
  return units;
46
53
  }
54
+ /** The physical location fields --paths adds to any hit shape. */
55
+ function pathFields(u) {
56
+ return {
57
+ path: u.path,
58
+ store_root: u.storeRoot,
59
+ physical_relative_path: u.physicalRelativePath,
60
+ ...(u.candidates.length > 1 ? { candidates: u.candidates.map((c) => candidateRow(c, u.candidates[0])) } : {}),
61
+ };
62
+ }
47
63
  export const findLeaf = defineLeaf({
48
64
  name: 'find',
49
65
  description: 'relevance search across memory documents',
50
- whenToUse: 'you already have a topic or keyword in mind and need to find which document covers it — ranks documents by relevance, weighted over name, the read-routing line, and short-form (add --body to also weigh body text). Searches every scope by default regardless of any visibility gate; --scope narrows to one. Reach for `crtr memory list` instead when you want the whole inventory rather than a topic match, and --grep when you need an exact regex or literal-string match across document bodies.',
66
+ whenToUse: 'the discovery surface when you do NOT know a document\u2019s canonical name — ranks documents by relevance over the canonical name, the store-local name, the physical path, the read-routing line, and short-form (add --body to also weigh body text), and every hit reports the canonical name `crtr memory read` takes. Searches every scope by default regardless of any visibility gate; --scope narrows to one class of store, --dir to one exact store. Reach for `crtr memory list` instead when you want the whole inventory rather than a topic match, and --grep when you need an exact regex or literal-string match across document bodies.',
51
67
  help: {
52
68
  name: 'memory find',
53
- summary: 'bounded relevance search across memory documents, weighted over name/routing-line/short-form (and body with --body)',
69
+ summary: 'bounded relevance search across memory documents, weighted over name/local name/physical path/routing-line/short-form (and body with --body)',
54
70
  params: [
55
- { kind: 'positional', name: 'query', required: true, constraint: 'With ranked search (default): whitespace-separated terms, matched case-insensitively and weighted over name, the read-routing line, and short-form (plus body with --body); documents matching more/stronger fields rank higher. By default returns at most 10 hits scoring at least half the top score. With --grep: an ECMAScript regex applied to each document body line.' },
71
+ { kind: 'positional', name: 'query', required: true, constraint: 'With ranked search (default): whitespace-separated terms, matched case-insensitively and weighted over the document\u2019s names (canonical, store-local, and physical path), the read-routing line, and short-form (plus body with --body); documents matching more/stronger fields rank higher. By default returns at most 10 hits scoring at least half the top score. With --grep: an ECMAScript regex applied to each document body line.' },
56
72
  { kind: 'flag', name: 'kind', type: 'enum', choices: [...MEMORY_KINDS], required: false, constraint: 'Filter to a single kind. Default: all kinds.' },
57
- { kind: 'flag', name: 'scope', type: 'enum', choices: [...MEMORY_SCOPES], required: false, constraint: 'Filter to a single scope. Default: all resolved scopes, builtin included.' },
73
+ selectorParam('scope-read', {}, 'Default: every resolved scope, builtin included.'),
74
+ selectorParam('dir', {}, 'Searches that store alone, and searches it physically: two documents sharing one canonical name are both searched and both reported, where the default view searches only the winner.'),
58
75
  { kind: 'flag', name: 'grep', type: 'bool', required: false, constraint: 'Treat the query as an ECMAScript regex and match it against document bodies, instead of weighted relevance ranking. Mutually exclusive with --body.' },
59
76
  { kind: 'flag', name: 'body', type: 'bool', required: false, constraint: 'Also weigh document body text in the relevance ranking (in addition to name/when/why/short-form). Ignored under --grep, which always scans bodies.' },
60
77
  { kind: 'flag', name: 'limit', type: 'int', required: false, constraint: 'Maximum hits to return per page after relevance filtering. Default 10, hard max 100.' },
61
78
  { kind: 'flag', name: 'cursor', type: 'string', required: false, constraint: 'Opaque token from next_cursor. Omit on first call.' },
62
79
  { kind: 'flag', name: 'min-score', type: 'int', required: false, constraint: 'Ranked mode only. Minimum score to return, replacing the default adaptive floor of half the top score. Must be at least 1.' },
63
- { kind: 'flag', name: 'paths', type: 'bool', required: false, constraint: 'Include absolute file paths in hits. Off by default; names are sufficient for `crtr memory read <name>`.' },
80
+ { kind: 'flag', name: 'paths', type: 'bool', required: false, constraint: 'Include each hit\u2019s physical location — absolute path, store root, store-relative path, and every other physical document sharing its canonical name. Off by default; the canonical name is sufficient for `crtr memory read <name>`.' },
64
81
  ],
65
82
  output: [
66
83
  { name: 'query', type: 'string', required: true, constraint: 'Echo of the input query.' },
67
84
  { name: 'matches', type: 'integer', required: true, constraint: 'Total matches before the default relevance floor and limit are applied (ranked mode) or total matching lines (grep mode) — across all pages, not just this one.' },
68
- { name: 'hits', type: 'object[]', required: true, constraint: 'Ranked mode — each: {name, kind, scope, score, short_form}, sorted by score descending. Grep mode — each: {name, scope, line, text}, sorted by name then line. With --paths, each shape also includes path. Paginated via --limit/--cursor.' },
85
+ { name: 'hits', type: 'object[]', required: true, constraint: 'Ranked mode — each: {name, kind, scope, score, short_form}, sorted by score descending. Grep mode — each: {name, scope, line, text}, sorted by name then line. `name` is always the canonical name, whatever field matched. With --paths, each shape also carries path, store_root, physical_relative_path, and candidates when several physical documents share the name. Paginated via --limit/--cursor.' },
69
86
  { name: 'next_cursor', type: 'string | null', required: true, constraint: 'Opaque token for the next page; null means no more items.' },
70
87
  { name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next commands for reading a hit in full or refining the search.' },
71
88
  ],
72
89
  outputKind: 'object',
73
- effects: ['None. Read-only.'],
90
+ effects: ['Records this search page as listing context for the current node without marking any matched document as loaded.'],
74
91
  },
75
92
  run: async (input) => {
76
93
  const query = input['query'];
77
94
  const kindFilter = input['kind'];
78
- const scopeFilter = input['scope'];
95
+ const dirArg = input['dir'];
96
+ const selector = resolveReadSelector({ scope: input['scope'], dir: dirArg });
79
97
  const grep = input['grep'];
80
98
  const weighBody = input['body'];
81
99
  const limitArg = input['limit'];
@@ -93,7 +111,21 @@ export const findLeaf = defineLeaf({
93
111
  if (minScore !== undefined && minScore < 1)
94
112
  throw usage('--min-score must be at least 1.');
95
113
  const limit = limitArg ?? 10;
96
- const units = candidates(kindFilter, scopeFilter);
114
+ const listingIdentity = `listing:memory-find:${JSON.stringify({
115
+ query,
116
+ kind: kindFilter ?? null,
117
+ scope: selector.scope ?? null,
118
+ dir: dirArg ?? null,
119
+ grep: grep === true,
120
+ body: weighBody === true,
121
+ limit,
122
+ cursor: cursor ?? null,
123
+ minScore: minScore ?? null,
124
+ paths: includePaths === true,
125
+ })}`;
126
+ const units = selector.store !== null
127
+ ? searchUnits(kindFilter, loadMemoryStoreView(requireMountedStore(selector.store), false).docs, false)
128
+ : searchUnits(kindFilter, listAllMemoryDocs(selector.scope, false, true), true);
97
129
  // --- grep mode: regex over every unit's body, one row per matching line ---
98
130
  if (grep) {
99
131
  let regex;
@@ -108,25 +140,32 @@ export const findLeaf = defineLeaf({
108
140
  const lines = u.loadBody().split('\n');
109
141
  lines.forEach((text, idx) => {
110
142
  if (regex.test(text))
111
- matches.push({ name: u.name, scope: u.scope, path: u.path, line: idx + 1, text });
143
+ matches.push({ name: u.name, scope: u.scope, unit: u, line: idx + 1, text });
112
144
  });
113
145
  }
146
+ // Physical path is part of the identity: under --dir two documents can
147
+ // share one canonical name, and pagination needs unique ascending keys.
114
148
  matches.sort((a, b) => {
115
149
  const nc = a.name.localeCompare(b.name);
116
150
  if (nc !== 0)
117
151
  return nc;
118
152
  const sc = a.scope.localeCompare(b.scope);
119
- return sc !== 0 ? sc : a.line - b.line;
153
+ if (sc !== 0)
154
+ return sc;
155
+ const pc = a.unit.physicalRelativePath.localeCompare(b.unit.physicalRelativePath);
156
+ return pc !== 0 ? pc : a.line - b.line;
120
157
  });
121
- const keyOf = (m) => `${m.name}:${m.scope}:${String(m.line).padStart(7, '0')}`;
158
+ const keyOf = (m) => `${m.name}:${m.scope}:${m.unit.physicalRelativePath}:${String(m.line).padStart(7, '0')}`;
122
159
  const page = paginate(matches, { limit, cursor }, { defaultLimit: 10, maxLimit: 100, keyOf, total: 'omit' });
123
- return {
160
+ const output = {
124
161
  query,
125
162
  matches: matches.length,
126
- hits: page.items.map(({ path, ...match }) => includePaths ? { ...match, path } : match),
163
+ hits: page.items.map(({ unit, ...match }) => (includePaths ? { ...match, ...pathFields(unit) } : match)),
127
164
  next_cursor: page.next_cursor,
128
165
  follow_up: 'Read a hit with `crtr memory read <name>`. Run `crtr memory find -h` for filters and verbosity.',
129
166
  };
167
+ registerMemoryListingExposure(listingIdentity);
168
+ return output;
130
169
  }
131
170
  // --- ranked mode: weighted relevance over name/routing-line/short-form(+body) ---
132
171
  const terms = query
@@ -137,13 +176,19 @@ export const findLeaf = defineLeaf({
137
176
  throw usage('query must contain at least one non-whitespace term');
138
177
  const hits = [];
139
178
  for (const u of units) {
179
+ // One name weight over three spellings of the same document: an agent
180
+ // searching a store-local name or a physical path is searching for the
181
+ // identity, and counting each spelling separately would inflate the band
182
+ // the adaptive floor is calibrated against.
140
183
  const nameLc = u.name.toLowerCase();
184
+ const localLc = u.localName.toLowerCase();
185
+ const physLc = u.physicalRelativePath.toLowerCase();
141
186
  const routingLc = u.routing.toLowerCase();
142
187
  const shortLc = u.shortForm.toLowerCase();
143
188
  const bodyLc = weighBody ? u.loadBody().toLowerCase() : null;
144
189
  let score = 0;
145
190
  for (const term of terms) {
146
- if (nameLc.includes(term))
191
+ if (nameLc.includes(term) || localLc.includes(term) || physLc.includes(term))
147
192
  score += 10;
148
193
  if (routingLc.includes(term))
149
194
  score += 5;
@@ -156,18 +201,20 @@ export const findLeaf = defineLeaf({
156
201
  hits.push({ unit: u, score });
157
202
  }
158
203
  // Sort comparator MUST match keyOf exactly (score desc, then scope asc,
159
- // then name asc as the final deterministic identity component — the
160
- // candidate dedup keys on scope+name, so this pair is always unique).
161
- // Sorting equal-score hits from different scopes by name alone while the
162
- // cursor key orders by scope first would let the array disagree with its
163
- // own keys, and paginate() would silently skip a later item.
204
+ // then name asc, then physical path asc — which is unique even under --dir,
205
+ // where two documents can share one canonical name). Sorting equal-score
206
+ // hits by one order while the cursor key uses another would let the array
207
+ // disagree with its own keys, and paginate() would silently skip an item.
164
208
  hits.sort((a, b) => {
165
209
  if (b.score !== a.score)
166
210
  return b.score - a.score;
167
211
  const scopeCmp = a.unit.scope.localeCompare(b.unit.scope);
168
212
  if (scopeCmp !== 0)
169
213
  return scopeCmp;
170
- return a.unit.name.localeCompare(b.unit.name);
214
+ const nameCmp = a.unit.name.localeCompare(b.unit.name);
215
+ if (nameCmp !== 0)
216
+ return nameCmp;
217
+ return a.unit.physicalRelativePath.localeCompare(b.unit.physicalRelativePath);
171
218
  });
172
219
  const adaptiveFloor = hits.length === 0 ? 1 : Math.max(1, Math.ceil(hits[0].score / 2));
173
220
  const floor = minScore ?? adaptiveFloor;
@@ -175,9 +222,9 @@ export const findLeaf = defineLeaf({
175
222
  // relevant is already sorted score-desc/scope-asc/name-asc, which is
176
223
  // exactly ascending order by this key (higher score → smaller key),
177
224
  // satisfying paginate's ascending-key precondition.
178
- const keyOf = (h) => `${String(1_000_000 - h.score).padStart(7, '0')}:${h.unit.scope}:${h.unit.name}`;
225
+ const keyOf = (h) => `${String(1_000_000 - h.score).padStart(7, '0')}:${h.unit.scope}:${h.unit.name}:${h.unit.physicalRelativePath}`;
179
226
  const page = paginate(relevant, { limit, cursor }, { defaultLimit: 10, maxLimit: 100, keyOf, total: 'omit' });
180
- return {
227
+ const output = {
181
228
  query,
182
229
  matches: hits.length,
183
230
  hits: page.items.map((h) => ({
@@ -186,10 +233,12 @@ export const findLeaf = defineLeaf({
186
233
  scope: h.unit.scope,
187
234
  score: h.score,
188
235
  short_form: h.unit.shortForm,
189
- ...(includePaths ? { path: h.unit.path } : {}),
236
+ ...(includePaths ? pathFields(h.unit) : {}),
190
237
  })),
191
238
  next_cursor: page.next_cursor,
192
239
  follow_up: 'Read a hit with `crtr memory read <name>`. Run `crtr memory find -h` for filters and verbosity.',
193
240
  };
241
+ registerMemoryListingExposure(listingIdentity);
242
+ return output;
194
243
  },
195
244
  });