@north-light/crouter 0.3.227 → 0.3.228

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 (286) 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/05-kinds/design/00-base.md +3 -4
  7. package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +4 -3
  8. package/dist/builtin-memory/05-kinds/design/design-contract.md +3 -6
  9. package/dist/builtin-memory/05-kinds/plan/00-base.md +3 -4
  10. package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +4 -5
  11. package/dist/builtin-memory/05-kinds/plan/plan-contract.md +5 -13
  12. package/dist/builtin-memory/design/guide.md +38 -16
  13. package/dist/builtin-memory/design/roadmap.md +4 -4
  14. package/dist/builtin-memory/insights/init.md +5 -5
  15. package/dist/builtin-memory/internal/INDEX.md +1 -1
  16. package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
  17. package/dist/builtin-memory/internal/memory-loading.md +6 -6
  18. package/dist/builtin-memory/internal/plugins.md +1 -1
  19. package/dist/builtin-memory/plan/guide.md +53 -0
  20. package/dist/builtin-memory/plan/roadmap.md +10 -8
  21. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +50 -37
  22. package/dist/clients/attach/__tests__/pane-tag-successor.test.js +56 -0
  23. package/dist/clients/attach/__tests__/profile-files.test.js +21 -0
  24. package/dist/clients/attach/chrome/inbox-strip.js +11 -7
  25. package/dist/clients/attach/session/pane-tag.d.ts +28 -2
  26. package/dist/clients/attach/session/pane-tag.js +48 -22
  27. package/dist/clients/attach/session/profile-files.d.ts +6 -0
  28. package/dist/clients/attach/session/profile-files.js +44 -11
  29. package/dist/clients/attach/slash/dispatch.js +4 -7
  30. package/dist/clients/attach/viewer.js +588 -596
  31. package/dist/commands/__tests__/surface-reload-target.test.js +21 -0
  32. package/dist/commands/api-client.d.ts +3 -7
  33. package/dist/commands/api-client.js +7 -12
  34. package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +292 -0
  35. package/dist/commands/memory/__tests__/repository-root-lint.test.js +146 -0
  36. package/dist/commands/memory/delete.js +51 -24
  37. package/dist/commands/memory/edit.js +50 -10
  38. package/dist/commands/memory/find.js +105 -56
  39. package/dist/commands/memory/history.js +51 -31
  40. package/dist/commands/memory/lint.d.ts +0 -9
  41. package/dist/commands/memory/lint.js +78 -322
  42. package/dist/commands/memory/list.d.ts +12 -4
  43. package/dist/commands/memory/list.js +64 -25
  44. package/dist/commands/memory/move.js +103 -73
  45. package/dist/commands/memory/origin.js +35 -5
  46. package/dist/commands/memory/read.d.ts +4 -0
  47. package/dist/commands/memory/read.js +132 -80
  48. package/dist/commands/memory/shared.d.ts +115 -25
  49. package/dist/commands/memory/shared.js +331 -74
  50. package/dist/commands/memory/write.js +72 -21
  51. package/dist/commands/memory.js +2 -2
  52. package/dist/commands/node/lifecycle.js +2 -2
  53. package/dist/commands/pkg/market-manage.js +25 -24
  54. package/dist/commands/pkg/plugin-manage.d.ts +22 -6
  55. package/dist/commands/pkg/plugin-manage.js +118 -25
  56. package/dist/commands/pkg/shared.d.ts +8 -0
  57. package/dist/commands/pkg/shared.js +23 -20
  58. package/dist/commands/surface-reload.d.ts +5 -0
  59. package/dist/commands/surface-reload.js +9 -1
  60. package/dist/commands/sys/__tests__/migrate.test.js +1140 -22
  61. package/dist/commands/sys/__tests__/sync-project-guidance.test.js +218 -0
  62. package/dist/commands/sys/migrate.js +126 -140
  63. package/dist/commands/sys/panels/profiles-panel.d.ts +66 -0
  64. package/dist/commands/sys/panels/profiles-panel.js +599 -0
  65. package/dist/commands/sys/settings-shell.d.ts +4 -2
  66. package/dist/commands/sys/settings-shell.js +35 -4
  67. package/dist/commands/sys/settings.js +21 -5
  68. package/dist/commands/sys/sync-deps.d.ts +2 -0
  69. package/dist/commands/sys/sync-deps.js +26 -15
  70. package/dist/commands/sys/sync-project-guidance.js +228 -145
  71. package/dist/commands/sys/sync-skills.js +28 -17
  72. package/dist/commands/sys/update.js +11 -3
  73. package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +15 -2
  74. package/dist/core/__tests__/config-change-delta.test.js +106 -0
  75. package/dist/core/__tests__/context-intro.test.js +13 -9
  76. package/dist/core/__tests__/daemon-boot.test.js +22 -16
  77. package/dist/core/__tests__/fixtures/memory-slash-live-probe.js +5 -1
  78. package/dist/core/__tests__/human-deliver.test.js +19 -0
  79. package/dist/core/__tests__/inline-memory-refs.test.js +50 -46
  80. package/dist/core/__tests__/{serial → integration}/broker-fork-seam.test.js +1 -1
  81. package/dist/core/__tests__/{serial → integration}/broker-sdk-wiring.test.js +1 -1
  82. package/dist/core/__tests__/{serial → integration}/broker-snapshot-history.test.js +1 -1
  83. package/dist/core/__tests__/{serial → integration}/command-plugins.test.js +94 -3
  84. package/dist/core/__tests__/{serial → integration}/deferred-no-wake.test.js +1 -1
  85. package/dist/core/__tests__/{serial → integration}/flagship-lifecycle.test.js +13 -27
  86. package/dist/core/__tests__/{serial → integration}/host-teardown-process-group.test.js +1 -1
  87. package/dist/core/__tests__/{serial → integration}/human-deliver-e2e.test.js +1 -1
  88. package/dist/core/__tests__/{live-mutation-verbs.test.js → integration/live-mutation-verbs.test.js} +22 -33
  89. package/dist/core/__tests__/{serial → integration}/live-mutation.test.js +21 -41
  90. package/dist/core/__tests__/integration/refresh-stall-recycle.test.d.ts +1 -0
  91. package/dist/core/__tests__/{serial → integration}/refresh-stall-recycle.test.js +1 -1
  92. package/dist/core/__tests__/integration/revive.test.d.ts +1 -0
  93. package/dist/core/__tests__/{serial → integration}/revive.test.js +38 -19
  94. package/dist/core/__tests__/integration/spawn-root.test.d.ts +1 -0
  95. package/dist/core/__tests__/{serial → integration}/spawn-root.test.js +104 -1
  96. package/dist/core/__tests__/integration/subscription-delivery.test.d.ts +1 -0
  97. package/dist/core/__tests__/{serial → integration}/subscription-delivery.test.js +1 -1
  98. package/dist/core/__tests__/integration/tmux-surface.test.d.ts +1 -0
  99. package/dist/core/__tests__/{serial → integration}/tmux-surface.test.js +1 -1
  100. package/dist/core/__tests__/integration/worktree-land.test.d.ts +1 -0
  101. package/dist/core/__tests__/integration/worktree-land.test.js +400 -0
  102. package/dist/core/__tests__/integration/worktree-reap.test.d.ts +1 -0
  103. package/dist/core/__tests__/{serial/worktree.test.js → integration/worktree-reap.test.js} +6 -338
  104. package/dist/core/__tests__/kickoff.test.js +16 -5
  105. package/dist/core/__tests__/memory-resolver-precedence.test.js +122 -91
  106. package/dist/core/__tests__/nested-store-discovery.test.js +5 -3
  107. package/dist/core/__tests__/on-read-crouter-home-fence.test.js +2 -1
  108. package/dist/core/__tests__/on-read-dedup-resume.test.js +39 -27
  109. package/dist/core/__tests__/on-read-nested-store.test.js +19 -13
  110. package/dist/core/__tests__/profile-project-memory-delivery.test.js +138 -44
  111. package/dist/core/__tests__/repository-association.test.d.ts +1 -0
  112. package/dist/core/__tests__/repository-association.test.js +153 -0
  113. package/dist/core/__tests__/repository-root-identity.test.d.ts +1 -0
  114. package/dist/core/__tests__/repository-root-identity.test.js +219 -0
  115. package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.js +27 -11
  116. package/dist/core/__tests__/warm-claim-preference-snapshot.test.d.ts +1 -0
  117. package/dist/core/__tests__/warm-claim-preference-snapshot.test.js +40 -0
  118. package/dist/core/canvas/canvas.d.ts +3 -0
  119. package/dist/core/canvas/canvas.js +30 -10
  120. package/dist/core/canvas/db.js +28 -1
  121. package/dist/core/canvas/paths.d.ts +4 -6
  122. package/dist/core/canvas/paths.js +8 -6
  123. package/dist/core/canvas/render-source.js +2 -2
  124. package/dist/core/canvas/types.d.ts +9 -13
  125. package/dist/core/exclusive-lock.d.ts +2 -0
  126. package/dist/core/exclusive-lock.js +21 -0
  127. package/dist/core/git.d.ts +0 -1
  128. package/dist/core/git.js +0 -3
  129. package/dist/core/human/__tests__/integration/inbox-core.test.d.ts +1 -0
  130. package/dist/core/human/feedback-companion.js +3 -0
  131. package/dist/core/human/scan.js +8 -1
  132. package/dist/core/keybindings/catalog.d.ts +2 -2
  133. package/dist/core/keybindings/catalog.js +2 -1
  134. package/dist/core/memory/doc-link-grammar.js +2 -2
  135. package/dist/core/memory/history.d.ts +24 -0
  136. package/dist/core/memory/history.js +66 -1
  137. package/dist/core/memory/identity.d.ts +65 -0
  138. package/dist/core/memory/identity.js +185 -0
  139. package/dist/core/memory/inline-ref-guidance.d.ts +1 -1
  140. package/dist/core/memory/inline-ref-guidance.js +1 -1
  141. package/dist/core/memory/inline-ref-inventory.d.ts +3 -10
  142. package/dist/core/memory/inline-ref-inventory.js +30 -62
  143. package/dist/core/memory/lint.d.ts +129 -0
  144. package/dist/core/memory/lint.js +517 -0
  145. package/dist/core/memory/project-namespace.d.ts +31 -0
  146. package/dist/core/memory/project-namespace.js +85 -0
  147. package/dist/core/memory/repository-association.d.ts +31 -0
  148. package/dist/core/memory/repository-association.js +129 -0
  149. package/dist/core/memory/tree.d.ts +39 -0
  150. package/dist/core/memory/tree.js +93 -0
  151. package/dist/core/memory-resolver.d.ts +193 -90
  152. package/dist/core/memory-resolver.js +461 -375
  153. package/dist/core/nested-stores.js +6 -13
  154. package/dist/core/profiles/select.d.ts +4 -1
  155. package/dist/core/profiles/select.js +108 -48
  156. package/dist/core/review/__tests__/capture-origin.test.js +2 -2
  157. package/dist/core/review/__tests__/stage-identity.test.js +2 -2
  158. package/dist/core/review/companion.js +11 -2
  159. package/dist/core/runtime/bearings.d.ts +2 -2
  160. package/dist/core/runtime/bearings.js +3 -3
  161. package/dist/core/runtime/broker/daemon-ops.d.ts +2 -2
  162. package/dist/core/runtime/broker/rebind.js +5 -0
  163. package/dist/core/runtime/broker-extension-render.d.ts +7 -3
  164. package/dist/core/runtime/broker-extension-render.js +10 -5
  165. package/dist/core/runtime/broker-persona-guidance.d.ts +19 -5
  166. package/dist/core/runtime/broker-persona-guidance.js +122 -27
  167. package/dist/core/runtime/deliver-live.d.ts +16 -4
  168. package/dist/core/runtime/deliver-live.js +29 -15
  169. package/dist/core/runtime/kickoff.d.ts +3 -3
  170. package/dist/core/runtime/kickoff.js +8 -16
  171. package/dist/core/runtime/lifecycle.js +2 -3
  172. package/dist/core/runtime/nodes.d.ts +3 -4
  173. package/dist/core/runtime/nodes.js +3 -4
  174. package/dist/core/runtime/persona.d.ts +8 -12
  175. package/dist/core/runtime/persona.js +24 -96
  176. package/dist/core/runtime/promote.d.ts +2 -2
  177. package/dist/core/runtime/promote.js +8 -21
  178. package/dist/core/runtime/revive.js +20 -17
  179. package/dist/core/runtime/spawn.js +18 -7
  180. package/dist/core/runtime/tmux-bindings.js +2 -3
  181. package/dist/core/runtime/warm-pool.js +3 -4
  182. package/dist/core/scope.js +2 -0
  183. package/dist/core/self-update.d.ts +0 -2
  184. package/dist/core/self-update.js +2 -35
  185. package/dist/core/substrate/__tests__/surface-match-memory-read.test.d.ts +1 -0
  186. package/dist/core/substrate/__tests__/surface-match-memory-read.test.js +28 -0
  187. package/dist/core/substrate/index.d.ts +2 -2
  188. package/dist/core/substrate/index.js +1 -1
  189. package/dist/core/substrate/injected-store.d.ts +43 -27
  190. package/dist/core/substrate/injected-store.js +208 -104
  191. package/dist/core/substrate/listings.d.ts +19 -12
  192. package/dist/core/substrate/listings.js +75 -52
  193. package/dist/core/substrate/on-read-node.d.ts +4 -7
  194. package/dist/core/substrate/on-read-node.js +6 -8
  195. package/dist/core/substrate/on-read.d.ts +22 -25
  196. package/dist/core/substrate/on-read.js +103 -147
  197. package/dist/core/substrate/render-node.d.ts +4 -7
  198. package/dist/core/substrate/render-node.js +5 -7
  199. package/dist/core/substrate/render.d.ts +21 -3
  200. package/dist/core/substrate/render.js +291 -223
  201. package/dist/core/substrate/schema.d.ts +1 -13
  202. package/dist/core/substrate/schema.js +5 -40
  203. package/dist/core/substrate/session-cache.d.ts +14 -4
  204. package/dist/core/substrate/session-cache.js +40 -22
  205. package/dist/core/substrate/surface-match.d.ts +9 -7
  206. package/dist/core/substrate/surface-match.js +26 -25
  207. package/dist/daemon/__tests__/helpers/source-daemon.d.ts +30 -0
  208. package/dist/daemon/__tests__/helpers/source-daemon.js +174 -0
  209. package/dist/daemon/__tests__/integration/migration-startup.test.d.ts +1 -0
  210. package/dist/daemon/__tests__/integration/migration-startup.test.js +97 -0
  211. package/dist/daemon/api/__tests__/bridge-heartbeat.test.js +21 -5
  212. package/dist/daemon/api/handlers/broker-ops.js +21 -16
  213. package/dist/daemon/api/handlers/memory.js +2 -0
  214. package/dist/daemon/crtrd.js +2 -0
  215. package/dist/daemon/human/finish.js +8 -1
  216. package/dist/daemon/manage.d.ts +0 -1
  217. package/dist/daemon/manage.js +7 -16
  218. package/dist/daemon/startup-policy.d.ts +1 -0
  219. package/dist/daemon/startup-policy.js +1 -0
  220. package/dist/migrations/001-surfaces-frontmatter.js +21 -109
  221. package/dist/migrations/002-profile-project-memory.js +1 -0
  222. package/dist/migrations/003-repository-root-memory-identity/front-door.d.ts +26 -0
  223. package/dist/migrations/003-repository-root-memory-identity/front-door.js +231 -0
  224. package/dist/migrations/003-repository-root-memory-identity/index.d.ts +2 -0
  225. package/dist/migrations/003-repository-root-memory-identity/index.js +513 -0
  226. package/dist/migrations/003-repository-root-memory-identity/references.d.ts +95 -0
  227. package/dist/migrations/003-repository-root-memory-identity/references.js +469 -0
  228. package/dist/migrations/003-repository-root-memory-identity/repository-facts.d.ts +39 -0
  229. package/dist/migrations/003-repository-root-memory-identity/repository-facts.js +349 -0
  230. package/dist/migrations/__tests__/activation-concurrency.test.d.ts +1 -0
  231. package/dist/migrations/__tests__/activation-concurrency.test.js +145 -0
  232. package/dist/migrations/__tests__/activation.test.d.ts +1 -0
  233. package/dist/migrations/__tests__/activation.test.js +149 -0
  234. package/dist/migrations/__tests__/deletion-and-root-declaration.test.d.ts +1 -0
  235. package/dist/migrations/__tests__/deletion-and-root-declaration.test.js +149 -0
  236. package/dist/migrations/activation.d.ts +16 -0
  237. package/dist/migrations/activation.js +78 -0
  238. package/dist/migrations/convergent.d.ts +14 -3
  239. package/dist/migrations/convergent.js +21 -10
  240. package/dist/migrations/corpus.d.ts +78 -0
  241. package/dist/migrations/corpus.js +497 -0
  242. package/dist/migrations/frontmatter-splice.d.ts +15 -0
  243. package/dist/migrations/frontmatter-splice.js +176 -0
  244. package/dist/migrations/registry.d.ts +6 -1
  245. package/dist/migrations/registry.js +7 -2
  246. package/dist/migrations/runner.d.ts +41 -0
  247. package/dist/migrations/runner.js +81 -0
  248. package/dist/migrations/types.d.ts +148 -9
  249. package/dist/migrations/types.js +9 -2
  250. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +225 -17
  251. package/dist/pi-extensions/__tests__/canvas-goal-capture-envelope.test.js +11 -3
  252. package/dist/pi-extensions/canvas-context-intro.d.ts +3 -5
  253. package/dist/pi-extensions/canvas-context-intro.js +50 -46
  254. package/dist/pi-extensions/canvas-doc-substrate.d.ts +1 -8
  255. package/dist/pi-extensions/canvas-doc-substrate.js +55 -122
  256. package/dist/pi-extensions/canvas-stophook.js +6 -13
  257. package/dist/shared/generated-context.d.ts +0 -3
  258. package/dist/shared/generated-context.js +0 -57
  259. package/dist/shared/tool-groups.js +2 -3
  260. package/package.json +5 -4
  261. package/runtime.lock.json +2 -2
  262. /package/dist/api/__tests__/{serial → integration}/client.test.d.ts +0 -0
  263. /package/dist/api/__tests__/{serial → integration}/client.test.js +0 -0
  264. /package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/{serial → integration}/provider-rotation.test.ts +0 -0
  265. /package/dist/clients/{inbox/__tests__/serial/inbox-controller.test.d.ts → attach/__tests__/pane-tag-successor.test.d.ts} +0 -0
  266. /package/dist/clients/{inbox/__tests__/serial/mount-panel.test.d.ts → attach/__tests__/profile-files.test.d.ts} +0 -0
  267. /package/dist/{core/__tests__/live-mutation-verbs.test.d.ts → clients/inbox/__tests__/integration/inbox-controller.test.d.ts} +0 -0
  268. /package/dist/clients/inbox/__tests__/{serial → integration}/inbox-controller.test.js +0 -0
  269. /package/dist/{core/__tests__/serial/broker-fork-seam.test.d.ts → clients/inbox/__tests__/integration/mount-panel.test.d.ts} +0 -0
  270. /package/dist/clients/inbox/__tests__/{serial → integration}/mount-panel.test.js +0 -0
  271. /package/dist/{core/__tests__/serial/broker-sdk-wiring.test.d.ts → commands/__tests__/surface-reload-target.test.d.ts} +0 -0
  272. /package/dist/{core/__tests__/serial/broker-snapshot-history.test.d.ts → commands/memory/__tests__/command-selector-and-mutation-guards.test.d.ts} +0 -0
  273. /package/dist/{core/__tests__/serial/command-plugins.test.d.ts → commands/memory/__tests__/repository-root-lint.test.d.ts} +0 -0
  274. /package/dist/{core/__tests__/serial/deferred-no-wake.test.d.ts → commands/sys/__tests__/sync-project-guidance.test.d.ts} +0 -0
  275. /package/dist/core/__tests__/{serial/flagship-lifecycle.test.d.ts → config-change-delta.test.d.ts} +0 -0
  276. /package/dist/core/__tests__/{serial/host-teardown-process-group.test.d.ts → integration/broker-fork-seam.test.d.ts} +0 -0
  277. /package/dist/core/__tests__/{serial/human-deliver-e2e.test.d.ts → integration/broker-sdk-wiring.test.d.ts} +0 -0
  278. /package/dist/core/__tests__/{serial/live-mutation.test.d.ts → integration/broker-snapshot-history.test.d.ts} +0 -0
  279. /package/dist/core/__tests__/{serial/refresh-stall-recycle.test.d.ts → integration/command-plugins.test.d.ts} +0 -0
  280. /package/dist/core/__tests__/{serial/revive.test.d.ts → integration/deferred-no-wake.test.d.ts} +0 -0
  281. /package/dist/core/__tests__/{serial/spawn-root.test.d.ts → integration/flagship-lifecycle.test.d.ts} +0 -0
  282. /package/dist/core/__tests__/{serial/subscription-delivery.test.d.ts → integration/host-teardown-process-group.test.d.ts} +0 -0
  283. /package/dist/core/__tests__/{serial/tmux-surface.test.d.ts → integration/human-deliver-e2e.test.d.ts} +0 -0
  284. /package/dist/core/__tests__/{serial/worktree.test.d.ts → integration/live-mutation-verbs.test.d.ts} +0 -0
  285. /package/dist/core/{human/__tests__/serial/inbox-core.test.d.ts → __tests__/integration/live-mutation.test.d.ts} +0 -0
  286. /package/dist/core/human/__tests__/{serial → integration}/inbox-core.test.js +0 -0
@@ -5,6 +5,7 @@ const POLL_MS = 10;
5
5
  const DEFAULT_STALE_MS = 30_000;
6
6
  const DEFAULT_TIMEOUT_MS = 5_000;
7
7
  function pause(ms) { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); }
8
+ function pauseAsync(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }
8
9
  function markerPath(path, token) { return `${path}/${MARKER_PREFIX}${token}`; }
9
10
  function observedMarkerToken(path) {
10
11
  try {
@@ -122,3 +123,23 @@ export function withExclusiveDirectoryLock(path, operation, options = {}) {
122
123
  rmSync(lock.path, { recursive: true, force: true });
123
124
  }
124
125
  }
126
+ /** Run an awaited operation under the same token-checked directory lock. */
127
+ export async function withExclusiveDirectoryLockAsync(path, operation, options = {}) {
128
+ const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
129
+ const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
130
+ let lock = tryAcquire(path, staleMs);
131
+ while (lock === null) {
132
+ if (Date.now() >= deadline) {
133
+ throw options.timeoutError?.() ?? new Error(`timed out waiting for the exclusive lock: ${path}`);
134
+ }
135
+ await pauseAsync(Math.min(POLL_MS, Math.max(0, deadline - Date.now())));
136
+ lock = tryAcquire(path, staleMs);
137
+ }
138
+ try {
139
+ return await operation();
140
+ }
141
+ finally {
142
+ if (existsSync(markerPath(lock.path, lock.token)))
143
+ rmSync(lock.path, { recursive: true, force: true });
144
+ }
145
+ }
@@ -13,7 +13,6 @@ export declare function clone(url: string, dest: string, opts?: {
13
13
  ref?: string;
14
14
  depth?: number;
15
15
  }): GitResult;
16
- export declare function pull(cwd: string): GitResult;
17
16
  export declare function fetch(cwd: string, ref?: string): GitResult;
18
17
  export declare function lsRemote(url: string): GitResult;
19
18
  export declare function currentSha(cwd: string): string | null;
package/dist/core/git.js CHANGED
@@ -35,9 +35,6 @@ export function clone(url, dest, opts = {}) {
35
35
  }
36
36
  return res;
37
37
  }
38
- export function pull(cwd) {
39
- return gitSync(['pull', '--ff-only'], cwd);
40
- }
41
38
  export function fetch(cwd, ref) {
42
39
  const args = ['fetch', 'origin'];
43
40
  if (ref)
@@ -16,6 +16,7 @@ import { headlessBrokerHost } from '../runtime/host.js';
16
16
  import { spawnNode } from '../runtime/nodes.js';
17
17
  import { reviveNode } from '../runtime/revive.js';
18
18
  import { materializeSessionBranchAt } from '../runtime/session-branch.js';
19
+ import { initializeForkContextExposure } from '../substrate/injected-store.js';
19
20
  import { pagePath } from './convention.js';
20
21
  export function feedbackCompanionBranchPath(dir) { return `${dir}/companion-branch.jsonl`; }
21
22
  /** Hex sha256 of the ticket's page document, recorded at first comment so the
@@ -73,6 +74,7 @@ export async function realizeFeedbackCompanion(args) {
73
74
  parent: args.senderNodeId,
74
75
  subscribe: false,
75
76
  forkFrom: branchFile,
77
+ forkSourceFile: branchFile,
76
78
  modelOverride: capture.modelSpec,
77
79
  reviewBinding: {
78
80
  kind: 'page_feedback',
@@ -85,6 +87,7 @@ export async function realizeFeedbackCompanion(args) {
85
87
  launch,
86
88
  });
87
89
  try {
90
+ initializeForkContextExposure(node.node_id, args.senderNodeId);
88
91
  reviveNode(node.node_id, { resume: false });
89
92
  if (!await awaitCompanionBind(node.node_id, COMPANION_BIND_DEADLINE_MS)) {
90
93
  throw new Error(`feedback companion did not bind within ${COMPANION_BIND_DEADLINE_MS}ms`);
@@ -1,4 +1,4 @@
1
- import { readdirSync, statSync } from 'node:fs';
1
+ import { existsSync, readdirSync, statSync } from 'node:fs';
2
2
  import { basename, join } from 'node:path';
3
3
  import { claimPath, isResolved, pageManifestPath, reviewPath } from './convention.js';
4
4
  import { readJsonOrNull } from '../fs-utils.js';
@@ -27,6 +27,13 @@ function reviewSummary(dir, id) {
27
27
  return { dir, id, kind: 'review', title: review.title, subtitle: review.subtitle, file: review.file, output: review.output, source: review.source, blockedSince: review.blockedSince, claim: claimSummary(dir) };
28
28
  }
29
29
  function pageSummary(dir, id) {
30
+ // The tickets root IS the canvas node root, so all but a handful of these
31
+ // directories hold no page at all. Gate on the manifest's presence with one
32
+ // stat instead of letting `parsePage` throw: a thrown ENOENT carries a V8
33
+ // stack capture, which over thousands of node directories is the whole cost
34
+ // of a scan.
35
+ if (!existsSync(pageManifestPath(dir)))
36
+ return null;
30
37
  let manifest;
31
38
  try {
32
39
  manifest = parsePage(dir);
@@ -4,12 +4,12 @@ type TmuxMenuAction = 'open' | 'attach' | 'show' | 'view-settings' | 'fold-tools
4
4
  type TmuxMenuEntryAction = Exclude<TmuxMenuAction, 'open' | 'attach'> | `issue.${'idea' | 'bug'}`;
5
5
  type GraphAction = 'down' | 'up' | 'first' | 'last' | 'collapse-or-manager' | 'expand-or-child' | 'focus' | 'revive' | 'focus-manager' | 'close-subtree' | 'dismiss';
6
6
  type BrowseAction = 'quit' | 'down' | 'up' | 'first' | 'last' | 'expand-or-child' | 'collapse-or-parent' | 'toggle-fold' | 'tab-next' | 'tab-previous' | `tab.${1 | 2 | 3 | 4 | 5}` | 'close-subtree' | 'confirm-close' | 'sort' | 'scope' | 'profile' | 'residents' | 'preview' | 'search' | 'revive-now' | 'focus' | 'focus-background' | 'yank-id';
7
- type ProfileMenuAction = 'up' | 'down' | 'select' | 'accept-default' | 'abort' | `slot.${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}` | 'create' | 'search-existing' | 'use-root' | 'set-default' | 'add-directory' | 'unpin';
7
+ type ProfileMenuAction = 'up' | 'down' | 'select' | 'accept-default' | 'abort' | `slot.${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}` | 'create' | 'search-existing' | 'use-root' | 'set-default' | 'add-directory' | 'unpin' | 'manage';
8
8
  type SetupAction = 'cancel' | 'tab-next' | 'tab-previous' | 'open' | 'list-down' | 'list-up' | 'toggle' | 'details' | 'advance' | 'cell-previous' | 'cell-next';
9
9
  type SetupKeybindingAction = 'edit' | 'commit-edit' | 'cancel-edit' | 'unbind' | 'reset-row' | 'reset-all' | 'save' | 'discard' | 'page-up' | 'page-down' | 'filter' | 'filter-delete' | 'filter-clear';
10
10
  type SetupModelsAction = 'open' | 'commit' | 'cancel' | 'scope-next';
11
11
  export type BindingId = `crtr.tmux.menu.${TmuxMenuAction}` | `crtr.tmux.menu.issue.${'idea' | 'bug'}` | AttachMenuBindingId | `crtr.tmux.menu-global.${TmuxMenuEntryAction}` | `crtr.tmux.node.${'next' | 'previous'}` | 'crtr.tmux.inbox.toggle' | `crtr.attach.scroll.${'up' | 'down' | 'page-up' | 'page-down' | 'live'}` | 'crtr.attach.mouse.toggle' | 'crtr.attach.copy.message' | 'crtr.attach.help.toggle' | `crtr.attach.search.${'next' | 'previous' | 'edit' | 'close'}` | AttachBindingId | `crtr.graph.${GraphAction}` | `crtr.graph.confirm.${'accept' | 'cancel'}` | `crtr.inspector.${'force-quit' | 'down' | 'up' | 'section-next' | 'section-prev' | 'page-down' | 'page-up' | 'toggle-detail' | 'cancel-item' | 'open-log' | 'confirm' | 'dismiss' | 'retarget' | 'refresh' | 'quit'}` | `crtr.browse.${BrowseAction}` | `crtr.browse.search.${'cancel' | 'commit' | 'delete' | 'quit'}` | `crtr.profile.menu.${ProfileMenuAction}` | `crtr.profile.search.${'up' | 'down' | 'pick' | 'back' | 'delete' | 'abort'}` | `crtr.setup.${SetupAction}` | `crtr.setup.confirm.${'accept' | 'back'}` | `crtr.setup.keybindings.${SetupKeybindingAction}` | `crtr.setup.models.${SetupModelsAction}`;
12
- export declare const BINDING_CATALOG: readonly [BindingDefinition<"crtr.tmux.menu.open">, BindingDefinition<"crtr.tmux.menu.attach">, ...(BindingDefinition<BindingId> | BindingDefinition<"crtr.tmux.node.next"> | BindingDefinition<"crtr.tmux.node.previous"> | BindingDefinition<"crtr.tmux.inbox.toggle"> | BindingDefinition<"crtr.attach.scroll.up"> | BindingDefinition<"crtr.attach.scroll.down"> | BindingDefinition<"crtr.attach.scroll.page-up"> | BindingDefinition<"crtr.attach.scroll.page-down"> | BindingDefinition<"crtr.attach.scroll.live"> | BindingDefinition<"crtr.attach.mouse.toggle"> | BindingDefinition<"crtr.attach.copy.message"> | BindingDefinition<"crtr.attach.search.next"> | BindingDefinition<"crtr.attach.search.previous"> | BindingDefinition<"crtr.attach.search.edit"> | BindingDefinition<"crtr.attach.search.close"> | BindingDefinition<"crtr.graph.down"> | BindingDefinition<"crtr.graph.up"> | BindingDefinition<"crtr.graph.first"> | BindingDefinition<"crtr.graph.last"> | BindingDefinition<"crtr.graph.collapse-or-manager"> | BindingDefinition<"crtr.graph.expand-or-child"> | BindingDefinition<"crtr.graph.focus"> | BindingDefinition<"crtr.graph.revive"> | BindingDefinition<"crtr.graph.focus-manager"> | BindingDefinition<"crtr.graph.close-subtree"> | BindingDefinition<"crtr.graph.dismiss"> | BindingDefinition<"crtr.graph.confirm.accept"> | BindingDefinition<"crtr.graph.confirm.cancel"> | BindingDefinition<"crtr.inspector.force-quit"> | BindingDefinition<"crtr.inspector.down"> | BindingDefinition<"crtr.inspector.up"> | BindingDefinition<"crtr.inspector.section-next"> | BindingDefinition<"crtr.inspector.section-prev"> | BindingDefinition<"crtr.inspector.page-down"> | BindingDefinition<"crtr.inspector.page-up"> | BindingDefinition<"crtr.inspector.toggle-detail"> | BindingDefinition<"crtr.inspector.cancel-item"> | BindingDefinition<"crtr.inspector.open-log"> | BindingDefinition<"crtr.inspector.confirm"> | BindingDefinition<"crtr.inspector.dismiss"> | BindingDefinition<"crtr.inspector.retarget"> | BindingDefinition<"crtr.inspector.refresh"> | BindingDefinition<"crtr.inspector.quit"> | BindingDefinition<"crtr.browse.quit"> | BindingDefinition<"crtr.browse.down"> | BindingDefinition<"crtr.browse.up"> | BindingDefinition<"crtr.browse.first"> | BindingDefinition<"crtr.browse.last"> | BindingDefinition<"crtr.browse.expand-or-child"> | BindingDefinition<"crtr.browse.collapse-or-parent"> | BindingDefinition<"crtr.browse.toggle-fold"> | BindingDefinition<"crtr.browse.tab-next"> | BindingDefinition<"crtr.browse.tab-previous"> | BindingDefinition<"crtr.browse.tab.2" | "crtr.browse.tab.1" | "crtr.browse.tab.3" | "crtr.browse.tab.4" | "crtr.browse.tab.5"> | BindingDefinition<"crtr.browse.close-subtree"> | BindingDefinition<"crtr.browse.confirm-close"> | BindingDefinition<"crtr.browse.sort"> | BindingDefinition<"crtr.browse.scope"> | BindingDefinition<"crtr.browse.profile"> | BindingDefinition<"crtr.browse.residents"> | BindingDefinition<"crtr.browse.preview"> | BindingDefinition<"crtr.browse.search"> | BindingDefinition<"crtr.browse.revive-now"> | BindingDefinition<"crtr.browse.focus"> | BindingDefinition<"crtr.browse.focus-background"> | BindingDefinition<"crtr.browse.yank-id"> | BindingDefinition<"crtr.browse.search.cancel"> | BindingDefinition<"crtr.browse.search.commit"> | BindingDefinition<"crtr.browse.search.delete"> | BindingDefinition<"crtr.browse.search.quit"> | BindingDefinition<"crtr.profile.menu.up"> | BindingDefinition<"crtr.profile.menu.down"> | BindingDefinition<"crtr.profile.menu.select"> | BindingDefinition<"crtr.profile.menu.accept-default"> | BindingDefinition<"crtr.profile.menu.abort"> | BindingDefinition<"crtr.profile.menu.slot.2" | "crtr.profile.menu.slot.1" | "crtr.profile.menu.slot.3" | "crtr.profile.menu.slot.4" | "crtr.profile.menu.slot.5" | "crtr.profile.menu.slot.6" | "crtr.profile.menu.slot.7" | "crtr.profile.menu.slot.8" | "crtr.profile.menu.slot.9">)[], BindingDefinition<"crtr.profile.menu.create">, BindingDefinition<"crtr.profile.menu.search-existing">, BindingDefinition<"crtr.profile.menu.use-root">, BindingDefinition<"crtr.profile.menu.set-default">, BindingDefinition<"crtr.profile.menu.add-directory">, BindingDefinition<"crtr.profile.menu.unpin">, BindingDefinition<"crtr.profile.search.up">, BindingDefinition<"crtr.profile.search.down">, BindingDefinition<"crtr.profile.search.pick">, BindingDefinition<"crtr.profile.search.back">, BindingDefinition<"crtr.profile.search.delete">, BindingDefinition<"crtr.profile.search.abort">, BindingDefinition<"crtr.setup.cancel">, BindingDefinition<"crtr.setup.tab-next">, BindingDefinition<"crtr.setup.tab-previous">, BindingDefinition<"crtr.setup.open">, BindingDefinition<"crtr.setup.list-down">, BindingDefinition<"crtr.setup.list-up">, BindingDefinition<"crtr.setup.toggle">, BindingDefinition<"crtr.setup.details">, BindingDefinition<"crtr.setup.advance">, BindingDefinition<"crtr.setup.confirm.accept">, BindingDefinition<"crtr.setup.confirm.back">, BindingDefinition<"crtr.setup.keybindings.edit">, BindingDefinition<"crtr.setup.keybindings.commit-edit">, BindingDefinition<"crtr.setup.keybindings.cancel-edit">, BindingDefinition<"crtr.setup.keybindings.unbind">, BindingDefinition<"crtr.setup.keybindings.reset-row">, BindingDefinition<"crtr.setup.keybindings.reset-all">, BindingDefinition<"crtr.setup.keybindings.save">, BindingDefinition<"crtr.setup.keybindings.discard">, BindingDefinition<"crtr.setup.keybindings.page-up">, BindingDefinition<"crtr.setup.keybindings.page-down">, BindingDefinition<"crtr.setup.keybindings.filter">, BindingDefinition<"crtr.setup.keybindings.filter-delete">, BindingDefinition<"crtr.setup.keybindings.filter-clear">, BindingDefinition<"crtr.setup.cell-previous">, BindingDefinition<"crtr.setup.cell-next">, BindingDefinition<"crtr.setup.models.open">, BindingDefinition<"crtr.setup.models.scope-next">, BindingDefinition<"crtr.setup.models.commit">, BindingDefinition<"crtr.setup.models.cancel">];
12
+ export declare const BINDING_CATALOG: readonly [BindingDefinition<"crtr.tmux.menu.open">, BindingDefinition<"crtr.tmux.menu.attach">, ...(BindingDefinition<BindingId> | BindingDefinition<"crtr.tmux.node.next"> | BindingDefinition<"crtr.tmux.node.previous"> | BindingDefinition<"crtr.tmux.inbox.toggle"> | BindingDefinition<"crtr.attach.scroll.up"> | BindingDefinition<"crtr.attach.scroll.down"> | BindingDefinition<"crtr.attach.scroll.page-up"> | BindingDefinition<"crtr.attach.scroll.page-down"> | BindingDefinition<"crtr.attach.scroll.live"> | BindingDefinition<"crtr.attach.mouse.toggle"> | BindingDefinition<"crtr.attach.copy.message"> | BindingDefinition<"crtr.attach.search.next"> | BindingDefinition<"crtr.attach.search.previous"> | BindingDefinition<"crtr.attach.search.edit"> | BindingDefinition<"crtr.attach.search.close"> | BindingDefinition<"crtr.graph.down"> | BindingDefinition<"crtr.graph.up"> | BindingDefinition<"crtr.graph.first"> | BindingDefinition<"crtr.graph.last"> | BindingDefinition<"crtr.graph.collapse-or-manager"> | BindingDefinition<"crtr.graph.expand-or-child"> | BindingDefinition<"crtr.graph.focus"> | BindingDefinition<"crtr.graph.revive"> | BindingDefinition<"crtr.graph.focus-manager"> | BindingDefinition<"crtr.graph.close-subtree"> | BindingDefinition<"crtr.graph.dismiss"> | BindingDefinition<"crtr.graph.confirm.accept"> | BindingDefinition<"crtr.graph.confirm.cancel"> | BindingDefinition<"crtr.inspector.force-quit"> | BindingDefinition<"crtr.inspector.down"> | BindingDefinition<"crtr.inspector.up"> | BindingDefinition<"crtr.inspector.section-next"> | BindingDefinition<"crtr.inspector.section-prev"> | BindingDefinition<"crtr.inspector.page-down"> | BindingDefinition<"crtr.inspector.page-up"> | BindingDefinition<"crtr.inspector.toggle-detail"> | BindingDefinition<"crtr.inspector.cancel-item"> | BindingDefinition<"crtr.inspector.open-log"> | BindingDefinition<"crtr.inspector.confirm"> | BindingDefinition<"crtr.inspector.dismiss"> | BindingDefinition<"crtr.inspector.retarget"> | BindingDefinition<"crtr.inspector.refresh"> | BindingDefinition<"crtr.inspector.quit"> | BindingDefinition<"crtr.browse.quit"> | BindingDefinition<"crtr.browse.down"> | BindingDefinition<"crtr.browse.up"> | BindingDefinition<"crtr.browse.first"> | BindingDefinition<"crtr.browse.last"> | BindingDefinition<"crtr.browse.expand-or-child"> | BindingDefinition<"crtr.browse.collapse-or-parent"> | BindingDefinition<"crtr.browse.toggle-fold"> | BindingDefinition<"crtr.browse.tab-next"> | BindingDefinition<"crtr.browse.tab-previous"> | BindingDefinition<"crtr.browse.tab.2" | "crtr.browse.tab.1" | "crtr.browse.tab.3" | "crtr.browse.tab.4" | "crtr.browse.tab.5"> | BindingDefinition<"crtr.browse.close-subtree"> | BindingDefinition<"crtr.browse.confirm-close"> | BindingDefinition<"crtr.browse.sort"> | BindingDefinition<"crtr.browse.scope"> | BindingDefinition<"crtr.browse.profile"> | BindingDefinition<"crtr.browse.residents"> | BindingDefinition<"crtr.browse.preview"> | BindingDefinition<"crtr.browse.search"> | BindingDefinition<"crtr.browse.revive-now"> | BindingDefinition<"crtr.browse.focus"> | BindingDefinition<"crtr.browse.focus-background"> | BindingDefinition<"crtr.browse.yank-id"> | BindingDefinition<"crtr.browse.search.cancel"> | BindingDefinition<"crtr.browse.search.commit"> | BindingDefinition<"crtr.browse.search.delete"> | BindingDefinition<"crtr.browse.search.quit"> | BindingDefinition<"crtr.profile.menu.up"> | BindingDefinition<"crtr.profile.menu.down"> | BindingDefinition<"crtr.profile.menu.select"> | BindingDefinition<"crtr.profile.menu.accept-default"> | BindingDefinition<"crtr.profile.menu.abort"> | BindingDefinition<"crtr.profile.menu.slot.2" | "crtr.profile.menu.slot.1" | "crtr.profile.menu.slot.3" | "crtr.profile.menu.slot.4" | "crtr.profile.menu.slot.5" | "crtr.profile.menu.slot.6" | "crtr.profile.menu.slot.7" | "crtr.profile.menu.slot.8" | "crtr.profile.menu.slot.9">)[], BindingDefinition<"crtr.profile.menu.create">, BindingDefinition<"crtr.profile.menu.search-existing">, BindingDefinition<"crtr.profile.menu.use-root">, BindingDefinition<"crtr.profile.menu.set-default">, BindingDefinition<"crtr.profile.menu.add-directory">, BindingDefinition<"crtr.profile.menu.unpin">, BindingDefinition<"crtr.profile.menu.manage">, BindingDefinition<"crtr.profile.search.up">, BindingDefinition<"crtr.profile.search.down">, BindingDefinition<"crtr.profile.search.pick">, BindingDefinition<"crtr.profile.search.back">, BindingDefinition<"crtr.profile.search.delete">, BindingDefinition<"crtr.profile.search.abort">, BindingDefinition<"crtr.setup.cancel">, BindingDefinition<"crtr.setup.tab-next">, BindingDefinition<"crtr.setup.tab-previous">, BindingDefinition<"crtr.setup.open">, BindingDefinition<"crtr.setup.list-down">, BindingDefinition<"crtr.setup.list-up">, BindingDefinition<"crtr.setup.toggle">, BindingDefinition<"crtr.setup.details">, BindingDefinition<"crtr.setup.advance">, BindingDefinition<"crtr.setup.confirm.accept">, BindingDefinition<"crtr.setup.confirm.back">, BindingDefinition<"crtr.setup.keybindings.edit">, BindingDefinition<"crtr.setup.keybindings.commit-edit">, BindingDefinition<"crtr.setup.keybindings.cancel-edit">, BindingDefinition<"crtr.setup.keybindings.unbind">, BindingDefinition<"crtr.setup.keybindings.reset-row">, BindingDefinition<"crtr.setup.keybindings.reset-all">, BindingDefinition<"crtr.setup.keybindings.save">, BindingDefinition<"crtr.setup.keybindings.discard">, BindingDefinition<"crtr.setup.keybindings.page-up">, BindingDefinition<"crtr.setup.keybindings.page-down">, BindingDefinition<"crtr.setup.keybindings.filter">, BindingDefinition<"crtr.setup.keybindings.filter-delete">, BindingDefinition<"crtr.setup.keybindings.filter-clear">, BindingDefinition<"crtr.setup.cell-previous">, BindingDefinition<"crtr.setup.cell-next">, BindingDefinition<"crtr.setup.models.open">, BindingDefinition<"crtr.setup.models.scope-next">, BindingDefinition<"crtr.setup.models.commit">, BindingDefinition<"crtr.setup.models.cancel">];
13
13
  export declare const BINDING_IDS: ReadonlySet<string>;
14
14
  /** True for an action whose direct gesture is handled inside an attach viewer.
15
15
  * Tmux menu selectors and root shortcuts are chrome, not pane input. */
@@ -77,7 +77,7 @@ export const BINDING_CATALOG = Object.freeze([
77
77
  ...attachEntry('crtr.tmux.menu.attach.model-ladder.previous', 'crtr.attach.model-ladder.previous', 'Previous model', 'Move to the previous model ladder rung.', ['['], ['alt+shift+m']),
78
78
  ...attachEntry('crtr.tmux.menu.attach.command.inspect', 'crtr.attach.command.inspect', 'Inspect loaded command', 'Open the loaded slash command’s expanded prompt.', ['h'], ['alt+shift+h']),
79
79
  ...attachEntry('crtr.tmux.menu.attach.file-review', 'crtr.attach.file-review', 'Review a file', 'Open a line-anchored review of a file named in the transcript.', [], ['alt+shift+r']),
80
- ...attachEntry('crtr.tmux.menu.attach.profile-files', 'crtr.attach.profile-files', 'Search profile files', 'Search files across the node’s profile projects, profile memory, cwd, and ancestor crouter memory stores.', ['f'], ['ctrl+f']),
80
+ ...attachEntry('crtr.tmux.menu.attach.profile-files', 'crtr.attach.profile-files', 'Search profile files', 'Fuzzy-match path segments across the node family’s working directories, profile projects and memory, and ancestor crouter memory stores; Ctrl+Y copies a selected path.', ['f'], ['ctrl+f']),
81
81
  ...attachEntry('crtr.tmux.menu.attach.search', 'crtr.attach.search', 'Search transcript', 'Search the transcript as it is currently displayed — folded tool output is not searched until it is unfolded.', ['/'], ['alt+/']),
82
82
  ...attachEntry('crtr.tmux.menu.attach.whip', 'crtr.attach.whip', 'Whip agent', 'Interrupt the agent and send a playful command.', ['w'], []),
83
83
  b('crtr.attach.scroll.up', 'Attach', 'Scroll up', 'Scroll the transcript one line up.', ['shift+up'], 'terminal', ATTACH_CONTEXTS),
@@ -171,6 +171,7 @@ function profileBindings() {
171
171
  b('crtr.profile.menu.set-default', 'Profiles', 'Set default', 'Set the selected profile as default.', ['d'], 'terminal', [menu]),
172
172
  b('crtr.profile.menu.add-directory', 'Profiles', 'Add directory', 'Add a directory to the profile.', ['a'], 'terminal', [menu]),
173
173
  b('crtr.profile.menu.unpin', 'Profiles', 'Unpin directory', 'Unpin the selected directory.', ['u'], 'terminal', [menu]),
174
+ b('crtr.profile.menu.manage', 'Profiles', 'Manage profiles', 'Open the profiles settings page in a popup.', ['v'], 'terminal', [menu]),
174
175
  b('crtr.profile.search.up', 'Profiles', 'Move up', 'Move to the previous search result.', ['up', 'ctrl+p'], 'terminal', [search]),
175
176
  b('crtr.profile.search.down', 'Profiles', 'Move down', 'Move to the next search result.', ['down', 'ctrl+n'], 'terminal', [search]),
176
177
  b('crtr.profile.search.pick', 'Profiles', 'Pick profile', 'Pick the highlighted profile.', ['enter'], 'terminal', [search]),
@@ -16,10 +16,10 @@
16
16
  // This module never resolves anything: `findDocLinks` returns candidate links;
17
17
  // a caller decides a link is live by testing `name` against the corpus of
18
18
  // resolvable canonical names.
19
- // A canonical doc name: one or more `[A-Za-z0-9_-]+` segments joined by `/`.
19
+ // A canonical doc name: one or more `[A-Za-z0-9._-]+` segments joined by `/`.
20
20
  // Anything else between `[[` `]]` (spaces, `|` aliases, empty segments) is NOT
21
21
  // a doc link — the grammar has no alias/label form, by design.
22
- const NAME_RE = /^[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_-]+)*$/;
22
+ const NAME_RE = /^[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)*$/;
23
23
  const LINK_RE = /\[\[([^\[\]\n]+)\]\]/g;
24
24
  /** Is `name` a well-formed canonical doc-link name? */
25
25
  export function isDocLinkName(name) {
@@ -1,3 +1,4 @@
1
+ import type { MemoryDoc } from '../memory-resolver.js';
1
2
  /** Sidecar tree name under a store's `memory/` dir. Dot-prefixed and holding
2
3
  * only `.jsonl`, so every substrate scan (which filters to `*.md`, and in
3
4
  * `lint`'s case skips dot-dirs outright) passes it over without an exclusion
@@ -37,6 +38,29 @@ export declare function buildHistoryRecord(input: {
37
38
  before: string;
38
39
  after: string;
39
40
  }): HistoryRecord;
41
+ /** Derive a live document's sidecar from its descriptor. The descriptor's
42
+ * physical path is authoritative: canonical names carry a project namespace,
43
+ * while sidecars remain in the owning store's physical tree. */
44
+ export declare function historyLogPathForDoc(doc: Pick<MemoryDoc, 'root' | 'path'>): string;
45
+ export interface DeletedHistoryCandidate {
46
+ path: string;
47
+ representation: 'directory-index' | 'leaf-file';
48
+ winner: boolean;
49
+ }
50
+ export interface DeletedHistoryResolution {
51
+ logPath: string;
52
+ candidates: readonly DeletedHistoryCandidate[];
53
+ collision?: {
54
+ paths: readonly string[];
55
+ winner: string;
56
+ };
57
+ }
58
+ /** Resolve a deleted document's physical history without reintroducing a
59
+ * canonical-name fallback. `localName` is already stripped of the selected
60
+ * project's exact namespace. The directory-index sidecar is deliberately
61
+ * probed first, and therefore wins deterministically if both physical forms
62
+ * survive; callers can expose `candidates`/`collision` for diagnostics. */
63
+ export declare function resolveDeletedHistoryLogPath(storeRoot: string, localName: string): DeletedHistoryResolution | null;
40
64
  /** Append one record. Single write of one line — concurrent appends interleave
41
65
  * whole records, and every record stands alone, so no lock is needed. */
42
66
  export declare function appendHistoryRecord(logPath: string, record: HistoryRecord): void;
@@ -10,11 +10,12 @@
10
10
  // The raw file is the consumer contract: an external reader (a product surface
11
11
  // reading a store off disk) parses one JSON object per line and computes its
12
12
  // own diffs. Nothing here shells out to git.
13
- import { appendFileSync } from 'node:fs';
13
+ import { appendFileSync, readdirSync } from 'node:fs';
14
14
  import { dirname, join, relative } from 'node:path';
15
15
  import { ensureDir, pathExists, readText } from '../fs-utils.js';
16
16
  import { general } from '../errors.js';
17
17
  import { parseFrontmatterGeneric } from '../frontmatter.js';
18
+ import { normalizeNameSegment } from './identity.js';
18
19
  /** Sidecar tree name under a store's `memory/` dir. Dot-prefixed and holding
19
20
  * only `.jsonl`, so every substrate scan (which filters to `*.md`, and in
20
21
  * `lint`'s case skips dot-dirs outright) passes it over without an exclusion
@@ -48,6 +49,70 @@ export function buildHistoryRecord(input) {
48
49
  };
49
50
  return record;
50
51
  }
52
+ /** Derive a live document's sidecar from its descriptor. The descriptor's
53
+ * physical path is authoritative: canonical names carry a project namespace,
54
+ * while sidecars remain in the owning store's physical tree. */
55
+ export function historyLogPathForDoc(doc) {
56
+ return historyLogPathFor(doc.root, doc.path);
57
+ }
58
+ function matchingDirs(parent, segment) {
59
+ let entries;
60
+ try {
61
+ entries = readdirSync(parent, { withFileTypes: true });
62
+ }
63
+ catch {
64
+ return [];
65
+ }
66
+ return entries
67
+ .filter((entry) => entry.isDirectory() && normalizeNameSegment(entry.name) === segment)
68
+ .sort((a, b) => (a.name === segment ? -1 : b.name === segment ? 1 : a.name.localeCompare(b.name)))
69
+ .map((entry) => join(parent, entry.name));
70
+ }
71
+ function matchingFiles(parent, stem) {
72
+ let entries;
73
+ try {
74
+ entries = readdirSync(parent, { withFileTypes: true });
75
+ }
76
+ catch {
77
+ return [];
78
+ }
79
+ return entries
80
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.jsonl') && normalizeNameSegment(entry.name.slice(0, -'.jsonl'.length)) === stem)
81
+ .sort((a, b) => (a.name === `${stem}.jsonl` ? -1 : b.name === `${stem}.jsonl` ? 1 : a.name.localeCompare(b.name)))
82
+ .map((entry) => join(parent, entry.name));
83
+ }
84
+ function historyFilesForSegments(historyDir, segments, stem) {
85
+ let parents = [historyDir];
86
+ for (const segment of segments) {
87
+ parents = parents.flatMap((parent) => matchingDirs(parent, segment));
88
+ if (parents.length === 0)
89
+ return [];
90
+ }
91
+ return parents.flatMap((parent) => matchingFiles(parent, stem));
92
+ }
93
+ /** Resolve a deleted document's physical history without reintroducing a
94
+ * canonical-name fallback. `localName` is already stripped of the selected
95
+ * project's exact namespace. The directory-index sidecar is deliberately
96
+ * probed first, and therefore wins deterministically if both physical forms
97
+ * survive; callers can expose `candidates`/`collision` for diagnostics. */
98
+ export function resolveDeletedHistoryLogPath(storeRoot, localName) {
99
+ const segments = localName.split('/').filter((segment) => segment !== '').map(normalizeNameSegment);
100
+ const historyDir = join(storeRoot, HISTORY_DIR);
101
+ const indexPaths = historyFilesForSegments(historyDir, segments, 'INDEX');
102
+ const leafPaths = segments.length === 0 ? [] : historyFilesForSegments(historyDir, segments.slice(0, -1), segments[segments.length - 1]);
103
+ const candidates = [
104
+ ...indexPaths.map((path) => ({ path, representation: 'directory-index', winner: false })),
105
+ ...leafPaths.map((path) => ({ path, representation: 'leaf-file', winner: false })),
106
+ ];
107
+ if (candidates.length === 0)
108
+ return null;
109
+ candidates[0].winner = true;
110
+ return {
111
+ logPath: candidates[0].path,
112
+ candidates,
113
+ ...(candidates.length > 1 ? { collision: { paths: candidates.map((candidate) => candidate.path), winner: candidates[0].path } } : {}),
114
+ };
115
+ }
51
116
  /** Append one record. Single write of one line — concurrent appends interleave
52
117
  * whole records, and every record stands alone, so no lock is needed. */
53
118
  export function appendHistoryRecord(logPath, record) {
@@ -0,0 +1,65 @@
1
+ /** The filename (and path segment) whose document fronts its directory. */
2
+ export declare const INDEX_SEGMENT = "INDEX";
3
+ /** Strip an optional `NN-` ordering prefix from ONE segment. */
4
+ export declare function normalizeNameSegment(segment: string): string;
5
+ /** Slugify one authored or filesystem label into a canonical segment. */
6
+ export declare function slugCanonicalSegment(raw: string): string;
7
+ /** Slugify a derived label into canonical path segments. Empty segments are
8
+ * intentionally dropped; owner paths use slugCanonicalSegment directly. */
9
+ export declare function slugCanonicalPath(raw: string): string;
10
+ /** Strip the ordering prefix from EVERY segment of a slash-separated name:
11
+ * `01-spine/00-has-manager` -> `spine/has-manager`. */
12
+ export declare function normalizeDocName(name: string): string;
13
+ /** Split a name into segments, dropping empties from `//`, a leading `/`, or an
14
+ * empty name. */
15
+ export declare function canonicalSegments(name: string): string[];
16
+ /** The canonical directory containing `name`, or `''` at the top. */
17
+ export declare function canonicalParent(name: string): string;
18
+ /** Remove EXACTLY ONE trailing `INDEX` segment. `a/INDEX` -> `a`; `INDEX` ->
19
+ * `''` (a store root); `a/INDEX/INDEX` -> `a/INDEX` (the inner segment is an
20
+ * ordinary directory name). */
21
+ export declare function collapseTrailingIndex(name: string): string;
22
+ export type DocRepresentation = 'leaf-file' | 'directory-index';
23
+ /** Which form a store-relative markdown path takes. */
24
+ export declare function representationForPath(physicalRelativePath: string): DocRepresentation;
25
+ /** Store-local name derived from a store-relative markdown path: ordering
26
+ * prefixes stripped from every segment and one trailing `INDEX` collapsed.
27
+ * Empty only for a store root `INDEX.md`. */
28
+ export declare function localNameFromPath(physicalRelativePath: string): string;
29
+ /** The store-relative markdown path a local name occupies in a given form.
30
+ * Both forms are checked before any create or move, so occupancy never depends
31
+ * on which one a store happens to use. */
32
+ export declare function physicalRelativePathFor(localName: string, representation: DocRepresentation): string;
33
+ /** A document's explicit frontmatter `name` as a local name — normalized with
34
+ * one trailing `INDEX` collapsed — or `null` when it carries none. */
35
+ export declare function explicitLocalName(fm: Record<string, unknown> | null | undefined): string | null;
36
+ /** A document's store-local name: its explicit frontmatter `name` when present,
37
+ * else the path-derived local name. Explicit names are local to their store —
38
+ * they never carry the store's namespace. */
39
+ export declare function resolveLocalName(fm: Record<string, unknown> | null | undefined, pathDerivedLocalName: string): string;
40
+ /** namespace ⊕ local — the only address. Composition is unconditional: a local
41
+ * name that already looks namespace-prefixed is composed anyway, so identity
42
+ * never depends on string coincidence. An empty result means a namespace-less
43
+ * store root, which has no valid canonical identity. */
44
+ export declare function composeCanonicalName(namespace: string, localName: string): string;
45
+ /** The canonical directory a document's `./` memory-read entries match against:
46
+ * a directory document anchors at its own name, an ordinary leaf at its
47
+ * canonical parent. */
48
+ export declare function routingAnchorFor(canonicalName: string, representation: DocRepresentation): string;
49
+ /** The local name a canonical name has inside `namespace`, or `null` when it
50
+ * falls outside it. Exact on segment boundaries: `a/bc` is not inside `a/b`.
51
+ * The namespace itself yields `''` — the store root document. */
52
+ export declare function stripNamespace(canonicalName: string, namespace: string): string | null;
53
+ export type IdentityValidation = {
54
+ ok: true;
55
+ } | {
56
+ ok: false;
57
+ reason: string;
58
+ };
59
+ /** A project store's declared namespace, or an installed plugin's mount prefix:
60
+ * one or more normalized segments, never ending in `INDEX`. */
61
+ export declare function validateNamespace(namespace: string): IdentityValidation;
62
+ /** An explicit non-root `name:`: a non-empty local name, never ending in
63
+ * `INDEX` (its directory name is the identity) and never carrying its own
64
+ * store's namespace. */
65
+ export declare function validateExplicitLocalName(localName: string, namespace?: string): IdentityValidation;
@@ -0,0 +1,185 @@
1
+ // identity.ts — the ONE memory-identity rule. Every loader, placement helper,
2
+ // linter, and migration derives names here rather than reproducing string
3
+ // arithmetic: a document has a store-local name, its store declares a namespace
4
+ // (a project's declaration, an installed plugin's mount prefix, or empty), and
5
+ // its canonical name — the only address resolution accepts — is namespace ⊕
6
+ // local. Pure and side-effect free.
7
+ // ---------------------------------------------------------------------------
8
+ // Segment normalization — the optional `NN-` ordering pin. A physical path may
9
+ // carry a two-digit prefix on any segment (`00-runtime-base/`, `01-spine/`)
10
+ // purely to pin structural order against natural sort. The prefix is NEVER part
11
+ // of identity: only the physical path keeps it.
12
+ // ---------------------------------------------------------------------------
13
+ const NUMERIC_PREFIX_RE = /^\d{2}-/;
14
+ const SEGMENT_RE = /^[A-Za-z0-9._-]+$/;
15
+ /** The filename (and path segment) whose document fronts its directory. */
16
+ export const INDEX_SEGMENT = 'INDEX';
17
+ /** Strip an optional `NN-` ordering prefix from ONE segment. */
18
+ export function normalizeNameSegment(segment) {
19
+ return segment.replace(NUMERIC_PREFIX_RE, '');
20
+ }
21
+ /** Slugify one authored or filesystem label into a canonical segment. */
22
+ export function slugCanonicalSegment(raw) {
23
+ return normalizeNameSegment(raw.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, ''));
24
+ }
25
+ /** Slugify a derived label into canonical path segments. Empty segments are
26
+ * intentionally dropped; owner paths use slugCanonicalSegment directly. */
27
+ export function slugCanonicalPath(raw) {
28
+ return raw
29
+ .replace(/\\/g, '/')
30
+ .replace(/\.md$/i, '')
31
+ .split('/')
32
+ .map(slugCanonicalSegment)
33
+ .filter(Boolean)
34
+ .join('/');
35
+ }
36
+ /** Strip the ordering prefix from EVERY segment of a slash-separated name:
37
+ * `01-spine/00-has-manager` -> `spine/has-manager`. */
38
+ export function normalizeDocName(name) {
39
+ return name.split('/').map(normalizeNameSegment).join('/');
40
+ }
41
+ /** Split a name into segments, dropping empties from `//`, a leading `/`, or an
42
+ * empty name. */
43
+ export function canonicalSegments(name) {
44
+ return name.split('/').filter((s) => s !== '');
45
+ }
46
+ /** The canonical directory containing `name`, or `''` at the top. */
47
+ export function canonicalParent(name) {
48
+ const segments = canonicalSegments(name);
49
+ return segments.slice(0, -1).join('/');
50
+ }
51
+ /** Remove EXACTLY ONE trailing `INDEX` segment. `a/INDEX` -> `a`; `INDEX` ->
52
+ * `''` (a store root); `a/INDEX/INDEX` -> `a/INDEX` (the inner segment is an
53
+ * ordinary directory name). */
54
+ export function collapseTrailingIndex(name) {
55
+ const segments = canonicalSegments(name);
56
+ if (segments[segments.length - 1] === INDEX_SEGMENT)
57
+ segments.pop();
58
+ return segments.join('/');
59
+ }
60
+ /** Normalized identity segments of a store-relative markdown path, extension
61
+ * stripped. */
62
+ function pathIdentitySegments(physicalRelativePath) {
63
+ return canonicalSegments(physicalRelativePath.replace(/\\/g, '/').replace(/\.md$/i, '')).map(normalizeNameSegment);
64
+ }
65
+ /** Which form a store-relative markdown path takes. */
66
+ export function representationForPath(physicalRelativePath) {
67
+ const segments = pathIdentitySegments(physicalRelativePath);
68
+ return segments[segments.length - 1] === INDEX_SEGMENT ? 'directory-index' : 'leaf-file';
69
+ }
70
+ /** Store-local name derived from a store-relative markdown path: ordering
71
+ * prefixes stripped from every segment and one trailing `INDEX` collapsed.
72
+ * Empty only for a store root `INDEX.md`. */
73
+ export function localNameFromPath(physicalRelativePath) {
74
+ return collapseTrailingIndex(pathIdentitySegments(physicalRelativePath).join('/'));
75
+ }
76
+ /** The store-relative markdown path a local name occupies in a given form.
77
+ * Both forms are checked before any create or move, so occupancy never depends
78
+ * on which one a store happens to use. */
79
+ export function physicalRelativePathFor(localName, representation) {
80
+ const local = canonicalSegments(localName).join('/');
81
+ if (local === '') {
82
+ if (representation === 'leaf-file')
83
+ throw new Error('a store root document has no leaf-file representation');
84
+ return `${INDEX_SEGMENT}.md`;
85
+ }
86
+ return representation === 'directory-index' ? `${local}/${INDEX_SEGMENT}.md` : `${local}.md`;
87
+ }
88
+ // ---------------------------------------------------------------------------
89
+ // Local name and canonical composition.
90
+ // ---------------------------------------------------------------------------
91
+ /** A document's explicit frontmatter `name` as a local name — normalized with
92
+ * one trailing `INDEX` collapsed — or `null` when it carries none. */
93
+ export function explicitLocalName(fm) {
94
+ const raw = fm?.['name'];
95
+ if (typeof raw !== 'string' || raw.trim() === '')
96
+ return null;
97
+ return collapseTrailingIndex(normalizeDocName(raw.trim()));
98
+ }
99
+ /** A document's store-local name: its explicit frontmatter `name` when present,
100
+ * else the path-derived local name. Explicit names are local to their store —
101
+ * they never carry the store's namespace. */
102
+ export function resolveLocalName(fm, pathDerivedLocalName) {
103
+ return explicitLocalName(fm) ?? pathDerivedLocalName;
104
+ }
105
+ /** namespace ⊕ local — the only address. Composition is unconditional: a local
106
+ * name that already looks namespace-prefixed is composed anyway, so identity
107
+ * never depends on string coincidence. An empty result means a namespace-less
108
+ * store root, which has no valid canonical identity. */
109
+ export function composeCanonicalName(namespace, localName) {
110
+ const ns = canonicalSegments(namespace).join('/');
111
+ const local = canonicalSegments(localName).join('/');
112
+ if (ns === '')
113
+ return local;
114
+ return local === '' ? ns : `${ns}/${local}`;
115
+ }
116
+ /** The canonical directory a document's `./` memory-read entries match against:
117
+ * a directory document anchors at its own name, an ordinary leaf at its
118
+ * canonical parent. */
119
+ export function routingAnchorFor(canonicalName, representation) {
120
+ return representation === 'directory-index' ? canonicalSegments(canonicalName).join('/') : canonicalParent(canonicalName);
121
+ }
122
+ /** The local name a canonical name has inside `namespace`, or `null` when it
123
+ * falls outside it. Exact on segment boundaries: `a/bc` is not inside `a/b`.
124
+ * The namespace itself yields `''` — the store root document. */
125
+ export function stripNamespace(canonicalName, namespace) {
126
+ const name = canonicalSegments(canonicalName);
127
+ const ns = canonicalSegments(namespace);
128
+ for (let i = 0; i < ns.length; i++) {
129
+ if (name[i] !== ns[i])
130
+ return null;
131
+ }
132
+ return name.slice(ns.length).join('/');
133
+ }
134
+ function invalidSegmentReason(name) {
135
+ for (const segment of name.split('/')) {
136
+ if (segment === '')
137
+ return `empty segment in \`${name}\``;
138
+ if (segment === '.' || segment === '..')
139
+ return `relative segment \`${segment}\` in \`${name}\``;
140
+ if (!SEGMENT_RE.test(segment))
141
+ return `segment \`${segment}\` is not [A-Za-z0-9._-]`;
142
+ if (NUMERIC_PREFIX_RE.test(segment)) {
143
+ return `segment \`${segment}\` carries an \`NN-\` ordering prefix, which is physical-path-only — use \`${normalizeNameSegment(segment)}\``;
144
+ }
145
+ }
146
+ return null;
147
+ }
148
+ /** A project store's declared namespace, or an installed plugin's mount prefix:
149
+ * one or more normalized segments, never ending in `INDEX`. */
150
+ export function validateNamespace(namespace) {
151
+ if (namespace.trim() !== namespace)
152
+ return { ok: false, reason: 'namespace has surrounding whitespace' };
153
+ if (namespace === '')
154
+ return { ok: false, reason: 'namespace is empty' };
155
+ const badSegment = invalidSegmentReason(namespace);
156
+ if (badSegment !== null)
157
+ return { ok: false, reason: badSegment };
158
+ if (canonicalSegments(namespace).pop() === INDEX_SEGMENT) {
159
+ return { ok: false, reason: `namespace ends in \`${INDEX_SEGMENT}\`` };
160
+ }
161
+ return { ok: true };
162
+ }
163
+ /** An explicit non-root `name:`: a non-empty local name, never ending in
164
+ * `INDEX` (its directory name is the identity) and never carrying its own
165
+ * store's namespace. */
166
+ export function validateExplicitLocalName(localName, namespace = '') {
167
+ if (localName.trim() !== localName)
168
+ return { ok: false, reason: 'name has surrounding whitespace' };
169
+ if (localName === '')
170
+ return { ok: false, reason: 'name is empty' };
171
+ const badSegment = invalidSegmentReason(localName);
172
+ if (badSegment !== null)
173
+ return { ok: false, reason: badSegment };
174
+ if (canonicalSegments(localName).pop() === INDEX_SEGMENT) {
175
+ const collapsed = collapseTrailingIndex(localName);
176
+ return {
177
+ ok: false,
178
+ reason: `name ends in \`${INDEX_SEGMENT}\` — a directory document answers at its directory name${collapsed === '' ? '' : `, so use \`${collapsed}\``}`,
179
+ };
180
+ }
181
+ if (namespace !== '' && stripNamespace(localName, namespace) !== null) {
182
+ return { ok: false, reason: `name repeats its store's namespace \`${namespace}\` — explicit names are store-local` };
183
+ }
184
+ return { ok: true };
185
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Resolve every slash token in `text` against `names` (a
2
+ * Resolve every slash token in `text` against `names` (the exact canonical
3
3
  * `buildRefInventory().names` set), collapse duplicates, and return one
4
4
  * directive line per distinct resolved ref in FIRST-APPEARANCE order.
5
5
  * Returns `null` when no token resolves, so the broker sends no guidance
@@ -11,7 +11,7 @@
11
11
  import { findRefTokens } from './inline-ref-grammar.js';
12
12
  const GUIDANCE_HEADER = 'The user explicitly called out specific memories. Read the relevant mentioned memory files:';
13
13
  /**
14
- * Resolve every slash token in `text` against `names` (a
14
+ * Resolve every slash token in `text` against `names` (the exact canonical
15
15
  * `buildRefInventory().names` set), collapse duplicates, and return one
16
16
  * directive line per distinct resolved ref in FIRST-APPEARANCE order.
17
17
  * Returns `null` when no token resolves, so the broker sends no guidance
@@ -1,14 +1,7 @@
1
1
  import type { RefMeta } from '../runtime/broker-protocol.js';
2
- /**
3
- * Build the inline memory-reference inventory: every referenceable document
4
- * in this node's corpus, precedence-deduped first-wins by canonical name,
5
- * both kinds, no gate/rung/`slash` filtering. Returns display metadata
6
- * (`refs`, stably sorted by winning scope then name) plus the resolution set
7
- * (`names` — doc names plus every proper directory prefix, because a bare-dir
8
- * ref is a legal listing link) that both the broker's per-submission resolver
9
- * and `buildGuidance` test tokens against. `refs` additionally labels Gateway
10
- * eligibility; the terminal viewer still receives every referenceable row.
11
- */
2
+ /** Build the inline inventory from exact target-view winners. `refs` carries
3
+ * metadata rows; `names` adds only canonical directory nodes with members for
4
+ * browse links. Gates, rungs, and `slash` do not affect referenceability. */
12
5
  export declare function buildRefInventory(): {
13
6
  refs: RefMeta[];
14
7
  names: Set<string>;