@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
|
@@ -1,121 +1,225 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
// Delivery channels (boot render seeding, workspace-open, on-read, memory-read
|
|
4
|
-
// listings and events, command events) dedup so a given doc surfaces at most
|
|
5
|
-
// once per conversation AT A GIVEN DISCLOSURE. The map keys on realpath and
|
|
6
|
-
// carries the highest rung rank delivered so far; higher rungs pierce lower —
|
|
7
|
-
// a listing line (preview rank) must not block a later explicit content
|
|
8
|
-
// delivery, while a content delivery silences everything after it. Directory
|
|
9
|
-
// listings participate under the directory's own realpath (a directory never
|
|
10
|
-
// collides with a file's realpath).
|
|
11
|
-
//
|
|
12
|
-
// The map USED to live only in the pi process heap, cleared on session_start.
|
|
13
|
-
// But a node's logical session — the .jsonl transcript — spans MULTIPLE pi
|
|
14
|
-
// processes: a dormancy → revive(resume) cycle exits the old process and
|
|
15
|
-
// launches a fresh `pi --session` that REUSES the same transcript. This module
|
|
16
|
-
// persists the map to `nodes/<id>/injected-docs.json` so the resumed process
|
|
17
|
-
// rehydrates it and skips docs already present in the transcript. The one
|
|
18
|
-
// launch path that begins a FRESH transcript (reviveNode with resume=false, in
|
|
19
|
-
// runtime/revive.ts) calls clearInjectedDocs(), so a new conversation starts
|
|
20
|
-
// empty. CLI processes (the `memory read` leaf) load/save the same file.
|
|
21
|
-
//
|
|
22
|
-
// Disk format: `{"v":2,"docs":{"<realpath>":<rank 1|2|3>}}` (rank =
|
|
23
|
-
// rungRank of the delivered rung). A legacy v1 string-array rehydrates at
|
|
24
|
-
// content rank — its entries were recorded under never-re-deliver semantics.
|
|
25
|
-
//
|
|
26
|
-
// All ops are best-effort: a failed read/write degrades to a possible
|
|
27
|
-
// re-inject, never a crash — a dedup miss must never break a read or a revive.
|
|
28
|
-
import { readFileSync, writeFileSync, rmSync } from 'node:fs';
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, readFileSync, rmSync } from 'node:fs';
|
|
29
3
|
import { injectedDocsPath } from '../canvas/paths.js';
|
|
4
|
+
import { atomicWriteJson, realpathOrSelf } from '../fs-utils.js';
|
|
30
5
|
import { rungRank } from './schema.js';
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
export function
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
6
|
+
const CONTENT_RANK = rungRank('content');
|
|
7
|
+
const cache = new Map();
|
|
8
|
+
export function emptyContextExposureState() {
|
|
9
|
+
return { preferenceSnapshot: null, exposures: new Map() };
|
|
10
|
+
}
|
|
11
|
+
export function exposureTarget(state, source) {
|
|
12
|
+
return { state, source };
|
|
13
|
+
}
|
|
14
|
+
function validRank(value) {
|
|
15
|
+
return typeof value === 'number' && Number.isInteger(value) && value >= 1 && value <= CONTENT_RANK;
|
|
16
|
+
}
|
|
17
|
+
function activeRank(ranks) {
|
|
18
|
+
return Math.max(ranks?.system ?? 0, ranks?.transcript ?? 0);
|
|
19
|
+
}
|
|
20
|
+
export function exposedAtOrAbove(state, identity, rung) {
|
|
21
|
+
return activeRank(state.exposures.get(identity)) >= rungRank(rung);
|
|
22
|
+
}
|
|
23
|
+
/** The sole delivery mutation: max-fold one loaded identity into its source rank. */
|
|
24
|
+
export function registerExposure(target, identity, rung) {
|
|
39
25
|
const rank = rungRank(rung);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
26
|
+
if (rank === 0)
|
|
27
|
+
return;
|
|
28
|
+
const current = target.state.exposures.get(identity) ?? {};
|
|
29
|
+
if ((current[target.source] ?? 0) >= rank)
|
|
30
|
+
return;
|
|
31
|
+
target.state.exposures.set(identity, { ...current, [target.source]: rank });
|
|
32
|
+
}
|
|
33
|
+
export function contentExposureIdentity(body) {
|
|
34
|
+
const canonical = body.trim();
|
|
35
|
+
return canonical === ''
|
|
36
|
+
? null
|
|
37
|
+
: `content-sha256:${createHash('sha256').update(canonical).digest('hex')}`;
|
|
38
|
+
}
|
|
39
|
+
export function documentExposedAtOrAbove(state, path, body, rung) {
|
|
40
|
+
if (exposedAtOrAbove(state, realpathOrSelf(path), rung))
|
|
41
|
+
return true;
|
|
42
|
+
const contentIdentity = contentExposureIdentity(body);
|
|
43
|
+
return contentIdentity !== null && exposedAtOrAbove(state, contentIdentity, rung);
|
|
44
|
+
}
|
|
45
|
+
/** Register both the physical document and its non-empty content alias. */
|
|
46
|
+
export function registerDocumentExposure(target, path, body, rung) {
|
|
47
|
+
registerExposure(target, realpathOrSelf(path), rung);
|
|
48
|
+
const contentIdentity = contentExposureIdentity(body);
|
|
49
|
+
if (contentIdentity !== null)
|
|
50
|
+
registerExposure(target, contentIdentity, rung);
|
|
51
|
+
}
|
|
52
|
+
export function freezePreferenceSnapshot(state, rendered) {
|
|
53
|
+
if (state.preferenceSnapshot === null)
|
|
54
|
+
state.preferenceSnapshot = rendered;
|
|
55
|
+
return state.preferenceSnapshot;
|
|
56
|
+
}
|
|
57
|
+
export function cloneContextExposureState(source) {
|
|
58
|
+
return {
|
|
59
|
+
preferenceSnapshot: source.preferenceSnapshot,
|
|
60
|
+
exposures: new Map([...source.exposures].map(([identity, ranks]) => [identity, { ...ranks }])),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export function hasTranscriptExposure(state) {
|
|
64
|
+
return [...state.exposures.values()].some((ranks) => validRank(ranks.transcript));
|
|
65
|
+
}
|
|
66
|
+
/** The context copied into a fork excludes the source's system snapshot. */
|
|
67
|
+
export function cloneTranscriptExposureState(source) {
|
|
68
|
+
const clone = emptyContextExposureState();
|
|
69
|
+
const transcript = exposureTarget(clone, 'transcript');
|
|
70
|
+
for (const [identity, ranks] of source.exposures) {
|
|
71
|
+
const rank = ranks.transcript;
|
|
72
|
+
if (!validRank(rank))
|
|
73
|
+
continue;
|
|
74
|
+
registerExposure(transcript, identity, rank === 1 ? 'name' : rank === 2 ? 'preview' : 'content');
|
|
51
75
|
}
|
|
76
|
+
return clone;
|
|
52
77
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
/** The process-shared dedup map for a node: loaded from disk once, then the
|
|
59
|
-
* SAME Map instance on every call, so all hooks in this pi process grow and
|
|
60
|
-
* consult one map. Daemon-side deliveries (a warm-spare claim) write the disk
|
|
61
|
-
* file from another process; consumers that must see those merge the plain
|
|
62
|
-
* `loadInjectedDocs` result in before rendering. */
|
|
63
|
-
export function sharedInjectedDocs(nodeId) {
|
|
64
|
-
let seen = cache.get(nodeId);
|
|
65
|
-
if (seen === undefined) {
|
|
66
|
-
seen = loadInjectedDocs(nodeId);
|
|
67
|
-
cache.set(nodeId, seen);
|
|
78
|
+
export function replaceContextExposureState(target, source) {
|
|
79
|
+
target.preferenceSnapshot = source.preferenceSnapshot;
|
|
80
|
+
target.exposures.clear();
|
|
81
|
+
for (const [identity, ranks] of source.exposures) {
|
|
82
|
+
target.exposures.set(identity, { ...ranks });
|
|
68
83
|
}
|
|
69
|
-
return seen;
|
|
70
84
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
85
|
+
export function contextExposureStatesEqual(left, right) {
|
|
86
|
+
if (left.preferenceSnapshot !== right.preferenceSnapshot || left.exposures.size !== right.exposures.size)
|
|
87
|
+
return false;
|
|
88
|
+
for (const [identity, ranks] of left.exposures) {
|
|
89
|
+
const other = right.exposures.get(identity);
|
|
90
|
+
if (ranks.system !== other?.system || ranks.transcript !== other?.transcript)
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
/** Fold state written by another process into the live context state. */
|
|
96
|
+
export function mergeContextExposureState(target, source) {
|
|
97
|
+
if (target.preferenceSnapshot !== null &&
|
|
98
|
+
source.preferenceSnapshot !== null &&
|
|
99
|
+
target.preferenceSnapshot !== source.preferenceSnapshot) {
|
|
100
|
+
throw new Error('context exposure state contains conflicting preference snapshots');
|
|
101
|
+
}
|
|
102
|
+
if (target.preferenceSnapshot === null && source.preferenceSnapshot !== null) {
|
|
103
|
+
target.preferenceSnapshot = source.preferenceSnapshot;
|
|
104
|
+
}
|
|
105
|
+
for (const [identity, ranks] of source.exposures) {
|
|
106
|
+
const current = target.exposures.get(identity) ?? {};
|
|
107
|
+
const system = Math.max(current.system ?? 0, ranks.system ?? 0);
|
|
108
|
+
const transcript = Math.max(current.transcript ?? 0, ranks.transcript ?? 0);
|
|
109
|
+
target.exposures.set(identity, {
|
|
110
|
+
...(system > 0 ? { system } : {}),
|
|
111
|
+
...(transcript > 0 ? { transcript } : {}),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
export function sharedContextExposureState(nodeId) {
|
|
116
|
+
let state = cache.get(nodeId);
|
|
117
|
+
if (state === undefined) {
|
|
118
|
+
state = loadContextExposureState(nodeId);
|
|
119
|
+
cache.set(nodeId, state);
|
|
120
|
+
}
|
|
121
|
+
return state;
|
|
122
|
+
}
|
|
123
|
+
/** v1/v2 ranks predate provenance and conservatively become transcript exposure. */
|
|
124
|
+
export function loadContextExposureState(nodeId) {
|
|
125
|
+
const path = injectedDocsPath(nodeId);
|
|
126
|
+
let raw;
|
|
76
127
|
try {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
128
|
+
raw = JSON.parse(readFileSync(path, 'utf8'));
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
if (error.code === 'ENOENT')
|
|
132
|
+
return emptyContextExposureState();
|
|
133
|
+
throw new Error(`failed to read context exposure state at ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
134
|
+
}
|
|
135
|
+
const state = emptyContextExposureState();
|
|
136
|
+
const transcript = exposureTarget(state, 'transcript');
|
|
137
|
+
if (Array.isArray(raw)) {
|
|
138
|
+
for (const identity of raw) {
|
|
139
|
+
if (typeof identity !== 'string')
|
|
140
|
+
throw new Error(`invalid v1 context exposure state at ${path}`);
|
|
141
|
+
registerExposure(transcript, identity, 'content');
|
|
142
|
+
}
|
|
143
|
+
return state;
|
|
144
|
+
}
|
|
145
|
+
if (raw === null || typeof raw !== 'object') {
|
|
146
|
+
throw new Error(`invalid context exposure state at ${path}`);
|
|
147
|
+
}
|
|
148
|
+
const record = raw;
|
|
149
|
+
if (record['v'] === 3) {
|
|
150
|
+
const preferenceSnapshot = record['preferenceSnapshot'];
|
|
151
|
+
const exposures = record['exposures'];
|
|
152
|
+
if ((preferenceSnapshot !== null && typeof preferenceSnapshot !== 'string') ||
|
|
153
|
+
exposures === null ||
|
|
154
|
+
typeof exposures !== 'object' ||
|
|
155
|
+
Array.isArray(exposures)) {
|
|
156
|
+
throw new Error(`invalid v3 context exposure state at ${path}`);
|
|
85
157
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
158
|
+
state.preferenceSnapshot = preferenceSnapshot;
|
|
159
|
+
for (const [identity, value] of Object.entries(exposures)) {
|
|
160
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
161
|
+
throw new Error(`invalid v3 exposure entry '${identity}' at ${path}`);
|
|
162
|
+
}
|
|
163
|
+
const ranks = value;
|
|
164
|
+
const system = ranks['system'];
|
|
165
|
+
const transcriptRank = ranks['transcript'];
|
|
166
|
+
if ((system !== undefined && !validRank(system)) ||
|
|
167
|
+
(transcriptRank !== undefined && !validRank(transcriptRank)) ||
|
|
168
|
+
(system === undefined && transcriptRank === undefined)) {
|
|
169
|
+
throw new Error(`invalid v3 exposure ranks for '${identity}' at ${path}`);
|
|
93
170
|
}
|
|
171
|
+
state.exposures.set(identity, {
|
|
172
|
+
...(validRank(system) ? { system } : {}),
|
|
173
|
+
...(validRank(transcriptRank) ? { transcript: transcriptRank } : {}),
|
|
174
|
+
});
|
|
94
175
|
}
|
|
95
|
-
return
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
return new Map();
|
|
176
|
+
return state;
|
|
99
177
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
writeFileSync(injectedDocsPath(nodeId), JSON.stringify({ v: 2, docs: Object.fromEntries(seen) }));
|
|
178
|
+
if (record['v'] !== 2)
|
|
179
|
+
throw new Error(`unsupported context exposure state at ${path}`);
|
|
180
|
+
const docs = record['docs'];
|
|
181
|
+
if (docs === null || typeof docs !== 'object' || Array.isArray(docs)) {
|
|
182
|
+
throw new Error(`invalid v2 context exposure state at ${path}`);
|
|
106
183
|
}
|
|
107
|
-
|
|
108
|
-
|
|
184
|
+
for (const [identity, rank] of Object.entries(docs)) {
|
|
185
|
+
if (!validRank(rank))
|
|
186
|
+
throw new Error(`invalid v2 exposure rank for '${identity}' at ${path}`);
|
|
187
|
+
registerExposure(transcript, identity, rank === 1 ? 'name' : rank === 2 ? 'preview' : 'content');
|
|
109
188
|
}
|
|
189
|
+
return state;
|
|
110
190
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
191
|
+
export function saveContextExposureState(nodeId, state) {
|
|
192
|
+
atomicWriteJson(injectedDocsPath(nodeId), {
|
|
193
|
+
v: 3,
|
|
194
|
+
preferenceSnapshot: state.preferenceSnapshot,
|
|
195
|
+
exposures: Object.fromEntries(state.exposures),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
/** Clear in place so every extension closure observes the new context boundary. */
|
|
199
|
+
export function clearContextExposureState(nodeId) {
|
|
200
|
+
const live = cache.get(nodeId);
|
|
201
|
+
if (live !== undefined)
|
|
202
|
+
replaceContextExposureState(live, emptyContextExposureState());
|
|
203
|
+
rmSync(injectedDocsPath(nodeId), { force: true });
|
|
204
|
+
}
|
|
205
|
+
/** Seed a fork with only context copied in the source transcript. */
|
|
206
|
+
export function initializeForkContextExposure(destinationNodeId, sourceNodeId) {
|
|
207
|
+
if (existsSync(injectedDocsPath(destinationNodeId))) {
|
|
208
|
+
const existing = loadContextExposureState(destinationNodeId);
|
|
209
|
+
const live = cache.get(destinationNodeId);
|
|
210
|
+
if (live === undefined)
|
|
211
|
+
cache.set(destinationNodeId, existing);
|
|
212
|
+
else
|
|
213
|
+
replaceContextExposureState(live, existing);
|
|
214
|
+
return;
|
|
120
215
|
}
|
|
216
|
+
const destination = sourceNodeId === null
|
|
217
|
+
? emptyContextExposureState()
|
|
218
|
+
: cloneTranscriptExposureState(loadContextExposureState(sourceNodeId));
|
|
219
|
+
const live = cache.get(destinationNodeId);
|
|
220
|
+
if (live === undefined)
|
|
221
|
+
cache.set(destinationNodeId, destination);
|
|
222
|
+
else
|
|
223
|
+
replaceContextExposureState(live, destination);
|
|
224
|
+
saveContextExposureState(destinationNodeId, destination);
|
|
121
225
|
}
|
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
import type { MemoryDoc } from '../memory-resolver.js';
|
|
2
|
-
import { type
|
|
3
|
-
/**
|
|
4
|
-
* so the scheme prefix never collides with a
|
|
2
|
+
import { type ExposureTarget } from './injected-store.js';
|
|
3
|
+
/** Exposure identity for a whole directory listing. Realpath identities start
|
|
4
|
+
* with `/`, so the scheme prefix never collides with a document. */
|
|
5
5
|
export declare function dirDedupKey(dir: string): string;
|
|
6
6
|
/** First-occurrence-wins name map over a precedence-ordered corpus: the doc
|
|
7
7
|
* that renders a name's line is the doc a read of that name would return. */
|
|
8
8
|
export declare function docsByName(corpus: readonly MemoryDoc[]): Map<string, MemoryDoc>;
|
|
9
|
-
/** Is `name` a directory in the merged name tree — i.e. does
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
/** Is `name` a directory in the merged name tree — i.e. does it have members?
|
|
10
|
+
* A doc that fronts a directory answers true here and still returns its own
|
|
11
|
+
* body, because one node carries both. The corpus root ('') is never a
|
|
12
|
+
* directory here: store roots merge into the whole catalog, which
|
|
13
|
+
* `memory list` owns. */
|
|
12
14
|
export declare function isDirName(byName: ReadonlyMap<string, MemoryDoc>, name: string): boolean;
|
|
13
15
|
/** The dirs a doc read discloses: the doc's own directory, then each ancestor
|
|
14
16
|
* walking up, the root excluded. `a/b/c` → `['a/b', 'a']`. */
|
|
15
17
|
export declare function ancestorDirsOf(name: string): string[];
|
|
16
|
-
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
/** The listings a read of `name` discloses, in render order: the node's own
|
|
19
|
+
* listing first when it has members (a directory document's members belong
|
|
20
|
+
* with its body), then each ancestor walking up. */
|
|
21
|
+
export declare function docListingDirs(byName: ReadonlyMap<string, MemoryDoc>, name: string): string[];
|
|
22
|
+
/** Render one directory's listing lines: `[[name]]: <when-and-why>` per member
|
|
23
|
+
* (bare `[[name]]` when it carries no routing line), childless members first
|
|
24
|
+
* and subdirectories after. `unlisted` members are omitted always;
|
|
25
|
+
* `filterDelivered` additionally drops members already exposed at preview rank
|
|
26
|
+
* or higher. A subdirectory always keeps its row — it advertises a subtree, so
|
|
27
|
+
* an omitted preview leaves the bare name. */
|
|
28
|
+
export declare function renderDirListing(byName: ReadonlyMap<string, MemoryDoc>, dir: string, target: ExposureTarget | null, filterDelivered: boolean): string[];
|
|
@@ -1,24 +1,26 @@
|
|
|
1
|
-
// listings.ts — system-owned directory listings over the
|
|
1
|
+
// listings.ts — system-owned directory listings over the canonical tree.
|
|
2
2
|
//
|
|
3
3
|
// Reading a doc discloses where it sits: `ls` semantics over the corpus
|
|
4
4
|
// namespace (directories merge across stores), consulting no frontmatter
|
|
5
|
-
// except `unlisted`. A listing renders one when-and-why line per member
|
|
6
|
-
//
|
|
5
|
+
// except `unlisted`. A listing renders one when-and-why line per member,
|
|
6
|
+
// including a subdirectory that carries its own document — never recursive
|
|
7
|
+
// contents, never gates. Structure comes from `memory/tree.ts`, so a directory
|
|
8
|
+
// document and its members are one row rather than two.
|
|
7
9
|
//
|
|
8
10
|
// Two render modes share one function. An EXPLICIT directory read (`crtr
|
|
9
11
|
// memory read <dir>`) is a deliberate act, so every line renders (unlisted
|
|
10
12
|
// excluded) — mirroring how an explicit doc read always returns content. The
|
|
11
13
|
// AUTOMATIC neighborhood listings a doc read prepends are unsolicited, so
|
|
12
|
-
// each member line
|
|
13
|
-
//
|
|
14
|
+
// each member line filters against the active exposure ledger and each whole
|
|
15
|
+
// directory registers a synthetic `dir://<name>` identity (a name-tree
|
|
14
16
|
// directory spans stores, so no single physical realpath identifies it).
|
|
15
|
-
// Either mode
|
|
16
|
-
//
|
|
17
|
-
import {
|
|
17
|
+
// Either mode registers rendered member docs at preview rank when a target is
|
|
18
|
+
// supplied, because a listing line is a preview-rank render.
|
|
19
|
+
import { buildMemoryTree } from '../memory/tree.js';
|
|
18
20
|
import { parseSubstrateDoc, previewLine } from './schema.js';
|
|
19
|
-
import {
|
|
20
|
-
/**
|
|
21
|
-
* so the scheme prefix never collides with a
|
|
21
|
+
import { documentExposedAtOrAbove, registerDocumentExposure, } from './injected-store.js';
|
|
22
|
+
/** Exposure identity for a whole directory listing. Realpath identities start
|
|
23
|
+
* with `/`, so the scheme prefix never collides with a document. */
|
|
22
24
|
export function dirDedupKey(dir) {
|
|
23
25
|
return `dir://${dir}`;
|
|
24
26
|
}
|
|
@@ -31,17 +33,25 @@ export function docsByName(corpus) {
|
|
|
31
33
|
byName.set(d.name, d);
|
|
32
34
|
return byName;
|
|
33
35
|
}
|
|
34
|
-
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
36
|
+
/** One tree per winner map. A single read asks about its own directory, each
|
|
37
|
+
* ancestor, and every `[[link]]` in its body; rebuilding the tree per question
|
|
38
|
+
* would walk the whole corpus each time. */
|
|
39
|
+
const trees = new WeakMap();
|
|
40
|
+
function treeFor(byName) {
|
|
41
|
+
const cached = trees.get(byName);
|
|
42
|
+
if (cached !== undefined)
|
|
43
|
+
return cached;
|
|
44
|
+
const tree = buildMemoryTree([...byName.values()]);
|
|
45
|
+
trees.set(byName, tree);
|
|
46
|
+
return tree;
|
|
47
|
+
}
|
|
48
|
+
/** Is `name` a directory in the merged name tree — i.e. does it have members?
|
|
49
|
+
* A doc that fronts a directory answers true here and still returns its own
|
|
50
|
+
* body, because one node carries both. The corpus root ('') is never a
|
|
51
|
+
* directory here: store roots merge into the whole catalog, which
|
|
52
|
+
* `memory list` owns. */
|
|
37
53
|
export function isDirName(byName, name) {
|
|
38
|
-
|
|
39
|
-
return false;
|
|
40
|
-
const prefix = name + '/';
|
|
41
|
-
for (const n of byName.keys())
|
|
42
|
-
if (n.startsWith(prefix))
|
|
43
|
-
return true;
|
|
44
|
-
return false;
|
|
54
|
+
return name !== '' && treeFor(byName).isDirectory(name);
|
|
45
55
|
}
|
|
46
56
|
/** The dirs a doc read discloses: the doc's own directory, then each ancestor
|
|
47
57
|
* walking up, the root excluded. `a/b/c` → `['a/b', 'a']`. */
|
|
@@ -52,37 +62,50 @@ export function ancestorDirsOf(name) {
|
|
|
52
62
|
dirs.push(segs.slice(0, i).join('/'));
|
|
53
63
|
return dirs;
|
|
54
64
|
}
|
|
55
|
-
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
65
|
+
/** The listings a read of `name` discloses, in render order: the node's own
|
|
66
|
+
* listing first when it has members (a directory document's members belong
|
|
67
|
+
* with its body), then each ancestor walking up. */
|
|
68
|
+
export function docListingDirs(byName, name) {
|
|
69
|
+
return [...(isDirName(byName, name) ? [name] : []), ...ancestorDirsOf(name)];
|
|
70
|
+
}
|
|
71
|
+
/** The when-and-why suffix a doc contributes to its line, or null when it must
|
|
72
|
+
* not preview here: `unlisted` always, and under `filterDelivered` a doc
|
|
73
|
+
* already in this context at preview rank or higher. A rendered preview IS a
|
|
74
|
+
* preview-rank delivery, so it registers. */
|
|
75
|
+
function previewSuffix(doc, target, filterDelivered) {
|
|
76
|
+
const sub = parseSubstrateDoc(doc);
|
|
77
|
+
if (sub?.unlisted)
|
|
78
|
+
return null;
|
|
79
|
+
if (filterDelivered &&
|
|
80
|
+
target !== null &&
|
|
81
|
+
documentExposedAtOrAbove(target.state, doc.path, doc.body, 'preview'))
|
|
82
|
+
return null;
|
|
83
|
+
if (target !== null)
|
|
84
|
+
registerDocumentExposure(target, doc.path, doc.body, 'preview');
|
|
85
|
+
return sub === null ? '' : previewLine(sub);
|
|
86
|
+
}
|
|
87
|
+
function line(name, suffix) {
|
|
88
|
+
return suffix === null || suffix === '' ? `[[${name}]]` : `[[${name}]]: ${suffix}`;
|
|
89
|
+
}
|
|
90
|
+
/** Render one directory's listing lines: `[[name]]: <when-and-why>` per member
|
|
91
|
+
* (bare `[[name]]` when it carries no routing line), childless members first
|
|
92
|
+
* and subdirectories after. `unlisted` members are omitted always;
|
|
93
|
+
* `filterDelivered` additionally drops members already exposed at preview rank
|
|
94
|
+
* or higher. A subdirectory always keeps its row — it advertises a subtree, so
|
|
95
|
+
* an omitted preview leaves the bare name. */
|
|
96
|
+
export function renderDirListing(byName, dir, target, filterDelivered) {
|
|
97
|
+
const node = treeFor(byName).node(dir);
|
|
98
|
+
if (node === null)
|
|
99
|
+
return [];
|
|
100
|
+
const lines = [];
|
|
101
|
+
for (const member of node.childDocuments) {
|
|
102
|
+
const suffix = previewSuffix(member.document, target, filterDelivered);
|
|
103
|
+
if (suffix === null)
|
|
78
104
|
continue;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
105
|
+
lines.push(line(member.name, suffix));
|
|
106
|
+
}
|
|
107
|
+
for (const sub of node.childDirectories) {
|
|
108
|
+
lines.push(line(sub.name, sub.document === null ? null : previewSuffix(sub.document, target, filterDelivered)));
|
|
83
109
|
}
|
|
84
|
-
return
|
|
85
|
-
...[...memberLines.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([, l]) => l),
|
|
86
|
-
...[...subdirs].sort((a, b) => a.localeCompare(b)).map((s) => `[[${prefix}${s}]]`),
|
|
87
|
-
];
|
|
110
|
+
return lines;
|
|
88
111
|
}
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ExposureTarget } from './injected-store.js';
|
|
2
2
|
/** Surface docs matched by a successful read tool call. An unknown node id or
|
|
3
3
|
* an unreadable canvas renders nothing. */
|
|
4
|
-
export declare function renderOnReadDocs(nodeId: string, readFilePath: string,
|
|
5
|
-
/** Surface workspace-
|
|
6
|
-
|
|
7
|
-
* a `workspace-open` surfaces entry deliver here. `seen` is the
|
|
8
|
-
* transcript-scoped dedup set shared with the on-read hook. */
|
|
9
|
-
export declare function renderWorkspaceOpenDocs(nodeId: string, seen?: InjectedDocs): string;
|
|
4
|
+
export declare function renderOnReadDocs(nodeId: string, readFilePath: string, target?: ExposureTarget): string;
|
|
5
|
+
/** Surface matching workspace-open docs from the node's mounted project stores. */
|
|
6
|
+
export declare function renderWorkspaceOpenDocs(nodeId: string, target?: ExposureTarget): string;
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
// that already holds a subject imports on-read.js without subject.js's
|
|
5
5
|
// canvas-db reach.
|
|
6
6
|
import { getNode } from '../canvas/index.js';
|
|
7
|
+
import { emptyContextExposureState, exposureTarget, } from './injected-store.js';
|
|
7
8
|
import { assembleNodeSubject } from './subject.js';
|
|
8
9
|
import { renderOnReadDocsForSubject, renderWorkspaceOpenDocsForSubject } from './on-read.js';
|
|
9
10
|
/** Surface docs matched by a successful read tool call. An unknown node id or
|
|
10
11
|
* an unreadable canvas renders nothing. */
|
|
11
|
-
export function renderOnReadDocs(nodeId, readFilePath,
|
|
12
|
+
export function renderOnReadDocs(nodeId, readFilePath, target = exposureTarget(emptyContextExposureState(), 'transcript')) {
|
|
12
13
|
let subject;
|
|
13
14
|
try {
|
|
14
15
|
subject = assembleNodeSubject(nodeId);
|
|
@@ -18,19 +19,16 @@ export function renderOnReadDocs(nodeId, readFilePath, seen = new Map()) {
|
|
|
18
19
|
}
|
|
19
20
|
if (subject === null)
|
|
20
21
|
return '';
|
|
21
|
-
return renderOnReadDocsForSubject(subject, readFilePath,
|
|
22
|
+
return renderOnReadDocsForSubject(subject, readFilePath, target);
|
|
22
23
|
}
|
|
23
|
-
/** Surface workspace-
|
|
24
|
-
|
|
25
|
-
* a `workspace-open` surfaces entry deliver here. `seen` is the
|
|
26
|
-
* transcript-scoped dedup set shared with the on-read hook. */
|
|
27
|
-
export function renderWorkspaceOpenDocs(nodeId, seen = new Map()) {
|
|
24
|
+
/** Surface matching workspace-open docs from the node's mounted project stores. */
|
|
25
|
+
export function renderWorkspaceOpenDocs(nodeId, target = exposureTarget(emptyContextExposureState(), 'transcript')) {
|
|
28
26
|
try {
|
|
29
27
|
const subject = assembleNodeSubject(nodeId);
|
|
30
28
|
const node = getNode(nodeId);
|
|
31
29
|
if (subject === null || node === null)
|
|
32
30
|
return '';
|
|
33
|
-
return renderWorkspaceOpenDocsForSubject(subject, node.cwd, node.profile_id ?? null,
|
|
31
|
+
return renderWorkspaceOpenDocsForSubject(subject, node.cwd, node.profile_id ?? null, target);
|
|
34
32
|
}
|
|
35
33
|
catch {
|
|
36
34
|
return '';
|