@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
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// Repository-root project-memory identity: real Git and non-Git fixtures prove
|
|
2
|
+
// that declarations live at a repository root while every nested store derives
|
|
3
|
+
// its effective prefix from its physical owner path.
|
|
4
|
+
//
|
|
5
|
+
// Run: node --conditions=crtr-src --import tsx/esm --test src/core/__tests__/repository-root-identity.test.ts
|
|
6
|
+
import { after, before, beforeEach, test } from 'node:test';
|
|
7
|
+
import assert from 'node:assert/strict';
|
|
8
|
+
import { mkdtempSync, mkdirSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import { tmpdir } from 'node:os';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { gitSync } from '../git.js';
|
|
12
|
+
import { createProfile } from '../profiles/manifest.js';
|
|
13
|
+
import { loadMemoryStoreView, memoryStoresInPrecedence, openProjectMemoryStore, resolveMemoryDocForTarget, } from '../memory-resolver.js';
|
|
14
|
+
import { resetScopeCache } from '../scope.js';
|
|
15
|
+
const cleanups = [];
|
|
16
|
+
let previousHome;
|
|
17
|
+
before(() => {
|
|
18
|
+
previousHome = process.env.HOME;
|
|
19
|
+
});
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
process.env.HOME = fixtureDir('crtr-repository-root-identity-home-');
|
|
22
|
+
resetScopeCache();
|
|
23
|
+
});
|
|
24
|
+
after(() => {
|
|
25
|
+
resetScopeCache();
|
|
26
|
+
for (const path of cleanups)
|
|
27
|
+
rmSync(path, { recursive: true, force: true });
|
|
28
|
+
if (previousHome === undefined)
|
|
29
|
+
delete process.env.HOME;
|
|
30
|
+
else
|
|
31
|
+
process.env.HOME = previousHome;
|
|
32
|
+
});
|
|
33
|
+
function fixtureDir(prefix) {
|
|
34
|
+
const path = realpathSync(mkdtempSync(join(tmpdir(), prefix)));
|
|
35
|
+
cleanups.push(path);
|
|
36
|
+
return path;
|
|
37
|
+
}
|
|
38
|
+
function writeDoc(path, body, frontmatter) {
|
|
39
|
+
mkdirSync(join(path, '..'), { recursive: true });
|
|
40
|
+
const yaml = frontmatter === undefined ? '' : `---\n${frontmatter}\n---\n`;
|
|
41
|
+
writeFileSync(path, `${yaml}${body}\n`, 'utf8');
|
|
42
|
+
}
|
|
43
|
+
function writeStore(owner, namespace) {
|
|
44
|
+
const frontmatter = namespace === undefined ? undefined : `namespace: ${namespace}`;
|
|
45
|
+
writeDoc(join(owner, '.crouter', 'memory', 'INDEX.md'), 'STORE INDEX', frontmatter);
|
|
46
|
+
}
|
|
47
|
+
function initGit(repository) {
|
|
48
|
+
mkdirSync(repository, { recursive: true });
|
|
49
|
+
assert.equal(gitSync(['-C', repository, 'init', '-q'], undefined).status, 0, 'git init succeeds');
|
|
50
|
+
}
|
|
51
|
+
function commitInitial(repository) {
|
|
52
|
+
writeFileSync(join(repository, 'README.md'), 'fixture\n', 'utf8');
|
|
53
|
+
assert.equal(gitSync(['-C', repository, 'add', 'README.md'], undefined).status, 0);
|
|
54
|
+
assert.equal(gitSync(['-C', repository, '-c', 'user.name=Fixture', '-c', 'user.email=fixture@example.com', 'commit', '-qm', 'initial'], undefined).status, 0);
|
|
55
|
+
}
|
|
56
|
+
function target(cwd, profileId = null) {
|
|
57
|
+
return { cwd, profileId, nodeId: null };
|
|
58
|
+
}
|
|
59
|
+
test('a Git root declaration gives a metadata-free nested store its repository-relative prefix', () => {
|
|
60
|
+
const repository = join(process.env.HOME, 'repository');
|
|
61
|
+
const nested = join(repository, 'packages', 'agent');
|
|
62
|
+
initGit(repository);
|
|
63
|
+
writeStore(repository, 'acme');
|
|
64
|
+
mkdirSync(join(nested, '.crouter', 'memory'), { recursive: true });
|
|
65
|
+
writeDoc(join(nested, '.crouter', 'memory', 'guide.md'), 'NESTED GUIDE');
|
|
66
|
+
const doc = resolveMemoryDocForTarget('acme/packages/agent/guide', target(nested), { scope: 'project' });
|
|
67
|
+
assert.equal(doc.path, join(nested, '.crouter', 'memory', 'guide.md'));
|
|
68
|
+
assert.equal(doc.name, 'acme/packages/agent/guide');
|
|
69
|
+
const store = openProjectMemoryStore(nested);
|
|
70
|
+
assert.equal(store.mountStatus, 'ready');
|
|
71
|
+
assert.equal(store.repositoryRoot, repository);
|
|
72
|
+
assert.equal(store.ownerRelativePath, 'packages/agent');
|
|
73
|
+
assert.equal(store.namespacePath, join(repository, '.crouter', 'memory', 'INDEX.md'));
|
|
74
|
+
});
|
|
75
|
+
test('repository-relative ordering prefixes are stripped before nested identity composition', () => {
|
|
76
|
+
const repository = join(process.env.HOME, 'repository');
|
|
77
|
+
const nested = join(repository, '01-api');
|
|
78
|
+
initGit(repository);
|
|
79
|
+
writeStore(repository, 'acme');
|
|
80
|
+
writeStore(nested);
|
|
81
|
+
writeDoc(join(nested, '.crouter', 'memory', 'guide.md'), 'ORDERED GUIDE');
|
|
82
|
+
const store = openProjectMemoryStore(nested);
|
|
83
|
+
assert.equal(store.mountStatus, 'ready');
|
|
84
|
+
assert.equal(store.ownerRelativePath, 'api');
|
|
85
|
+
assert.equal(store.namespace, 'acme/api');
|
|
86
|
+
const doc = resolveMemoryDocForTarget('acme/api/guide', target(nested), { scope: 'project' });
|
|
87
|
+
assert.equal(doc.path, join(nested, '.crouter', 'memory', 'guide.md'));
|
|
88
|
+
assert.equal(doc.name, 'acme/api/guide');
|
|
89
|
+
});
|
|
90
|
+
test('a metadata-free nested INDEX answers exactly at the nested effective prefix', () => {
|
|
91
|
+
const repository = join(process.env.HOME, 'repository');
|
|
92
|
+
const nested = join(repository, 'packages', 'agent');
|
|
93
|
+
initGit(repository);
|
|
94
|
+
writeStore(repository, 'acme');
|
|
95
|
+
writeStore(nested);
|
|
96
|
+
writeDoc(join(nested, '.crouter', 'memory', 'guide.md'), 'NESTED GUIDE');
|
|
97
|
+
const store = openProjectMemoryStore(nested);
|
|
98
|
+
assert.equal(store.mountStatus, 'ready');
|
|
99
|
+
assert.equal(store.namespace, 'acme/packages/agent');
|
|
100
|
+
const view = loadMemoryStoreView(store, true);
|
|
101
|
+
assert.deepEqual(view.docs.map((doc) => doc.name), ['acme/packages/agent', 'acme/packages/agent/guide']);
|
|
102
|
+
assert.equal(resolveMemoryDocForTarget('acme/packages/agent', target(nested), { scope: 'project' }).path, join(nested, '.crouter', 'memory', 'INDEX.md'));
|
|
103
|
+
});
|
|
104
|
+
test('linked worktree owners have identical canonical names from different physical paths', () => {
|
|
105
|
+
const main = join(process.env.HOME, 'main');
|
|
106
|
+
const linked = join(process.env.HOME, 'linked');
|
|
107
|
+
initGit(main);
|
|
108
|
+
commitInitial(main);
|
|
109
|
+
assert.equal(gitSync(['-C', main, 'worktree', 'add', '-q', '-b', 'linked', linked], undefined).status, 0);
|
|
110
|
+
const mainOwner = join(main, 'packages', 'agent');
|
|
111
|
+
const linkedOwner = join(linked, 'packages', 'agent');
|
|
112
|
+
writeStore(main, 'acme');
|
|
113
|
+
writeStore(linked, 'acme');
|
|
114
|
+
writeStore(mainOwner);
|
|
115
|
+
writeStore(linkedOwner);
|
|
116
|
+
writeDoc(join(mainOwner, '.crouter', 'memory', 'guide.md'), 'MAIN GUIDE');
|
|
117
|
+
writeDoc(join(linkedOwner, '.crouter', 'memory', 'guide.md'), 'LINKED GUIDE');
|
|
118
|
+
const mainDoc = resolveMemoryDocForTarget('acme/packages/agent/guide', target(mainOwner), { scope: 'project' });
|
|
119
|
+
const linkedDoc = resolveMemoryDocForTarget('acme/packages/agent/guide', target(linkedOwner), { scope: 'project' });
|
|
120
|
+
assert.equal(mainDoc.name, linkedDoc.name);
|
|
121
|
+
assert.equal(mainDoc.path, join(mainOwner, '.crouter', 'memory', 'guide.md'));
|
|
122
|
+
assert.equal(linkedDoc.path, join(linkedOwner, '.crouter', 'memory', 'guide.md'));
|
|
123
|
+
const mainStore = openProjectMemoryStore(mainOwner);
|
|
124
|
+
const linkedStore = openProjectMemoryStore(linkedOwner);
|
|
125
|
+
assert.equal(mainStore.repositoryKey, linkedStore.repositoryKey);
|
|
126
|
+
assert.notEqual(mainStore.repositoryRoot, linkedStore.repositoryRoot);
|
|
127
|
+
});
|
|
128
|
+
test('an embedded Git repository uses its own declaration from cwd and explicit store opening', () => {
|
|
129
|
+
const outer = join(process.env.HOME, 'outer');
|
|
130
|
+
const embedded = join(outer, 'vendor');
|
|
131
|
+
const nested = join(embedded, 'pkg');
|
|
132
|
+
initGit(outer);
|
|
133
|
+
writeStore(outer, 'outer');
|
|
134
|
+
initGit(embedded);
|
|
135
|
+
writeStore(embedded, 'inner');
|
|
136
|
+
writeStore(nested);
|
|
137
|
+
writeDoc(join(nested, '.crouter', 'memory', 'guide.md'), 'EMBEDDED GUIDE');
|
|
138
|
+
const fromCwd = resolveMemoryDocForTarget('inner/pkg/guide', target(nested), { scope: 'project' });
|
|
139
|
+
assert.equal(fromCwd.path, join(nested, '.crouter', 'memory', 'guide.md'));
|
|
140
|
+
const explicit = openProjectMemoryStore(nested);
|
|
141
|
+
assert.equal(explicit.repositoryRoot, embedded);
|
|
142
|
+
assert.equal(explicit.namespace, 'inner/pkg');
|
|
143
|
+
assert.equal(loadMemoryStoreView(explicit, true).resolve('inner/pkg/guide').path, fromCwd.path);
|
|
144
|
+
});
|
|
145
|
+
test('a non-Git nested store derives from the outermost ancestor project store', () => {
|
|
146
|
+
const root = join(process.env.HOME, 'non-git');
|
|
147
|
+
const nested = join(root, 'packages', 'agent');
|
|
148
|
+
writeStore(root, 'workspace');
|
|
149
|
+
writeStore(nested);
|
|
150
|
+
writeDoc(join(nested, '.crouter', 'memory', 'guide.md'), 'NON-GIT GUIDE');
|
|
151
|
+
const store = openProjectMemoryStore(nested);
|
|
152
|
+
assert.equal(store.mountStatus, 'ready');
|
|
153
|
+
assert.equal(store.repositoryRoot, root);
|
|
154
|
+
assert.equal(store.repositoryKey, `path:${root}`);
|
|
155
|
+
assert.equal(store.ownerRelativePath, 'packages/agent');
|
|
156
|
+
assert.equal(store.namespace, 'workspace/packages/agent');
|
|
157
|
+
assert.equal(resolveMemoryDocForTarget('workspace/packages/agent/guide', target(nested), { scope: 'project' }).path, join(nested, '.crouter', 'memory', 'guide.md'));
|
|
158
|
+
});
|
|
159
|
+
test('a Git descendant without a root store is undeclared until the root declaration is created', () => {
|
|
160
|
+
const repository = join(process.env.HOME, 'repository');
|
|
161
|
+
const nested = join(repository, 'packages', 'agent');
|
|
162
|
+
initGit(repository);
|
|
163
|
+
writeStore(nested);
|
|
164
|
+
writeDoc(join(nested, '.crouter', 'memory', 'guide.md'), 'NESTED GUIDE');
|
|
165
|
+
const before = openProjectMemoryStore(nested);
|
|
166
|
+
assert.equal(before.mountStatus, 'undeclared');
|
|
167
|
+
assert.equal(before.mountFailure, 'undeclared-root');
|
|
168
|
+
assert.match(before.diagnostic ?? '', new RegExp(repository.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
|
169
|
+
writeStore(repository, 'acme');
|
|
170
|
+
const after = openProjectMemoryStore(nested);
|
|
171
|
+
assert.equal(after.mountStatus, 'ready');
|
|
172
|
+
assert.equal(after.namespace, 'acme/packages/agent');
|
|
173
|
+
});
|
|
174
|
+
test('sibling owner paths that normalize to one prefix, including ordering prefixes, leave both stores invalid', () => {
|
|
175
|
+
const repository = join(process.env.HOME, 'repository');
|
|
176
|
+
const first = join(repository, '01-My Notes');
|
|
177
|
+
const second = join(repository, 'my-notes');
|
|
178
|
+
initGit(repository);
|
|
179
|
+
writeStore(repository, 'acme');
|
|
180
|
+
writeStore(first);
|
|
181
|
+
writeStore(second);
|
|
182
|
+
const profileId = createProfile('collision', [{ path: second, memory: 'content' }]).profileId;
|
|
183
|
+
const stores = memoryStoresInPrecedence(target(first, profileId), 'project');
|
|
184
|
+
const colliding = stores.filter((store) => store.ownerDir === first || store.ownerDir === second);
|
|
185
|
+
assert.equal(colliding.length, 2);
|
|
186
|
+
for (const store of colliding) {
|
|
187
|
+
assert.equal(store.mountStatus, 'invalid');
|
|
188
|
+
assert.equal(store.mountFailure, 'collision');
|
|
189
|
+
assert.equal(store.namespace, 'acme/my-notes');
|
|
190
|
+
assert.match(store.diagnostic ?? '', /My Notes/);
|
|
191
|
+
assert.match(store.diagnostic ?? '', /my-notes/);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
test('profile presence and order do not change a nested store canonical name', () => {
|
|
195
|
+
const repository = join(process.env.HOME, 'repository');
|
|
196
|
+
const nested = join(repository, 'packages', 'agent');
|
|
197
|
+
const unrelated = join(process.env.HOME, 'unrelated');
|
|
198
|
+
initGit(repository);
|
|
199
|
+
writeStore(repository, 'acme');
|
|
200
|
+
writeStore(nested);
|
|
201
|
+
writeDoc(join(nested, '.crouter', 'memory', 'guide.md'), 'NESTED GUIDE');
|
|
202
|
+
mkdirSync(unrelated, { recursive: true });
|
|
203
|
+
const absent = resolveMemoryDocForTarget('acme/packages/agent/guide', target(nested), { scope: 'project' });
|
|
204
|
+
const rootSecond = createProfile('root-second', [
|
|
205
|
+
{ path: unrelated, memory: 'content' },
|
|
206
|
+
{ path: repository, memory: 'content' },
|
|
207
|
+
]).profileId;
|
|
208
|
+
const rootFirst = createProfile('root-first', [
|
|
209
|
+
{ path: repository, memory: 'content' },
|
|
210
|
+
{ path: unrelated, memory: 'content' },
|
|
211
|
+
]).profileId;
|
|
212
|
+
const withRootSecond = resolveMemoryDocForTarget('acme/packages/agent/guide', target(nested, rootSecond), { scope: 'project' });
|
|
213
|
+
const withRootFirst = resolveMemoryDocForTarget('acme/packages/agent/guide', target(nested, rootFirst), { scope: 'project' });
|
|
214
|
+
assert.equal(absent.name, 'acme/packages/agent/guide');
|
|
215
|
+
assert.equal(withRootSecond.name, absent.name);
|
|
216
|
+
assert.equal(withRootFirst.name, absent.name);
|
|
217
|
+
assert.equal(withRootSecond.path, absent.path);
|
|
218
|
+
assert.equal(withRootFirst.path, absent.path);
|
|
219
|
+
});
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// persona keeps the stronger default needed to synthesise a broad review.
|
|
6
6
|
import { after, before, test } from 'node:test';
|
|
7
7
|
import assert from 'node:assert/strict';
|
|
8
|
-
import { mkdtempSync, rmSync } from 'node:fs';
|
|
8
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
9
9
|
import { tmpdir } from 'node:os';
|
|
10
10
|
import { join } from 'node:path';
|
|
11
11
|
import { defaultKindsConfig, defaultModelLaddersConfig } from '../../types.js';
|
|
@@ -19,6 +19,15 @@ let home = '';
|
|
|
19
19
|
before(() => {
|
|
20
20
|
home = mkdtempSync(join(tmpdir(), 'crtr-review-model-floor-'));
|
|
21
21
|
process.env['HOME'] = home;
|
|
22
|
+
mkdirSync(join(home, '.crouter'), { recursive: true });
|
|
23
|
+
writeFileSync(join(home, '.crouter', 'config.json'), JSON.stringify({
|
|
24
|
+
kinds: {
|
|
25
|
+
'audit/reviewers/security': {
|
|
26
|
+
whenToUse: 'Review one security surface.',
|
|
27
|
+
model: 'medium',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
}));
|
|
22
31
|
});
|
|
23
32
|
after(() => {
|
|
24
33
|
if (priorHome === undefined)
|
|
@@ -43,9 +52,8 @@ test('base review ignores a light override and keeps its medium floor', () => {
|
|
|
43
52
|
assert.equal(spec('review', 'light'), expected(REVIEW_FLOOR));
|
|
44
53
|
assert.equal(spec('review', 'haiku'), expected(REVIEW_FLOOR));
|
|
45
54
|
});
|
|
46
|
-
test('
|
|
47
|
-
assert.equal(spec('
|
|
48
|
-
assert.equal(spec('plan/reviewers/requirements-coverage', 'light'), expected('medium'));
|
|
55
|
+
test('plugin-shaped reviewer sub-kinds keep their configured worker floor', () => {
|
|
56
|
+
assert.equal(spec('audit/reviewers/security', 'light'), expected('medium'));
|
|
49
57
|
});
|
|
50
58
|
test('review orchestrator uses its stronger coordinating model', () => {
|
|
51
59
|
assert.equal(buildLaunchSpec('review', 'orchestrator', { lifecycle: 'terminal', hasManager: true, cwd: home, profileId: null }).launch.model, expected(defaultKindsConfig()['review'].orchestratorModel));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { dirname, join } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
@@ -9,7 +9,9 @@ import { loadedCommandPreview } from '../../../clients/attach/slash/prompt-previ
|
|
|
9
9
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '../../../..');
|
|
10
10
|
const EXTENSION = join(ROOT, 'dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts');
|
|
11
11
|
const LIVE_PROBE = join(ROOT, 'dist/core/__tests__/fixtures/memory-slash-live-probe.js');
|
|
12
|
-
const
|
|
12
|
+
const COMMAND_LOCALS = ['acceptance-context-token', 'acceptance-node-token', 'acceptance-independent'];
|
|
13
|
+
const PROJECT_NAMESPACE = 'workspace';
|
|
14
|
+
const COMMAND_NAMES = COMMAND_LOCALS.map((name) => `${PROJECT_NAMESPACE}:workspace:${name}`);
|
|
13
15
|
function writeSlashDoc(memoryDir, name, description, body) {
|
|
14
16
|
writeFileSync(join(memoryDir, `${name}.md`), `---\nkind: knowledge\nwhen-and-why-to-read: Acceptance fixture.\nshort-form: ${description}\nslash: true\n---\n${body}`);
|
|
15
17
|
}
|
|
@@ -60,15 +62,19 @@ function fixtureRows(rows) {
|
|
|
60
62
|
return row;
|
|
61
63
|
});
|
|
62
64
|
}
|
|
63
|
-
test('node-relative memory slash commands omit preview metadata
|
|
64
|
-
const
|
|
65
|
-
const
|
|
65
|
+
test('node-relative memory slash commands omit preview metadata and register invoked bodies at content', async () => {
|
|
66
|
+
const repositoryRoot = mkdtempSync(join(tmpdir(), 'crtr-memory-slash-inventory-'));
|
|
67
|
+
const cwd = join(repositoryRoot, 'workspace');
|
|
68
|
+
const home = join(repositoryRoot, 'canvas');
|
|
66
69
|
const memoryDir = join(cwd, '.crouter', 'memory');
|
|
70
|
+
const repositoryMemory = join(repositoryRoot, '.crouter', 'memory');
|
|
67
71
|
const nodeId = 'acceptance-live-node';
|
|
68
72
|
mkdirSync(memoryDir, { recursive: true });
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
writeSlashDoc(memoryDir,
|
|
73
|
+
mkdirSync(repositoryMemory, { recursive: true });
|
|
74
|
+
writeFileSync(join(repositoryMemory, 'INDEX.md'), `---\nnamespace: ${PROJECT_NAMESPACE}\n---\nWorkspace front door.\n`);
|
|
75
|
+
writeSlashDoc(memoryDir, COMMAND_LOCALS[0], 'Context-relative fixture.', 'Store $ARGUMENTS in $CRTR_CONTEXT_DIR/result.md.\n');
|
|
76
|
+
writeSlashDoc(memoryDir, COMMAND_LOCALS[1], 'Node-relative fixture.', 'Node $CRTR_NODE_ID received $ARGUMENTS.\n');
|
|
77
|
+
writeSlashDoc(memoryDir, COMMAND_LOCALS[2], 'Node-independent fixture.', 'Independent payload: $ARGUMENTS.\n');
|
|
72
78
|
try {
|
|
73
79
|
const live = await liveProbe({
|
|
74
80
|
cwd,
|
|
@@ -83,6 +89,9 @@ test('node-relative memory slash commands omit preview metadata without changing
|
|
|
83
89
|
const prospectiveRows = fixtureRows(live.prospective.commands);
|
|
84
90
|
const liveRows = fixtureRows(live.commands);
|
|
85
91
|
assert.deepEqual(liveRows, prospectiveRows);
|
|
92
|
+
for (const local of COMMAND_LOCALS) {
|
|
93
|
+
assert.equal(live.commands.some((row) => row.name === local), false, `/${local} is not retained as an old command`);
|
|
94
|
+
}
|
|
86
95
|
assert.deepEqual(prospectiveRows, [
|
|
87
96
|
{ name: COMMAND_NAMES[0], description: 'Context-relative fixture.', source: 'command' },
|
|
88
97
|
{ name: COMMAND_NAMES[1], description: 'Node-relative fixture.', source: 'command' },
|
|
@@ -101,8 +110,9 @@ test('node-relative memory slash commands omit preview metadata without changing
|
|
|
101
110
|
assert.ok(insightsInit, 'Gateway completion includes /insights:init');
|
|
102
111
|
assert.equal(insightsInit.expansion, undefined);
|
|
103
112
|
assert.equal(loadedCommandPreview('/insights:init agent-facing CLI', live.commands), undefined);
|
|
104
|
-
assert.equal(loadedCommandPreview(
|
|
105
|
-
assert.equal(loadedCommandPreview(
|
|
113
|
+
assert.equal(loadedCommandPreview(`/${COMMAND_NAMES[2]} first evidence`, live.commands)?.content, 'Independent payload: first evidence.\n');
|
|
114
|
+
assert.equal(loadedCommandPreview(`/${COMMAND_NAMES[2]} changed evidence`, live.commands)?.content, 'Independent payload: changed evidence.\n');
|
|
115
|
+
assert.equal(loadedCommandPreview('/acceptance-independent first evidence', live.commands), undefined);
|
|
106
116
|
assert.deepEqual(live.notifications, []);
|
|
107
117
|
const sentContent = Object.fromEntries(live.sent.map((send) => [send.name, send.message.content]));
|
|
108
118
|
assert.deepEqual(sentContent, {
|
|
@@ -120,8 +130,14 @@ test('node-relative memory slash commands omit preview metadata without changing
|
|
|
120
130
|
}
|
|
121
131
|
assert.doesNotMatch(sentContent[COMMAND_NAMES[0]], /\$CRTR_CONTEXT_DIR|\$CRTR_NODE_ID/);
|
|
122
132
|
assert.doesNotMatch(sentContent[COMMAND_NAMES[1]], /\$CRTR_CONTEXT_DIR|\$CRTR_NODE_ID/);
|
|
133
|
+
const persisted = JSON.parse(readFileSync(join(home, 'nodes', nodeId, 'injected-docs.json'), 'utf8'));
|
|
134
|
+
assert.equal(persisted.v, 3);
|
|
135
|
+
for (const name of COMMAND_LOCALS) {
|
|
136
|
+
const path = realpathSync(join(memoryDir, `${name}.md`));
|
|
137
|
+
assert.deepEqual(persisted.exposures[path], { transcript: 3 }, `/${name} registers the invoked document body as transcript content`);
|
|
138
|
+
}
|
|
123
139
|
}
|
|
124
140
|
finally {
|
|
125
|
-
rmSync(
|
|
141
|
+
rmSync(repositoryRoot, { recursive: true, force: true });
|
|
126
142
|
}
|
|
127
143
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// The warm-pool claim must leave a FROZEN preference snapshot behind.
|
|
2
|
+
//
|
|
3
|
+
// A spare boots with no claimant, so canvas-context-intro skips it and
|
|
4
|
+
// canvas-doc-substrate's before_agent_start is the only other freezer — and Pi
|
|
5
|
+
// emits that event on the typed-prompt path only. A claimed spare whose first
|
|
6
|
+
// turn is a slash command (`sendMessage({ triggerTurn: true })`) therefore
|
|
7
|
+
// reached the persona gate with no snapshot and died with "context preference
|
|
8
|
+
// snapshot is missing". Claim time is where the snapshot is frozen.
|
|
9
|
+
//
|
|
10
|
+
// Run: node --import tsx/esm --test src/core/__tests__/warm-claim-preference-snapshot.test.ts
|
|
11
|
+
import { test, before, beforeEach, after } from 'node:test';
|
|
12
|
+
import assert from 'node:assert/strict';
|
|
13
|
+
import { mkdtempSync, mkdirSync, rmSync } from 'node:fs';
|
|
14
|
+
import { tmpdir } from 'node:os';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { closeDb } from '../canvas/db.js';
|
|
17
|
+
import { spawnNode } from '../runtime/nodes.js';
|
|
18
|
+
import { deliverBearingsLive } from '../runtime/deliver-live.js';
|
|
19
|
+
import { loadContextExposureState } from '../substrate/injected-store.js';
|
|
20
|
+
let home;
|
|
21
|
+
before(() => {
|
|
22
|
+
home = mkdtempSync(join(tmpdir(), 'crtr-warm-snapshot-'));
|
|
23
|
+
process.env['CRTR_HOME'] = home;
|
|
24
|
+
});
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
closeDb();
|
|
27
|
+
rmSync(home, { recursive: true, force: true });
|
|
28
|
+
mkdirSync(home, { recursive: true });
|
|
29
|
+
});
|
|
30
|
+
after(() => {
|
|
31
|
+
closeDb();
|
|
32
|
+
rmSync(home, { recursive: true, force: true });
|
|
33
|
+
delete process.env['CRTR_HOME'];
|
|
34
|
+
});
|
|
35
|
+
test('the claim path persists a frozen preference snapshot', async () => {
|
|
36
|
+
const meta = spawnNode({ kind: 'general', cwd: '/tmp/work', parent: null });
|
|
37
|
+
assert.equal(loadContextExposureState(meta.node_id).preferenceSnapshot, null, 'a spare mints with nothing frozen');
|
|
38
|
+
await deliverBearingsLive(meta.node_id);
|
|
39
|
+
assert.notEqual(loadContextExposureState(meta.node_id).preferenceSnapshot, null, 'claim freezes the snapshot the persona gate requires');
|
|
40
|
+
});
|
|
@@ -5,6 +5,9 @@ export declare function createNode(meta: NodeMeta): NodeMeta;
|
|
|
5
5
|
/** The canonical node record: durable identity (meta.json) ∪ authoritative
|
|
6
6
|
* runtime (the row). Null if unknown. */
|
|
7
7
|
export declare function getNode(nodeId: string): NodeMeta | null;
|
|
8
|
+
export declare function canonicalSessionFile(sessionFile: string): string;
|
|
9
|
+
/** Resolve the crouter node that owns one exact Pi session file. */
|
|
10
|
+
export declare function findNodeBySessionFile(sessionFile: string): NodeMeta | null;
|
|
8
11
|
/** The indexed row (from the db) — cheap for queries that don't need full meta. */
|
|
9
12
|
export declare function getRow(nodeId: string): NodeRow | null;
|
|
10
13
|
/** The node row whose durable LOCATION pane is `pane`, or null. Lets placement
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
// Source-of-truth split: a node's meta.json is canonical for its own fields;
|
|
5
5
|
// the db row is a queryable index re-derivable from it. The subscribes_to edges
|
|
6
6
|
// are db-authoritative (mutable, many-writers — what WAL is for).
|
|
7
|
-
import { existsSync, readFileSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
8
|
-
import { join } from 'node:path';
|
|
7
|
+
import { existsSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from 'node:fs';
|
|
8
|
+
import { join, resolve } from 'node:path';
|
|
9
9
|
import { openDb, withCanvasWrite } from './db.js';
|
|
10
10
|
import { withMetaLock, readMetaObject, writeMetaObjectLocked, writeJsonFileDurably } from './meta-file.js';
|
|
11
11
|
import { isPidAlive, recordedPidLiveness, capturePidIdentities, identitiesMatch, isLegacyTicksIdentity, isBootScopedIdentity, } from './pid.js';
|
|
@@ -22,7 +22,7 @@ import { ensureHome, ensureNodeDirs, nodeMetaPath, nodeDir, nodesRoot, jobDir, }
|
|
|
22
22
|
* can ever leak onto disk even when a fully-hydrated NodeMeta is handed in. */
|
|
23
23
|
const IDENTITY_KEYS = [
|
|
24
24
|
'node_id', 'name', 'description', 'icon', 'cycles', 'created', 'cwd', 'host_kind', 'profile_id', 'kind', 'mode',
|
|
25
|
-
'lifecycle', 'persona_ack', 'parent', 'spawned_by', 'fork_from', 'review_binding', 'passive_default', 'pi_session_id',
|
|
25
|
+
'lifecycle', 'persona_ack', 'parent', 'spawned_by', 'fork_from', 'fork_source_file', 'review_binding', 'passive_default', 'pi_session_id',
|
|
26
26
|
'pi_session_file', 'cycle_pending', 'model_override',
|
|
27
27
|
'launch', 'managed_worktree',
|
|
28
28
|
];
|
|
@@ -33,6 +33,10 @@ function toIdentity(m) {
|
|
|
33
33
|
if (m[k] !== undefined)
|
|
34
34
|
out[k] = m[k];
|
|
35
35
|
}
|
|
36
|
+
for (const key of ['pi_session_file', 'fork_source_file']) {
|
|
37
|
+
if (typeof out[key] === 'string')
|
|
38
|
+
out[key] = canonicalSessionFile(out[key]);
|
|
39
|
+
}
|
|
36
40
|
return out;
|
|
37
41
|
}
|
|
38
42
|
function readMeta(nodeId) {
|
|
@@ -83,13 +87,14 @@ function writeMeta(meta) {
|
|
|
83
87
|
* state. A fresh insert takes the schema defaults for runtime. */
|
|
84
88
|
function upsertRow(meta) {
|
|
85
89
|
openDb()
|
|
86
|
-
.prepare(`INSERT INTO nodes (node_id, name, kind, mode, lifecycle, cwd, host_kind, profile_id, parent, created)
|
|
87
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
90
|
+
.prepare(`INSERT INTO nodes (node_id, name, kind, mode, lifecycle, cwd, host_kind, profile_id, parent, created, pi_session_file)
|
|
91
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
88
92
|
ON CONFLICT(node_id) DO UPDATE SET
|
|
89
93
|
name=excluded.name, kind=excluded.kind, mode=excluded.mode,
|
|
90
94
|
lifecycle=excluded.lifecycle, cwd=excluded.cwd, host_kind=excluded.host_kind,
|
|
91
|
-
profile_id=excluded.profile_id, parent=excluded.parent
|
|
92
|
-
|
|
95
|
+
profile_id=excluded.profile_id, parent=excluded.parent,
|
|
96
|
+
pi_session_file=excluded.pi_session_file`)
|
|
97
|
+
.run(meta.node_id, meta.name, meta.kind, meta.mode, meta.lifecycle, meta.cwd, meta.host_kind ?? null, meta.profile_id ?? null, meta.parent ?? null, meta.created, typeof meta.pi_session_file === 'string' ? canonicalSessionFile(meta.pi_session_file) : null);
|
|
93
98
|
}
|
|
94
99
|
/** Seed a node's row at BIRTH: identity columns + runtime columns taken from the
|
|
95
100
|
* incoming meta (defaults: status='active', the rest null). The only writer
|
|
@@ -98,18 +103,19 @@ function upsertRow(meta) {
|
|
|
98
103
|
function seedRow(meta) {
|
|
99
104
|
openDb()
|
|
100
105
|
.prepare(`INSERT INTO nodes
|
|
101
|
-
(node_id, name, kind, mode, lifecycle, cwd, host_kind, profile_id, parent, created,
|
|
106
|
+
(node_id, name, kind, mode, lifecycle, cwd, host_kind, profile_id, parent, created, pi_session_file,
|
|
102
107
|
status, intent, waiting_for, final_report, finalized_at, pi_pid, "window", tmux_session, pane)
|
|
103
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
108
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
104
109
|
ON CONFLICT(node_id) DO UPDATE SET
|
|
105
110
|
name=excluded.name, kind=excluded.kind, mode=excluded.mode,
|
|
106
111
|
lifecycle=excluded.lifecycle, cwd=excluded.cwd, host_kind=excluded.host_kind,
|
|
107
112
|
profile_id=excluded.profile_id, parent=excluded.parent,
|
|
113
|
+
pi_session_file=excluded.pi_session_file,
|
|
108
114
|
status=excluded.status, intent=excluded.intent, waiting_for=excluded.waiting_for,
|
|
109
115
|
final_report=excluded.final_report, finalized_at=excluded.finalized_at,
|
|
110
116
|
pi_pid=excluded.pi_pid, "window"=excluded."window", tmux_session=excluded.tmux_session,
|
|
111
117
|
pane=excluded.pane`)
|
|
112
|
-
.run(meta.node_id, meta.name, meta.kind, meta.mode, meta.lifecycle, meta.cwd, meta.host_kind ?? null, meta.profile_id ?? null, meta.parent ?? null, meta.created, meta.status ?? 'active', meta.intent ?? null, meta.waiting_for ?? null, meta.final_report ?? null, meta.finalized_at ?? null, meta.pi_pid ?? null, meta.window ?? null, meta.tmux_session ?? null, meta.pane ?? null);
|
|
118
|
+
.run(meta.node_id, meta.name, meta.kind, meta.mode, meta.lifecycle, meta.cwd, meta.host_kind ?? null, meta.profile_id ?? null, meta.parent ?? null, meta.created, typeof meta.pi_session_file === 'string' ? canonicalSessionFile(meta.pi_session_file) : null, meta.status ?? 'active', meta.intent ?? null, meta.waiting_for ?? null, meta.final_report ?? null, meta.finalized_at ?? null, meta.pi_pid ?? null, meta.window ?? null, meta.tmux_session ?? null, meta.pane ?? null);
|
|
113
119
|
}
|
|
114
120
|
function rowFrom(r) {
|
|
115
121
|
return {
|
|
@@ -179,6 +185,20 @@ export function getNode(nodeId) {
|
|
|
179
185
|
// exists yet (rare — pre-rebuild), keep whatever the meta carried.
|
|
180
186
|
return { ...ident, ...(rt ?? {}) };
|
|
181
187
|
}
|
|
188
|
+
export function canonicalSessionFile(sessionFile) {
|
|
189
|
+
const absolute = resolve(sessionFile);
|
|
190
|
+
return existsSync(absolute) ? realpathSync(absolute) : absolute;
|
|
191
|
+
}
|
|
192
|
+
/** Resolve the crouter node that owns one exact Pi session file. */
|
|
193
|
+
export function findNodeBySessionFile(sessionFile) {
|
|
194
|
+
const canonical = canonicalSessionFile(sessionFile);
|
|
195
|
+
const matches = openDb()
|
|
196
|
+
.prepare('SELECT node_id FROM nodes WHERE pi_session_file = ? ORDER BY created')
|
|
197
|
+
.all(canonical);
|
|
198
|
+
if (matches.length > 1)
|
|
199
|
+
throw new Error(`multiple nodes own pi session file: ${canonical}`);
|
|
200
|
+
return matches[0] === undefined ? null : getNode(matches[0].node_id);
|
|
201
|
+
}
|
|
182
202
|
/** The indexed row (from the db) — cheap for queries that don't need full meta. */
|
|
183
203
|
export function getRow(nodeId) {
|
|
184
204
|
const r = openDb()
|
package/dist/core/canvas/db.js
CHANGED
|
@@ -1175,6 +1175,27 @@ CREATE TABLE human_action_deliveries (
|
|
|
1175
1175
|
CREATE INDEX idx_human_action_deliveries_due ON human_action_deliveries(state, next_attempt_at);
|
|
1176
1176
|
`);
|
|
1177
1177
|
}
|
|
1178
|
+
/** v34 — index the exact Pi session file coordinate used to recover fork
|
|
1179
|
+
* exposure provenance without hydrating every node meta. Meta remains authoritative;
|
|
1180
|
+
* normal identity upserts keep this projection current after the one-time backfill. */
|
|
1181
|
+
function addNodeSessionFileIndex(db) {
|
|
1182
|
+
const columns = nodeColumns(db);
|
|
1183
|
+
addNodeColumnIfMissing(db, columns, 'pi_session_file', 'ALTER TABLE nodes ADD COLUMN pi_session_file TEXT;');
|
|
1184
|
+
for (const entry of readdirSync(nodesRoot(), { withFileTypes: true })) {
|
|
1185
|
+
if (!entry.isDirectory())
|
|
1186
|
+
continue;
|
|
1187
|
+
try {
|
|
1188
|
+
const meta = JSON.parse(readFileSync(nodeMetaPath(entry.name), 'utf8'));
|
|
1189
|
+
if (typeof meta.pi_session_file === 'string') {
|
|
1190
|
+
db.prepare('UPDATE nodes SET pi_session_file = ? WHERE node_id = ?').run(meta.pi_session_file, entry.name);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
catch {
|
|
1194
|
+
// Corrupt node metadata is ignored by the ordinary canvas rebuild too.
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_nodes_pi_session_file ON nodes(pi_session_file);');
|
|
1198
|
+
}
|
|
1178
1199
|
/** The journaled lane of the state-migration registry, adapted to this chain's
|
|
1179
1200
|
* step shape. Registry order is preserved, so these versions stay stable as
|
|
1180
1201
|
* long as the registry is append-only (same contract as the local list). */
|
|
@@ -1221,6 +1242,7 @@ export const MIGRATIONS = [
|
|
|
1221
1242
|
/* v31 */ addReviewSubmitRequested,
|
|
1222
1243
|
/* v32 */ addCronHeldColumns,
|
|
1223
1244
|
/* v33 */ addHumanActionDeliveries,
|
|
1245
|
+
/* v34 */ addNodeSessionFileIndex,
|
|
1224
1246
|
...JOURNALED_STATE_MIGRATIONS,
|
|
1225
1247
|
];
|
|
1226
1248
|
/** Migration indexes that manage their OWN transaction and therefore must not
|
|
@@ -1348,11 +1370,16 @@ export function openDb() {
|
|
|
1348
1370
|
ensureHome();
|
|
1349
1371
|
recordOpenLedger();
|
|
1350
1372
|
const db = new DatabaseSync(path);
|
|
1373
|
+
// FIRST, before any pragma that can contend. `journal_mode = WAL` takes a
|
|
1374
|
+
// brief exclusive lock, so on the default zero busy_timeout a cold open that
|
|
1375
|
+
// races a concurrent writer throws `database is locked` outright — crtrd, a
|
|
1376
|
+
// CLI child and a broker all open this same file, and every one of them opens
|
|
1377
|
+
// it cold. Setting the wait window first makes that open wait its turn.
|
|
1378
|
+
db.exec('PRAGMA busy_timeout = 5000;');
|
|
1351
1379
|
db.exec('PRAGMA journal_mode = WAL;');
|
|
1352
1380
|
// Load-bearing as of migration v4: the edges→nodes FK (ON DELETE CASCADE)
|
|
1353
1381
|
// needs this ON at the deleting connection so a node delete reaps its edges.
|
|
1354
1382
|
db.exec('PRAGMA foreign_keys = ON;');
|
|
1355
|
-
db.exec('PRAGMA busy_timeout = 5000;');
|
|
1356
1383
|
migrate(db);
|
|
1357
1384
|
handles.set(path, db);
|
|
1358
1385
|
return db;
|
|
@@ -10,6 +10,8 @@ export declare function crtrHome(): string;
|
|
|
10
10
|
* local disk — so the db shares the node data's location and survives a
|
|
11
11
|
* sandbox recreate together with `nodes/`. */
|
|
12
12
|
export declare function canvasDbPath(): string;
|
|
13
|
+
export declare function onDiskMigrationLockPath(): string;
|
|
14
|
+
export declare function onDiskMigrationMarkerPath(): string;
|
|
13
15
|
/** Absolute path to a node's broker `view.sock` — always `view.sock` under the
|
|
14
16
|
* node dir. Stale socket files (a host reboot, a sandbox recreate on a
|
|
15
17
|
* durable volume) are harmless: the broker unlinks any existing socket before
|
|
@@ -73,12 +75,8 @@ export declare function situationalContextPath(nodeId: string): string;
|
|
|
73
75
|
export declare function passivePath(nodeId: string): string;
|
|
74
76
|
export declare function transcriptPath(nodeId: string): string;
|
|
75
77
|
export declare function sessionPtrPath(nodeId: string): string;
|
|
76
|
-
/**
|
|
77
|
-
*
|
|
78
|
-
* transcript dedup survives a dormancy → revive(resume) cycle: a resume reuses
|
|
79
|
-
* the same .jsonl transcript in a NEW pi process, so the in-memory set would
|
|
80
|
-
* otherwise start empty and re-inject docs already present in the transcript.
|
|
81
|
-
* Deleted by the launch paths that start a FRESH transcript. */
|
|
78
|
+
/** Frozen preference snapshot and rank-aware exposure state for the node's
|
|
79
|
+
* currently loaded LLM context. The filename remains stable across schema versions. */
|
|
82
80
|
export declare function injectedDocsPath(nodeId: string): string;
|
|
83
81
|
/** Create the full directory skeleton for a node. Idempotent. */
|
|
84
82
|
export declare function ensureNodeDirs(nodeId: string): void;
|
|
@@ -36,6 +36,12 @@ export function crtrHome() {
|
|
|
36
36
|
export function canvasDbPath() {
|
|
37
37
|
return join(crtrHome(), 'canvas.db');
|
|
38
38
|
}
|
|
39
|
+
export function onDiskMigrationLockPath() {
|
|
40
|
+
return join(crtrHome(), 'on-disk-migration.lock');
|
|
41
|
+
}
|
|
42
|
+
export function onDiskMigrationMarkerPath() {
|
|
43
|
+
return join(crtrHome(), 'on-disk-migrations.json');
|
|
44
|
+
}
|
|
39
45
|
/** Absolute path to a node's broker `view.sock` — always `view.sock` under the
|
|
40
46
|
* node dir. Stale socket files (a host reboot, a sandbox recreate on a
|
|
41
47
|
* durable volume) are harmless: the broker unlinks any existing socket before
|
|
@@ -159,12 +165,8 @@ export function transcriptPath(nodeId) {
|
|
|
159
165
|
export function sessionPtrPath(nodeId) {
|
|
160
166
|
return join(nodeDir(nodeId), 'session.ptr');
|
|
161
167
|
}
|
|
162
|
-
/**
|
|
163
|
-
*
|
|
164
|
-
* transcript dedup survives a dormancy → revive(resume) cycle: a resume reuses
|
|
165
|
-
* the same .jsonl transcript in a NEW pi process, so the in-memory set would
|
|
166
|
-
* otherwise start empty and re-inject docs already present in the transcript.
|
|
167
|
-
* Deleted by the launch paths that start a FRESH transcript. */
|
|
168
|
+
/** Frozen preference snapshot and rank-aware exposure state for the node's
|
|
169
|
+
* currently loaded LLM context. The filename remains stable across schema versions. */
|
|
168
170
|
export function injectedDocsPath(nodeId) {
|
|
169
171
|
return join(nodeDir(nodeId), 'injected-docs.json');
|
|
170
172
|
}
|
|
@@ -20,7 +20,7 @@ import { jobDir, contextDir } from './paths.js';
|
|
|
20
20
|
import { isPidAlive } from './pid.js';
|
|
21
21
|
import { resolveNodeVisual, faultSummary } from './status-glyph.js';
|
|
22
22
|
import { readFault } from '../runtime/fault.js';
|
|
23
|
-
import {
|
|
23
|
+
import { parseCard } from '../../shared/generated-context.js';
|
|
24
24
|
// ---------------------------------------------------------------------------
|
|
25
25
|
// Active-fault overlay: stale daemon-owned provider faults are hidden once the
|
|
26
26
|
// broker pid is gone; manual/client faults stay visible until cleared.
|
|
@@ -192,7 +192,7 @@ export function readSessionParts(sessionFile) {
|
|
|
192
192
|
const text = extractUserText(rec.message.content);
|
|
193
193
|
if (text === '')
|
|
194
194
|
continue;
|
|
195
|
-
if (parseCard({ role: 'user', content: text }) !== null
|
|
195
|
+
if (parseCard({ role: 'user', content: text }) !== null)
|
|
196
196
|
continue;
|
|
197
197
|
const capped = text.length > CONVO_MSG_CAP ? text.slice(0, CONVO_MSG_CAP) : text;
|
|
198
198
|
parts.push(capped);
|