@north-light/crouter 0.3.226 → 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 +37 -9
- package/dist/daemon/api/bridge.d.ts +35 -4
- package/dist/daemon/api/bridge.js +50 -8
- 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
|
@@ -1,34 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dirname, join, relative, sep } from 'node:path';
|
|
2
2
|
import { readdirSync } from 'node:fs';
|
|
3
3
|
import { CRTR_DIR_NAME } from '../types.js';
|
|
4
|
-
import { pathExists, readText, walkFiles } from './fs-utils.js';
|
|
4
|
+
import { pathExists, readText, realpathOrSelf, walkFiles } from './fs-utils.js';
|
|
5
5
|
import { parseFrontmatterGeneric } from './frontmatter.js';
|
|
6
|
-
import { listInstalledPlugins, listInstalledPluginsInRoot
|
|
7
|
-
import {
|
|
6
|
+
import { listInstalledPlugins, listInstalledPluginsInRoot } from './resolver.js';
|
|
7
|
+
import { notFound, usage } from './errors.js';
|
|
8
8
|
import { warn } from './output.js';
|
|
9
9
|
import { NEUTRAL_PROJECT_MEMORY, pluginMemoryDir, projectScopeRoots, projectScopeRootsWithMemory, scopeMemoryDir } from './scope.js';
|
|
10
|
-
import { effectiveDocKind
|
|
10
|
+
import { effectiveDocKind } from './substrate/schema.js';
|
|
11
|
+
import { INDEX_SEGMENT, canonicalSegments, composeCanonicalName, localNameFromPath, normalizeNameSegment, representationForPath, resolveLocalName, routingAnchorFor, validateNamespace, } from './memory/identity.js';
|
|
11
12
|
import { loadProfileManifest, profileMemoryDir } from './profiles/manifest.js';
|
|
12
13
|
import { memoryDir as nodeMemoryDir } from './runtime/memory.js';
|
|
13
14
|
import { descendantStoreRoots } from './nested-stores.js';
|
|
14
|
-
|
|
15
|
-
* project stack > profile > user, builtin excluded as a read-only corpus.
|
|
16
|
-
* Addresses a store directly when the document itself may be absent, which is
|
|
17
|
-
* how a DELETED doc's revision log is still found: history outlives the doc,
|
|
18
|
-
* so its lookup cannot go through document resolution. */
|
|
19
|
-
export function nativeMemoryStoresInPrecedence(scope, includeDescendants = false) {
|
|
20
|
-
const out = [];
|
|
21
|
-
for (const source of memorySourcesInPrecedence(ambientTarget(), scope, includeDescendants)) {
|
|
22
|
-
if (source.scope === 'builtin' || source.memoryDir === null)
|
|
23
|
-
continue;
|
|
24
|
-
out.push({ scope: source.scope, memoryDir: source.memoryDir });
|
|
25
|
-
}
|
|
26
|
-
return out;
|
|
27
|
-
}
|
|
28
|
-
/** Canonical, unambiguous identifier for a memory document: `<scope>/<name>`. */
|
|
29
|
-
export function memoryDocId(doc) {
|
|
30
|
-
return `${doc.scope}/${doc.name}`;
|
|
31
|
-
}
|
|
15
|
+
import { associateRepository } from './memory/repository-association.js';
|
|
32
16
|
/** The target the ambient process describes: cwd + the two env seams every
|
|
33
17
|
* profile/node-aware memory path reads through (`CRTR_PROFILE_ID`,
|
|
34
18
|
* `CRTR_NODE_ID`). */
|
|
@@ -43,6 +27,10 @@ function ambientTarget() {
|
|
|
43
27
|
function selectedProfileId() {
|
|
44
28
|
return ambientTarget().profileId ?? '';
|
|
45
29
|
}
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Store discovery: which physical stores a target sees, in what order, and
|
|
32
|
+
// whether each one's namespace lets it mount.
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
46
34
|
/** The memory scopes in resolution precedence: node > project stack > profile >
|
|
47
35
|
* user > builtin. Node is included only when the target names one and ranks
|
|
48
36
|
* NEAREST — a node doc overrides any wider scope. Project is included only when
|
|
@@ -63,38 +51,168 @@ function scopesInPrecedence(target, scope) {
|
|
|
63
51
|
out.push('builtin');
|
|
64
52
|
return out;
|
|
65
53
|
}
|
|
66
|
-
/**
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
54
|
+
/** Resolve a project store through its repository-root declaration. */
|
|
55
|
+
function projectStoreDescriptor(ownerDir, storeRoot, projectMemory) {
|
|
56
|
+
const base = { scope: 'project', storeRoot, ownerDir, projectMemory };
|
|
57
|
+
if (!pathExists(storeRoot))
|
|
58
|
+
return { ...base, namespace: '', mountStatus: 'absent' };
|
|
59
|
+
const association = associateRepository(ownerDir);
|
|
60
|
+
if (!association.ok) {
|
|
61
|
+
const remedy = `run \`crtr sys migrate --dir ${ownerDir}\``;
|
|
62
|
+
if (association.code === 'no-project-root') {
|
|
63
|
+
return {
|
|
64
|
+
...base,
|
|
65
|
+
namespace: '',
|
|
66
|
+
mountStatus: 'undeclared',
|
|
67
|
+
mountFailure: 'association',
|
|
68
|
+
diagnostic: `project memory store at ${storeRoot} has no repository root declaring \`namespace:\` — ${remedy}`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
...base,
|
|
73
|
+
namespace: '',
|
|
74
|
+
mountStatus: 'invalid',
|
|
75
|
+
mountFailure: 'association',
|
|
76
|
+
diagnostic: `project memory store at ${storeRoot} cannot be associated: ${association.reason} — ${remedy}`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const { association: repository } = association;
|
|
80
|
+
const repositoryFields = {
|
|
81
|
+
namespacePath: repository.declarationPath,
|
|
82
|
+
repositoryRoot: repository.repositoryRoot,
|
|
83
|
+
repositoryKey: repository.repositoryKey,
|
|
84
|
+
ownerRelativePath: repository.ownerRelativePath,
|
|
85
|
+
};
|
|
86
|
+
const rootIndex = repository.declarationPath;
|
|
87
|
+
const nested = repository.ownerRelativePath !== '';
|
|
88
|
+
const remedy = `run \`crtr sys migrate --dir ${repository.repositoryRoot}\``;
|
|
89
|
+
const missingDiagnostic = nested
|
|
90
|
+
? `nested project memory store at ${storeRoot} takes its identity from ${rootIndex}, which is missing root ${INDEX_SEGMENT}.md declaring \`namespace:\` — ${remedy}`
|
|
91
|
+
: `project memory store has no root ${INDEX_SEGMENT}.md declaring \`namespace:\` — ${remedy}`;
|
|
92
|
+
if (!pathExists(rootIndex)) {
|
|
93
|
+
return {
|
|
94
|
+
...base,
|
|
95
|
+
...repositoryFields,
|
|
96
|
+
namespace: '',
|
|
97
|
+
mountStatus: 'undeclared',
|
|
98
|
+
mountFailure: 'undeclared-root',
|
|
99
|
+
diagnostic: missingDiagnostic,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
let declared;
|
|
103
|
+
try {
|
|
104
|
+
declared = parseFrontmatterGeneric(readText(rootIndex)).data?.['namespace'];
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
return {
|
|
108
|
+
...base,
|
|
109
|
+
...repositoryFields,
|
|
110
|
+
namespace: '',
|
|
111
|
+
mountStatus: 'invalid',
|
|
112
|
+
mountFailure: 'invalid-root',
|
|
113
|
+
diagnostic: `project memory store's ${rootIndex} does not parse (${firstLine(e)}) — ${remedy}`,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
if (declared === undefined || declared === null || declared === '') {
|
|
117
|
+
const diagnostic = nested
|
|
118
|
+
? `nested project memory store at ${storeRoot} takes its identity from ${rootIndex}, which declares no \`namespace:\` — ${remedy}`
|
|
119
|
+
: `project memory store's ${rootIndex} declares no \`namespace:\` — ${remedy}`;
|
|
120
|
+
return {
|
|
121
|
+
...base,
|
|
122
|
+
...repositoryFields,
|
|
123
|
+
namespace: '',
|
|
124
|
+
mountStatus: 'undeclared',
|
|
125
|
+
mountFailure: 'undeclared-root',
|
|
126
|
+
diagnostic,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
// No coercion: a numeric or otherwise non-string declaration is a malformed
|
|
130
|
+
// store, not a namespace. Coercing it would mount a store whose namespace no
|
|
131
|
+
// memory command can then edit.
|
|
132
|
+
if (typeof declared !== 'string') {
|
|
133
|
+
return {
|
|
134
|
+
...base,
|
|
135
|
+
...repositoryFields,
|
|
136
|
+
namespace: '',
|
|
137
|
+
mountStatus: 'invalid',
|
|
138
|
+
mountFailure: 'invalid-root',
|
|
139
|
+
diagnostic: `project memory store declares a non-string \`namespace:\` (${JSON.stringify(declared)}) — quote it in ${rootIndex}, or ${remedy}`,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const valid = validateNamespace(declared);
|
|
143
|
+
if (!valid.ok) {
|
|
144
|
+
return {
|
|
145
|
+
...base,
|
|
146
|
+
...repositoryFields,
|
|
147
|
+
namespace: '',
|
|
148
|
+
mountStatus: 'invalid',
|
|
149
|
+
mountFailure: 'invalid-root',
|
|
150
|
+
diagnostic: `project memory store declares an invalid namespace \`${declared}\` (${valid.reason}) — ${remedy}`,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
...base,
|
|
155
|
+
...repositoryFields,
|
|
156
|
+
repositoryNamespace: declared,
|
|
157
|
+
namespace: composeCanonicalName(declared, repository.ownerRelativePath),
|
|
158
|
+
mountStatus: 'ready',
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/** A store with no namespace of its own: user, profile, node, builtin. */
|
|
162
|
+
function plainStoreDescriptor(scope, storeRoot) {
|
|
163
|
+
if (storeRoot === null) {
|
|
164
|
+
return { scope, storeRoot: '', namespace: '', mountStatus: 'absent', projectMemory: NEUTRAL_PROJECT_MEMORY };
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
scope,
|
|
168
|
+
storeRoot,
|
|
169
|
+
namespace: '',
|
|
170
|
+
mountStatus: pathExists(storeRoot) ? 'ready' : 'absent',
|
|
171
|
+
projectMemory: NEUTRAL_PROJECT_MEMORY,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/** An installed plugin's store, mounted under the plugin's own name. */
|
|
175
|
+
function pluginStoreDescriptor(plugin, scope, projectMemory) {
|
|
176
|
+
const storeRoot = pluginMemoryDir(plugin);
|
|
177
|
+
return {
|
|
178
|
+
scope,
|
|
179
|
+
storeRoot,
|
|
180
|
+
namespace: plugin.name,
|
|
181
|
+
plugin: plugin.name,
|
|
182
|
+
mountStatus: pathExists(storeRoot) ? 'ready' : 'absent',
|
|
183
|
+
projectMemory,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/** The exact project store owned by `ownerDir` — the `--dir` selector's store,
|
|
187
|
+
* loaded whatever its mount status so lint and migration can inspect a store
|
|
188
|
+
* the runtime refuses to mount. */
|
|
189
|
+
export function openProjectMemoryStore(ownerDir) {
|
|
190
|
+
const owner = realpathOrSelf(ownerDir);
|
|
191
|
+
return projectStoreDescriptor(owner, join(owner, CRTR_DIR_NAME, 'memory'), NEUTRAL_PROJECT_MEMORY);
|
|
192
|
+
}
|
|
193
|
+
/** Every store a target sees, in precedence order: node, each project store
|
|
194
|
+
* nearest-first (its enabled plugins after it), then profile, user, builtin.
|
|
195
|
+
* One real store reached twice contributes once, at its first position. */
|
|
196
|
+
export function memoryStoresInPrecedence(target, scope, includeDescendants = false) {
|
|
74
197
|
const out = [];
|
|
198
|
+
const projectStores = (crtrRoot, projectMemory) => {
|
|
199
|
+
out.push(projectStoreDescriptor(dirname(crtrRoot), join(crtrRoot, 'memory'), projectMemory));
|
|
200
|
+
for (const plugin of listInstalledPluginsInRoot('project', crtrRoot).filter((p) => p.enabled)) {
|
|
201
|
+
out.push(pluginStoreDescriptor(plugin, 'project', projectMemory));
|
|
202
|
+
}
|
|
203
|
+
};
|
|
75
204
|
for (const s of scopesInPrecedence(target, scope)) {
|
|
76
205
|
if (s === 'project') {
|
|
77
206
|
const annotated = projectScopeRootsWithMemory(target.cwd, target.profileId);
|
|
78
|
-
for (const { root, memory } of annotated)
|
|
79
|
-
|
|
80
|
-
scope: 'project',
|
|
81
|
-
memoryDir: join(root, 'memory'),
|
|
82
|
-
plugins: listInstalledPluginsInRoot('project', root).filter((p) => p.enabled),
|
|
83
|
-
projectMemory: memory,
|
|
84
|
-
});
|
|
85
|
-
}
|
|
207
|
+
for (const { root, memory } of annotated)
|
|
208
|
+
projectStores(root, memory);
|
|
86
209
|
// Descendant stores rank after every ancestor source (and before
|
|
87
|
-
// profile): an ancestor doc always
|
|
88
|
-
// They carry the neutral ceiling — no profile entry names
|
|
89
|
-
// descendant discovery is off for every automatic delivery path.
|
|
210
|
+
// profile): an ancestor doc always wins an equal-canonical tie against a
|
|
211
|
+
// nested one. They carry the neutral ceiling — no profile entry names
|
|
212
|
+
// them, and descendant discovery is off for every automatic delivery path.
|
|
90
213
|
if (includeDescendants) {
|
|
91
214
|
for (const root of descendantStoreRoots(annotated.map(({ root: r }) => r))) {
|
|
92
|
-
|
|
93
|
-
scope: 'project',
|
|
94
|
-
memoryDir: join(root, 'memory'),
|
|
95
|
-
plugins: listInstalledPluginsInRoot('project', root).filter((p) => p.enabled),
|
|
96
|
-
projectMemory: NEUTRAL_PROJECT_MEMORY,
|
|
97
|
-
});
|
|
215
|
+
projectStores(root, NEUTRAL_PROJECT_MEMORY);
|
|
98
216
|
}
|
|
99
217
|
}
|
|
100
218
|
}
|
|
@@ -103,173 +221,330 @@ function memorySourcesInPrecedence(target, scope, includeDescendants = false) {
|
|
|
103
221
|
if (profileId !== '') {
|
|
104
222
|
try {
|
|
105
223
|
const { profileId: resolvedId } = loadProfileManifest(profileId);
|
|
106
|
-
out.push(
|
|
224
|
+
out.push(plainStoreDescriptor('profile', profileMemoryDir(resolvedId)));
|
|
107
225
|
}
|
|
108
226
|
catch {
|
|
109
|
-
// Missing/deleted/invalid selected profile: no profile memory
|
|
227
|
+
// Missing/deleted/invalid selected profile: no profile memory store.
|
|
110
228
|
}
|
|
111
229
|
}
|
|
112
230
|
}
|
|
113
231
|
else if (s === 'node') {
|
|
114
|
-
// The this-node store — a singleton dir in the node's context, no plugins.
|
|
115
|
-
// Silently absent when the target names no node.
|
|
116
232
|
const nodeId = target.nodeId ?? '';
|
|
117
233
|
if (nodeId !== '')
|
|
118
|
-
out.push(
|
|
234
|
+
out.push(plainStoreDescriptor('node', nodeMemoryDir(nodeId)));
|
|
119
235
|
}
|
|
120
236
|
else {
|
|
121
|
-
out.push(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
projectMemory: NEUTRAL_PROJECT_MEMORY,
|
|
126
|
-
});
|
|
237
|
+
out.push(plainStoreDescriptor(s, scopeMemoryDir(s)));
|
|
238
|
+
for (const plugin of listInstalledPlugins(s).filter((p) => p.enabled)) {
|
|
239
|
+
out.push(pluginStoreDescriptor(plugin, s, NEUTRAL_PROJECT_MEMORY));
|
|
240
|
+
}
|
|
127
241
|
}
|
|
128
242
|
}
|
|
129
|
-
|
|
243
|
+
const seen = new Set();
|
|
244
|
+
const deduped = out.filter((store) => {
|
|
245
|
+
if (store.storeRoot === '')
|
|
246
|
+
return false;
|
|
247
|
+
const key = realpathOrSelf(store.storeRoot);
|
|
248
|
+
if (seen.has(key))
|
|
249
|
+
return false;
|
|
250
|
+
seen.add(key);
|
|
251
|
+
return true;
|
|
252
|
+
});
|
|
253
|
+
const collisions = new Map();
|
|
254
|
+
for (const store of deduped) {
|
|
255
|
+
if (store.scope !== 'project' ||
|
|
256
|
+
store.mountStatus !== 'ready' ||
|
|
257
|
+
store.repositoryRoot === undefined ||
|
|
258
|
+
store.ownerRelativePath === undefined)
|
|
259
|
+
continue;
|
|
260
|
+
const key = `${store.repositoryRoot}\u0000${store.ownerRelativePath}`;
|
|
261
|
+
const group = collisions.get(key);
|
|
262
|
+
if (group)
|
|
263
|
+
group.push(store);
|
|
264
|
+
else
|
|
265
|
+
collisions.set(key, [store]);
|
|
266
|
+
}
|
|
267
|
+
return deduped.map((store) => {
|
|
268
|
+
if (store.scope !== 'project' ||
|
|
269
|
+
store.mountStatus !== 'ready' ||
|
|
270
|
+
store.repositoryRoot === undefined ||
|
|
271
|
+
store.ownerRelativePath === undefined)
|
|
272
|
+
return store;
|
|
273
|
+
const group = collisions.get(`${store.repositoryRoot}\u0000${store.ownerRelativePath}`);
|
|
274
|
+
if (group === undefined || group.length < 2)
|
|
275
|
+
return store;
|
|
276
|
+
const owners = group.map((member) => member.ownerDir ?? member.storeRoot).join(', ');
|
|
277
|
+
return {
|
|
278
|
+
...store,
|
|
279
|
+
mountStatus: 'invalid',
|
|
280
|
+
mountFailure: 'collision',
|
|
281
|
+
diagnostic: `project memory stores at ${owners} normalize to the same effective prefix \`${store.namespace}\` — rename one owner directory`,
|
|
282
|
+
};
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
/** The native (non-plugin) MOUNTED store dirs in resolution precedence.
|
|
286
|
+
* Addresses a store directly when the document itself may be absent, which is
|
|
287
|
+
* how a DELETED doc's revision log is still found: history outlives the doc,
|
|
288
|
+
* so its lookup cannot go through document resolution. */
|
|
289
|
+
export function nativeMemoryStoresInPrecedence(scope, includeDescendants = false) {
|
|
290
|
+
return memoryStoresInPrecedence(ambientTarget(), scope, includeDescendants)
|
|
291
|
+
.filter((store) => store.mountStatus === 'ready' && store.scope !== 'builtin' && store.plugin === undefined)
|
|
292
|
+
.map((store) => ({ scope: store.scope, memoryDir: store.storeRoot, store }));
|
|
293
|
+
}
|
|
294
|
+
// ---------------------------------------------------------------------------
|
|
295
|
+
// Document loading: one store's markdown files become canonically-identified
|
|
296
|
+
// documents.
|
|
297
|
+
// ---------------------------------------------------------------------------
|
|
298
|
+
function firstLine(e) {
|
|
299
|
+
return (e instanceof Error ? e.message : String(e)).split('\n')[0] ?? '';
|
|
130
300
|
}
|
|
131
|
-
|
|
301
|
+
/** Load one markdown file as a document of `store`, or null when it has no
|
|
302
|
+
* canonical identity — a root `INDEX.md` in a store without a namespace, which
|
|
303
|
+
* would compose to the empty name. */
|
|
304
|
+
function loadMemoryDoc(store, path, physicalRelativePath) {
|
|
132
305
|
const { data, body } = parseFrontmatterGeneric(readText(path));
|
|
133
|
-
|
|
306
|
+
const pathLocal = localNameFromPath(physicalRelativePath);
|
|
307
|
+
const representation = representationForPath(physicalRelativePath);
|
|
308
|
+
// A store's root INDEX.md is the store's own document: its identity is the
|
|
309
|
+
// namespace itself, never an explicit `name` (lint rejects one there).
|
|
310
|
+
const localName = pathLocal === '' ? '' : resolveLocalName(data, pathLocal);
|
|
311
|
+
const name = composeCanonicalName(store.namespace, localName);
|
|
312
|
+
if (name === '')
|
|
313
|
+
return null;
|
|
314
|
+
return {
|
|
315
|
+
name,
|
|
316
|
+
localName,
|
|
317
|
+
scope: store.scope,
|
|
318
|
+
path,
|
|
319
|
+
root: store.storeRoot,
|
|
320
|
+
physicalRelativePath,
|
|
321
|
+
representation,
|
|
322
|
+
routingAnchor: routingAnchorFor(name, representation),
|
|
323
|
+
store,
|
|
324
|
+
frontmatter: data,
|
|
325
|
+
body,
|
|
326
|
+
projectMemory: store.projectMemory,
|
|
327
|
+
...(store.plugin === undefined ? {} : { plugin: store.plugin }),
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/** Within one store, `foo/INDEX.md` deterministically precedes `foo.md` for the
|
|
331
|
+
* same canonical identity: the directory form physically fronts the directory.
|
|
332
|
+
* Lint errors on the pair and every mutation refuses it, but a read still has
|
|
333
|
+
* one answer. */
|
|
334
|
+
function compareDocs(a, b) {
|
|
335
|
+
const byName = a.name.localeCompare(b.name);
|
|
336
|
+
if (byName !== 0)
|
|
337
|
+
return byName;
|
|
338
|
+
if (a.representation !== b.representation)
|
|
339
|
+
return a.representation === 'directory-index' ? -1 : 1;
|
|
340
|
+
return a.physicalRelativePath.localeCompare(b.physicalRelativePath);
|
|
134
341
|
}
|
|
135
|
-
/**
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (!dir || !pathExists(dir))
|
|
342
|
+
/** Every document in one store, sorted by canonical identity. SKILL.md bundles
|
|
343
|
+
* are Agent Skills, not memory docs. Enumeration stops at a nested `.crouter/`:
|
|
344
|
+
* that is another store's mount point, never this store's content. */
|
|
345
|
+
export function loadStoreMemoryDocs(store, quiet = false) {
|
|
346
|
+
if (store.storeRoot === '' || !pathExists(store.storeRoot))
|
|
141
347
|
return [];
|
|
142
348
|
const docs = [];
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
while (stack.length) {
|
|
146
|
-
const d = stack.pop();
|
|
147
|
-
let entries;
|
|
148
|
-
try {
|
|
149
|
-
entries = readdirSync(d, { withFileTypes: true });
|
|
150
|
-
}
|
|
151
|
-
catch {
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
for (const e of entries) {
|
|
155
|
-
// Enumeration stops at store boundaries: a nested `.crouter/` is another
|
|
156
|
-
// scope's mount point (e.g. a dir-scoped store inside a dir that is itself
|
|
157
|
-
// a store, like builtin-memory in the crouter repo), never this store's
|
|
158
|
-
// own content — walking into it would register a different tier's docs.
|
|
159
|
-
if (e.isDirectory()) {
|
|
160
|
-
if (e.name !== CRTR_DIR_NAME)
|
|
161
|
-
stack.push(join(d, e.name));
|
|
162
|
-
}
|
|
163
|
-
else if (e.isFile() && e.name.endsWith('.md') && e.name !== 'SKILL.md') {
|
|
164
|
-
const file = join(d, e.name);
|
|
165
|
-
const raw = relative(dir, file).replace(/\.md$/i, '').split(sep).join('/');
|
|
166
|
-
const name = normalizeDocName(raw);
|
|
167
|
-
if (name)
|
|
168
|
-
found.push({ path: file, name });
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
for (const { path, name } of found) {
|
|
349
|
+
for (const path of walkFiles(store.storeRoot, (n) => n.endsWith('.md') && n !== 'SKILL.md', (d) => d === CRTR_DIR_NAME)) {
|
|
350
|
+
const physicalRelativePath = relative(store.storeRoot, path).split(sep).join('/');
|
|
173
351
|
// COLLECTION layer: the strict frontmatter parser throws on invalid YAML.
|
|
174
|
-
// Isolate one malformed doc with a
|
|
175
|
-
//
|
|
176
|
-
// `quiet` suppresses the notice for a targeted resolve
|
|
177
|
-
//
|
|
352
|
+
// Isolate one malformed doc with a scoped notice + skip, so a single bad
|
|
353
|
+
// file can't brick `memory list`/`find` or the substrate boot render.
|
|
354
|
+
// `quiet` suppresses the notice for a targeted resolve, where another doc's
|
|
355
|
+
// health is noise before the result.
|
|
178
356
|
try {
|
|
179
|
-
|
|
357
|
+
const doc = loadMemoryDoc(store, path, physicalRelativePath);
|
|
358
|
+
if (doc === null) {
|
|
359
|
+
if (!quiet)
|
|
360
|
+
warn(`${path}: a root ${INDEX_SEGMENT}.md has no canonical name in a store without a namespace`);
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
docs.push(doc);
|
|
180
364
|
}
|
|
181
365
|
catch (e) {
|
|
182
|
-
const msg = (e instanceof Error ? e.message : String(e)).split('\n')[0];
|
|
183
366
|
if (!quiet)
|
|
184
|
-
warn(`invalid frontmatter in ${path}: ${
|
|
367
|
+
warn(`invalid frontmatter in ${path}: ${firstLine(e)}`);
|
|
185
368
|
}
|
|
186
369
|
}
|
|
187
|
-
return docs.sort(
|
|
370
|
+
return docs.sort(compareDocs);
|
|
188
371
|
}
|
|
189
|
-
/** All
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
* through `loadProfileManifest`; user and builtin are singleton stores. */
|
|
193
|
-
export function listMemoryDocs(scope, quiet = false) {
|
|
194
|
-
const target = ambientTarget();
|
|
195
|
-
if (scope === 'project') {
|
|
196
|
-
return listProjectMemoryDocs(target.cwd, target.profileId, quiet);
|
|
197
|
-
}
|
|
198
|
-
if (scope === 'profile') {
|
|
199
|
-
const profileId = target.profileId ?? '';
|
|
200
|
-
if (profileId === '')
|
|
201
|
-
return [];
|
|
202
|
-
try {
|
|
203
|
-
const { profileId: resolvedId } = loadProfileManifest(profileId);
|
|
204
|
-
return listMemoryDocsInDir('profile', profileMemoryDir(resolvedId), quiet);
|
|
205
|
-
}
|
|
206
|
-
catch {
|
|
207
|
-
return [];
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
if (scope === 'node') {
|
|
211
|
-
const nodeId = target.nodeId ?? '';
|
|
212
|
-
if (nodeId === '')
|
|
213
|
-
return [];
|
|
214
|
-
return listMemoryDocsInDir('node', nodeMemoryDir(nodeId), quiet);
|
|
215
|
-
}
|
|
216
|
-
return listMemoryDocsInDir(scope, scopeMemoryDir(scope), quiet);
|
|
217
|
-
}
|
|
218
|
-
/** All of one plugin's substrate docs, mounted under the virtual `<pluginName>/`
|
|
219
|
-
* namespace. Walks `pluginMemoryDir(plugin)` recursively for *.md, deriving each
|
|
220
|
-
* doc's name exactly as `listMemoryDocs` does (path-relative, no extension,
|
|
221
|
-
* slash-separated) then prefixing the plugin name. Builtin has no plugins. */
|
|
372
|
+
/** All of one plugin's substrate docs, mounted under the plugin's own name:
|
|
373
|
+
* each doc's store-local name (explicit or path-derived) composes under that
|
|
374
|
+
* mount prefix. */
|
|
222
375
|
export function listPluginMemoryDocs(plugin, scope, quiet = false, projectMemory = NEUTRAL_PROJECT_MEMORY) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
376
|
+
return loadStoreMemoryDocs(pluginStoreDescriptor(plugin, scope, projectMemory), quiet);
|
|
377
|
+
}
|
|
378
|
+
// ---------------------------------------------------------------------------
|
|
379
|
+
// Views: exact canonical resolution over a loaded corpus.
|
|
380
|
+
// ---------------------------------------------------------------------------
|
|
381
|
+
/** The canonical form of a query: empty segments dropped. An `NN-` ordering
|
|
382
|
+
* prefix is physical-path-only and is NOT stripped here — a canonical name
|
|
383
|
+
* never carries one, so `04-base` is simply no document's address. */
|
|
384
|
+
function normalizeQuery(rawName) {
|
|
385
|
+
return canonicalSegments(rawName.trim()).join('/');
|
|
386
|
+
}
|
|
387
|
+
function notFoundError(rawName, name, scope) {
|
|
388
|
+
const segments = canonicalSegments(name);
|
|
389
|
+
const collapsedIndex = segments[segments.length - 1] === INDEX_SEGMENT;
|
|
390
|
+
if (collapsedIndex)
|
|
391
|
+
segments.pop();
|
|
392
|
+
const leaf = segments[segments.length - 1] ?? name;
|
|
393
|
+
const next = collapsedIndex
|
|
394
|
+
? `A directory's own document answers at the directory name — drop the trailing /${INDEX_SEGMENT}. Run \`crtr memory find ${leaf}\` to find the canonical name.`
|
|
395
|
+
: `Resolution takes the full canonical name (a project doc is <namespace>/<local name>). Run \`crtr memory find ${leaf}\` to find it.`;
|
|
396
|
+
return notFound(`memory document not found: ${rawName}`, { memory: name, ...(scope ? { scope } : {}), next });
|
|
397
|
+
}
|
|
398
|
+
function makeView(stores, docs, scope) {
|
|
399
|
+
const candidatesFor = (rawName, opts) => {
|
|
400
|
+
const name = normalizeQuery(rawName);
|
|
401
|
+
if (name === '')
|
|
402
|
+
return null;
|
|
403
|
+
const kind = opts?.kind;
|
|
404
|
+
const candidates = docs.filter((d) => d.name === name && (kind === undefined || effectiveDocKind(d) === kind));
|
|
405
|
+
if (candidates.length === 0)
|
|
406
|
+
return null;
|
|
407
|
+
return { name, winner: candidates[0], candidates };
|
|
408
|
+
};
|
|
409
|
+
return {
|
|
410
|
+
stores,
|
|
411
|
+
docs,
|
|
412
|
+
candidates: candidatesFor,
|
|
413
|
+
candidateSets(opts) {
|
|
414
|
+
const kind = opts?.kind;
|
|
415
|
+
const grouped = new Map();
|
|
416
|
+
for (const doc of docs) {
|
|
417
|
+
if (kind !== undefined && effectiveDocKind(doc) !== kind)
|
|
418
|
+
continue;
|
|
419
|
+
const bucket = grouped.get(doc.name);
|
|
420
|
+
if (bucket)
|
|
421
|
+
bucket.push(doc);
|
|
422
|
+
else
|
|
423
|
+
grouped.set(doc.name, [doc]);
|
|
424
|
+
}
|
|
425
|
+
return [...grouped.entries()]
|
|
426
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
427
|
+
.map(([name, candidates]) => ({ name, winner: candidates[0], candidates }));
|
|
428
|
+
},
|
|
429
|
+
resolve(rawName, opts) {
|
|
430
|
+
const name = normalizeQuery(rawName);
|
|
431
|
+
if (name === '')
|
|
432
|
+
throw usage('memory document name required');
|
|
433
|
+
const set = candidatesFor(name, opts);
|
|
434
|
+
if (set === null)
|
|
435
|
+
throw notFoundError(rawName, name, scope);
|
|
436
|
+
return set.winner;
|
|
437
|
+
},
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
/** Load one target's whole corpus: every mounted store's documents in source
|
|
441
|
+
* order. A project store that does not mount is reported once (unless quiet)
|
|
442
|
+
* with its migration remedy and contributes nothing. */
|
|
443
|
+
export function loadMemoryTargetView(target, opts = {}) {
|
|
444
|
+
const quiet = opts.quiet ?? false;
|
|
445
|
+
const stores = memoryStoresInPrecedence(target, opts.scope, opts.includeDescendants ?? false);
|
|
226
446
|
const docs = [];
|
|
227
|
-
for (const
|
|
228
|
-
|
|
229
|
-
|
|
447
|
+
for (const store of stores) {
|
|
448
|
+
if (store.mountStatus === 'absent')
|
|
449
|
+
continue;
|
|
450
|
+
if (store.mountStatus !== 'ready') {
|
|
451
|
+
if (!quiet && store.diagnostic !== undefined)
|
|
452
|
+
warn(`memory store not mounted: ${store.diagnostic}`);
|
|
230
453
|
continue;
|
|
231
|
-
const name = `${plugin.name}/${derived}`;
|
|
232
|
-
try {
|
|
233
|
-
docs.push({ ...loadMemoryDoc(scope, dir, file, name, projectMemory), plugin: plugin.name });
|
|
234
|
-
}
|
|
235
|
-
catch (e) {
|
|
236
|
-
const msg = (e instanceof Error ? e.message : String(e)).split('\n')[0];
|
|
237
|
-
if (!quiet)
|
|
238
|
-
warn(`invalid frontmatter in ${file}: ${msg}`);
|
|
239
454
|
}
|
|
455
|
+
docs.push(...loadStoreMemoryDocs(store, quiet));
|
|
240
456
|
}
|
|
241
|
-
return
|
|
457
|
+
return { target, ...makeView(stores, docs, opts.scope) };
|
|
242
458
|
}
|
|
243
|
-
/**
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
return [
|
|
249
|
-
...listMemoryDocsInDir(source.scope, source.memoryDir, quiet, source.projectMemory),
|
|
250
|
-
...source.plugins.flatMap((p) => listPluginMemoryDocs(p, source.scope, quiet, source.projectMemory)),
|
|
251
|
-
];
|
|
459
|
+
/** Load ONE exact physical store. An undeclared/invalid project store loads
|
|
460
|
+
* under the empty namespace so lint and migration can inspect what it holds;
|
|
461
|
+
* no target view ever sees those documents. */
|
|
462
|
+
export function loadMemoryStoreView(store, quiet = false) {
|
|
463
|
+
return { store, ...makeView([store], loadStoreMemoryDocs(store, quiet)) };
|
|
252
464
|
}
|
|
465
|
+
// ---------------------------------------------------------------------------
|
|
466
|
+
// Corpus listing.
|
|
467
|
+
// ---------------------------------------------------------------------------
|
|
253
468
|
/** All project-scoped docs visible from an explicit node workspace/profile — the
|
|
254
469
|
* project-only slice of a `MemoryTarget` view, used where only workspace docs
|
|
255
470
|
* are wanted (a workspace-open render). For a full-precedence target-addressed
|
|
256
471
|
* lookup, use `resolveMemoryDocForTarget`. */
|
|
257
472
|
export function listProjectMemoryDocs(startDir = process.cwd(), profileId = selectedProfileId() || null, quiet = false) {
|
|
258
|
-
return
|
|
259
|
-
scope: 'project',
|
|
260
|
-
|
|
261
|
-
plugins: listInstalledPluginsInRoot('project', root).filter((plugin) => plugin.enabled),
|
|
262
|
-
projectMemory: memory,
|
|
263
|
-
}, quiet));
|
|
473
|
+
return [
|
|
474
|
+
...loadMemoryTargetView({ cwd: startDir, profileId, nodeId: null }, { scope: 'project', quiet }).docs,
|
|
475
|
+
];
|
|
264
476
|
}
|
|
265
477
|
/** All memory docs across the resolved sources, in precedence order: each
|
|
266
478
|
* ancestor project `.crouter/` from nearest to farthest, then the selected
|
|
267
479
|
* profile's memory (if any), then user, then builtin. Within each source,
|
|
268
|
-
* native docs are emitted before enabled-plugin docs,
|
|
269
|
-
* caller's first-wins dedup
|
|
480
|
+
* native docs are emitted before enabled-plugin docs, and equal-canonical
|
|
481
|
+
* candidates stay in source order, so a caller's first-wins dedup yields the
|
|
482
|
+
* address winners. */
|
|
270
483
|
export function listAllMemoryDocs(scope, quiet = false, includeDescendants = false) {
|
|
271
|
-
return
|
|
484
|
+
return [...loadMemoryTargetView(ambientTarget(), { ...(scope ? { scope } : {}), quiet, includeDescendants }).docs];
|
|
485
|
+
}
|
|
486
|
+
export function createMemoryDocSnapshot(target = ambientTarget()) {
|
|
487
|
+
// Quiet: a snapshot feeds command registration and migration planning, where
|
|
488
|
+
// another doc's frontmatter health is noise — corpus health is `lint`'s job.
|
|
489
|
+
const view = loadMemoryTargetView(target, { quiet: true });
|
|
490
|
+
return {
|
|
491
|
+
docs: view.docs,
|
|
492
|
+
resolve: (names) => {
|
|
493
|
+
const resolved = new Map();
|
|
494
|
+
for (const rawName of names) {
|
|
495
|
+
const set = view.candidates(rawName);
|
|
496
|
+
// A command registration reports its own body-read failure; one
|
|
497
|
+
// malformed/stale identity must not suppress every other slash command.
|
|
498
|
+
if (set !== null)
|
|
499
|
+
resolved.set(rawName, set.winner);
|
|
500
|
+
}
|
|
501
|
+
return resolved;
|
|
502
|
+
},
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
/** Resolve several canonical names over one memory-source snapshot. Failed
|
|
506
|
+
* names are omitted so callers can preserve their per-document error behavior. */
|
|
507
|
+
export function resolveMemoryDocs(names) {
|
|
508
|
+
return createMemoryDocSnapshot().resolve(names);
|
|
272
509
|
}
|
|
510
|
+
/** The address winner for an exact canonical name in the ambient target. */
|
|
511
|
+
export function resolveMemoryDoc(rawName, opts = {}) {
|
|
512
|
+
return resolveMemoryDocForTarget(rawName, ambientTarget(), opts);
|
|
513
|
+
}
|
|
514
|
+
/** Resolve a memory document as ANOTHER node would see it — the same precedence
|
|
515
|
+
* chain (node-local > project stack > profile > user > builtin), read from the
|
|
516
|
+
* target's cwd/profile/node rather than the host process's. This is what crtrd
|
|
517
|
+
* resolves a `[[name]]` link through: the daemon's own cwd and env name no
|
|
518
|
+
* node, and the same name can be a different document for two nodes. */
|
|
519
|
+
export function resolveMemoryDocForTarget(rawName, target, opts = {}) {
|
|
520
|
+
return resolveMemoryCandidatesForTarget(rawName, target, opts).winner;
|
|
521
|
+
}
|
|
522
|
+
/** Every physical candidate for an exact canonical name in the ambient target,
|
|
523
|
+
* in source order — what path-aware inventory, diagnostics, and automatic
|
|
524
|
+
* event eligibility consume instead of the winner alone. */
|
|
525
|
+
export function resolveMemoryCandidates(rawName, opts = {}) {
|
|
526
|
+
return resolveMemoryCandidatesForTarget(rawName, ambientTarget(), opts);
|
|
527
|
+
}
|
|
528
|
+
export function resolveMemoryCandidatesForTarget(rawName, target, opts = {}) {
|
|
529
|
+
const view = loadMemoryTargetView(target, { ...opts, quiet: true });
|
|
530
|
+
const name = normalizeQuery(rawName);
|
|
531
|
+
if (name === '')
|
|
532
|
+
throw usage('memory document name required');
|
|
533
|
+
const set = view.candidates(name, opts);
|
|
534
|
+
if (set === null)
|
|
535
|
+
throw notFoundError(rawName, name, opts.scope);
|
|
536
|
+
return set;
|
|
537
|
+
}
|
|
538
|
+
/** The address winner for an exact canonical name inside ONE project store —
|
|
539
|
+
* the `--dir` selector's resolution. The query is still the full canonical
|
|
540
|
+
* name, including that store's namespace. */
|
|
541
|
+
export function resolveMemoryDocInStore(rawName, store, opts = {}) {
|
|
542
|
+
return loadMemoryStoreView(store, true).resolve(rawName, opts);
|
|
543
|
+
}
|
|
544
|
+
// ---------------------------------------------------------------------------
|
|
545
|
+
// Physical sidecar lookup: the revision log mirrors the doc tree, so it is
|
|
546
|
+
// addressed by PHYSICAL path segments, not by canonical identity.
|
|
547
|
+
// ---------------------------------------------------------------------------
|
|
273
548
|
/** Find the direct child of `dir` — an `ext` file (matched on name minus
|
|
274
549
|
* extension) or a directory — whose NORMALIZED display name equals
|
|
275
550
|
* `segment`. An exact literal match (no prefix to strip) wins over a
|
|
@@ -309,215 +584,26 @@ function matchNormalizedChild(dir, segment, want, ext = '.md') {
|
|
|
309
584
|
return normalizedHit;
|
|
310
585
|
}
|
|
311
586
|
/** Resolve a NORMALIZED (numeric-prefix-stripped) segment path against a
|
|
312
|
-
* physical
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
|
|
318
|
-
* lookup is prefix-blind. */
|
|
319
|
-
function resolveNormalizedPath(baseDir, segments, ext = '.md') {
|
|
587
|
+
* physical dir: walk intermediate segments matching each as a directory by
|
|
588
|
+
* normalized name, then resolve the final segment as an `ext` file. Null when
|
|
589
|
+
* the segment path does not resolve. This is what keeps a sidecar findable
|
|
590
|
+
* when its physical path carries ordering pins — `00-topic.md` logged at
|
|
591
|
+
* `.history/00-topic.jsonl` still answers to `topic`. */
|
|
592
|
+
function resolveNormalizedFile(baseDir, segments, ext) {
|
|
320
593
|
let curDir = baseDir;
|
|
321
594
|
for (let i = 0; i < segments.length - 1; i++) {
|
|
322
595
|
const next = matchNormalizedChild(curDir, segments[i], 'dir', ext);
|
|
323
596
|
if (!next)
|
|
324
|
-
return
|
|
597
|
+
return null;
|
|
325
598
|
curDir = next;
|
|
326
599
|
}
|
|
327
|
-
|
|
328
|
-
return {
|
|
329
|
-
filePath: matchNormalizedChild(curDir, last, 'file', ext),
|
|
330
|
-
dirPath: matchNormalizedChild(curDir, last, 'dir', ext),
|
|
331
|
-
};
|
|
600
|
+
return matchNormalizedChild(curDir, segments[segments.length - 1], 'file', ext);
|
|
332
601
|
}
|
|
333
|
-
/** Resolve
|
|
334
|
-
* tree segment for segment with `.jsonl` in place of `.md`.
|
|
335
|
-
*
|
|
336
|
-
* numeric prefixes stay prefix-blind (`00-topic.md` logged at
|
|
337
|
-
* `.history/00-topic.jsonl` still answers to `topic`). Returns null when
|
|
338
|
-
* nothing resolves. */
|
|
602
|
+
/** Resolve PHYSICAL path segments against a `.history` tree, which mirrors the
|
|
603
|
+
* doc tree segment for segment with `.jsonl` in place of `.md`. A log outlives
|
|
604
|
+
* its document, so this lookup cannot go through document resolution. */
|
|
339
605
|
export function resolveHistoryLogPath(historyDir, segments) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
* source's single hit, or undefined — a source can produce at most one direct
|
|
344
|
-
* match (native wins over plugin within the source, and at most one plugin's
|
|
345
|
-
* name can match). A directory name matches NO doc here: the read leaf
|
|
346
|
-
* answers a directory with its listing, never a stand-in document.
|
|
347
|
-
*
|
|
348
|
-
* `name`'s segments are already normalized (a caller-supplied identifier is
|
|
349
|
-
* never authored with a numeric prefix); resolution against the physical tree
|
|
350
|
-
* is prefix-blind via `resolveNormalizedPath` so a normalized name finds an
|
|
351
|
-
* `NN-`-pinned physical file/dir. */
|
|
352
|
-
function findMemoryMatchInSource(name, segments, source, legacyDirectoryIndex = false) {
|
|
353
|
-
const isLegacySkillDoc = segments.at(-1) === 'SKILL';
|
|
354
|
-
// Native memory dir first inside this source (native-before-plugin
|
|
355
|
-
// precedence), then its enabled plugins.
|
|
356
|
-
const dir = source.memoryDir;
|
|
357
|
-
if (dir) {
|
|
358
|
-
const { filePath, dirPath } = resolveNormalizedPath(dir, segments);
|
|
359
|
-
if (!isLegacySkillDoc && filePath !== null)
|
|
360
|
-
return loadMemoryDoc(source.scope, dir, filePath, name, source.projectMemory);
|
|
361
|
-
if (legacyDirectoryIndex && dirPath !== null) {
|
|
362
|
-
const indexPath = join(dirPath, 'INDEX.md');
|
|
363
|
-
if (pathExists(indexPath))
|
|
364
|
-
return loadMemoryDoc(source.scope, dir, indexPath, name, source.projectMemory);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
// Plugin memory dir: a `<plugin>/<rest>` name resolves against that enabled
|
|
368
|
-
// plugin's memory/ tree (the `<pluginName>/` mount that listAllMemoryDocs
|
|
369
|
-
// enumerates — `read` must resolve what `list` shows).
|
|
370
|
-
const slash = name.indexOf('/');
|
|
371
|
-
if (slash <= 0 && !legacyDirectoryIndex)
|
|
372
|
-
return undefined;
|
|
373
|
-
const pluginName = slash > 0 ? name.slice(0, slash) : name;
|
|
374
|
-
const rest = slash > 0 ? name.slice(slash + 1) : '';
|
|
375
|
-
for (const p of source.plugins) {
|
|
376
|
-
if (p.name !== pluginName)
|
|
377
|
-
continue;
|
|
378
|
-
const pdir = pluginMemoryDir(p);
|
|
379
|
-
const restSegments = rest === '' ? [] : rest.split('/');
|
|
380
|
-
if (restSegments.length > 0) {
|
|
381
|
-
const { filePath } = resolveNormalizedPath(pdir, restSegments);
|
|
382
|
-
if (restSegments.at(-1) !== 'SKILL' && filePath !== null)
|
|
383
|
-
return loadMemoryDoc(source.scope, pdir, filePath, name, source.projectMemory);
|
|
384
|
-
}
|
|
385
|
-
if (legacyDirectoryIndex) {
|
|
386
|
-
const indexDir = rest === '' ? pdir : resolveNormalizedPath(pdir, restSegments).dirPath;
|
|
387
|
-
if (indexDir !== null) {
|
|
388
|
-
const indexPath = join(indexDir, 'INDEX.md');
|
|
389
|
-
if (pathExists(indexPath))
|
|
390
|
-
return loadMemoryDoc(source.scope, pdir, indexPath, name, source.projectMemory);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
return undefined;
|
|
395
|
-
}
|
|
396
|
-
function formatLeafAmbiguous(leaf, matches) {
|
|
397
|
-
const ids = matches.map(memoryDocId).join(', ');
|
|
398
|
-
return `ambiguous memory document: ${leaf} matches multiple documents: ${ids}`;
|
|
399
|
-
}
|
|
400
|
-
function loadMemorySources(target, scope, includeDescendants = false) {
|
|
401
|
-
// Quiet: a targeted read must not spew other docs' frontmatter warnings
|
|
402
|
-
// before its own result (esp. a not_found) — corpus health is `lint`'s job.
|
|
403
|
-
return memorySourcesInPrecedence(target, scope, includeDescendants).map((source) => ({
|
|
404
|
-
source,
|
|
405
|
-
docs: sourceMemoryDocs(source, true),
|
|
406
|
-
}));
|
|
407
|
-
}
|
|
408
|
-
export function createMemoryDocSnapshot(target = ambientTarget()) {
|
|
409
|
-
const defaultSources = loadMemorySources(target);
|
|
410
|
-
const sourcesByScope = new Map([[undefined, defaultSources]]);
|
|
411
|
-
const sourceFor = (scope) => {
|
|
412
|
-
let sources = sourcesByScope.get(scope);
|
|
413
|
-
if (sources === undefined) {
|
|
414
|
-
sources = loadMemorySources(target, scope);
|
|
415
|
-
sourcesByScope.set(scope, sources);
|
|
416
|
-
}
|
|
417
|
-
return sources;
|
|
418
|
-
};
|
|
419
|
-
return {
|
|
420
|
-
docs: defaultSources.flatMap(({ docs }) => docs),
|
|
421
|
-
resolve: (names) => {
|
|
422
|
-
const resolved = new Map();
|
|
423
|
-
for (const rawName of names) {
|
|
424
|
-
try {
|
|
425
|
-
const parsed = parseSkillQualifier(rawName);
|
|
426
|
-
resolved.set(rawName, resolveMemoryDocFromSources(rawName, {}, sourceFor(parsed.scope)));
|
|
427
|
-
}
|
|
428
|
-
catch {
|
|
429
|
-
// A command registration reports its own body-read failure below; one
|
|
430
|
-
// malformed/stale identity must not suppress every other slash command.
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
return resolved;
|
|
434
|
-
},
|
|
435
|
-
legacyDirectoryIndex: (name) => {
|
|
436
|
-
if (name === 'INDEX' || name.endsWith('/INDEX'))
|
|
437
|
-
return null;
|
|
438
|
-
try {
|
|
439
|
-
const parsed = parseSkillQualifier(name);
|
|
440
|
-
const sources = sourceFor(parsed.scope);
|
|
441
|
-
const legacy = resolveMemoryDocFromSources(name, {}, sources, true);
|
|
442
|
-
if (basename(legacy.path) !== 'INDEX.md')
|
|
443
|
-
return null;
|
|
444
|
-
const explicitName = `${name}/INDEX`;
|
|
445
|
-
const explicit = resolveMemoryDocFromSources(explicitName, {}, sources);
|
|
446
|
-
return explicit.path === legacy.path ? explicitName : null;
|
|
447
|
-
}
|
|
448
|
-
catch {
|
|
449
|
-
return null;
|
|
450
|
-
}
|
|
451
|
-
},
|
|
452
|
-
};
|
|
453
|
-
}
|
|
454
|
-
function resolveMemoryDocFromSources(rawName, opts, sources, legacyDirectoryIndex = false) {
|
|
455
|
-
const parsed = parseSkillQualifier(rawName);
|
|
456
|
-
if (parsed.scope && opts.scope && parsed.scope !== opts.scope) {
|
|
457
|
-
throw usage(`scope conflict: identifier "${rawName}" uses scope "${parsed.scope}" but --scope is "${opts.scope}"`);
|
|
458
|
-
}
|
|
459
|
-
const name = parsed.segments.join('/');
|
|
460
|
-
if (name === '')
|
|
461
|
-
throw usage(`memory document name required`);
|
|
462
|
-
const segments = name.split('/');
|
|
463
|
-
const isLeafQuery = !name.includes('/');
|
|
464
|
-
const kindOk = (d) => opts.kind === undefined || effectiveDocKind(d) === opts.kind;
|
|
465
|
-
// Strong matches across the full scope stack come first. This preserves
|
|
466
|
-
// nearest-scope shadowing for canonical/direct collisions without allowing a
|
|
467
|
-
// nested plugin template's leaf to hide a user doc whose full identity is the
|
|
468
|
-
// query.
|
|
469
|
-
for (const { source, docs } of sources) {
|
|
470
|
-
const identity = docs.find((d) => d.name === name);
|
|
471
|
-
if (identity && kindOk(identity))
|
|
472
|
-
return identity;
|
|
473
|
-
const direct = findMemoryMatchInSource(name, segments, source, legacyDirectoryIndex);
|
|
474
|
-
if (direct && kindOk(direct))
|
|
475
|
-
return direct;
|
|
476
|
-
}
|
|
477
|
-
if (isLeafQuery) {
|
|
478
|
-
for (const { docs } of sources) {
|
|
479
|
-
const leafMatches = docs.filter((d) => (d.name.split('/').pop() ?? d.name) === name && kindOk(d));
|
|
480
|
-
if (leafMatches.length === 0)
|
|
481
|
-
continue;
|
|
482
|
-
// Same path-derived name within this source → precedence wins (return
|
|
483
|
-
// first); genuinely different docs sharing a leaf → ambiguous.
|
|
484
|
-
const distinctNames = new Set(leafMatches.map((d) => d.name));
|
|
485
|
-
if (distinctNames.size === 1)
|
|
486
|
-
return leafMatches[0];
|
|
487
|
-
throw ambiguous(formatLeafAmbiguous(name, leafMatches), {
|
|
488
|
-
memory: name,
|
|
489
|
-
candidates: leafMatches.map((d) => ({
|
|
490
|
-
id: memoryDocId(d),
|
|
491
|
-
scope: d.scope,
|
|
492
|
-
path: d.path,
|
|
493
|
-
})),
|
|
494
|
-
next: 'Multiple documents share this leaf name. Re-run with one of the full names in candidates.',
|
|
495
|
-
});
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
throw notFound(`memory document not found: ${rawName}`, {
|
|
499
|
-
memory: name,
|
|
500
|
-
scope: parsed.scope,
|
|
501
|
-
});
|
|
502
|
-
}
|
|
503
|
-
/** Resolve several unqualified names over one memory-source snapshot. Failed
|
|
504
|
-
* names are omitted so callers can preserve their per-document error behavior. */
|
|
505
|
-
export function resolveMemoryDocs(names) {
|
|
506
|
-
return createMemoryDocSnapshot().resolve(names);
|
|
507
|
-
}
|
|
508
|
-
export function resolveMemoryDoc(rawName, opts = {}) {
|
|
509
|
-
return resolveMemoryDocForTarget(rawName, ambientTarget(), opts);
|
|
510
|
-
}
|
|
511
|
-
/** Resolve a memory document as ANOTHER node would see it — the same precedence
|
|
512
|
-
* chain (node-local > project stack > profile > user > builtin), read from the
|
|
513
|
-
* target's cwd/profile/node rather than the host process's. This is what crtrd
|
|
514
|
-
* resolves a `[[name]]` link through: the daemon's own cwd and env name no
|
|
515
|
-
* node, and the same name can be a different document for two nodes. */
|
|
516
|
-
export function resolveMemoryDocForTarget(rawName, target, opts = {}) {
|
|
517
|
-
const parsed = parseSkillQualifier(rawName);
|
|
518
|
-
if (parsed.scope && opts.scope && parsed.scope !== opts.scope) {
|
|
519
|
-
throw usage(`scope conflict: identifier "${rawName}" uses scope "${parsed.scope}" but --scope is "${opts.scope}"`);
|
|
520
|
-
}
|
|
521
|
-
const effectiveScope = opts.scope ?? parsed.scope;
|
|
522
|
-
return resolveMemoryDocFromSources(rawName, opts, loadMemorySources(target, effectiveScope, opts.includeDescendants ?? false));
|
|
606
|
+
if (segments.length === 0)
|
|
607
|
+
return null;
|
|
608
|
+
return resolveNormalizedFile(historyDir, segments, '.jsonl');
|
|
523
609
|
}
|