@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
|
@@ -42,11 +42,17 @@ function freshNode(nodeId) {
|
|
|
42
42
|
}
|
|
43
43
|
function makeFakePi() {
|
|
44
44
|
const handlers = {};
|
|
45
|
+
let resolveFirstInjection;
|
|
46
|
+
const firstInjection = new Promise((resolve) => { resolveFirstInjection = resolve; });
|
|
45
47
|
return {
|
|
46
48
|
injected: [],
|
|
49
|
+
firstInjection,
|
|
47
50
|
sentMessages: [],
|
|
48
51
|
on(e, h) { handlers[e] = h; },
|
|
49
|
-
sendUserMessage(content, options) {
|
|
52
|
+
sendUserMessage(content, options) {
|
|
53
|
+
this.injected.push({ content, deliverAs: options?.deliverAs });
|
|
54
|
+
resolveFirstInjection?.();
|
|
55
|
+
},
|
|
50
56
|
sendMessage(message) { this.sentMessages.push(message); },
|
|
51
57
|
async fire(e, ev, ctx) { await handlers[e]?.(ev, ctx); },
|
|
52
58
|
};
|
|
@@ -77,6 +83,13 @@ async function shutdown(pi, opts = {}) {
|
|
|
77
83
|
await pi.fire('session_shutdown', {}, undefined);
|
|
78
84
|
}
|
|
79
85
|
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
86
|
+
async function waitForFirstInjection(pi, timeoutMs = 2_000) {
|
|
87
|
+
const timeout = new Promise((_, reject) => {
|
|
88
|
+
const timer = setTimeout(() => reject(new Error('watcher did not inject the inbox handoff')), timeoutMs);
|
|
89
|
+
timer.unref();
|
|
90
|
+
});
|
|
91
|
+
await Promise.race([pi.firstInjection, timeout]);
|
|
92
|
+
}
|
|
80
93
|
// Register the watcher, then fire session_start — the real lifecycle gate
|
|
81
94
|
// that arms its poll interval and initial tick (see canvas-inbox-watcher.ts).
|
|
82
95
|
// registerCanvasInboxWatcher() alone only wires up handlers; without this the
|
|
@@ -161,7 +174,7 @@ describe('canvas inbox watcher — hold + idle delivery', () => {
|
|
|
161
174
|
pi.fire('agent_start', { type: 'agent_start' }, { isIdle: () => false });
|
|
162
175
|
await wait(TICK_MS + 100);
|
|
163
176
|
appendInbox('node-refresh', { from: 'child-x', tier: 'urgent', kind: 'final', label: 'done' });
|
|
164
|
-
await
|
|
177
|
+
await waitForFirstInjection(pi);
|
|
165
178
|
assert.equal(pi.injected.length, 1, 'the live broker receives the handoff before its durable cursor boundary');
|
|
166
179
|
// The cursor operation decides the refresh hold at its durable commit
|
|
167
180
|
// boundary; clearing intent does not manufacture a second handoff.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// A node whose kind/mode/lifecycle changes mid-context gets only memory newly
|
|
2
|
+
// visible or raised above the context's active exposure ranks. Removals stay
|
|
3
|
+
// silent because already loaded context cannot be unseen.
|
|
4
|
+
//
|
|
5
|
+
// The load-bearing case is the gated orchestration kernel: a base → orchestrator
|
|
6
|
+
// change must select its real builtin document through the ordinary renderer.
|
|
7
|
+
//
|
|
8
|
+
// Run: node --conditions=crtr-src --import tsx/esm --test src/core/__tests__/config-change-delta.test.ts
|
|
9
|
+
import { test, before, after } from 'node:test';
|
|
10
|
+
import assert from 'node:assert/strict';
|
|
11
|
+
import { mkdtempSync, mkdirSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import { tmpdir } from 'node:os';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { resetScopeCache } from '../scope.js';
|
|
15
|
+
import { clearSessionCache } from '../substrate/session-cache.js';
|
|
16
|
+
import { renderConfigChangeDelta, renderKnowledgeForSubject, renderPreferencesForSubject, } from '../substrate/render.js';
|
|
17
|
+
import { emptyContextExposureState, exposureTarget, } from '../substrate/injected-store.js';
|
|
18
|
+
const NODE = 'n-delta-test';
|
|
19
|
+
const DEV_PREFERENCE = 'BODY-DELTA-DEV-PREFERENCE';
|
|
20
|
+
const DEV_KNOWLEDGE_WHEN = 'When the developer-only guide is routed';
|
|
21
|
+
const KERNEL_MARKER = '## You are an orchestrator';
|
|
22
|
+
let home;
|
|
23
|
+
let work;
|
|
24
|
+
let prevHome;
|
|
25
|
+
let prevCanvasHome;
|
|
26
|
+
let prevProfile;
|
|
27
|
+
let prevCwd;
|
|
28
|
+
function subject(over = {}) {
|
|
29
|
+
return {
|
|
30
|
+
kind: 'general',
|
|
31
|
+
mode: 'base',
|
|
32
|
+
lifecycle: 'terminal',
|
|
33
|
+
hasManager: true,
|
|
34
|
+
cwd: work,
|
|
35
|
+
scope: 'user',
|
|
36
|
+
orchestration: { depth: 1 },
|
|
37
|
+
profile: null,
|
|
38
|
+
...over,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Everything the node's boot channels render for `s` — the baseline every
|
|
42
|
+
* later delta is diffed against. */
|
|
43
|
+
function seedBoot(state, s) {
|
|
44
|
+
renderPreferencesForSubject(s, exposureTarget(state, 'system'));
|
|
45
|
+
renderKnowledgeForSubject(s, NODE, exposureTarget(state, 'transcript'));
|
|
46
|
+
}
|
|
47
|
+
before(() => {
|
|
48
|
+
prevHome = process.env['HOME'];
|
|
49
|
+
prevCanvasHome = process.env['CRTR_HOME'];
|
|
50
|
+
prevProfile = process.env['CRTR_PROFILE_ID'];
|
|
51
|
+
prevCwd = process.cwd();
|
|
52
|
+
home = realpathSync(mkdtempSync(join(tmpdir(), 'crtr-delta-home-')));
|
|
53
|
+
work = realpathSync(mkdtempSync(join(tmpdir(), 'crtr-delta-work-')));
|
|
54
|
+
process.env['HOME'] = home;
|
|
55
|
+
process.env['CRTR_HOME'] = realpathSync(mkdtempSync(join(tmpdir(), 'crtr-delta-canvas-')));
|
|
56
|
+
delete process.env['CRTR_PROFILE_ID'];
|
|
57
|
+
const user = join(home, '.crouter', 'memory');
|
|
58
|
+
mkdirSync(user, { recursive: true });
|
|
59
|
+
writeFileSync(join(user, 'delta-dev-preference.md'), ['---', 'kind: preference', 'gate: {kind: developer}', 'surfaces:', ' - {on: boot, at: content}', '---', '', DEV_PREFERENCE, ''].join('\n'));
|
|
60
|
+
writeFileSync(join(user, 'delta-dev-knowledge.md'), ['---', 'kind: knowledge', `when-and-why-to-read: "${DEV_KNOWLEDGE_WHEN}"`, 'gate: {kind: developer}', 'surfaces:', ' - {on: boot, at: preview}', '---', '', 'ignored body', ''].join('\n'));
|
|
61
|
+
process.chdir(work);
|
|
62
|
+
resetScopeCache();
|
|
63
|
+
clearSessionCache();
|
|
64
|
+
});
|
|
65
|
+
after(() => {
|
|
66
|
+
process.chdir(prevCwd);
|
|
67
|
+
resetScopeCache();
|
|
68
|
+
rmSync(home, { recursive: true, force: true });
|
|
69
|
+
rmSync(work, { recursive: true, force: true });
|
|
70
|
+
rmSync(process.env['CRTR_HOME'], { recursive: true, force: true });
|
|
71
|
+
if (prevHome === undefined)
|
|
72
|
+
delete process.env['HOME'];
|
|
73
|
+
else
|
|
74
|
+
process.env['HOME'] = prevHome;
|
|
75
|
+
if (prevCanvasHome === undefined)
|
|
76
|
+
delete process.env['CRTR_HOME'];
|
|
77
|
+
else
|
|
78
|
+
process.env['CRTR_HOME'] = prevCanvasHome;
|
|
79
|
+
if (prevProfile !== undefined)
|
|
80
|
+
process.env['CRTR_PROFILE_ID'] = prevProfile;
|
|
81
|
+
});
|
|
82
|
+
test('a config change delivers only what the new configuration newly exposes', () => {
|
|
83
|
+
const state = emptyContextExposureState();
|
|
84
|
+
const transcript = exposureTarget(state, 'transcript');
|
|
85
|
+
const base = subject();
|
|
86
|
+
seedBoot(state, base);
|
|
87
|
+
assert.equal(renderConfigChangeDelta(base, NODE, transcript), '', 'no change → nothing to deliver; boot already showed this configuration');
|
|
88
|
+
// base → orchestrator: the builtin orchestration kernel is gated
|
|
89
|
+
// `{mode: orchestrator}`, so this transition is what unlocks it.
|
|
90
|
+
const promoted = subject({ mode: 'orchestrator' });
|
|
91
|
+
const promoteDelta = renderConfigChangeDelta(promoted, NODE, transcript);
|
|
92
|
+
assert.ok(promoteDelta.includes(KERNEL_MARKER), 'the promote delta carries the builtin orchestration kernel');
|
|
93
|
+
assert.ok(!promoteDelta.includes('Waiting is a way to end a turn'), 'a doc boot already delivered is not repeated');
|
|
94
|
+
assert.equal(renderConfigChangeDelta(promoted, NODE, transcript), '', 'the same change twice delivers nothing the second time');
|
|
95
|
+
// kind flip: a content-rung preference and a preview-rung knowledge doc, each
|
|
96
|
+
// gated on the new kind, in their own blocks.
|
|
97
|
+
const developer = subject({ mode: 'orchestrator', kind: 'developer' });
|
|
98
|
+
const kindDelta = renderConfigChangeDelta(developer, NODE, transcript);
|
|
99
|
+
assert.ok(kindDelta.includes(DEV_PREFERENCE), 'the newly-eligible preference body is delivered');
|
|
100
|
+
assert.ok(kindDelta.includes('<memory kind="preference">'), 'agent-editable preferences ride a preference block');
|
|
101
|
+
assert.ok(kindDelta.includes(`delta-dev-knowledge # read when: ${DEV_KNOWLEDGE_WHEN}`), 'the newly-eligible knowledge doc is routed at its preview rung');
|
|
102
|
+
assert.ok(kindDelta.includes('<memory kind="knowledge">'), 'knowledge rides its own block');
|
|
103
|
+
assert.ok(!kindDelta.includes(KERNEL_MARKER), 'the kernel is not re-delivered — the promote already showed it');
|
|
104
|
+
// Removals are silent, and nothing already shown comes back.
|
|
105
|
+
assert.equal(renderConfigChangeDelta(subject({ mode: 'orchestrator' }), NODE, transcript), '', 'dropping back to the old kind announces nothing — the new prose overrides');
|
|
106
|
+
});
|
|
@@ -57,18 +57,22 @@ test('the stored situational card rides the bearings body; absent when unset', (
|
|
|
57
57
|
assert.match(block, /<runtime kind="situational">/, 'card present once situational context is set');
|
|
58
58
|
assert.match(block, /user is looking at the billing applet/, 'card carries the ambient text');
|
|
59
59
|
});
|
|
60
|
-
function makeFakePi() {
|
|
60
|
+
function makeFakePi(entries = []) {
|
|
61
61
|
return {
|
|
62
62
|
sent: [],
|
|
63
|
+
entries,
|
|
63
64
|
renderers: {},
|
|
64
65
|
on(e, h) { if (e === 'session_start')
|
|
65
66
|
this.handler = h; },
|
|
66
|
-
sendMessage(m) {
|
|
67
|
+
sendMessage(m) {
|
|
68
|
+
this.sent.push(m);
|
|
69
|
+
this.entries.push({ type: 'custom_message', ...m });
|
|
70
|
+
},
|
|
67
71
|
registerMessageRenderer(t, r) { this.renderers[t] = r; },
|
|
68
72
|
};
|
|
69
73
|
}
|
|
70
|
-
function fakeCtx(
|
|
71
|
-
return { sessionManager: { getEntries: () => entries } };
|
|
74
|
+
function fakeCtx(pi) {
|
|
75
|
+
return { sessionManager: { getEntries: () => pi.entries } };
|
|
72
76
|
}
|
|
73
77
|
test('session_start injects the block as the first message of an empty session', async () => {
|
|
74
78
|
const meta = spawnNode({ kind: 'general', cwd: '/tmp/work', parent: null });
|
|
@@ -78,7 +82,7 @@ test('session_start injects the block as the first message of an empty session',
|
|
|
78
82
|
assert.ok(pi.handler, 'session_start handler registered');
|
|
79
83
|
// Empty session → inject the block as its own custom message (no delivery
|
|
80
84
|
// options → it precedes the first prompt).
|
|
81
|
-
await pi.handler({ reason: 'startup' }, fakeCtx());
|
|
85
|
+
await pi.handler({ reason: 'startup' }, fakeCtx(pi));
|
|
82
86
|
assert.equal(pi.sent.length, 1, 'one message injected');
|
|
83
87
|
assert.equal(pi.sent[0].customType, CONTEXT_INTRO_CUSTOM_TYPE);
|
|
84
88
|
assert.equal(pi.sent[0].display, true);
|
|
@@ -92,19 +96,19 @@ test('session_start is idempotent across resume (skips if already in history)',
|
|
|
92
96
|
// skips when the present bearings name THIS node (a fork inherits the
|
|
93
97
|
// SOURCE's bearings, which name a different node — see canvas-context-intro
|
|
94
98
|
// tests), so a realistic resume entry must carry the real block content.
|
|
95
|
-
const pi = makeFakePi();
|
|
96
|
-
registerCanvasContextIntro(pi);
|
|
97
99
|
const entries = [
|
|
98
100
|
{ type: 'custom_message', customType: CONTEXT_INTRO_CUSTOM_TYPE, content: buildContextIntro(meta.node_id) },
|
|
99
101
|
];
|
|
100
|
-
|
|
102
|
+
const pi = makeFakePi(entries);
|
|
103
|
+
registerCanvasContextIntro(pi);
|
|
104
|
+
await pi.handler({ reason: 'resume' }, fakeCtx(pi));
|
|
101
105
|
assert.equal(pi.sent.length, 0, 'our own block already in history → skip');
|
|
102
106
|
});
|
|
103
107
|
test('session_start is inert when CRTR_NODE_ID is absent', async () => {
|
|
104
108
|
delete process.env['CRTR_NODE_ID'];
|
|
105
109
|
const pi = makeFakePi();
|
|
106
110
|
registerCanvasContextIntro(pi);
|
|
107
|
-
await pi.handler({ reason: 'startup' }, fakeCtx());
|
|
111
|
+
await pi.handler({ reason: 'startup' }, fakeCtx(pi));
|
|
108
112
|
assert.equal(pi.sent.length, 0);
|
|
109
113
|
});
|
|
110
114
|
test('renderer registered for the customType; collapsed hides body, ctrl+o expand reveals it', () => {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { test, before, after, beforeEach } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { dirname, join } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
6
7
|
import { spawnSync, spawn } from 'node:child_process';
|
|
7
8
|
import { createServer } from 'node:net';
|
|
8
9
|
import { createNode, getNode, subscribe } from '../canvas/canvas.js';
|
|
@@ -177,6 +178,18 @@ test('verifyDaemonStartup waits for pidfile + live pid, then reports success for
|
|
|
177
178
|
assert.equal(pidAliveReady, true, 'pid must have been alive before success');
|
|
178
179
|
assert.equal(slept > 0, true, 'the guard must actually poll');
|
|
179
180
|
});
|
|
181
|
+
test('runDaemon awaits activation before ownership and readiness', () => {
|
|
182
|
+
const source = readFileSync(fileURLToPath(new URL('../../daemon/crtrd.ts', import.meta.url)), 'utf8');
|
|
183
|
+
const runDaemonAt = source.indexOf('export async function runDaemon');
|
|
184
|
+
const activationAt = source.indexOf('await ensureOnDiskMigrations();', runDaemonAt);
|
|
185
|
+
const ownershipAt = source.indexOf('claimDaemonOwnership(', runDaemonAt);
|
|
186
|
+
const readinessAt = source.indexOf('writePidfile();', runDaemonAt);
|
|
187
|
+
assert.ok(runDaemonAt >= 0, 'runDaemon exists');
|
|
188
|
+
assert.ok(activationAt > runDaemonAt, 'runDaemon awaits on-disk activation');
|
|
189
|
+
assert.equal(source.slice(runDaemonAt, activationAt).includes('await '), false, 'activation is the first awaited operation');
|
|
190
|
+
assert.ok(activationAt < ownershipAt, 'activation precedes the ownership claim that opens canvas.db');
|
|
191
|
+
assert.ok(ownershipAt < readinessAt, 'ownership follows activation before readiness is published');
|
|
192
|
+
});
|
|
180
193
|
// Regression for the cold-guest recreate blocker: the production startup window
|
|
181
194
|
// must tolerate a slower cold start than the old 500ms bound.
|
|
182
195
|
test('verifyDaemonStartup default window tolerates a slower cold start', async () => {
|
|
@@ -197,28 +210,21 @@ test('verifyDaemonStartup default window tolerates a slower cold start', async (
|
|
|
197
210
|
});
|
|
198
211
|
assert.equal(existingPid, null, 'the production startup window must allow a cold guest to settle');
|
|
199
212
|
});
|
|
200
|
-
|
|
201
|
-
// boot right after `install-runtime`, where the daemon's first-ever module
|
|
202
|
-
// load + possible db migration can push writePidfile() past 5s even though
|
|
203
|
-
// the daemon is starting up fine. The production window must comfortably
|
|
204
|
-
// tolerate a pidfile that only appears well past the old 5s bound.
|
|
205
|
-
test('verifyDaemonStartup default window tolerates a cold start slower than the old 5s bound', async () => {
|
|
213
|
+
test('startup verification waits for delayed activation inside the shared window', async () => {
|
|
206
214
|
let now = 0;
|
|
207
|
-
let
|
|
208
|
-
let pidAliveReady = false;
|
|
215
|
+
let activationComplete = false;
|
|
209
216
|
const existingPid = await verifyDaemonStartup(42, undefined, {
|
|
210
217
|
now: () => now,
|
|
211
|
-
readPidfile: () => (
|
|
212
|
-
isPidAlive: () =>
|
|
218
|
+
readPidfile: () => (activationComplete ? 42 : null),
|
|
219
|
+
isPidAlive: () => activationComplete,
|
|
213
220
|
sleepMs: (ms) => {
|
|
214
221
|
now += ms;
|
|
215
|
-
if (now >=
|
|
216
|
-
|
|
217
|
-
if (now >= 12_100)
|
|
218
|
-
pidAliveReady = true;
|
|
222
|
+
if (now >= 25_000)
|
|
223
|
+
activationComplete = true;
|
|
219
224
|
},
|
|
220
225
|
});
|
|
221
|
-
assert.equal(existingPid, null, '
|
|
226
|
+
assert.equal(existingPid, null, 'activation beyond the old 20s bound still reaches readiness');
|
|
227
|
+
assert.equal(activationComplete, true, 'readiness follows activation completion');
|
|
222
228
|
});
|
|
223
229
|
// A concurrent start race is success when another live daemon already owns the
|
|
224
230
|
// pidfile; the loser must return that existing pid instead of timing out.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { DefaultResourceLoader, ExtensionRunner, } from '@earendil-works/pi-coding-agent';
|
|
4
4
|
import { buildRefInventory } from '../../memory/inline-ref-inventory.js';
|
|
@@ -17,6 +17,10 @@ async function main() {
|
|
|
17
17
|
const stateBeforeProspective = existsSync(home);
|
|
18
18
|
const prospective = await runProspectiveInventory({ cwd, profileId: null, extensions: [extensionPath] });
|
|
19
19
|
const stateAfterProspective = existsSync(home);
|
|
20
|
+
const nodeId = process.env['CRTR_NODE_ID'];
|
|
21
|
+
if (nodeId === undefined || nodeId === '')
|
|
22
|
+
throw new Error('CRTR_NODE_ID is required');
|
|
23
|
+
mkdirSync(join(home, 'nodes', nodeId), { recursive: true });
|
|
20
24
|
const loader = new DefaultResourceLoader({
|
|
21
25
|
cwd,
|
|
22
26
|
agentDir: join(cwd, '.pi'),
|
|
@@ -6,6 +6,7 @@ import assert from 'node:assert/strict';
|
|
|
6
6
|
import { mkdtempSync, rmSync, writeFileSync, readFileSync, existsSync } from 'node:fs';
|
|
7
7
|
import { tmpdir } from 'node:os';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
|
+
import { ApiError } from '../../api/index.js';
|
|
9
10
|
import { createApiServer } from '../../daemon/api/server.js';
|
|
10
11
|
import { cancelHumanTicket, deliverTerminalResult, resolvePageTicket, settleFeedbackCompanion } from '../../daemon/human/finish.js';
|
|
11
12
|
import { humanSend } from '../../commands/human/prompts.js';
|
|
@@ -219,6 +220,24 @@ describe('terminal ticket delivery', () => {
|
|
|
219
220
|
assert.equal(notes[0].tier, 'normal');
|
|
220
221
|
assert.match(notes[0].label, /canceled/);
|
|
221
222
|
});
|
|
223
|
+
test('cancellation reaches subscribers when the bridge is already done, and retries conflict rather than fail', async () => {
|
|
224
|
+
const actor = spawnNode({ kind: 'general', parent: null, cwd: home, name: 'actor' }).node_id;
|
|
225
|
+
const bridge = spawnNode({ kind: 'human', parent: actor, cwd: home, name: 'human-page', lifecycle: 'terminal' }).node_id;
|
|
226
|
+
const other = spawnNode({ kind: 'general', parent: null, cwd: home, name: 'other' }).node_id;
|
|
227
|
+
const { subscribe } = await import('../canvas/canvas.js');
|
|
228
|
+
subscribe(other, bridge, true);
|
|
229
|
+
submitBridgePage(bridge);
|
|
230
|
+
// The bridge is parked done with no canonical final while its page is still
|
|
231
|
+
// pending — done is where cancellation wants it, so cancelling must deliver.
|
|
232
|
+
transition(bridge, 'finish');
|
|
233
|
+
const result = await cancelHumanTicket(bridge, { reason: 'stale', actor });
|
|
234
|
+
assert.equal(result.kind, 'canceled');
|
|
235
|
+
assert.equal(getNode(bridge)?.status, 'done');
|
|
236
|
+
assert.equal(readInboxSince(other).length, 1, 'the other waiter is told no answer is coming');
|
|
237
|
+
// The recorded cancellation is final: a retry is an already-resolved
|
|
238
|
+
// conflict, never a delivery failure that keeps repeating.
|
|
239
|
+
await assert.rejects(() => cancelHumanTicket(bridge, { reason: 'stale', actor }), (error) => error instanceof ApiError && error.status === 409 && error.code === 'ticket_already_resolved');
|
|
240
|
+
});
|
|
222
241
|
});
|
|
223
242
|
test('a display-only page publishes passively and creates no canvas node', async () => {
|
|
224
243
|
const page = join(home, 'build-done.tsx');
|
|
@@ -25,6 +25,8 @@ import { gatewayMemoryRows } from '../runtime/chat-inventory-rows.js';
|
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
26
26
|
describe('buildRefInventory (fixture-backed corpus)', () => {
|
|
27
27
|
const NODE_ID = 'crtr-inline-ref-fixture-node';
|
|
28
|
+
const PROJECT_NAMESPACE = 'crtr-inline-ref-fixture-project';
|
|
29
|
+
const projectName = (local) => `${PROJECT_NAMESPACE}/root/${local}`;
|
|
28
30
|
let canvasHome;
|
|
29
31
|
let userHome;
|
|
30
32
|
let projectBase;
|
|
@@ -75,6 +77,10 @@ describe('buildRefInventory (fixture-backed corpus)', () => {
|
|
|
75
77
|
projectBase = mkdtempSync(join(tmpdir(), 'crtr-inline-ref-project-'));
|
|
76
78
|
projectRoot = join(projectBase, 'root');
|
|
77
79
|
mkdirSync(projectRoot, { recursive: true });
|
|
80
|
+
const repositoryMemory = join(projectBase, '.crouter', 'memory');
|
|
81
|
+
mkdirSync(repositoryMemory, { recursive: true });
|
|
82
|
+
mkdirSync(projectMemoryDir(), { recursive: true });
|
|
83
|
+
writeFileSync(join(repositoryMemory, 'INDEX.md'), `---\nnamespace: ${PROJECT_NAMESPACE}\n---\nProject front door.\n`, 'utf8');
|
|
78
84
|
process.env['CRTR_HOME'] = canvasHome;
|
|
79
85
|
process.env['HOME'] = userHome;
|
|
80
86
|
process.env['CRTR_NODE_ID'] = NODE_ID;
|
|
@@ -102,28 +108,25 @@ describe('buildRefInventory (fixture-backed corpus)', () => {
|
|
|
102
108
|
else
|
|
103
109
|
process.env['CRTR_NODE_ID'] = prevNodeId;
|
|
104
110
|
});
|
|
105
|
-
test('
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
writeDoc(
|
|
109
|
-
writeDoc(
|
|
111
|
+
test('canonical qualification removes project shadowing while equal identities keep precedence', () => {
|
|
112
|
+
const local = 'crtr-inline-ref-fixture-shared';
|
|
113
|
+
const qualified = projectName(local);
|
|
114
|
+
writeDoc(userMemoryDir(), `${local}.md`, "kind: knowledge\nshort-form: 'user copy'");
|
|
115
|
+
writeDoc(projectMemoryDir(), `${local}.md`, "kind: knowledge\nshort-form: 'project copy'");
|
|
116
|
+
writeDoc(nodeScopeMemoryDir(), `${local}.md`, "kind: knowledge\nshort-form: 'node copy'");
|
|
110
117
|
let { refs, names } = buildRefInventory();
|
|
111
|
-
assert.ok(names.has(
|
|
112
|
-
|
|
118
|
+
assert.ok(names.has(local));
|
|
119
|
+
assert.ok(names.has(qualified));
|
|
120
|
+
let winner = refs.find((r) => r.name === local);
|
|
113
121
|
assert.equal(winner?.scope, 'node');
|
|
114
122
|
assert.equal(winner?.shortForm, 'node copy');
|
|
115
|
-
|
|
116
|
-
rmSync(join(nodeScopeMemoryDir(), `${name}.md`));
|
|
117
|
-
clearSessionCache();
|
|
118
|
-
({ refs, names } = buildRefInventory());
|
|
119
|
-
winner = refs.find((r) => r.name === name);
|
|
123
|
+
winner = refs.find((r) => r.name === qualified);
|
|
120
124
|
assert.equal(winner?.scope, 'project');
|
|
121
125
|
assert.equal(winner?.shortForm, 'project copy');
|
|
122
|
-
|
|
123
|
-
rmSync(join(projectMemoryDir(), `${name}.md`));
|
|
126
|
+
rmSync(join(nodeScopeMemoryDir(), `${local}.md`));
|
|
124
127
|
clearSessionCache();
|
|
125
128
|
({ refs, names } = buildRefInventory());
|
|
126
|
-
winner = refs.find((r) => r.name ===
|
|
129
|
+
winner = refs.find((r) => r.name === local);
|
|
127
130
|
assert.equal(winner?.scope, 'user');
|
|
128
131
|
assert.equal(winner?.shortForm, 'user copy');
|
|
129
132
|
});
|
|
@@ -133,41 +136,41 @@ describe('buildRefInventory (fixture-backed corpus)', () => {
|
|
|
133
136
|
writeDoc(projectMemoryDir(), 'crtr-inline-ref-fixture-gated.md', 'kind: knowledge\ngate:\n never-matches: true');
|
|
134
137
|
writeDoc(projectMemoryDir(), 'crtr-inline-ref-fixture-slash.md', 'kind: knowledge\nslash: true');
|
|
135
138
|
const { refs, names } = buildRefInventory();
|
|
136
|
-
for (const
|
|
139
|
+
for (const local of [
|
|
137
140
|
'crtr-inline-ref-fixture-knowledge',
|
|
138
141
|
'crtr-inline-ref-fixture-preference',
|
|
139
142
|
'crtr-inline-ref-fixture-gated',
|
|
140
143
|
'crtr-inline-ref-fixture-slash',
|
|
141
144
|
]) {
|
|
145
|
+
const name = projectName(local);
|
|
142
146
|
assert.ok(names.has(name), `expected ${name} to be referenceable`);
|
|
143
147
|
}
|
|
144
|
-
assert.equal(refs.find((r) => r.name === 'crtr-inline-ref-fixture-knowledge')?.kind, 'knowledge');
|
|
145
|
-
assert.equal(refs.find((r) => r.name === 'crtr-inline-ref-fixture-preference')?.kind, 'preference');
|
|
148
|
+
assert.equal(refs.find((r) => r.name === projectName('crtr-inline-ref-fixture-knowledge'))?.kind, 'knowledge');
|
|
149
|
+
assert.equal(refs.find((r) => r.name === projectName('crtr-inline-ref-fixture-preference'))?.kind, 'preference');
|
|
146
150
|
});
|
|
147
|
-
test('
|
|
148
|
-
|
|
151
|
+
test('a collapsed directory document keeps one canonical name and registers its browse node', () => {
|
|
152
|
+
const local = 'crtr-inline-ref-fixture-taste';
|
|
153
|
+
const name = projectName(local);
|
|
154
|
+
writeDoc(join(projectMemoryDir(), local), 'INDEX.md', "kind: knowledge\nshort-form: 'taste index'");
|
|
149
155
|
const { refs, names } = buildRefInventory();
|
|
150
|
-
assert.ok(names.has(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
assert.
|
|
154
|
-
assert.ok(!refs.some((r) => r.name === 'crtr-inline-ref-fixture-taste'));
|
|
155
|
-
const winner = refs.find((r) => r.name === 'crtr-inline-ref-fixture-taste/INDEX');
|
|
156
|
-
assert.equal(winner?.shortForm, 'taste index');
|
|
156
|
+
assert.ok(!names.has(`${name}/INDEX`));
|
|
157
|
+
assert.ok(names.has(name));
|
|
158
|
+
assert.ok(refs.some((r) => r.name === name));
|
|
159
|
+
assert.equal(refs.find((r) => r.name === name)?.shortForm, 'taste index');
|
|
157
160
|
});
|
|
158
|
-
test('a root INDEX
|
|
159
|
-
writeDoc(
|
|
160
|
-
|
|
161
|
+
test('a project root INDEX document is omitted from refs but its namespace remains usable', () => {
|
|
162
|
+
writeDoc(join(projectBase, '.crouter', 'memory'), 'INDEX.md', `namespace: ${PROJECT_NAMESPACE}\nkind: knowledge\nshort-form: 'project root index'`);
|
|
163
|
+
const local = 'crtr-inline-ref-fixture-sibling';
|
|
164
|
+
writeDoc(projectMemoryDir(), `${local}.md`, 'kind: knowledge');
|
|
161
165
|
const { refs, names } = buildRefInventory();
|
|
162
166
|
assert.ok(!names.has(''));
|
|
163
167
|
assert.ok(!refs.some((r) => r.name === ''));
|
|
164
|
-
|
|
165
|
-
assert.ok(names.has('crtr-inline-ref-fixture-sibling'));
|
|
168
|
+
assert.ok(names.has(projectName(local)));
|
|
166
169
|
});
|
|
167
170
|
test('refs are metadata-only: exactly {name, kind, scope, shortForm, gatewayVisible}, no body/path', () => {
|
|
168
171
|
writeDoc(projectMemoryDir(), 'crtr-inline-ref-fixture-meta.md', "kind: knowledge\nshort-form: 'hook'");
|
|
169
172
|
const { refs } = buildRefInventory();
|
|
170
|
-
const ref = refs.find((r) => r.name === 'crtr-inline-ref-fixture-meta');
|
|
173
|
+
const ref = refs.find((r) => r.name === projectName('crtr-inline-ref-fixture-meta'));
|
|
171
174
|
assert.ok(ref);
|
|
172
175
|
assert.deepEqual(Object.keys(ref).sort(), ['gatewayVisible', 'kind', 'name', 'scope', 'shortForm']);
|
|
173
176
|
});
|
|
@@ -176,30 +179,31 @@ describe('buildRefInventory (fixture-backed corpus)', () => {
|
|
|
176
179
|
writeDoc(projectMemoryDir(), 'crtr-inline-ref-fixture-hidden.md', 'kind: knowledge\nextensions:\n northlight:\n visibility: false');
|
|
177
180
|
writeDoc(projectMemoryDir(), 'crtr-inline-ref-fixture-visible.md', 'kind: knowledge');
|
|
178
181
|
const { refs, names } = buildRefInventory();
|
|
179
|
-
const hidden = refs.find((ref) => ref.name === 'crtr-inline-ref-fixture-hidden');
|
|
180
|
-
const visible = refs.find((ref) => ref.name === 'crtr-inline-ref-fixture-visible');
|
|
182
|
+
const hidden = refs.find((ref) => ref.name === projectName('crtr-inline-ref-fixture-hidden'));
|
|
183
|
+
const visible = refs.find((ref) => ref.name === projectName('crtr-inline-ref-fixture-visible'));
|
|
181
184
|
assert.equal(hidden?.gatewayVisible, false);
|
|
182
185
|
assert.equal(visible?.gatewayVisible, true);
|
|
183
|
-
assert.ok(names.has('crtr-inline-ref-fixture-hidden'));
|
|
184
|
-
assert.ok(names.has('crtr-inline-ref-fixture-visible'));
|
|
186
|
+
assert.ok(names.has(projectName('crtr-inline-ref-fixture-hidden')));
|
|
187
|
+
assert.ok(names.has(projectName('crtr-inline-ref-fixture-visible')));
|
|
185
188
|
assert.ok(refs.some((ref) => ref.scope === 'builtin'));
|
|
186
189
|
assert.ok(refs.filter((ref) => ref.scope === 'builtin').every((ref) => !ref.gatewayVisible));
|
|
187
190
|
const chatRows = gatewayMemoryRows(refs);
|
|
188
|
-
assert.ok(!chatRows.some((ref) => ref.name === 'crtr-inline-ref-fixture-hidden'));
|
|
189
|
-
assert.ok(chatRows.some((ref) => ref.name === 'crtr-inline-ref-fixture-visible'));
|
|
191
|
+
assert.ok(!chatRows.some((ref) => ref.name === projectName('crtr-inline-ref-fixture-hidden')));
|
|
192
|
+
assert.ok(chatRows.some((ref) => ref.name === projectName('crtr-inline-ref-fixture-visible')));
|
|
190
193
|
assert.ok(!chatRows.some((ref) => ref.scope === 'builtin'));
|
|
191
194
|
});
|
|
192
195
|
test('a malformed doc (no valid kind) is omitted from the inventory', () => {
|
|
193
196
|
writeDoc(projectMemoryDir(), 'crtr-inline-ref-fixture-malformed.md', "short-form: 'no kind here'");
|
|
194
197
|
const { names } = buildRefInventory();
|
|
195
|
-
assert.ok(!names.has('crtr-inline-ref-fixture-malformed'));
|
|
198
|
+
assert.ok(!names.has(projectName('crtr-inline-ref-fixture-malformed')));
|
|
196
199
|
});
|
|
197
|
-
test('
|
|
198
|
-
const
|
|
199
|
-
writeDoc(userMemoryDir(), `${
|
|
200
|
-
writeDoc(projectMemoryDir(), `${
|
|
200
|
+
test('project qualification keeps distinct scope identities separate', () => {
|
|
201
|
+
const local = 'crtr-inline-ref-fixture-dedup';
|
|
202
|
+
writeDoc(userMemoryDir(), `${local}.md`, 'kind: knowledge');
|
|
203
|
+
writeDoc(projectMemoryDir(), `${local}.md`, 'kind: knowledge');
|
|
201
204
|
const { refs } = buildRefInventory();
|
|
202
|
-
assert.equal(refs.filter((r) => r.name ===
|
|
205
|
+
assert.equal(refs.filter((r) => r.name === local).length, 1);
|
|
206
|
+
assert.equal(refs.filter((r) => r.name === projectName(local)).length, 1);
|
|
203
207
|
});
|
|
204
208
|
test('refs are stably sorted by winning scope precedence then name', () => {
|
|
205
209
|
writeDoc(nodeScopeMemoryDir(), 'crtr-inline-ref-fixture-b-node.md', 'kind: knowledge');
|
|
@@ -209,7 +213,7 @@ describe('buildRefInventory (fixture-backed corpus)', () => {
|
|
|
209
213
|
const fixtureRefs = refs.filter((r) => r.name.startsWith('crtr-inline-ref-fixture-'));
|
|
210
214
|
const idxA = fixtureRefs.findIndex((r) => r.name === 'crtr-inline-ref-fixture-a-node');
|
|
211
215
|
const idxB = fixtureRefs.findIndex((r) => r.name === 'crtr-inline-ref-fixture-b-node');
|
|
212
|
-
const idxC = fixtureRefs.findIndex((r) => r.name === 'crtr-inline-ref-fixture-c-project');
|
|
216
|
+
const idxC = fixtureRefs.findIndex((r) => r.name === projectName('crtr-inline-ref-fixture-c-project'));
|
|
213
217
|
// Node-scope docs (a, b) sort before the project-scope doc (c); within the
|
|
214
218
|
// same scope, alphabetically by name.
|
|
215
219
|
assert.ok(idxA < idxB, 'a-node before b-node (alphabetical within node scope)');
|