@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.
- package/dist/api/client.d.ts +5 -5
- package/dist/api/client.js +3 -3
- package/dist/api/dto/broker-ops.d.ts +8 -0
- package/dist/api/dto/memory.d.ts +8 -7
- package/dist/api/dto/memory.js +2 -2
- package/dist/builtin-memory/05-kinds/design/00-base.md +3 -4
- package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +4 -3
- package/dist/builtin-memory/05-kinds/design/design-contract.md +3 -6
- package/dist/builtin-memory/05-kinds/plan/00-base.md +3 -4
- package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +4 -5
- package/dist/builtin-memory/05-kinds/plan/plan-contract.md +5 -13
- package/dist/builtin-memory/design/guide.md +38 -16
- package/dist/builtin-memory/design/roadmap.md +4 -4
- package/dist/builtin-memory/insights/init.md +5 -5
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
- package/dist/builtin-memory/internal/memory-loading.md +6 -6
- package/dist/builtin-memory/internal/plugins.md +1 -1
- package/dist/builtin-memory/plan/guide.md +53 -0
- package/dist/builtin-memory/plan/roadmap.md +10 -8
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +50 -37
- package/dist/clients/attach/__tests__/pane-tag-successor.test.js +56 -0
- package/dist/clients/attach/__tests__/profile-files.test.js +21 -0
- package/dist/clients/attach/chrome/inbox-strip.js +11 -7
- package/dist/clients/attach/session/pane-tag.d.ts +28 -2
- package/dist/clients/attach/session/pane-tag.js +48 -22
- package/dist/clients/attach/session/profile-files.d.ts +6 -0
- package/dist/clients/attach/session/profile-files.js +44 -11
- package/dist/clients/attach/slash/dispatch.js +4 -7
- package/dist/clients/attach/viewer.js +588 -596
- package/dist/commands/__tests__/surface-reload-target.test.js +21 -0
- package/dist/commands/api-client.d.ts +3 -7
- package/dist/commands/api-client.js +7 -12
- package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +292 -0
- package/dist/commands/memory/__tests__/repository-root-lint.test.js +146 -0
- package/dist/commands/memory/delete.js +51 -24
- package/dist/commands/memory/edit.js +50 -10
- package/dist/commands/memory/find.js +105 -56
- package/dist/commands/memory/history.js +51 -31
- package/dist/commands/memory/lint.d.ts +0 -9
- package/dist/commands/memory/lint.js +78 -322
- package/dist/commands/memory/list.d.ts +12 -4
- package/dist/commands/memory/list.js +64 -25
- package/dist/commands/memory/move.js +103 -73
- package/dist/commands/memory/origin.js +35 -5
- package/dist/commands/memory/read.d.ts +4 -0
- package/dist/commands/memory/read.js +132 -80
- package/dist/commands/memory/shared.d.ts +115 -25
- package/dist/commands/memory/shared.js +331 -74
- package/dist/commands/memory/write.js +72 -21
- package/dist/commands/memory.js +2 -2
- package/dist/commands/node/lifecycle.js +2 -2
- package/dist/commands/pkg/market-manage.js +25 -24
- package/dist/commands/pkg/plugin-manage.d.ts +22 -6
- package/dist/commands/pkg/plugin-manage.js +118 -25
- package/dist/commands/pkg/shared.d.ts +8 -0
- package/dist/commands/pkg/shared.js +23 -20
- package/dist/commands/surface-reload.d.ts +5 -0
- package/dist/commands/surface-reload.js +9 -1
- package/dist/commands/sys/__tests__/migrate.test.js +1140 -22
- package/dist/commands/sys/__tests__/sync-project-guidance.test.js +218 -0
- package/dist/commands/sys/migrate.js +126 -140
- package/dist/commands/sys/panels/profiles-panel.d.ts +66 -0
- package/dist/commands/sys/panels/profiles-panel.js +599 -0
- package/dist/commands/sys/settings-shell.d.ts +4 -2
- package/dist/commands/sys/settings-shell.js +35 -4
- package/dist/commands/sys/settings.js +21 -5
- package/dist/commands/sys/sync-deps.d.ts +2 -0
- package/dist/commands/sys/sync-deps.js +26 -15
- package/dist/commands/sys/sync-project-guidance.js +228 -145
- package/dist/commands/sys/sync-skills.js +28 -17
- package/dist/commands/sys/update.js +11 -3
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +15 -2
- package/dist/core/__tests__/config-change-delta.test.js +106 -0
- package/dist/core/__tests__/context-intro.test.js +13 -9
- package/dist/core/__tests__/daemon-boot.test.js +22 -16
- package/dist/core/__tests__/fixtures/memory-slash-live-probe.js +5 -1
- package/dist/core/__tests__/human-deliver.test.js +19 -0
- package/dist/core/__tests__/inline-memory-refs.test.js +50 -46
- package/dist/core/__tests__/{serial → integration}/broker-fork-seam.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/broker-sdk-wiring.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/broker-snapshot-history.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/command-plugins.test.js +94 -3
- package/dist/core/__tests__/{serial → integration}/deferred-no-wake.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/flagship-lifecycle.test.js +13 -27
- package/dist/core/__tests__/{serial → integration}/host-teardown-process-group.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/human-deliver-e2e.test.js +1 -1
- package/dist/core/__tests__/{live-mutation-verbs.test.js → integration/live-mutation-verbs.test.js} +22 -33
- package/dist/core/__tests__/{serial → integration}/live-mutation.test.js +21 -41
- package/dist/core/__tests__/integration/refresh-stall-recycle.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/refresh-stall-recycle.test.js +1 -1
- package/dist/core/__tests__/integration/revive.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/revive.test.js +38 -19
- package/dist/core/__tests__/integration/spawn-root.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/spawn-root.test.js +104 -1
- package/dist/core/__tests__/integration/subscription-delivery.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/subscription-delivery.test.js +1 -1
- package/dist/core/__tests__/integration/tmux-surface.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/tmux-surface.test.js +1 -1
- package/dist/core/__tests__/integration/worktree-land.test.d.ts +1 -0
- package/dist/core/__tests__/integration/worktree-land.test.js +400 -0
- package/dist/core/__tests__/integration/worktree-reap.test.d.ts +1 -0
- package/dist/core/__tests__/{serial/worktree.test.js → integration/worktree-reap.test.js} +6 -338
- package/dist/core/__tests__/kickoff.test.js +16 -5
- package/dist/core/__tests__/memory-resolver-precedence.test.js +122 -91
- package/dist/core/__tests__/nested-store-discovery.test.js +5 -3
- package/dist/core/__tests__/on-read-crouter-home-fence.test.js +2 -1
- package/dist/core/__tests__/on-read-dedup-resume.test.js +39 -27
- package/dist/core/__tests__/on-read-nested-store.test.js +19 -13
- package/dist/core/__tests__/profile-project-memory-delivery.test.js +138 -44
- package/dist/core/__tests__/repository-association.test.d.ts +1 -0
- package/dist/core/__tests__/repository-association.test.js +153 -0
- package/dist/core/__tests__/repository-root-identity.test.d.ts +1 -0
- package/dist/core/__tests__/repository-root-identity.test.js +219 -0
- package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.js +27 -11
- package/dist/core/__tests__/warm-claim-preference-snapshot.test.d.ts +1 -0
- package/dist/core/__tests__/warm-claim-preference-snapshot.test.js +40 -0
- package/dist/core/canvas/canvas.d.ts +3 -0
- package/dist/core/canvas/canvas.js +30 -10
- package/dist/core/canvas/db.js +28 -1
- package/dist/core/canvas/paths.d.ts +4 -6
- package/dist/core/canvas/paths.js +8 -6
- package/dist/core/canvas/render-source.js +2 -2
- package/dist/core/canvas/types.d.ts +9 -13
- package/dist/core/exclusive-lock.d.ts +2 -0
- package/dist/core/exclusive-lock.js +21 -0
- package/dist/core/git.d.ts +0 -1
- package/dist/core/git.js +0 -3
- package/dist/core/human/__tests__/integration/inbox-core.test.d.ts +1 -0
- package/dist/core/human/feedback-companion.js +3 -0
- package/dist/core/human/scan.js +8 -1
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +2 -1
- package/dist/core/memory/doc-link-grammar.js +2 -2
- package/dist/core/memory/history.d.ts +24 -0
- package/dist/core/memory/history.js +66 -1
- package/dist/core/memory/identity.d.ts +65 -0
- package/dist/core/memory/identity.js +185 -0
- package/dist/core/memory/inline-ref-guidance.d.ts +1 -1
- package/dist/core/memory/inline-ref-guidance.js +1 -1
- package/dist/core/memory/inline-ref-inventory.d.ts +3 -10
- package/dist/core/memory/inline-ref-inventory.js +30 -62
- package/dist/core/memory/lint.d.ts +129 -0
- package/dist/core/memory/lint.js +517 -0
- package/dist/core/memory/project-namespace.d.ts +31 -0
- package/dist/core/memory/project-namespace.js +85 -0
- package/dist/core/memory/repository-association.d.ts +31 -0
- package/dist/core/memory/repository-association.js +129 -0
- package/dist/core/memory/tree.d.ts +39 -0
- package/dist/core/memory/tree.js +93 -0
- package/dist/core/memory-resolver.d.ts +193 -90
- package/dist/core/memory-resolver.js +461 -375
- package/dist/core/nested-stores.js +6 -13
- package/dist/core/profiles/select.d.ts +4 -1
- package/dist/core/profiles/select.js +108 -48
- package/dist/core/review/__tests__/capture-origin.test.js +2 -2
- package/dist/core/review/__tests__/stage-identity.test.js +2 -2
- package/dist/core/review/companion.js +11 -2
- package/dist/core/runtime/bearings.d.ts +2 -2
- package/dist/core/runtime/bearings.js +3 -3
- package/dist/core/runtime/broker/daemon-ops.d.ts +2 -2
- package/dist/core/runtime/broker/rebind.js +5 -0
- package/dist/core/runtime/broker-extension-render.d.ts +7 -3
- package/dist/core/runtime/broker-extension-render.js +10 -5
- package/dist/core/runtime/broker-persona-guidance.d.ts +19 -5
- package/dist/core/runtime/broker-persona-guidance.js +122 -27
- package/dist/core/runtime/deliver-live.d.ts +16 -4
- package/dist/core/runtime/deliver-live.js +29 -15
- package/dist/core/runtime/kickoff.d.ts +3 -3
- package/dist/core/runtime/kickoff.js +8 -16
- package/dist/core/runtime/lifecycle.js +2 -3
- package/dist/core/runtime/nodes.d.ts +3 -4
- package/dist/core/runtime/nodes.js +3 -4
- package/dist/core/runtime/persona.d.ts +8 -12
- package/dist/core/runtime/persona.js +24 -96
- package/dist/core/runtime/promote.d.ts +2 -2
- package/dist/core/runtime/promote.js +8 -21
- package/dist/core/runtime/revive.js +20 -17
- package/dist/core/runtime/spawn.js +18 -7
- package/dist/core/runtime/tmux-bindings.js +2 -3
- package/dist/core/runtime/warm-pool.js +3 -4
- package/dist/core/scope.js +2 -0
- package/dist/core/self-update.d.ts +0 -2
- package/dist/core/self-update.js +2 -35
- package/dist/core/substrate/__tests__/surface-match-memory-read.test.d.ts +1 -0
- package/dist/core/substrate/__tests__/surface-match-memory-read.test.js +28 -0
- package/dist/core/substrate/index.d.ts +2 -2
- package/dist/core/substrate/index.js +1 -1
- package/dist/core/substrate/injected-store.d.ts +43 -27
- package/dist/core/substrate/injected-store.js +208 -104
- package/dist/core/substrate/listings.d.ts +19 -12
- package/dist/core/substrate/listings.js +75 -52
- package/dist/core/substrate/on-read-node.d.ts +4 -7
- package/dist/core/substrate/on-read-node.js +6 -8
- package/dist/core/substrate/on-read.d.ts +22 -25
- package/dist/core/substrate/on-read.js +103 -147
- package/dist/core/substrate/render-node.d.ts +4 -7
- package/dist/core/substrate/render-node.js +5 -7
- package/dist/core/substrate/render.d.ts +21 -3
- package/dist/core/substrate/render.js +291 -223
- package/dist/core/substrate/schema.d.ts +1 -13
- package/dist/core/substrate/schema.js +5 -40
- package/dist/core/substrate/session-cache.d.ts +14 -4
- package/dist/core/substrate/session-cache.js +40 -22
- package/dist/core/substrate/surface-match.d.ts +9 -7
- package/dist/core/substrate/surface-match.js +26 -25
- package/dist/daemon/__tests__/helpers/source-daemon.d.ts +30 -0
- package/dist/daemon/__tests__/helpers/source-daemon.js +174 -0
- package/dist/daemon/__tests__/integration/migration-startup.test.d.ts +1 -0
- package/dist/daemon/__tests__/integration/migration-startup.test.js +97 -0
- package/dist/daemon/api/__tests__/bridge-heartbeat.test.js +21 -5
- package/dist/daemon/api/handlers/broker-ops.js +21 -16
- package/dist/daemon/api/handlers/memory.js +2 -0
- package/dist/daemon/crtrd.js +2 -0
- package/dist/daemon/human/finish.js +8 -1
- package/dist/daemon/manage.d.ts +0 -1
- package/dist/daemon/manage.js +7 -16
- package/dist/daemon/startup-policy.d.ts +1 -0
- package/dist/daemon/startup-policy.js +1 -0
- package/dist/migrations/001-surfaces-frontmatter.js +21 -109
- package/dist/migrations/002-profile-project-memory.js +1 -0
- package/dist/migrations/003-repository-root-memory-identity/front-door.d.ts +26 -0
- package/dist/migrations/003-repository-root-memory-identity/front-door.js +231 -0
- package/dist/migrations/003-repository-root-memory-identity/index.d.ts +2 -0
- package/dist/migrations/003-repository-root-memory-identity/index.js +513 -0
- package/dist/migrations/003-repository-root-memory-identity/references.d.ts +95 -0
- package/dist/migrations/003-repository-root-memory-identity/references.js +469 -0
- package/dist/migrations/003-repository-root-memory-identity/repository-facts.d.ts +39 -0
- package/dist/migrations/003-repository-root-memory-identity/repository-facts.js +349 -0
- package/dist/migrations/__tests__/activation-concurrency.test.d.ts +1 -0
- package/dist/migrations/__tests__/activation-concurrency.test.js +145 -0
- package/dist/migrations/__tests__/activation.test.d.ts +1 -0
- package/dist/migrations/__tests__/activation.test.js +149 -0
- package/dist/migrations/__tests__/deletion-and-root-declaration.test.d.ts +1 -0
- package/dist/migrations/__tests__/deletion-and-root-declaration.test.js +149 -0
- package/dist/migrations/activation.d.ts +16 -0
- package/dist/migrations/activation.js +78 -0
- package/dist/migrations/convergent.d.ts +14 -3
- package/dist/migrations/convergent.js +21 -10
- package/dist/migrations/corpus.d.ts +78 -0
- package/dist/migrations/corpus.js +497 -0
- package/dist/migrations/frontmatter-splice.d.ts +15 -0
- package/dist/migrations/frontmatter-splice.js +176 -0
- package/dist/migrations/registry.d.ts +6 -1
- package/dist/migrations/registry.js +7 -2
- package/dist/migrations/runner.d.ts +41 -0
- package/dist/migrations/runner.js +81 -0
- package/dist/migrations/types.d.ts +148 -9
- package/dist/migrations/types.js +9 -2
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +225 -17
- package/dist/pi-extensions/__tests__/canvas-goal-capture-envelope.test.js +11 -3
- package/dist/pi-extensions/canvas-context-intro.d.ts +3 -5
- package/dist/pi-extensions/canvas-context-intro.js +50 -46
- package/dist/pi-extensions/canvas-doc-substrate.d.ts +1 -8
- package/dist/pi-extensions/canvas-doc-substrate.js +55 -122
- package/dist/pi-extensions/canvas-stophook.js +6 -13
- package/dist/shared/generated-context.d.ts +0 -3
- package/dist/shared/generated-context.js +0 -57
- package/dist/shared/tool-groups.js +2 -3
- package/package.json +5 -4
- package/runtime.lock.json +2 -2
- /package/dist/api/__tests__/{serial → integration}/client.test.d.ts +0 -0
- /package/dist/api/__tests__/{serial → integration}/client.test.js +0 -0
- /package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/{serial → integration}/provider-rotation.test.ts +0 -0
- /package/dist/clients/{inbox/__tests__/serial/inbox-controller.test.d.ts → attach/__tests__/pane-tag-successor.test.d.ts} +0 -0
- /package/dist/clients/{inbox/__tests__/serial/mount-panel.test.d.ts → attach/__tests__/profile-files.test.d.ts} +0 -0
- /package/dist/{core/__tests__/live-mutation-verbs.test.d.ts → clients/inbox/__tests__/integration/inbox-controller.test.d.ts} +0 -0
- /package/dist/clients/inbox/__tests__/{serial → integration}/inbox-controller.test.js +0 -0
- /package/dist/{core/__tests__/serial/broker-fork-seam.test.d.ts → clients/inbox/__tests__/integration/mount-panel.test.d.ts} +0 -0
- /package/dist/clients/inbox/__tests__/{serial → integration}/mount-panel.test.js +0 -0
- /package/dist/{core/__tests__/serial/broker-sdk-wiring.test.d.ts → commands/__tests__/surface-reload-target.test.d.ts} +0 -0
- /package/dist/{core/__tests__/serial/broker-snapshot-history.test.d.ts → commands/memory/__tests__/command-selector-and-mutation-guards.test.d.ts} +0 -0
- /package/dist/{core/__tests__/serial/command-plugins.test.d.ts → commands/memory/__tests__/repository-root-lint.test.d.ts} +0 -0
- /package/dist/{core/__tests__/serial/deferred-no-wake.test.d.ts → commands/sys/__tests__/sync-project-guidance.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/flagship-lifecycle.test.d.ts → config-change-delta.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/host-teardown-process-group.test.d.ts → integration/broker-fork-seam.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/human-deliver-e2e.test.d.ts → integration/broker-sdk-wiring.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/live-mutation.test.d.ts → integration/broker-snapshot-history.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/refresh-stall-recycle.test.d.ts → integration/command-plugins.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/revive.test.d.ts → integration/deferred-no-wake.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/spawn-root.test.d.ts → integration/flagship-lifecycle.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/subscription-delivery.test.d.ts → integration/host-teardown-process-group.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/tmux-surface.test.d.ts → integration/human-deliver-e2e.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/worktree.test.d.ts → integration/live-mutation-verbs.test.d.ts} +0 -0
- /package/dist/core/{human/__tests__/serial/inbox-core.test.d.ts → __tests__/integration/live-mutation.test.d.ts} +0 -0
- /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
|
+
}
|
package/dist/core/git.d.ts
CHANGED
|
@@ -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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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`);
|
package/dist/core/human/scan.js
CHANGED
|
@@ -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', '
|
|
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-
|
|
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-
|
|
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` (
|
|
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` (
|
|
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
|
-
*
|
|
4
|
-
*
|
|
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>;
|