@north-light/crouter 0.3.227 → 0.3.228
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/client.d.ts +5 -5
- package/dist/api/client.js +3 -3
- package/dist/api/dto/broker-ops.d.ts +8 -0
- package/dist/api/dto/memory.d.ts +8 -7
- package/dist/api/dto/memory.js +2 -2
- package/dist/builtin-memory/05-kinds/design/00-base.md +3 -4
- package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +4 -3
- package/dist/builtin-memory/05-kinds/design/design-contract.md +3 -6
- package/dist/builtin-memory/05-kinds/plan/00-base.md +3 -4
- package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +4 -5
- package/dist/builtin-memory/05-kinds/plan/plan-contract.md +5 -13
- package/dist/builtin-memory/design/guide.md +38 -16
- package/dist/builtin-memory/design/roadmap.md +4 -4
- package/dist/builtin-memory/insights/init.md +5 -5
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
- package/dist/builtin-memory/internal/memory-loading.md +6 -6
- package/dist/builtin-memory/internal/plugins.md +1 -1
- package/dist/builtin-memory/plan/guide.md +53 -0
- package/dist/builtin-memory/plan/roadmap.md +10 -8
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +50 -37
- package/dist/clients/attach/__tests__/pane-tag-successor.test.js +56 -0
- package/dist/clients/attach/__tests__/profile-files.test.js +21 -0
- package/dist/clients/attach/chrome/inbox-strip.js +11 -7
- package/dist/clients/attach/session/pane-tag.d.ts +28 -2
- package/dist/clients/attach/session/pane-tag.js +48 -22
- package/dist/clients/attach/session/profile-files.d.ts +6 -0
- package/dist/clients/attach/session/profile-files.js +44 -11
- package/dist/clients/attach/slash/dispatch.js +4 -7
- package/dist/clients/attach/viewer.js +588 -596
- package/dist/commands/__tests__/surface-reload-target.test.js +21 -0
- package/dist/commands/api-client.d.ts +3 -7
- package/dist/commands/api-client.js +7 -12
- package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +292 -0
- package/dist/commands/memory/__tests__/repository-root-lint.test.js +146 -0
- package/dist/commands/memory/delete.js +51 -24
- package/dist/commands/memory/edit.js +50 -10
- package/dist/commands/memory/find.js +105 -56
- package/dist/commands/memory/history.js +51 -31
- package/dist/commands/memory/lint.d.ts +0 -9
- package/dist/commands/memory/lint.js +78 -322
- package/dist/commands/memory/list.d.ts +12 -4
- package/dist/commands/memory/list.js +64 -25
- package/dist/commands/memory/move.js +103 -73
- package/dist/commands/memory/origin.js +35 -5
- package/dist/commands/memory/read.d.ts +4 -0
- package/dist/commands/memory/read.js +132 -80
- package/dist/commands/memory/shared.d.ts +115 -25
- package/dist/commands/memory/shared.js +331 -74
- package/dist/commands/memory/write.js +72 -21
- package/dist/commands/memory.js +2 -2
- package/dist/commands/node/lifecycle.js +2 -2
- package/dist/commands/pkg/market-manage.js +25 -24
- package/dist/commands/pkg/plugin-manage.d.ts +22 -6
- package/dist/commands/pkg/plugin-manage.js +118 -25
- package/dist/commands/pkg/shared.d.ts +8 -0
- package/dist/commands/pkg/shared.js +23 -20
- package/dist/commands/surface-reload.d.ts +5 -0
- package/dist/commands/surface-reload.js +9 -1
- package/dist/commands/sys/__tests__/migrate.test.js +1140 -22
- package/dist/commands/sys/__tests__/sync-project-guidance.test.js +218 -0
- package/dist/commands/sys/migrate.js +126 -140
- package/dist/commands/sys/panels/profiles-panel.d.ts +66 -0
- package/dist/commands/sys/panels/profiles-panel.js +599 -0
- package/dist/commands/sys/settings-shell.d.ts +4 -2
- package/dist/commands/sys/settings-shell.js +35 -4
- package/dist/commands/sys/settings.js +21 -5
- package/dist/commands/sys/sync-deps.d.ts +2 -0
- package/dist/commands/sys/sync-deps.js +26 -15
- package/dist/commands/sys/sync-project-guidance.js +228 -145
- package/dist/commands/sys/sync-skills.js +28 -17
- package/dist/commands/sys/update.js +11 -3
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +15 -2
- package/dist/core/__tests__/config-change-delta.test.js +106 -0
- package/dist/core/__tests__/context-intro.test.js +13 -9
- package/dist/core/__tests__/daemon-boot.test.js +22 -16
- package/dist/core/__tests__/fixtures/memory-slash-live-probe.js +5 -1
- package/dist/core/__tests__/human-deliver.test.js +19 -0
- package/dist/core/__tests__/inline-memory-refs.test.js +50 -46
- package/dist/core/__tests__/{serial → integration}/broker-fork-seam.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/broker-sdk-wiring.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/broker-snapshot-history.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/command-plugins.test.js +94 -3
- package/dist/core/__tests__/{serial → integration}/deferred-no-wake.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/flagship-lifecycle.test.js +13 -27
- package/dist/core/__tests__/{serial → integration}/host-teardown-process-group.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/human-deliver-e2e.test.js +1 -1
- package/dist/core/__tests__/{live-mutation-verbs.test.js → integration/live-mutation-verbs.test.js} +22 -33
- package/dist/core/__tests__/{serial → integration}/live-mutation.test.js +21 -41
- package/dist/core/__tests__/integration/refresh-stall-recycle.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/refresh-stall-recycle.test.js +1 -1
- package/dist/core/__tests__/integration/revive.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/revive.test.js +38 -19
- package/dist/core/__tests__/integration/spawn-root.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/spawn-root.test.js +104 -1
- package/dist/core/__tests__/integration/subscription-delivery.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/subscription-delivery.test.js +1 -1
- package/dist/core/__tests__/integration/tmux-surface.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/tmux-surface.test.js +1 -1
- package/dist/core/__tests__/integration/worktree-land.test.d.ts +1 -0
- package/dist/core/__tests__/integration/worktree-land.test.js +400 -0
- package/dist/core/__tests__/integration/worktree-reap.test.d.ts +1 -0
- package/dist/core/__tests__/{serial/worktree.test.js → integration/worktree-reap.test.js} +6 -338
- package/dist/core/__tests__/kickoff.test.js +16 -5
- package/dist/core/__tests__/memory-resolver-precedence.test.js +122 -91
- package/dist/core/__tests__/nested-store-discovery.test.js +5 -3
- package/dist/core/__tests__/on-read-crouter-home-fence.test.js +2 -1
- package/dist/core/__tests__/on-read-dedup-resume.test.js +39 -27
- package/dist/core/__tests__/on-read-nested-store.test.js +19 -13
- package/dist/core/__tests__/profile-project-memory-delivery.test.js +138 -44
- package/dist/core/__tests__/repository-association.test.d.ts +1 -0
- package/dist/core/__tests__/repository-association.test.js +153 -0
- package/dist/core/__tests__/repository-root-identity.test.d.ts +1 -0
- package/dist/core/__tests__/repository-root-identity.test.js +219 -0
- package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.js +27 -11
- package/dist/core/__tests__/warm-claim-preference-snapshot.test.d.ts +1 -0
- package/dist/core/__tests__/warm-claim-preference-snapshot.test.js +40 -0
- package/dist/core/canvas/canvas.d.ts +3 -0
- package/dist/core/canvas/canvas.js +30 -10
- package/dist/core/canvas/db.js +28 -1
- package/dist/core/canvas/paths.d.ts +4 -6
- package/dist/core/canvas/paths.js +8 -6
- package/dist/core/canvas/render-source.js +2 -2
- package/dist/core/canvas/types.d.ts +9 -13
- package/dist/core/exclusive-lock.d.ts +2 -0
- package/dist/core/exclusive-lock.js +21 -0
- package/dist/core/git.d.ts +0 -1
- package/dist/core/git.js +0 -3
- package/dist/core/human/__tests__/integration/inbox-core.test.d.ts +1 -0
- package/dist/core/human/feedback-companion.js +3 -0
- package/dist/core/human/scan.js +8 -1
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +2 -1
- package/dist/core/memory/doc-link-grammar.js +2 -2
- package/dist/core/memory/history.d.ts +24 -0
- package/dist/core/memory/history.js +66 -1
- package/dist/core/memory/identity.d.ts +65 -0
- package/dist/core/memory/identity.js +185 -0
- package/dist/core/memory/inline-ref-guidance.d.ts +1 -1
- package/dist/core/memory/inline-ref-guidance.js +1 -1
- package/dist/core/memory/inline-ref-inventory.d.ts +3 -10
- package/dist/core/memory/inline-ref-inventory.js +30 -62
- package/dist/core/memory/lint.d.ts +129 -0
- package/dist/core/memory/lint.js +517 -0
- package/dist/core/memory/project-namespace.d.ts +31 -0
- package/dist/core/memory/project-namespace.js +85 -0
- package/dist/core/memory/repository-association.d.ts +31 -0
- package/dist/core/memory/repository-association.js +129 -0
- package/dist/core/memory/tree.d.ts +39 -0
- package/dist/core/memory/tree.js +93 -0
- package/dist/core/memory-resolver.d.ts +193 -90
- package/dist/core/memory-resolver.js +461 -375
- package/dist/core/nested-stores.js +6 -13
- package/dist/core/profiles/select.d.ts +4 -1
- package/dist/core/profiles/select.js +108 -48
- package/dist/core/review/__tests__/capture-origin.test.js +2 -2
- package/dist/core/review/__tests__/stage-identity.test.js +2 -2
- package/dist/core/review/companion.js +11 -2
- package/dist/core/runtime/bearings.d.ts +2 -2
- package/dist/core/runtime/bearings.js +3 -3
- package/dist/core/runtime/broker/daemon-ops.d.ts +2 -2
- package/dist/core/runtime/broker/rebind.js +5 -0
- package/dist/core/runtime/broker-extension-render.d.ts +7 -3
- package/dist/core/runtime/broker-extension-render.js +10 -5
- package/dist/core/runtime/broker-persona-guidance.d.ts +19 -5
- package/dist/core/runtime/broker-persona-guidance.js +122 -27
- package/dist/core/runtime/deliver-live.d.ts +16 -4
- package/dist/core/runtime/deliver-live.js +29 -15
- package/dist/core/runtime/kickoff.d.ts +3 -3
- package/dist/core/runtime/kickoff.js +8 -16
- package/dist/core/runtime/lifecycle.js +2 -3
- package/dist/core/runtime/nodes.d.ts +3 -4
- package/dist/core/runtime/nodes.js +3 -4
- package/dist/core/runtime/persona.d.ts +8 -12
- package/dist/core/runtime/persona.js +24 -96
- package/dist/core/runtime/promote.d.ts +2 -2
- package/dist/core/runtime/promote.js +8 -21
- package/dist/core/runtime/revive.js +20 -17
- package/dist/core/runtime/spawn.js +18 -7
- package/dist/core/runtime/tmux-bindings.js +2 -3
- package/dist/core/runtime/warm-pool.js +3 -4
- package/dist/core/scope.js +2 -0
- package/dist/core/self-update.d.ts +0 -2
- package/dist/core/self-update.js +2 -35
- package/dist/core/substrate/__tests__/surface-match-memory-read.test.d.ts +1 -0
- package/dist/core/substrate/__tests__/surface-match-memory-read.test.js +28 -0
- package/dist/core/substrate/index.d.ts +2 -2
- package/dist/core/substrate/index.js +1 -1
- package/dist/core/substrate/injected-store.d.ts +43 -27
- package/dist/core/substrate/injected-store.js +208 -104
- package/dist/core/substrate/listings.d.ts +19 -12
- package/dist/core/substrate/listings.js +75 -52
- package/dist/core/substrate/on-read-node.d.ts +4 -7
- package/dist/core/substrate/on-read-node.js +6 -8
- package/dist/core/substrate/on-read.d.ts +22 -25
- package/dist/core/substrate/on-read.js +103 -147
- package/dist/core/substrate/render-node.d.ts +4 -7
- package/dist/core/substrate/render-node.js +5 -7
- package/dist/core/substrate/render.d.ts +21 -3
- package/dist/core/substrate/render.js +291 -223
- package/dist/core/substrate/schema.d.ts +1 -13
- package/dist/core/substrate/schema.js +5 -40
- package/dist/core/substrate/session-cache.d.ts +14 -4
- package/dist/core/substrate/session-cache.js +40 -22
- package/dist/core/substrate/surface-match.d.ts +9 -7
- package/dist/core/substrate/surface-match.js +26 -25
- package/dist/daemon/__tests__/helpers/source-daemon.d.ts +30 -0
- package/dist/daemon/__tests__/helpers/source-daemon.js +174 -0
- package/dist/daemon/__tests__/integration/migration-startup.test.d.ts +1 -0
- package/dist/daemon/__tests__/integration/migration-startup.test.js +97 -0
- package/dist/daemon/api/__tests__/bridge-heartbeat.test.js +21 -5
- package/dist/daemon/api/handlers/broker-ops.js +21 -16
- package/dist/daemon/api/handlers/memory.js +2 -0
- package/dist/daemon/crtrd.js +2 -0
- package/dist/daemon/human/finish.js +8 -1
- package/dist/daemon/manage.d.ts +0 -1
- package/dist/daemon/manage.js +7 -16
- package/dist/daemon/startup-policy.d.ts +1 -0
- package/dist/daemon/startup-policy.js +1 -0
- package/dist/migrations/001-surfaces-frontmatter.js +21 -109
- package/dist/migrations/002-profile-project-memory.js +1 -0
- package/dist/migrations/003-repository-root-memory-identity/front-door.d.ts +26 -0
- package/dist/migrations/003-repository-root-memory-identity/front-door.js +231 -0
- package/dist/migrations/003-repository-root-memory-identity/index.d.ts +2 -0
- package/dist/migrations/003-repository-root-memory-identity/index.js +513 -0
- package/dist/migrations/003-repository-root-memory-identity/references.d.ts +95 -0
- package/dist/migrations/003-repository-root-memory-identity/references.js +469 -0
- package/dist/migrations/003-repository-root-memory-identity/repository-facts.d.ts +39 -0
- package/dist/migrations/003-repository-root-memory-identity/repository-facts.js +349 -0
- package/dist/migrations/__tests__/activation-concurrency.test.d.ts +1 -0
- package/dist/migrations/__tests__/activation-concurrency.test.js +145 -0
- package/dist/migrations/__tests__/activation.test.d.ts +1 -0
- package/dist/migrations/__tests__/activation.test.js +149 -0
- package/dist/migrations/__tests__/deletion-and-root-declaration.test.d.ts +1 -0
- package/dist/migrations/__tests__/deletion-and-root-declaration.test.js +149 -0
- package/dist/migrations/activation.d.ts +16 -0
- package/dist/migrations/activation.js +78 -0
- package/dist/migrations/convergent.d.ts +14 -3
- package/dist/migrations/convergent.js +21 -10
- package/dist/migrations/corpus.d.ts +78 -0
- package/dist/migrations/corpus.js +497 -0
- package/dist/migrations/frontmatter-splice.d.ts +15 -0
- package/dist/migrations/frontmatter-splice.js +176 -0
- package/dist/migrations/registry.d.ts +6 -1
- package/dist/migrations/registry.js +7 -2
- package/dist/migrations/runner.d.ts +41 -0
- package/dist/migrations/runner.js +81 -0
- package/dist/migrations/types.d.ts +148 -9
- package/dist/migrations/types.js +9 -2
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +225 -17
- package/dist/pi-extensions/__tests__/canvas-goal-capture-envelope.test.js +11 -3
- package/dist/pi-extensions/canvas-context-intro.d.ts +3 -5
- package/dist/pi-extensions/canvas-context-intro.js +50 -46
- package/dist/pi-extensions/canvas-doc-substrate.d.ts +1 -8
- package/dist/pi-extensions/canvas-doc-substrate.js +55 -122
- package/dist/pi-extensions/canvas-stophook.js +6 -13
- package/dist/shared/generated-context.d.ts +0 -3
- package/dist/shared/generated-context.js +0 -57
- package/dist/shared/tool-groups.js +2 -3
- package/package.json +5 -4
- package/runtime.lock.json +2 -2
- /package/dist/api/__tests__/{serial → integration}/client.test.d.ts +0 -0
- /package/dist/api/__tests__/{serial → integration}/client.test.js +0 -0
- /package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/{serial → integration}/provider-rotation.test.ts +0 -0
- /package/dist/clients/{inbox/__tests__/serial/inbox-controller.test.d.ts → attach/__tests__/pane-tag-successor.test.d.ts} +0 -0
- /package/dist/clients/{inbox/__tests__/serial/mount-panel.test.d.ts → attach/__tests__/profile-files.test.d.ts} +0 -0
- /package/dist/{core/__tests__/live-mutation-verbs.test.d.ts → clients/inbox/__tests__/integration/inbox-controller.test.d.ts} +0 -0
- /package/dist/clients/inbox/__tests__/{serial → integration}/inbox-controller.test.js +0 -0
- /package/dist/{core/__tests__/serial/broker-fork-seam.test.d.ts → clients/inbox/__tests__/integration/mount-panel.test.d.ts} +0 -0
- /package/dist/clients/inbox/__tests__/{serial → integration}/mount-panel.test.js +0 -0
- /package/dist/{core/__tests__/serial/broker-sdk-wiring.test.d.ts → commands/__tests__/surface-reload-target.test.d.ts} +0 -0
- /package/dist/{core/__tests__/serial/broker-snapshot-history.test.d.ts → commands/memory/__tests__/command-selector-and-mutation-guards.test.d.ts} +0 -0
- /package/dist/{core/__tests__/serial/command-plugins.test.d.ts → commands/memory/__tests__/repository-root-lint.test.d.ts} +0 -0
- /package/dist/{core/__tests__/serial/deferred-no-wake.test.d.ts → commands/sys/__tests__/sync-project-guidance.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/flagship-lifecycle.test.d.ts → config-change-delta.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/host-teardown-process-group.test.d.ts → integration/broker-fork-seam.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/human-deliver-e2e.test.d.ts → integration/broker-sdk-wiring.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/live-mutation.test.d.ts → integration/broker-snapshot-history.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/refresh-stall-recycle.test.d.ts → integration/command-plugins.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/revive.test.d.ts → integration/deferred-no-wake.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/spawn-root.test.d.ts → integration/flagship-lifecycle.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/subscription-delivery.test.d.ts → integration/host-teardown-process-group.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/tmux-surface.test.d.ts → integration/human-deliver-e2e.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/worktree.test.d.ts → integration/live-mutation-verbs.test.d.ts} +0 -0
- /package/dist/core/{human/__tests__/serial/inbox-core.test.d.ts → __tests__/integration/live-mutation.test.d.ts} +0 -0
- /package/dist/core/human/__tests__/{serial → integration}/inbox-core.test.js +0 -0
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
//
|
|
6
6
|
// One real fixture drives the real scope resolver, the real boot renderer, the
|
|
7
7
|
// real workspace-open renderer, and the real `crtr memory read` leaf: a profile
|
|
8
|
-
// whose four projects sit at none/name/preview/content in manifest order,
|
|
9
|
-
// the `preview` project as cwd
|
|
10
|
-
//
|
|
8
|
+
// whose first four projects sit at none/name/preview/content in manifest order,
|
|
9
|
+
// with the `preview` project as cwd, plus two more projects declaring ONE shared
|
|
10
|
+
// namespace so a single canonical identity has two physical candidates split
|
|
11
|
+
// across a `none` and a `content` relationship.
|
|
11
12
|
//
|
|
12
13
|
// Migration convergence is NOT here — it lives in migration.test.ts.
|
|
13
14
|
//
|
|
@@ -25,8 +26,13 @@ import { memoryDir } from '../runtime/memory.js';
|
|
|
25
26
|
import { clearSessionCache } from '../substrate/session-cache.js';
|
|
26
27
|
import { renderKnowledgeBlock } from '../substrate/render-node.js';
|
|
27
28
|
import { renderWorkspaceOpenDocs } from '../substrate/on-read-node.js';
|
|
28
|
-
import {
|
|
29
|
+
import { documentExposedAtOrAbove, emptyContextExposureState, exposedAtOrAbove, exposureTarget, loadContextExposureState, saveContextExposureState, } from '../substrate/injected-store.js';
|
|
29
30
|
import { readLeaf } from '../../commands/memory/read.js';
|
|
31
|
+
import { listLeaf } from '../../commands/memory/list.js';
|
|
32
|
+
import { findLeaf } from '../../commands/memory/find.js';
|
|
33
|
+
function freshTranscriptTarget() {
|
|
34
|
+
return exposureTarget(emptyContextExposureState(), 'transcript');
|
|
35
|
+
}
|
|
30
36
|
const B = {
|
|
31
37
|
pnone: 'BODY-T9-PNONE',
|
|
32
38
|
pname: 'BODY-T9-PNAME',
|
|
@@ -38,6 +44,8 @@ const B = {
|
|
|
38
44
|
nameFromPcontent: 'BODY-T9-NAMEDUP-FROM-PCONTENT',
|
|
39
45
|
userDupFromPnone: 'BODY-T9-USERDUP-FROM-PNONE',
|
|
40
46
|
userDupFromUser: 'BODY-T9-USERDUP-FROM-USER',
|
|
47
|
+
dupNear: 'BODY-T9-DUP-FROM-NEAR',
|
|
48
|
+
dupFar: 'BODY-T9-DUP-FROM-FAR',
|
|
41
49
|
kindSplit: 'BODY-T9-KIND-SPLIT',
|
|
42
50
|
documentGateWins: 'BODY-T9-DOCUMENT-GATE-WINS',
|
|
43
51
|
};
|
|
@@ -91,31 +99,50 @@ before(() => {
|
|
|
91
99
|
canvasHome = realpathSync(mkdtempSync(join(tmpdir(), 'crtr-ppm-canvas-')));
|
|
92
100
|
process.env['HOME'] = home;
|
|
93
101
|
process.env['CRTR_HOME'] = canvasHome;
|
|
94
|
-
|
|
102
|
+
// A project store answers only under the namespace its root INDEX.md
|
|
103
|
+
// declares; without that declaration the store does not mount at all.
|
|
104
|
+
const store = (slug, namespace) => {
|
|
95
105
|
const dir = join(home, slug, '.crouter', 'memory');
|
|
96
106
|
mkdirSync(dir, { recursive: true });
|
|
107
|
+
writeFileSync(join(dir, 'INDEX.md'), [
|
|
108
|
+
'---',
|
|
109
|
+
'kind: knowledge',
|
|
110
|
+
`namespace: ${namespace}`,
|
|
111
|
+
`when-and-why-to-read: "When the ${slug} store is addressed"`,
|
|
112
|
+
'---',
|
|
113
|
+
'',
|
|
114
|
+
`STORE-ROOT-${slug.toUpperCase()}`,
|
|
115
|
+
'',
|
|
116
|
+
].join('\n'));
|
|
97
117
|
return dir;
|
|
98
118
|
};
|
|
99
|
-
const pnone = store('pnone');
|
|
100
|
-
const pname = store('pname');
|
|
101
|
-
const ppreview = store('ppreview');
|
|
102
|
-
const pcontent = store('pcontent');
|
|
119
|
+
const pnone = store('pnone', 'pnone');
|
|
120
|
+
const pname = store('pname', 'pname');
|
|
121
|
+
const ppreview = store('ppreview', 'ppreview');
|
|
122
|
+
const pcontent = store('pcontent', 'pcontent');
|
|
123
|
+
// Two stores declaring ONE namespace — the only way two projects can hold the
|
|
124
|
+
// same canonical identity.
|
|
125
|
+
const pdupnear = store('pdupnear', 'shared');
|
|
126
|
+
const pdupfar = store('pdupfar', 'shared');
|
|
103
127
|
const user = join(home, '.crouter', 'memory');
|
|
104
128
|
mkdirSync(user, { recursive: true });
|
|
105
129
|
writeDoc(pnone, 't9-pnone-doc', autoDoc('When the none project delivers', B.pnone));
|
|
106
130
|
writeDoc(pname, 't9-pname-doc', autoDoc('When the name project delivers', B.pname));
|
|
107
131
|
ppreviewDocPath = writeDoc(ppreview, 't9-ppreview-doc', autoDoc('When the cwd project delivers', B.ppreview));
|
|
108
132
|
writeDoc(pcontent, 't9-pcontent-doc', autoDoc('When the content project delivers', B.pcontent));
|
|
109
|
-
//
|
|
110
|
-
//
|
|
133
|
+
// Equal-canonical collision: `shared/dup-doc` in both stores that declare
|
|
134
|
+
// `shared`, the `none`-capped one nearer in source order. The cap must apply
|
|
135
|
+
// BEFORE dedupe, so the capped nearer candidate yields to the farther one
|
|
136
|
+
// instead of suppressing it.
|
|
137
|
+
writeDoc(pdupnear, 'dup-doc', autoDoc('When the shared identity resolves', B.dupNear));
|
|
138
|
+
writeDoc(pdupfar, 'dup-doc', autoDoc('When the shared identity resolves', B.dupFar));
|
|
139
|
+
// Distinct identities that only LOOK like collisions: each project composes
|
|
140
|
+
// its own namespace, so a capped project cannot suppress another store's
|
|
141
|
+
// document that merely shares a local name.
|
|
111
142
|
writeDoc(pnone, 't9-shared-doc', autoDoc('When the shared name resolves', B.sharedFromPnone));
|
|
112
143
|
writeDoc(pcontent, 't9-shared-doc', autoDoc('When the shared name resolves', B.sharedFromPcontent));
|
|
113
|
-
// Collision two: `name` vs `content`. A non-none cap keeps its project's
|
|
114
|
-
// precedence and discloses at the capped rung — it does not step aside.
|
|
115
144
|
writeDoc(pname, 't9-namedup-doc', autoDoc('When the name-capped duplicate resolves', B.nameFromPname));
|
|
116
145
|
writeDoc(pcontent, 't9-namedup-doc', autoDoc('When the name-capped duplicate resolves', B.nameFromPcontent));
|
|
117
|
-
// Collision three: `none` vs the wider user scope, which project precedence
|
|
118
|
-
// would otherwise beat.
|
|
119
146
|
writeDoc(pnone, 't9-userdup-doc', autoDoc('When the user duplicate resolves', B.userDupFromPnone));
|
|
120
147
|
writeDoc(user, 't9-userdup-doc', autoDoc('When the user duplicate resolves', B.userDupFromUser));
|
|
121
148
|
writeDoc(user, 't9-kind-split', [
|
|
@@ -151,6 +178,8 @@ before(() => {
|
|
|
151
178
|
{ path: join(home, 'pname'), memory: 'name' },
|
|
152
179
|
{ path: join(home, 'ppreview'), memory: 'preview' },
|
|
153
180
|
{ path: join(home, 'pcontent'), memory: 'content' },
|
|
181
|
+
{ path: join(home, 'pdupnear'), memory: 'none' },
|
|
182
|
+
{ path: join(home, 'pdupfar'), memory: 'content' },
|
|
154
183
|
]).profileId;
|
|
155
184
|
process.env['CRTR_PROFILE_ID'] = profileId;
|
|
156
185
|
// The node works INSIDE a capped project: the reported failure.
|
|
@@ -183,7 +212,7 @@ function newNode(kind = 'general') {
|
|
|
183
212
|
return spawnNode({ kind, cwd: cwdProject, parent: null, profile_id: profileId }).node_id;
|
|
184
213
|
}
|
|
185
214
|
test('boot delivery caps every profile project at its relationship, including the one used as cwd', () => {
|
|
186
|
-
const boot = renderKnowledgeBlock(newNode(),
|
|
215
|
+
const boot = renderKnowledgeBlock(newNode(), freshTranscriptTarget());
|
|
187
216
|
// The control: an uncapped relationship still renders its authored content.
|
|
188
217
|
assert.ok(boot.includes(B.pcontent), `the content-capped project renders its body; got:\n${boot}`);
|
|
189
218
|
// The reported failure. cwd reaches this root before the manifest pointer
|
|
@@ -197,20 +226,20 @@ test('boot delivery caps every profile project at its relationship, including th
|
|
|
197
226
|
});
|
|
198
227
|
test('surface-entry gates select the rung by node kind while the document gate remains absolute', () => {
|
|
199
228
|
for (const kind of ['design', 'spec', 'plan', 'developer']) {
|
|
200
|
-
const boot = renderKnowledgeBlock(newNode(kind),
|
|
229
|
+
const boot = renderKnowledgeBlock(newNode(kind), freshTranscriptTarget());
|
|
201
230
|
assert.ok(boot.includes(B.kindSplit), `${kind} receives the content rung; got:\n${boot}`);
|
|
202
231
|
}
|
|
203
232
|
for (const kind of ['general', 'review', 'advisor']) {
|
|
204
|
-
const boot = renderKnowledgeBlock(newNode(kind),
|
|
233
|
+
const boot = renderKnowledgeBlock(newNode(kind), freshTranscriptTarget());
|
|
205
234
|
assert.match(boot, /t9-kind-split {2}# read when: When the node needs this kind-specific guide\./, `${kind} receives the preview rung; got:\n${boot}`);
|
|
206
235
|
assert.ok(!boot.includes(B.kindSplit), `${kind} does not receive the body`);
|
|
207
236
|
}
|
|
208
237
|
for (const kind of ['explore', 'personal-assistant']) {
|
|
209
|
-
const boot = renderKnowledgeBlock(newNode(kind),
|
|
238
|
+
const boot = renderKnowledgeBlock(newNode(kind), freshTranscriptTarget());
|
|
210
239
|
assert.ok(!boot.includes('t9-kind-split'), `${kind} receives no kind-split entry; got:\n${boot}`);
|
|
211
240
|
assert.ok(!boot.includes(B.kindSplit), `${kind} receives no kind-split body`);
|
|
212
241
|
}
|
|
213
|
-
const developer = renderKnowledgeBlock(newNode('developer'),
|
|
242
|
+
const developer = renderKnowledgeBlock(newNode('developer'), freshTranscriptTarget());
|
|
214
243
|
assert.ok(!developer.includes('t9-document-gate-wins'), `a failing document gate excludes every entry; got:\n${developer}`);
|
|
215
244
|
assert.ok(!developer.includes(B.documentGateWins), 'a failing document gate excludes its content');
|
|
216
245
|
});
|
|
@@ -229,56 +258,121 @@ test('a node-local doc floors to name only without boot entries', () => {
|
|
|
229
258
|
'BODY-T9-NODE-LOCAL-ENTRY-GATED',
|
|
230
259
|
'',
|
|
231
260
|
].join('\n'));
|
|
232
|
-
const boot = renderKnowledgeBlock(node,
|
|
261
|
+
const boot = renderKnowledgeBlock(node, freshTranscriptTarget());
|
|
233
262
|
assert.match(boot, /[├└]─ t9-node-local-bootless$/m, `a bootless node-local doc falls to name; got:\n${boot}`);
|
|
234
263
|
assert.ok(!boot.includes('BODY-T9-NODE-LOCAL-BOOTLESS'), 'the fallback discloses no body');
|
|
235
264
|
assert.ok(!boot.includes('t9-node-local-entry-gated'), `ineligible boot entries keep a node-local doc absent; got:\n${boot}`);
|
|
236
265
|
assert.ok(!boot.includes('BODY-T9-NODE-LOCAL-ENTRY-GATED'), 'ineligible boot entries disclose no body');
|
|
237
266
|
});
|
|
238
267
|
test('a `none` project contributes nothing to boot — no body, no name, no hidden count, no shadow', () => {
|
|
239
|
-
const boot = renderKnowledgeBlock(newNode(),
|
|
268
|
+
const boot = renderKnowledgeBlock(newNode(), freshTranscriptTarget());
|
|
240
269
|
assert.ok(!boot.includes(B.pnone), 'a none-capped body must not render');
|
|
241
|
-
assert.ok(!boot.includes('
|
|
242
|
-
// Hidden counts are disclosure too: the control
|
|
243
|
-
// does surface as a `[+N more]` count, so the none project's
|
|
244
|
-
|
|
270
|
+
assert.ok(!boot.includes('pnone'), 'a none-capped store must not appear as a name either — not even its namespace row');
|
|
271
|
+
// Hidden counts are disclosure too: the control project's namespace row proves
|
|
272
|
+
// a bootless doc does surface as a `[+N more]` count, so the none project's
|
|
273
|
+
// total absence is the cap and not a rendering quirk.
|
|
274
|
+
assert.match(boot, /pcontent\/\n[^\n]*\[\+\d+ more\]/, `the control project's row shows its hidden count; got:\n${boot}`);
|
|
245
275
|
assert.ok(!boot.includes('t9-none-hidden'), 'a none-capped project contributes no `[+N more]` count');
|
|
246
276
|
// Shadowing: `none` wins name dedupe on precedence, so a cap applied after
|
|
247
277
|
// dedupe would silently delete the winner instead of deferring to it.
|
|
248
|
-
|
|
249
|
-
|
|
278
|
+
// A capped project cannot suppress a document it never shared an identity
|
|
279
|
+
// with: each store composes its own namespace, so these are distinct names.
|
|
280
|
+
assert.ok(boot.includes(B.sharedFromPcontent) && !boot.includes(B.sharedFromPnone), `pcontent/t9-shared-doc delivers while the none project contributes nothing; got:\n${boot}`);
|
|
281
|
+
assert.ok(boot.includes(B.userDupFromUser) && !boot.includes(B.userDupFromPnone), `the unqualified user identity delivers while the none project contributes nothing; got:\n${boot}`);
|
|
282
|
+
});
|
|
283
|
+
test('a capped nearer candidate yields the shared identity instead of suppressing it', async () => {
|
|
284
|
+
// Two project stores declare the SAME namespace, so `shared/dup-doc` has two
|
|
285
|
+
// physical candidates. Automatic delivery filters candidates by the profile
|
|
286
|
+
// cap BEFORE dedupe, so the `none`-capped nearer one drops out and the farther
|
|
287
|
+
// one delivers; a deliberate read stays winner-only on source order.
|
|
288
|
+
const boot = renderKnowledgeBlock(newNode(), freshTranscriptTarget());
|
|
289
|
+
assert.ok(boot.includes(B.dupFar), `the farther uncapped candidate delivers the shared identity; got:\n${boot}`);
|
|
290
|
+
assert.ok(!boot.includes(B.dupNear), 'the none-capped nearer candidate delivers nothing');
|
|
291
|
+
const read = (await readLeaf.run({
|
|
292
|
+
name: 'shared/dup-doc',
|
|
293
|
+
kind: undefined,
|
|
294
|
+
frontmatter: false,
|
|
295
|
+
scope: undefined,
|
|
296
|
+
dir: undefined,
|
|
297
|
+
}));
|
|
298
|
+
assert.ok(read.content.includes(B.dupNear) && !read.content.includes(B.dupFar), `a deliberate read resolves the source-order winner; got:\n${read.content}`);
|
|
250
299
|
});
|
|
251
300
|
test('a non-none cap keeps its project precedence and discloses the duplicate at the capped rung', () => {
|
|
252
|
-
const boot = renderKnowledgeBlock(newNode(),
|
|
253
|
-
assert.match(boot, /[├└]─ t9-namedup-doc$/m, `the name-capped
|
|
254
|
-
assert.ok(!boot.includes(B.nameFromPname), '
|
|
255
|
-
assert.ok(
|
|
301
|
+
const boot = renderKnowledgeBlock(newNode(), freshTranscriptTarget());
|
|
302
|
+
assert.match(boot, /[├└]─ t9-namedup-doc$/m, `the name-capped project discloses its doc as a bare name; got:\n${boot}`);
|
|
303
|
+
assert.ok(!boot.includes(B.nameFromPname), 'a name-capped doc discloses at `name`, never its body');
|
|
304
|
+
assert.ok(boot.includes(B.nameFromPcontent), 'the content project delivers its own distinct identity in full');
|
|
256
305
|
});
|
|
257
306
|
test('workspace-open clamps each project store the same way boot does', () => {
|
|
258
|
-
const opened = renderWorkspaceOpenDocs(newNode(),
|
|
307
|
+
const opened = renderWorkspaceOpenDocs(newNode(), freshTranscriptTarget());
|
|
259
308
|
assert.ok(opened.includes(B.pcontent), `the content-capped project delivers its body; got:\n${opened}`);
|
|
260
309
|
assert.ok(!opened.includes(B.ppreview), `the cwd project must not deliver its body; got:\n${opened}`);
|
|
261
|
-
assert.match(opened, /name="t9-ppreview-doc" readWhen="When the cwd project delivers\./, `the cwd project delivers its preview line; got:\n${opened}`);
|
|
262
|
-
assert.match(opened, /name="t9-pname-doc"[^>]*\/>/, `the name-capped doc delivers name-only; got:\n${opened}`);
|
|
310
|
+
assert.match(opened, /name="ppreview\/t9-ppreview-doc" readWhen="When the cwd project delivers\./, `the cwd project delivers its preview line under its canonical name; got:\n${opened}`);
|
|
311
|
+
assert.match(opened, /name="pname\/t9-pname-doc"[^>]*\/>/, `the name-capped doc delivers name-only; got:\n${opened}`);
|
|
263
312
|
assert.ok(!opened.includes('t9-pnone-doc'), 'the none-capped project delivers nothing at workspace-open');
|
|
264
313
|
assert.ok(!opened.includes(B.sharedFromPnone), 'nor its side of a name collision');
|
|
265
314
|
});
|
|
315
|
+
test('memory list and find record synthetic pages without crediting matched documents', async () => {
|
|
316
|
+
const node = newNode();
|
|
317
|
+
process.env['CRTR_NODE_ID'] = node;
|
|
318
|
+
try {
|
|
319
|
+
const listed = await listLeaf.run({
|
|
320
|
+
kind: undefined,
|
|
321
|
+
scope: undefined,
|
|
322
|
+
dir: undefined,
|
|
323
|
+
paths: false,
|
|
324
|
+
limit: 50,
|
|
325
|
+
cursor: undefined,
|
|
326
|
+
});
|
|
327
|
+
const found = await findLeaf.run({
|
|
328
|
+
query: 'ppreview/t9-ppreview-doc',
|
|
329
|
+
kind: undefined,
|
|
330
|
+
scope: undefined,
|
|
331
|
+
dir: undefined,
|
|
332
|
+
grep: false,
|
|
333
|
+
body: false,
|
|
334
|
+
limit: 10,
|
|
335
|
+
cursor: undefined,
|
|
336
|
+
minScore: undefined,
|
|
337
|
+
paths: false,
|
|
338
|
+
});
|
|
339
|
+
assert.ok(listed.items.some((item) => item.name === 'ppreview/t9-ppreview-doc'), 'the inventory page listed the fixture document under its canonical name');
|
|
340
|
+
assert.ok(found.hits.some((hit) => hit.name === 'ppreview/t9-ppreview-doc'), 'the search page matched the fixture document under its canonical name');
|
|
341
|
+
const state = loadContextExposureState(node);
|
|
342
|
+
const listingKeys = [...state.exposures.keys()].filter((identity) => identity.startsWith('listing:memory-'));
|
|
343
|
+
assert.equal(listingKeys.length, 2, 'list and find each register their delivered page');
|
|
344
|
+
assert.ok(listingKeys.some((identity) => identity.startsWith('listing:memory-list:')));
|
|
345
|
+
assert.ok(listingKeys.some((identity) => identity.startsWith('listing:memory-find:')));
|
|
346
|
+
assert.ok(listingKeys.every((identity) => exposedAtOrAbove(state, identity, 'content')), 'each synthetic page is registered at its complete delivered representation');
|
|
347
|
+
assert.equal(documentExposedAtOrAbove(state, ppreviewDocPath, B.ppreview, 'name'), false, 'inventory metadata and matching lines never claim disclosure of the document itself');
|
|
348
|
+
}
|
|
349
|
+
finally {
|
|
350
|
+
delete process.env['CRTR_NODE_ID'];
|
|
351
|
+
}
|
|
352
|
+
});
|
|
266
353
|
test('an explicit `crtr memory read` returns and records the full body a cap withheld', async () => {
|
|
267
354
|
const node = newNode();
|
|
268
355
|
process.env['CRTR_NODE_ID'] = node;
|
|
269
|
-
const
|
|
270
|
-
const
|
|
356
|
+
const state = loadContextExposureState(node);
|
|
357
|
+
const transcript = exposureTarget(state, 'transcript');
|
|
358
|
+
const opened = renderWorkspaceOpenDocs(node, transcript);
|
|
271
359
|
assert.ok(!opened.includes(B.ppreview), 'workspace-open disclosed the capped doc at preview only');
|
|
272
|
-
|
|
273
|
-
const result = (await readLeaf.run({
|
|
360
|
+
saveContextExposureState(node, state);
|
|
361
|
+
const result = (await readLeaf.run({
|
|
362
|
+
name: 'ppreview/t9-ppreview-doc',
|
|
363
|
+
kind: undefined,
|
|
364
|
+
frontmatter: false,
|
|
365
|
+
scope: undefined,
|
|
366
|
+
dir: undefined,
|
|
367
|
+
}));
|
|
274
368
|
assert.equal(result.path, ppreviewDocPath, 'the read resolved the cwd project doc');
|
|
275
369
|
assert.ok(result.content.includes(B.ppreview), `an explicit read is uncapped and returns the full body; got:\n${result.content}`);
|
|
276
|
-
const afterRead =
|
|
277
|
-
assert.ok(
|
|
370
|
+
const afterRead = loadContextExposureState(node);
|
|
371
|
+
assert.ok(documentExposedAtOrAbove(afterRead, ppreviewDocPath, B.ppreview, 'content'), 'the explicit read records a content delivery, upgrading the preview disclosure');
|
|
278
372
|
// A later automatic event runs at the capped rung, which is now below what the
|
|
279
373
|
// transcript holds, so it re-delivers nothing.
|
|
280
|
-
assert.equal(renderWorkspaceOpenDocs(node, afterRead), '', 'a later lower automatic event re-delivers nothing');
|
|
281
|
-
//
|
|
282
|
-
assert.ok(renderWorkspaceOpenDocs(node,
|
|
374
|
+
assert.equal(renderWorkspaceOpenDocs(node, exposureTarget(afterRead, 'transcript')), '', 'a later lower automatic event re-delivers nothing');
|
|
375
|
+
// A fresh ledger proves the renderer itself is still live.
|
|
376
|
+
assert.ok(renderWorkspaceOpenDocs(node, freshTranscriptTarget()).includes('ppreview/t9-ppreview-doc'), 'the same call against a fresh transcript still delivers');
|
|
283
377
|
delete process.env['CRTR_NODE_ID'];
|
|
284
378
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { test, before, beforeEach, after } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { gitSync } from '../git.js';
|
|
7
|
+
import { realpathOrSelf } from '../fs-utils.js';
|
|
8
|
+
import { associateRepository, normalizeOwnerRelativePath, resetRepositoryAssociationCache, } from '../memory/repository-association.js';
|
|
9
|
+
import { slugCanonicalPath, slugCanonicalSegment } from '../memory/identity.js';
|
|
10
|
+
const cleanups = [];
|
|
11
|
+
let previousHome;
|
|
12
|
+
before(() => {
|
|
13
|
+
previousHome = process.env['HOME'];
|
|
14
|
+
});
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
process.env['HOME'] = realpathOrSelf(mkdtempSync(join('/tmp', 'crtr-association-home-')));
|
|
17
|
+
cleanups.push(process.env['HOME']);
|
|
18
|
+
resetRepositoryAssociationCache();
|
|
19
|
+
});
|
|
20
|
+
after(() => {
|
|
21
|
+
resetRepositoryAssociationCache();
|
|
22
|
+
for (const path of cleanups)
|
|
23
|
+
rmSync(path, { recursive: true, force: true });
|
|
24
|
+
if (previousHome === undefined)
|
|
25
|
+
delete process.env['HOME'];
|
|
26
|
+
else
|
|
27
|
+
process.env['HOME'] = previousHome;
|
|
28
|
+
});
|
|
29
|
+
function temp(prefix) {
|
|
30
|
+
const path = realpathOrSelf(mkdtempSync(join('/tmp', prefix)));
|
|
31
|
+
cleanups.push(path);
|
|
32
|
+
return path;
|
|
33
|
+
}
|
|
34
|
+
function initGit(path) {
|
|
35
|
+
mkdirSync(path, { recursive: true });
|
|
36
|
+
assert.equal(gitSync(['-C', path, 'init', '-q'], undefined).status, 0);
|
|
37
|
+
}
|
|
38
|
+
function makeStore(owner) {
|
|
39
|
+
mkdirSync(join(owner, '.crouter', 'memory'), { recursive: true });
|
|
40
|
+
writeFileSync(join(owner, '.crouter', 'memory', 'INDEX.md'), '# store\n');
|
|
41
|
+
}
|
|
42
|
+
test('associates git roots, nested owners, and embedded repositories', () => {
|
|
43
|
+
const root = temp('crtr-association-git-');
|
|
44
|
+
initGit(root);
|
|
45
|
+
const nested = join(root, 'packages', 'api');
|
|
46
|
+
mkdirSync(nested, { recursive: true });
|
|
47
|
+
const atRoot = associateRepository(root);
|
|
48
|
+
assert.equal(atRoot.ok, true);
|
|
49
|
+
if (!atRoot.ok)
|
|
50
|
+
return;
|
|
51
|
+
assert.equal(atRoot.association.kind, 'git');
|
|
52
|
+
assert.equal(atRoot.association.repositoryRoot, root);
|
|
53
|
+
assert.equal(atRoot.association.ownerRelativePath, '');
|
|
54
|
+
assert.match(atRoot.association.repositoryKey, /^git:/);
|
|
55
|
+
assert.equal(atRoot.association.declarationPath, join(root, '.crouter', 'memory', 'INDEX.md'));
|
|
56
|
+
const belowRoot = associateRepository(nested);
|
|
57
|
+
assert.equal(belowRoot.ok, true);
|
|
58
|
+
if (!belowRoot.ok)
|
|
59
|
+
return;
|
|
60
|
+
assert.equal(belowRoot.association.repositoryRoot, root);
|
|
61
|
+
assert.equal(belowRoot.association.ownerRelativePath, 'packages/api');
|
|
62
|
+
const embedded = join(root, 'embedded');
|
|
63
|
+
initGit(embedded);
|
|
64
|
+
const embeddedOwner = join(embedded, 'pkg');
|
|
65
|
+
mkdirSync(embeddedOwner, { recursive: true });
|
|
66
|
+
const embeddedResult = associateRepository(embeddedOwner);
|
|
67
|
+
assert.equal(embeddedResult.ok, true);
|
|
68
|
+
if (!embeddedResult.ok)
|
|
69
|
+
return;
|
|
70
|
+
assert.equal(embeddedResult.association.repositoryRoot, embedded);
|
|
71
|
+
assert.equal(embeddedResult.association.ownerRelativePath, 'pkg');
|
|
72
|
+
});
|
|
73
|
+
test('linked worktrees share repository key and owner-relative path', () => {
|
|
74
|
+
const main = temp('crtr-association-main-');
|
|
75
|
+
initGit(main);
|
|
76
|
+
writeFileSync(join(main, 'README'), 'repo\n');
|
|
77
|
+
assert.equal(gitSync(['-C', main, 'add', 'README'], undefined).status, 0);
|
|
78
|
+
assert.equal(gitSync(['-C', main, '-c', 'user.name=Test', '-c', 'user.email=test@example.com', 'commit', '-qm', 'initial'], undefined).status, 0);
|
|
79
|
+
const linked = join(temp('crtr-association-linked-parent-'), 'checkout');
|
|
80
|
+
assert.equal(gitSync(['-C', main, 'worktree', 'add', '-q', linked, 'HEAD'], undefined).status, 0);
|
|
81
|
+
const mainOwner = join(main, 'packages', 'api');
|
|
82
|
+
const linkedOwner = join(linked, 'packages', 'api');
|
|
83
|
+
mkdirSync(mainOwner, { recursive: true });
|
|
84
|
+
mkdirSync(linkedOwner, { recursive: true });
|
|
85
|
+
const first = associateRepository(mainOwner);
|
|
86
|
+
const second = associateRepository(linkedOwner);
|
|
87
|
+
assert.equal(first.ok, true);
|
|
88
|
+
assert.equal(second.ok, true);
|
|
89
|
+
if (!first.ok || !second.ok)
|
|
90
|
+
return;
|
|
91
|
+
assert.equal(first.association.repositoryKey, second.association.repositoryKey);
|
|
92
|
+
assert.equal(first.association.ownerRelativePath, second.association.ownerRelativePath);
|
|
93
|
+
assert.notEqual(first.association.repositoryRoot, second.association.repositoryRoot);
|
|
94
|
+
});
|
|
95
|
+
test('non-git association chooses the outermost ancestor store and respects the home fence', () => {
|
|
96
|
+
const outer = join(homedir(), 'workspace');
|
|
97
|
+
const inner = join(outer, 'packages', 'api');
|
|
98
|
+
mkdirSync(inner, { recursive: true });
|
|
99
|
+
makeStore(outer);
|
|
100
|
+
makeStore(inner);
|
|
101
|
+
const result = associateRepository(inner);
|
|
102
|
+
assert.equal(result.ok, true);
|
|
103
|
+
if (!result.ok)
|
|
104
|
+
return;
|
|
105
|
+
assert.equal(result.association.kind, 'non-git');
|
|
106
|
+
assert.equal(result.association.repositoryRoot, outer);
|
|
107
|
+
assert.equal(result.association.repositoryKey, `path:${outer}`);
|
|
108
|
+
assert.equal(result.association.ownerRelativePath, 'packages/api');
|
|
109
|
+
const homeStoreOwner = join(homedir(), 'not-a-project');
|
|
110
|
+
mkdirSync(homeStoreOwner, { recursive: true });
|
|
111
|
+
makeStore(homedir());
|
|
112
|
+
const fenced = associateRepository(homeStoreOwner);
|
|
113
|
+
assert.equal(fenced.ok, false);
|
|
114
|
+
if (!fenced.ok)
|
|
115
|
+
assert.equal(fenced.code, 'no-project-root');
|
|
116
|
+
});
|
|
117
|
+
test('non-git failures are not cached and symlinked owners realpath before deriving paths', () => {
|
|
118
|
+
const root = temp('crtr-association-heal-');
|
|
119
|
+
const owner = join(root, 'docs');
|
|
120
|
+
mkdirSync(owner, { recursive: true });
|
|
121
|
+
const before = associateRepository(owner);
|
|
122
|
+
assert.equal(before.ok, false);
|
|
123
|
+
if (!before.ok)
|
|
124
|
+
assert.equal(before.code, 'no-project-root');
|
|
125
|
+
makeStore(root);
|
|
126
|
+
const healed = associateRepository(owner);
|
|
127
|
+
assert.equal(healed.ok, true);
|
|
128
|
+
if (!healed.ok)
|
|
129
|
+
return;
|
|
130
|
+
assert.equal(healed.association.repositoryRoot, root);
|
|
131
|
+
assert.equal(healed.association.ownerRelativePath, 'docs');
|
|
132
|
+
const link = join(root, 'alias');
|
|
133
|
+
symlinkSync(owner, link, 'dir');
|
|
134
|
+
const linked = associateRepository(link);
|
|
135
|
+
assert.equal(linked.ok, true);
|
|
136
|
+
if (!linked.ok)
|
|
137
|
+
return;
|
|
138
|
+
assert.equal(linked.association.ownerRelativePath, 'docs');
|
|
139
|
+
assert.equal(linked.association.repositoryRoot, root);
|
|
140
|
+
});
|
|
141
|
+
test('slugging and owner-path validation are explicit and lossless', () => {
|
|
142
|
+
assert.equal(slugCanonicalSegment(' My Notes!!! '), 'my-notes');
|
|
143
|
+
assert.equal(slugCanonicalSegment('notes.md'), 'notes.md');
|
|
144
|
+
assert.equal(slugCanonicalPath('01-My Notes!!!/notes.md'), 'my-notes/notes');
|
|
145
|
+
assert.deepEqual(normalizeOwnerRelativePath('My Notes!!!\\notes.md'), { ok: true, path: 'my-notes/notes.md' });
|
|
146
|
+
assert.deepEqual(normalizeOwnerRelativePath('01-api/00-Notes'), { ok: true, path: 'api/notes' });
|
|
147
|
+
assert.deepEqual(normalizeOwnerRelativePath(''), { ok: true, path: '' });
|
|
148
|
+
assert.deepEqual(normalizeOwnerRelativePath('.'), { ok: true, path: '' });
|
|
149
|
+
for (const path of ['!!!', 'INDEX']) {
|
|
150
|
+
const result = normalizeOwnerRelativePath(path);
|
|
151
|
+
assert.equal(result.ok, false, `${path} must be rejected`);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|