@north-light/crouter 0.3.227 → 0.3.229
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/04-base-worker.md +1 -1
- package/dist/builtin-memory/insights/init.md +5 -5
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/agent-shaping.md +11 -11
- 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-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/create.d.ts +1 -1
- package/dist/commands/node/create.js +1 -1
- package/dist/commands/node/inspect.js +1 -1
- package/dist/commands/node/lifecycle.js +5 -5
- 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__/review-model-floor.test.js +12 -4
- 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/config.d.ts +3 -3
- package/dist/core/config.js +3 -3
- 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/launch.js +1 -1
- 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/dist/types.d.ts +8 -11
- package/dist/types.js +4 -31
- package/package.json +5 -4
- package/runtime.lock.json +2 -2
- package/dist/builtin-memory/05-kinds/design/00-base.md +0 -17
- package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +0 -15
- package/dist/builtin-memory/05-kinds/design/design-contract.md +0 -19
- package/dist/builtin-memory/05-kinds/developer/00-base.md +0 -17
- package/dist/builtin-memory/05-kinds/developer/01-orchestrator.md +0 -15
- package/dist/builtin-memory/05-kinds/plan/00-base.md +0 -17
- package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +0 -17
- package/dist/builtin-memory/05-kinds/plan/plan-contract.md +0 -28
- package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +0 -15
- package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +0 -15
- package/dist/builtin-memory/05-kinds/plan/reviewers/lens-contract.md +0 -13
- package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +0 -17
- package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +0 -17
- package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +0 -17
- package/dist/builtin-memory/05-kinds/spec/00-base.md +0 -17
- package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +0 -15
- package/dist/builtin-memory/05-kinds/spec/requirements.md +0 -15
- package/dist/builtin-memory/design/guide.md +0 -35
- package/dist/builtin-memory/design/roadmap.md +0 -21
- package/dist/builtin-memory/development.md +0 -113
- package/dist/builtin-memory/plan/roadmap.md +0 -25
- package/dist/builtin-memory/spec/guide.md +0 -53
- package/dist/builtin-memory/spec/requirements.md +0 -29
- package/dist/builtin-memory/spec/roadmap.md +0 -36
- package/dist/builtin-memory/testing.md +0 -39
- /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
|
@@ -7,104 +7,165 @@ import { existsSync, readdirSync, realpathSync, rmdirSync, statSync } from 'node
|
|
|
7
7
|
import { dirname, join, resolve as resolvePath } from 'node:path';
|
|
8
8
|
import { stringify as yamlStringify, parse as yamlParse } from 'yaml';
|
|
9
9
|
import { stateBlock } from '../../core/help.js';
|
|
10
|
+
import { loadStoreMemoryDocs, openProjectMemoryStore, } from '../../core/memory-resolver.js';
|
|
11
|
+
import { canonicalSegments, physicalRelativePathFor, stripNamespace, validateExplicitLocalName, } from '../../core/memory/identity.js';
|
|
12
|
+
import { pathExists, realpathOrSelf } from '../../core/fs-utils.js';
|
|
10
13
|
import { usage } from '../../core/errors.js';
|
|
11
14
|
import { memoryExtensionValidationCatalog, } from '../../core/memory/extensions.js';
|
|
12
15
|
import { CRTR_DIR_NAME } from '../../types.js';
|
|
13
|
-
import { scopeMemoryDir, projectScopeRoot, ensureProjectScopeRoot, resetScopeCache, } from '../../core/scope.js';
|
|
16
|
+
import { NEUTRAL_PROJECT_MEMORY, scopeMemoryDir, projectScopeRoot, ensureProjectScopeRoot, resetScopeCache, } from '../../core/scope.js';
|
|
14
17
|
import { loadProfileManifest, profileMemoryDir } from '../../core/profiles/manifest.js';
|
|
15
18
|
import { memoryDir as nodeMemoryDir } from '../../core/runtime/memory.js';
|
|
16
19
|
import { SURFACE_EVENTS, SURFACE_RUNGS } from '../../core/substrate/schema.js';
|
|
20
|
+
import { exposureTarget, loadContextExposureState, registerExposure, saveContextExposureState, } from '../../core/substrate/injected-store.js';
|
|
17
21
|
// The two memory kinds — knowledge (consult: procedural playbooks + factual
|
|
18
22
|
// references merged) vs preference (behave: standing directives). Used as the
|
|
19
23
|
// `--kind` enum choices everywhere.
|
|
20
24
|
export const MEMORY_KINDS = ['knowledge', 'preference'];
|
|
21
|
-
// Scope choices for filtering
|
|
25
|
+
// Scope choices for READ-ONLY source filtering — every scope a target view
|
|
26
|
+
// mounts, builtin included. A scope narrows the source stack only: the query is
|
|
27
|
+
// still the full canonical name.
|
|
28
|
+
export const MEMORY_READ_SCOPES = ['user', 'project', 'profile', 'node', 'builtin'];
|
|
29
|
+
// Scope choices a mutation may target. builtin ships with the package and
|
|
30
|
+
// installed-plugin stores are managed by `crtr pkg`, so neither is writable.
|
|
22
31
|
// `node` is the this-node store, writable only inside a running node.
|
|
23
|
-
export const
|
|
32
|
+
export const MEMORY_WRITE_SCOPES = ['user', 'project', 'profile', 'node'];
|
|
33
|
+
// Retained for the leaves that have not yet split their read filter from their
|
|
34
|
+
// write target; each cuts over to the pair above in its own phase.
|
|
35
|
+
export const MEMORY_SCOPES = MEMORY_WRITE_SCOPES;
|
|
24
36
|
/** Scope sort weight matching resolution precedence (node > project stack >
|
|
25
37
|
* profile > user > builtin). Used by `list` for its "scope then kind then
|
|
26
38
|
* name" ordering. */
|
|
27
39
|
export function scopeRank(scope) {
|
|
28
40
|
return scope === 'node' ? -1 : scope === 'project' ? 0 : scope === 'profile' ? 1 : scope === 'user' ? 2 : 3;
|
|
29
41
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
real = realpathSync(abs);
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
/* fall back to the resolved path */
|
|
68
|
-
}
|
|
69
|
-
// The scaffold (mkdir of `.crouter/memory/`) happens at write time via the
|
|
70
|
-
// writer's ensureDir; drop the scope cache so this process's later
|
|
71
|
-
// resolves see the new root.
|
|
72
|
-
resetScopeCache();
|
|
73
|
-
return { scope: 'project', memoryDir: join(real, CRTR_DIR_NAME, 'memory') };
|
|
42
|
+
export function registerMemoryListingExposure(identity) {
|
|
43
|
+
const nodeId = process.env['CRTR_NODE_ID'];
|
|
44
|
+
if (nodeId === undefined || nodeId === '')
|
|
45
|
+
return;
|
|
46
|
+
const state = loadContextExposureState(nodeId);
|
|
47
|
+
registerExposure(exposureTarget(state, 'transcript'), identity, 'content');
|
|
48
|
+
saveContextExposureState(nodeId, state);
|
|
49
|
+
}
|
|
50
|
+
function rejectDirConflicts(input) {
|
|
51
|
+
if (input.scope !== undefined && input.scope !== '') {
|
|
52
|
+
throw usage(`--dir selects the project store itself and cannot combine with --scope ${input.scope}`, {
|
|
53
|
+
received: `--dir ${input.dir} --scope ${input.scope}`,
|
|
54
|
+
next: 'Drop --scope: --dir already pins the exact project store.',
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (input.profile !== undefined && input.profile !== '') {
|
|
58
|
+
throw usage('--profile only applies to --scope profile; --dir selects a project store', {
|
|
59
|
+
received: input.profile,
|
|
60
|
+
next: 'Drop --profile when using --dir.',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** The realpathed project owner directory `--dir` names. */
|
|
65
|
+
function ownerDirFromArg(dirArg) {
|
|
66
|
+
const abs = resolvePath(dirArg);
|
|
67
|
+
if (!existsSync(abs) || !statSync(abs).isDirectory()) {
|
|
68
|
+
throw usage(`--dir does not exist or is not a directory: ${dirArg}`, {
|
|
69
|
+
received: dirArg,
|
|
70
|
+
next: 'Pass an existing project directory.',
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
return realpathSync(abs);
|
|
74
75
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
scope = scopeArg;
|
|
76
|
+
catch {
|
|
77
|
+
return abs;
|
|
78
78
|
}
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
}
|
|
80
|
+
function parseScope(scopeArg, allowed) {
|
|
81
|
+
if (scopeArg === undefined || scopeArg === '')
|
|
82
|
+
return undefined;
|
|
83
|
+
if (!allowed.includes(scopeArg)) {
|
|
84
|
+
throw usage(`invalid --scope: ${scopeArg} (expected ${allowed.join('|')})`);
|
|
81
85
|
}
|
|
82
|
-
|
|
83
|
-
|
|
86
|
+
return scopeArg;
|
|
87
|
+
}
|
|
88
|
+
/** A store with no namespace of its own: user, profile, node. */
|
|
89
|
+
function nativeStoreDescriptor(scope, memoryDir) {
|
|
90
|
+
return {
|
|
91
|
+
scope,
|
|
92
|
+
storeRoot: memoryDir,
|
|
93
|
+
namespace: '',
|
|
94
|
+
mountStatus: pathExists(memoryDir) ? 'ready' : 'absent',
|
|
95
|
+
projectMemory: NEUTRAL_PROJECT_MEMORY,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/** Resolve the read selection: a scope filter, or the exact store `--dir`
|
|
99
|
+
* names. */
|
|
100
|
+
export function resolveReadSelector(input) {
|
|
101
|
+
if (input.dir !== undefined && input.dir !== '') {
|
|
102
|
+
rejectDirConflicts(input);
|
|
103
|
+
return { store: openProjectMemoryStore(ownerDirFromArg(input.dir)) };
|
|
104
|
+
}
|
|
105
|
+
if (input.profile !== undefined && input.profile !== '' && input.scope !== 'profile') {
|
|
106
|
+
throw usage(`--profile only applies to --scope profile (received scope: ${input.scope ?? 'none'})`, {
|
|
107
|
+
received: input.profile,
|
|
108
|
+
next: 'Drop --profile, or pass --scope profile to target a specific profile store.',
|
|
109
|
+
});
|
|
84
110
|
}
|
|
85
|
-
|
|
111
|
+
const scope = parseScope(input.scope, MEMORY_READ_SCOPES);
|
|
112
|
+
if (input.profile !== undefined && input.profile !== '') {
|
|
113
|
+
// Naming a profile selects THAT profile's store exactly; the ambient
|
|
114
|
+
// selection would otherwise answer for whichever profile this process runs
|
|
115
|
+
// under, silently ignoring the flag.
|
|
116
|
+
const { profileId } = loadProfileManifest(input.profile);
|
|
117
|
+
return { scope: 'profile', store: nativeStoreDescriptor('profile', profileMemoryDir(profileId)) };
|
|
118
|
+
}
|
|
119
|
+
return { ...(scope === undefined ? {} : { scope }), store: null };
|
|
120
|
+
}
|
|
121
|
+
/** Resolve the one store a mutation writes to. Default: project when a project
|
|
122
|
+
* scope exists for the cwd, else user. An explicit `--scope project` with no
|
|
123
|
+
* project scope yet scaffolds one (`ensureProjectScopeRoot`). `--scope
|
|
124
|
+
* profile` is NEVER a default — it requires a selected profile, from
|
|
125
|
+
* `--profile <id-or-name>` or else the process's `CRTR_PROFILE_ID`, resolved
|
|
126
|
+
* through the centralized `loadProfileManifest` (never a raw path join).
|
|
127
|
+
* `--scope node` targets the this-node store
|
|
128
|
+
* (`nodes/<CRTR_NODE_ID>/context/memory/`) and requires a running node. */
|
|
129
|
+
export function resolveWriteSelector(input) {
|
|
130
|
+
if (input.dir !== undefined && input.dir !== '') {
|
|
131
|
+
rejectDirConflicts(input);
|
|
132
|
+
const owner = ownerDirFromArg(input.dir);
|
|
133
|
+
// The scaffold (mkdir of `.crouter/memory/`) happens at write time via the
|
|
134
|
+
// writer's ensureDir; drop the scope cache so this process's later resolves
|
|
135
|
+
// see the new root.
|
|
136
|
+
resetScopeCache();
|
|
137
|
+
return {
|
|
138
|
+
scope: 'project',
|
|
139
|
+
memoryDir: join(owner, CRTR_DIR_NAME, 'memory'),
|
|
140
|
+
store: openProjectMemoryStore(owner),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const requested = parseScope(input.scope, MEMORY_WRITE_SCOPES);
|
|
144
|
+
const scope = requested ?? (projectScopeRoot() !== null ? 'project' : 'user');
|
|
145
|
+
if (input.profile !== undefined && input.profile !== '' && scope !== 'profile') {
|
|
86
146
|
throw usage(`--profile only applies to --scope profile (resolved scope: ${scope})`, {
|
|
87
|
-
received:
|
|
147
|
+
received: input.profile,
|
|
88
148
|
next: 'Drop --profile, or pass --scope profile to target a specific profile store.',
|
|
89
149
|
});
|
|
90
150
|
}
|
|
91
151
|
if (scope === 'node') {
|
|
92
|
-
// The this-node store:
|
|
93
|
-
//
|
|
94
|
-
// at boot, so a doc written here rides into this node's knowledge block.
|
|
152
|
+
// The this-node store: the same dir render.ts's nodeLocalDocs loads at
|
|
153
|
+
// boot, so a doc written here rides into this node's knowledge block.
|
|
95
154
|
const nodeId = process.env['CRTR_NODE_ID'] || '';
|
|
96
155
|
if (nodeId === '') {
|
|
97
156
|
throw usage('node scope requires a running node (CRTR_NODE_ID unset); rerun inside a node');
|
|
98
157
|
}
|
|
99
|
-
|
|
158
|
+
const memoryDir = nodeMemoryDir(nodeId);
|
|
159
|
+
return { scope, memoryDir, store: nativeStoreDescriptor(scope, memoryDir) };
|
|
100
160
|
}
|
|
101
161
|
if (scope === 'profile') {
|
|
102
|
-
const profileIdOrName =
|
|
162
|
+
const profileIdOrName = input.profile && input.profile !== '' ? input.profile : process.env['CRTR_PROFILE_ID'] || '';
|
|
103
163
|
if (profileIdOrName === '') {
|
|
104
164
|
throw usage('profile scope requires a selected profile; rerun inside a profiled node or pass --profile');
|
|
105
165
|
}
|
|
106
166
|
const { profileId } = loadProfileManifest(profileIdOrName);
|
|
107
|
-
|
|
167
|
+
const memoryDir = profileMemoryDir(profileId);
|
|
168
|
+
return { scope, memoryDir, store: nativeStoreDescriptor(scope, memoryDir) };
|
|
108
169
|
}
|
|
109
170
|
let memoryDir = scopeMemoryDir(scope);
|
|
110
171
|
if (!memoryDir && scope === 'project') {
|
|
@@ -113,17 +174,166 @@ export function resolveWriteTarget(scopeArg, profileArg, dirArg) {
|
|
|
113
174
|
}
|
|
114
175
|
if (!memoryDir)
|
|
115
176
|
throw usage(`no ${scope} scope available for writing memory documents`);
|
|
177
|
+
if (scope === 'project') {
|
|
178
|
+
return { scope, memoryDir, store: openProjectMemoryStore(dirname(dirname(memoryDir))) };
|
|
179
|
+
}
|
|
180
|
+
return { scope, memoryDir, store: nativeStoreDescriptor(scope, memoryDir) };
|
|
181
|
+
}
|
|
182
|
+
/** The scope + memory dir a write lands in. Retained for the leaves that still
|
|
183
|
+
* place documents by path alone; they cut over to `resolveWriteSelector` and
|
|
184
|
+
* `planNewDocumentPlacement` in their own phases. */
|
|
185
|
+
export function resolveWriteTarget(scopeArg, profileArg, dirArg) {
|
|
186
|
+
const { scope, memoryDir } = resolveWriteSelector({ scope: scopeArg, profile: profileArg, dir: dirArg });
|
|
116
187
|
return { scope, memoryDir };
|
|
117
188
|
}
|
|
189
|
+
/** Refuse a project store the runtime will not resolve through. A project
|
|
190
|
+
* repository-root store carries the identity every canonical name in it composes under, so
|
|
191
|
+
* without a valid declaration no name can be validated, stripped, or placed;
|
|
192
|
+
* user, profile, and node stores have no declaration and always pass, even
|
|
193
|
+
* before their directory exists. */
|
|
194
|
+
export function requireMountedStore(store) {
|
|
195
|
+
if (store.scope !== 'project' || store.mountStatus === 'ready')
|
|
196
|
+
return store;
|
|
197
|
+
const owner = store.ownerDir ?? store.storeRoot;
|
|
198
|
+
if (store.mountStatus === 'absent') {
|
|
199
|
+
throw usage(`no memory store at ${store.storeRoot}`, {
|
|
200
|
+
next: `Initialize it with \`crtr memory write <namespace> --dir ${owner}\`, which declares the repository namespace.`,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
const repositoryRoot = store.repositoryRoot ?? owner;
|
|
204
|
+
throw usage(store.diagnostic ?? `project memory store at ${store.storeRoot} does not declare a namespace`, {
|
|
205
|
+
next: `Run \`crtr sys migrate --dir ${repositoryRoot}\`.`,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
// Canonical name → physical placement
|
|
210
|
+
//
|
|
211
|
+
// Every surface takes the FULL canonical name. Placement strips the selected
|
|
212
|
+
// store's namespace off it, checks that neither the identity nor either
|
|
213
|
+
// physical representation of the local name is already taken, and picks the
|
|
214
|
+
// form the new file takes.
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
/** Normalize and validate a full canonical name: ordering prefixes are
|
|
217
|
+
* physical-path-only, a trailing `INDEX` is never an address, and the segment
|
|
218
|
+
* charset is fixed. */
|
|
219
|
+
export function requireCanonicalName(rawName) {
|
|
220
|
+
const name = canonicalSegments(rawName.trim()).join('/');
|
|
221
|
+
if (name === '')
|
|
222
|
+
throw usage('memory document name required');
|
|
223
|
+
const valid = validateExplicitLocalName(name);
|
|
224
|
+
if (!valid.ok) {
|
|
225
|
+
throw usage(`invalid memory document name \`${rawName}\`: ${valid.reason}`, {
|
|
226
|
+
received: rawName,
|
|
227
|
+
next: 'Pass the full canonical name (a project document is <namespace>/<local name>); run `crtr memory find <leaf>` to look one up.',
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
return name;
|
|
231
|
+
}
|
|
232
|
+
/** The store-local name a full canonical name has inside one exact store, or a
|
|
233
|
+
* usage error when it falls outside that store's namespace. `''` names the
|
|
234
|
+
* store's own root document — a project store answers at its namespace. */
|
|
235
|
+
export function requireLocalNameInStore(store, rawName) {
|
|
236
|
+
requireMountedStore(store);
|
|
237
|
+
const name = requireCanonicalName(rawName);
|
|
238
|
+
if (store.namespace === '')
|
|
239
|
+
return name;
|
|
240
|
+
const local = stripNamespace(name, store.namespace);
|
|
241
|
+
if (local === null) {
|
|
242
|
+
throw usage(`\`${name}\` is not in the namespace of the store at ${store.storeRoot} (\`${store.namespace}\`)`, {
|
|
243
|
+
memory: name,
|
|
244
|
+
next: `Every document there answers at \`${store.namespace}/<local name>\`; the store's own document answers at \`${store.namespace}\`.`,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
return local;
|
|
248
|
+
}
|
|
249
|
+
/** Where a NEW document (a `write`, a `move` destination) physically lands in
|
|
250
|
+
* one exact store, after proving the canonical identity is free.
|
|
251
|
+
*
|
|
252
|
+
* Both physical forms carry the same identity, so occupancy is checked against
|
|
253
|
+
* the loaded identities AND both `<local>.md` and `<local>/INDEX.md` — a file
|
|
254
|
+
* whose explicit `name:` points elsewhere still owns its path. The chosen form
|
|
255
|
+
* is `<local>/INDEX.md` when the canonical directory already has descendants
|
|
256
|
+
* in this store (the document fronts a directory that exists), else
|
|
257
|
+
* `<local>.md`; descendants created later never relocate it, because either
|
|
258
|
+
* form validly owns the identity. Pass `storeDocs` when the caller already
|
|
259
|
+
* loaded the store, and `source` when planning a move, whose own identity and
|
|
260
|
+
* file are being vacated. */
|
|
261
|
+
export function planNewDocumentPlacement(store, rawName, storeDocs, source) {
|
|
262
|
+
const localName = requireLocalNameInStore(store, rawName);
|
|
263
|
+
const canonicalName = requireCanonicalName(rawName);
|
|
264
|
+
const docs = storeDocs ?? loadStoreMemoryDocs(store, true);
|
|
265
|
+
// A move's own source occupies neither the identity nor the path it is
|
|
266
|
+
// vacating: retiring an explicit `name:` pin back to the file's path-derived
|
|
267
|
+
// identity lands the document exactly where it already sits.
|
|
268
|
+
const sourceReal = source === undefined ? null : realpathOrSelf(source.path);
|
|
269
|
+
const owner = docs.find((d) => d.name === canonicalName && realpathOrSelf(d.path) !== sourceReal);
|
|
270
|
+
if (owner !== undefined) {
|
|
271
|
+
throw usage(`${canonicalName} already exists in ${store.storeRoot}`, {
|
|
272
|
+
memory: canonicalName,
|
|
273
|
+
path: owner.path,
|
|
274
|
+
next: `Revise it with \`crtr memory edit ${canonicalName}\`, which records the change, or move it with \`crtr memory move\`.`,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
const forms = localName === '' ? ['directory-index'] : ['directory-index', 'leaf-file'];
|
|
278
|
+
const taken = forms
|
|
279
|
+
.map((representation) => join(store.storeRoot, physicalRelativePathFor(localName, representation)))
|
|
280
|
+
.filter((path) => pathExists(path) && realpathOrSelf(path) !== sourceReal);
|
|
281
|
+
if (taken.length > 0) {
|
|
282
|
+
throw usage(`${canonicalName} cannot be created: ${taken.join(' and ')} already exists`, {
|
|
283
|
+
memory: canonicalName,
|
|
284
|
+
path: taken.join(', '),
|
|
285
|
+
next: 'Both `<name>.md` and `<name>/INDEX.md` carry that identity. Read the existing file, then edit, move, or delete it.',
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
const representation = localName === '' || docs.some((d) => d.localName.startsWith(`${localName}/`)) ? 'directory-index' : 'leaf-file';
|
|
289
|
+
const physicalRelativePath = physicalRelativePathFor(localName, representation);
|
|
290
|
+
return {
|
|
291
|
+
store,
|
|
292
|
+
canonicalName,
|
|
293
|
+
localName,
|
|
294
|
+
representation,
|
|
295
|
+
physicalRelativePath,
|
|
296
|
+
path: join(store.storeRoot, physicalRelativePath),
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
/** The document a mutation acts on, refusing an identity two files in ONE
|
|
300
|
+
* store both own. Reads stay answerable (`<name>/INDEX.md` deterministically
|
|
301
|
+
* wins), but a mutation would silently pick one of two files, so it stops with
|
|
302
|
+
* both paths. Candidates in OTHER stores are ordinary alternates — precedence
|
|
303
|
+
* picked the winner and `--dir` reaches the rest. */
|
|
304
|
+
export function requireUnambiguousDoc(set) {
|
|
305
|
+
const winner = set.winner;
|
|
306
|
+
const sameStore = set.candidates.filter((d) => d.store.storeRoot === winner.store.storeRoot);
|
|
307
|
+
if (sameStore.length > 1) {
|
|
308
|
+
throw usage(`${set.name} is owned by ${sameStore.length} files in one store: ${sameStore.map((d) => d.path).join(' and ')}`, {
|
|
309
|
+
memory: set.name,
|
|
310
|
+
path: sameStore.map((d) => d.path).join(', '),
|
|
311
|
+
next: `Recover the collision with \`crtr memory move ${set.name} --to <new-free-name>\`; it selects the first physical path in stable lexical order. Do not remove files by hand; \`crtr memory lint\` reports every such collision.`,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
return winner;
|
|
315
|
+
}
|
|
316
|
+
export function selectMutationDoc(set) {
|
|
317
|
+
const sameStore = set.candidates.filter((d) => d.store.storeRoot === set.winner.store.storeRoot);
|
|
318
|
+
const ordered = [...sameStore].sort((a, b) => {
|
|
319
|
+
const byRelativePath = a.physicalRelativePath.localeCompare(b.physicalRelativePath);
|
|
320
|
+
return byRelativePath !== 0 ? byRelativePath : a.path.localeCompare(b.path);
|
|
321
|
+
});
|
|
322
|
+
return {
|
|
323
|
+
doc: ordered[0] ?? set.winner,
|
|
324
|
+
collisionPaths: ordered.length > 1 ? ordered.map((d) => d.path) : [],
|
|
325
|
+
};
|
|
326
|
+
}
|
|
118
327
|
/** Remove a just-removed file's now-empty parent directories up to (never
|
|
119
|
-
* including) its
|
|
120
|
-
* prefix does not orphan an empty directory.
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* `.history/<
|
|
124
|
-
|
|
328
|
+
* including) its store root, so removing the last entry under an `area/`
|
|
329
|
+
* prefix does not orphan an empty directory. `pathSegments` counts the
|
|
330
|
+
* PHYSICAL store-relative path (`doc.physicalRelativePath.split('/').length`),
|
|
331
|
+
* never the canonical name, which carries the store's namespace on top of it —
|
|
332
|
+
* the same count addresses a `.history/<path>.jsonl` sidecar. Stops at the
|
|
333
|
+
* first non-empty ancestor. */
|
|
334
|
+
export function pruneEmptyParents(filePath, pathSegments) {
|
|
125
335
|
let treeRoot = filePath;
|
|
126
|
-
for (let i = 0; i <
|
|
336
|
+
for (let i = 0; i < pathSegments; i += 1)
|
|
127
337
|
treeRoot = dirname(treeRoot);
|
|
128
338
|
let dir = dirname(filePath);
|
|
129
339
|
while (dir !== treeRoot && dir.startsWith(treeRoot) && existsSync(dir) && readdirSync(dir).length === 0) {
|
|
@@ -131,8 +341,10 @@ export function pruneEmptyParents(filePath, nameSegments) {
|
|
|
131
341
|
dir = dirname(dir);
|
|
132
342
|
}
|
|
133
343
|
}
|
|
134
|
-
/** Map a
|
|
135
|
-
* memory dir, guarding against traversal/absolute escapes.
|
|
344
|
+
/** Map a STORE-LOCAL name (`topic`, `area/topic`, `INDEX`) to its file path
|
|
345
|
+
* under a memory dir, guarding against traversal/absolute escapes. Physical
|
|
346
|
+
* mapping only: it composes no namespace and collapses no `INDEX`, so a
|
|
347
|
+
* canonical name reaches it through `planNewDocumentPlacement`. */
|
|
136
348
|
export function memoryFilePath(memoryDir, name) {
|
|
137
349
|
const segments = name.split('/').filter((s) => s.length > 0);
|
|
138
350
|
if (segments.length === 0)
|
|
@@ -503,12 +715,56 @@ export function applyExtensionChanges(frontmatter, sets, unsets) {
|
|
|
503
715
|
/** The frontmatter-overlay flags, keyed by flag name, carrying the prose true
|
|
504
716
|
* on BOTH leaves. `write` appends its creation clauses with `withConstraint`;
|
|
505
717
|
* neither leaf restates the other's rules. */
|
|
718
|
+
/** The selector flags, keyed by flag name, carrying the prose true on every
|
|
719
|
+
* leaf that accepts them. A leaf appends its own clause with `selectorParam`;
|
|
720
|
+
* none of them restates the shared rules. */
|
|
721
|
+
export const SELECTOR_PARAMS = {
|
|
722
|
+
'scope-read': {
|
|
723
|
+
kind: 'flag',
|
|
724
|
+
name: 'scope',
|
|
725
|
+
type: 'enum',
|
|
726
|
+
choices: [...MEMORY_READ_SCOPES],
|
|
727
|
+
required: false,
|
|
728
|
+
constraint: 'Restrict resolution to one source class. A source filter only: the name stays the full canonical name, and project scope keeps its nearest-first precedence. Conflicts with --dir, which selects one exact store instead.',
|
|
729
|
+
},
|
|
730
|
+
'scope-write': {
|
|
731
|
+
kind: 'flag',
|
|
732
|
+
name: 'scope',
|
|
733
|
+
type: 'enum',
|
|
734
|
+
choices: [...MEMORY_WRITE_SCOPES],
|
|
735
|
+
required: false,
|
|
736
|
+
constraint: 'Store to write to (builtin documents ship with the package and installed-plugin documents are managed by `crtr pkg`, so neither is writable). Default: project when cwd sits in a project, else user. `project` resolves to the NEAREST ancestor `.crouter/` walking up from cwd — in a nested workspace that can be a parent’s store, not the dir you are standing in; pass --dir to pin the exact project. `profile` requires a selected profile (CRTR_PROFILE_ID) or an explicit --profile. `node` writes the this-node store (`nodes/<CRTR_NODE_ID>/context/memory/`), the nearest scope, seen only by this running node. Conflicts with --dir.',
|
|
737
|
+
},
|
|
738
|
+
'dir': {
|
|
739
|
+
kind: 'flag',
|
|
740
|
+
name: 'dir',
|
|
741
|
+
type: 'string',
|
|
742
|
+
required: false,
|
|
743
|
+
constraint: 'Exact project directory to operate in — selects `<dir>/.crouter/memory/` by realpath, regardless of cwd, ancestor stores, or which store a target view would pick. THE way to reach one specific project store: another project in your profile’s purview, a store shadowed by an ancestor, or a non-winning duplicate such as a linked worktree that declares the same namespace as its main checkout. The name is still the FULL canonical name — the repository namespace plus that store’s path under the repository root, plus the store-local name. It selects the store itself, so it conflicts with any explicit --scope and with --profile.',
|
|
744
|
+
},
|
|
745
|
+
'profile': {
|
|
746
|
+
kind: 'flag',
|
|
747
|
+
name: 'profile',
|
|
748
|
+
type: 'string',
|
|
749
|
+
required: false,
|
|
750
|
+
constraint: 'Profile id or name to target, for --scope profile. Names that profile’s store exactly, whichever profile this process runs under. Default: the process CRTR_PROFILE_ID (the node’s selected profile). Resolved through the same profile lookup as `crtr profile show`. Rejected (usage error) unless --scope profile is also selected — never silently ignored for another scope — and rejected with --dir.',
|
|
751
|
+
},
|
|
752
|
+
};
|
|
753
|
+
/** One selector flag, with leaf-specific prose appended to the shared
|
|
754
|
+
* constraint and any leaf-specific schema override applied. */
|
|
755
|
+
export function selectorParam(name, overrides = {}, extraConstraint) {
|
|
756
|
+
const base = SELECTOR_PARAMS[name];
|
|
757
|
+
if (base === undefined)
|
|
758
|
+
throw new Error(`unknown selector param: ${name}`);
|
|
759
|
+
const constraint = extraConstraint === undefined ? base.constraint : `${base.constraint} ${extraConstraint}`;
|
|
760
|
+
return { ...base, ...overrides, constraint };
|
|
761
|
+
}
|
|
506
762
|
export const FRONTMATTER_OVERLAY_PARAMS = {
|
|
507
763
|
'kind': { kind: 'flag', name: 'kind', type: 'enum', choices: [...MEMORY_KINDS], required: false, constraint: 'Document kind.' },
|
|
508
764
|
'when-and-why-to-read': { kind: 'flag', name: 'when-and-why-to-read', type: 'string', required: false, constraint: 'ONE routing sentence: "When <circumstance>, this <kind> should be read because <broader downstream payoff>." WHY is the reader\u2019s payoff \u2014 the consequence they secure for their task by reading \u2014 NEVER the doc summary, its rule, or that rule reworded as an outcome (a benefit-shaped restatement still fails). Rendered verbatim as the preview.' },
|
|
509
765
|
'short-form': { kind: 'flag', name: 'short-form', type: 'string', required: false, constraint: 'Frontmatter short-form \u2014 a very abbreviated version of the content, the hook shown in `crtr memory list`.' },
|
|
510
766
|
'unlisted': { kind: 'flag', name: 'unlisted', type: 'bool', required: false, default: false, constraint: 'Suppress this doc from directory listings. Suppression only — explicit reads, [[links]], and surfaces entries still work.' },
|
|
511
|
-
'surface': { kind: 'flag', name: 'surface', type: 'string', required: false, repeatable: true, constraint: 'One routing entry per occurrence, as a YAML/JSON object `{on, at, match?, match-frontmatter?, gate?}`; the flag set replaces the document’s whole surfaces list. `on` is boot|workspace-open|read|memory-read|command; `at` is name|preview|content. `match` holds the event’s globs — required on read/memory-read/command (a read entry may carry `match-frontmatter`, a predicate over the read file’s own frontmatter, instead), meaningless on boot/workspace-open. Optional entry `gate` is a node-config predicate using the same vocabulary as the document gate; its event constraints and gate both match before it participates. A `./`-anchored glob is relative: for `read` to the store’s owning repo dir, for `memory-read` to
|
|
767
|
+
'surface': { kind: 'flag', name: 'surface', type: 'string', required: false, repeatable: true, constraint: 'One routing entry per occurrence, as a YAML/JSON object `{on, at, match?, match-frontmatter?, gate?}`; the flag set replaces the document’s whole surfaces list. `on` is boot|workspace-open|read|memory-read|command; `at` is name|preview|content. `match` holds the event’s globs — required on read/memory-read/command (a read entry may carry `match-frontmatter`, a predicate over the read file’s own frontmatter, instead), meaningless on boot/workspace-open. Optional entry `gate` is a node-config predicate using the same vocabulary as the document gate; its event constraints and gate both match before it participates. A `./`-anchored glob is relative: for `read` to the store’s owning repo dir, for `memory-read` to this doc’s routing anchor — its own canonical name when the doc is its directory’s document (`<dir>/INDEX.md`), otherwise the canonical directory it sits in. Participating entries fold to their highest `at`; there is no cross-entry deny precedence.' },
|
|
512
768
|
'gate': { kind: 'flag', name: 'gate', type: 'string', required: false, constraint: 'Frontmatter gate \u2014 YAML/JSON object predicate over node config using the same field/matcher vocabulary described in the guide.' },
|
|
513
769
|
'slash': { kind: 'flag', name: 'slash', type: 'bool', required: false, default: false, constraint: 'Presence flags this doc invocable as a pi slash command (`/<name>`, `/` in a nested name rendered as `:`) \u2014 the doc body becomes the command\u2019s injected prompt. Default false: most docs are consulted, not invoked.' },
|
|
514
770
|
};
|
|
@@ -526,7 +782,8 @@ export function overlayParam(name, overrides = {}, extraConstraint) {
|
|
|
526
782
|
* `--doc-rationale`, because `--rationale` there means why THIS REVISION is
|
|
527
783
|
* happening. Same field, same prose, two flag names that cannot be confused. */
|
|
528
784
|
export const DOC_RATIONALE_CONSTRAINT = 'Frontmatter rationale \u2014 the observed agent failure that made this doc necessary. Maintainer-facing only: never ships in any delivered surface (boot render, on-read injection, `memory read` content), visible only via `memory read --frontmatter`. Omitting the flag preserves an existing rationale unchanged.';
|
|
529
|
-
export const GUIDE_SURFACES = 'Every doc appears in its directory’s listing by default (suppress with --unlisted); everything beyond the listing is explicit `surfaces` routing — entries declaring WHEN the doc delivers and how much. Events: `boot` delivers in the boot catalog every agent sees; `workspace-open` delivers in first-message context when cwd/profile mounts the doc’s project store (project stores only); `read` fires when the agent reads a matching file (globs vs the file’s absolute path and basename, `./`-anchored globs vs its path relative to the store’s owning repo dir, `match-frontmatter` predicates over the read file’s own YAML frontmatter); `memory-read` fires when another memory doc is read (globs vs that doc’s canonical name, `./` anchored to this doc’s own name directory); `command` fires when a matching shell command runs (globs vs the whole command string, `*` crossing `/`). An entry may also carry `gate`, a node-config predicate using the same vocabulary as the document-level `gate`: event constraints and entry gate both must match, then all participating entries fold to the highest `at` (`content` > `preview` > `name`), with no cross-entry deny precedence. The document-level `gate` remains the hard eligibility check: when it fails, no automatic surface can deliver the document. `at` sets how much delivers: `name` the bare tag, `preview` the routing line, `content` the whole body. Explicit `memory read` and listings remain deliberate access and ignore gates/rungs; only companion docs routed by their `memory-read` entries use entry gates. Each entry is paid by every future agent it fires on, so default down: no surfaces at all is correct for reference depth reached through listings and [[links]], and when a doc fits in a single sentence, deliver `content` directly — a `preview` routing line would run longer than the doc itself. Sentence-length `content` docs are correct; never pad a memory to be more verbose than the rule or fact it carries.';
|
|
785
|
+
export const GUIDE_SURFACES = 'Every doc appears in its directory’s listing by default (suppress with --unlisted); everything beyond the listing is explicit `surfaces` routing — entries declaring WHEN the doc delivers and how much. Events: `boot` delivers in the boot catalog every agent sees; `workspace-open` delivers in first-message context when cwd/profile mounts the doc’s project store (project stores only); `read` fires when the agent reads a matching file (globs vs the file’s absolute path and basename, `./`-anchored globs vs its path relative to the store’s owning repo dir, `match-frontmatter` predicates over the read file’s own YAML frontmatter); `memory-read` fires when another memory doc is read (globs vs that doc’s canonical name, `./` anchored to this doc’s routing anchor — its own canonical name when it is its directory’s document, otherwise the canonical directory it sits in); `command` fires when a matching shell command runs (globs vs the whole command string, `*` crossing `/`). An entry may also carry `gate`, a node-config predicate using the same vocabulary as the document-level `gate`: event constraints and entry gate both must match, then all participating entries fold to the highest `at` (`content` > `preview` > `name`), with no cross-entry deny precedence. The document-level `gate` remains the hard eligibility check: when it fails, no automatic surface can deliver the document. `at` sets how much delivers: `name` the bare tag, `preview` the routing line, `content` the whole body. Explicit `memory read` and listings remain deliberate access and ignore gates/rungs; only companion docs routed by their `memory-read` entries use entry gates. Each entry is paid by every future agent it fires on, so default down: no surfaces at all is correct for reference depth reached through listings and [[links]], and when a doc fits in a single sentence, deliver `content` directly — a `preview` routing line would run longer than the doc itself. Sentence-length `content` docs are correct; never pad a memory to be more verbose than the rule or fact it carries.';
|
|
530
786
|
export const GUIDE_ROUTING_LINE = 'The routing line (--when-and-why-to-read) is the only text an agent reads before deciding to load the doc. The test for its when-clause: it names an observable circumstance in the reader\u2019s current work, not the doc\u2019s topic reworded as an activity. If the WHEN can be inferred from the title alone, it is not a real trigger. Bad on a todo list: "When planning or prioritizing work across this profile." Good: "When the user mentions something from their todos, or asks what is still outstanding across this profile." The test for its because-clause: if it can be derived by paraphrasing the doc\u2019s advice, it is a restatement, not a payoff \u2014 a real payoff names a consequence in the reader\u2019s world that the document itself never asserts. Bad: "because only genuine first principles belong in taste memory." Bad: "because keeping the test loop fast and free of speculative tests protects the development pace" \u2014 the doc\u2019s rule as an outcome, derivable straight from its advice. Good: "because identifying throughlines in user taste lets future decisions be made faster and with less re-litigation." Someone mid-task who has not read the doc must be able to decide from this line alone whether the read is worth it. If you cannot name the concrete situation that triggers it, you do not yet understand the memory \u2014 ask the user one sharp question instead of improvising.';
|
|
531
787
|
export const GUIDE_PREDICATE_VOCABULARY = 'Document gates, surface-entry gates, and match-frontmatter share the same predicate language: a field map is AND-ed across fields; dotted fields resolve nested values; field matchers may be scalar, array, or object. Scalar matchers do exact equality, with arrays matching any element. Array matchers do membership or intersection. Object matchers accept `eq`, `ne`, `in`, `nin`, `exists`, `contains`, `containsAll`, `containsAny`, `matches`, `imatches`, `gt`, `gte`, `lt`, and `lte`. Combinators are `all`, `any`, and `not`; sibling field matchers next to them are AND-ed in. An empty condition is inert. An unknown op never matches.';
|
|
532
|
-
export const GUIDE_DOC_LINKS = 'Link related docs with `[[canonical/name]]`. A doc link in any body is a first-class cross-reference to another memory document by its exact canonical name \u2014 the same identifier `crtr memory read` takes. A bare directory name is a valid link too: following it returns that directory\u2019s
|
|
788
|
+
export const GUIDE_DOC_LINKS = 'Link related docs with `[[canonical/name]]`. A doc link in any body is a first-class cross-reference to another memory document by its exact canonical name \u2014 the same identifier `crtr memory read` takes, namespace included for a project document. A bare directory name is a valid link too: following it returns that directory\u2019s own document when it has one, plus the directory listing. Links are pointers, never transclusion: the linked body is loaded only when a reader follows it, so a link costs nothing until needed. `crtr memory lint` fails on a link that resolves to no document or directory, and `crtr memory read` lists a doc\u2019s resolvable links alongside its body. There is no alias, label, or old-name form \u2014 a renamed target needs its links rewritten.';
|
|
789
|
+
export const GUIDE_CANONICAL_NAMES = 'A document has exactly one address: its canonical name, the store\u2019s namespace composed with the document\u2019s store-local name. A repository declares its namespace once, as `namespace:` on the root `INDEX.md` of `<repository root>/.crouter/memory/`; a store nested inside that repository composes under the repository namespace plus its own directory path relative to the repository root, and declares nothing itself. An installed plugin\u2019s store mounts under the plugin\u2019s own name; user, profile, node, and builtin stores have no namespace, so their local names are already canonical. A local doc `unit-tests` in a store that declares `namespace: acme/core` therefore answers only at `acme/core/unit-tests`, and the store\u2019s root document answers at `acme/core`; a store at `packages/api/.crouter/memory/` in a repository declaring `namespace: acme` answers under `acme/packages/api`. A non-root explicit `name:` in frontmatter is store-LOCAL \u2014 it never carries the namespace, and composition is unconditional, so a name that merely looks prefixed is prefixed again. The namespace itself is immutable through the memory commands: `crtr memory write <namespace> --dir <repository root>` declares it once in an empty repository-root store, `crtr sys migrate --dir <repository root>` converts a legacy one, and nothing else rewrites it. A trailing `INDEX` is never part of an address in any store: `area/INDEX.md` is the document attached to directory `area`, so it answers at `area` and reading `area` returns that body together with the directory\u2019s immediate listing. Resolution is exact \u2014 a file path, a bare leaf, a `<scope>/<name>` spelling, and a trailing `/INDEX` all fail \u2014 so run `crtr memory find <leaf>` when you do not know the canonical name.';
|