@vellumai/assistant 0.8.9-staging.2 → 0.8.9-staging.4
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/docs/activation-funnel-telemetry.md +310 -0
- package/openapi.yaml +16 -115
- package/package.json +1 -1
- package/src/__tests__/activation-early-marking.test.ts +120 -0
- package/src/__tests__/agent-loop-output-hooks.test.ts +13 -13
- package/src/__tests__/anthropic-provider.test.ts +23 -8
- package/src/__tests__/approval-cascade.test.ts +1 -1
- package/src/__tests__/compaction-direct.test.ts +32 -18
- package/src/__tests__/compaction-events.test.ts +2 -2
- package/src/__tests__/compaction.benchmark.test.ts +1 -1
- package/src/__tests__/context-overflow-reducer.test.ts +5 -5
- package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
- package/src/__tests__/conversation-error.test.ts +15 -1
- package/src/__tests__/conversation-history-web-search.test.ts +5 -0
- package/src/__tests__/conversation-media-retry.test.ts +1 -1
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +40 -0
- package/src/__tests__/conversation-process-callsite.test.ts +1 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
- package/src/__tests__/conversation-queue.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +71 -0
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
- package/src/__tests__/conversation-speed-override.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +44 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
- package/src/__tests__/conversation-undo.test.ts +2 -2
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/cu-unified-flow.test.ts +36 -0
- package/src/__tests__/history-repair-hook.test.ts +2 -0
- package/src/__tests__/llm-resolver.test.ts +73 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +1 -2
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
- package/src/__tests__/persist-unsendable-image.test.ts +97 -1
- package/src/__tests__/plugin-external-api.test.ts +68 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
- package/src/__tests__/published-app-updater.test.ts +138 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +5 -7
- package/src/__tests__/title-generate-hook.test.ts +2 -0
- package/src/__tests__/web-fetch.test.ts +45 -0
- package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
- package/src/acp/resolve-agent.test.ts +0 -56
- package/src/acp/resolve-agent.ts +10 -38
- package/src/agent/loop.ts +13 -27
- package/src/api/responses/memory-v3-selection-log.ts +19 -10
- package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
- package/src/cli/commands/memory-v3.ts +57 -199
- package/src/cli/lib/__tests__/install-from-github.test.ts +232 -29
- package/src/cli/lib/__tests__/plugin-details.test.ts +28 -19
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +57 -7
- package/src/cli/lib/__tests__/search-plugins.test.ts +17 -10
- package/src/cli/lib/install-from-github.ts +258 -41
- package/src/cli/lib/plugin-details.ts +20 -13
- package/src/cli/lib/plugin-marketplace.ts +23 -5
- package/src/cli/lib/search-plugins.ts +14 -8
- package/src/config/acp-defaults.ts +3 -3
- package/src/config/acp-schema.ts +1 -7
- package/src/config/bundled-skills/acp/SKILL.md +4 -17
- package/src/config/bundled-skills/acp/TOOLS.json +2 -2
- package/src/config/call-site-defaults.ts +0 -1
- package/src/config/feature-flag-registry.json +3 -18
- package/src/config/llm-resolver.ts +39 -7
- package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
- package/src/config/schemas/call-site-catalog.ts +0 -7
- package/src/config/schemas/llm.ts +17 -1
- package/src/config/schemas/memory-v3.ts +58 -8
- package/src/config/seed-inference-profiles.ts +18 -0
- package/src/context/post-turn-tool-result-truncation.ts +39 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +8 -1
- package/src/daemon/conversation-agent-loop.ts +87 -95
- package/src/daemon/conversation-error.ts +31 -4
- package/src/daemon/conversation-history.ts +1 -1
- package/src/daemon/conversation-media-retry.ts +19 -6
- package/src/daemon/conversation-messaging.ts +17 -0
- package/src/daemon/conversation-process.ts +14 -5
- package/src/daemon/conversation-queue-manager.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +37 -1
- package/src/daemon/conversation-surfaces.ts +141 -3
- package/src/daemon/conversation.ts +48 -13
- package/src/daemon/external-plugins-bootstrap.ts +8 -3
- package/src/daemon/persist-unsendable-image.ts +62 -25
- package/src/daemon/process-message.ts +1 -1
- package/src/daemon/tool-side-effects.ts +15 -0
- package/src/memory/__tests__/activation-session-store.test.ts +41 -0
- package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
- package/src/memory/activation-session-store.ts +43 -0
- package/src/memory/db-init.ts +4 -0
- package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
- package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/onboarding-events-store.ts +66 -18
- package/src/memory/schema/infrastructure.ts +13 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
- package/src/memory/v2/__tests__/page-store.test.ts +22 -0
- package/src/memory/v2/consolidation-job.ts +2 -72
- package/src/memory/v2/types.ts +5 -0
- package/src/messaging/providers/telegram-bot/api.ts +14 -5
- package/src/notifications/adapters/telegram.ts +7 -1
- package/src/plugin-api/constants.ts +2 -2
- package/src/plugin-api/index.ts +2 -2
- package/src/plugin-api/types.ts +19 -5
- package/src/plugins/defaults/compaction/compact.ts +24 -15
- package/src/plugins/defaults/compaction/context-overflow-reducer.ts +4 -4
- package/src/plugins/defaults/compaction/manager-store.ts +1 -1
- package/src/{context → plugins/defaults/compaction}/window-manager.ts +68 -12
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +12 -18
- package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +19 -66
- package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +139 -129
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +332 -164
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +516 -293
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +51 -21
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +66 -11
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +271 -110
- package/src/plugins/defaults/memory-v3-shadow/__tests__/types.test.ts +0 -22
- package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +26 -62
- package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
- package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +3 -2
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +415 -181
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +196 -56
- package/src/plugins/defaults/memory-v3-shadow/page-content.ts +39 -2
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
- package/src/plugins/defaults/memory-v3-shadow/render-injection.ts +15 -7
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
- package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
- package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +75 -3
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +111 -78
- package/src/plugins/defaults/memory-v3-shadow/types.ts +52 -19
- package/src/plugins/defaults/memory-v3-shadow/working-set.ts +4 -1
- package/src/plugins/external-api.ts +114 -0
- package/src/prompts/system-prompt.ts +61 -10
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +37 -2
- package/src/providers/anthropic/client.ts +9 -10
- package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
- package/src/providers/inference/logit-bias.ts +55 -0
- package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
- package/src/providers/openai/chat-completions-provider.ts +44 -1
- package/src/providers/retry.ts +22 -0
- package/src/providers/types.ts +6 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
- package/src/runtime/routes/acp-routes.test.ts +3 -20
- package/src/runtime/routes/app-management-routes.ts +3 -0
- package/src/runtime/routes/conversation-routes.ts +2 -0
- package/src/runtime/routes/memory-v3-routes.ts +64 -314
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
- package/src/runtime/routes/stt-routes.ts +45 -12
- package/src/runtime/routes/workspace-routes.ts +50 -15
- package/src/services/published-app-updater.ts +30 -8
- package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
- package/src/telemetry/activation-funnel.ts +167 -0
- package/src/telemetry/types.ts +13 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
- package/src/telemetry/usage-telemetry-reporter.ts +26 -1
- package/src/tools/acp/list-agents.test.ts +2 -18
- package/src/tools/acp/list-agents.ts +3 -15
- package/src/tools/acp/spawn.test.ts +0 -10
- package/src/tools/browser/browser-execution.ts +12 -2
- package/src/tools/network/web-fetch.ts +65 -24
- package/src/tools/skills/load.ts +1 -1
- package/src/tools/ui-surface/definitions.ts +7 -0
- package/src/acp/feature-gate.test.ts +0 -48
- package/src/acp/feature-gate.ts +0 -34
- package/src/plugins/defaults/memory-v3-shadow/__tests__/assign.test.ts +0 -242
- package/src/plugins/defaults/memory-v3-shadow/__tests__/core.test.ts +0 -39
- package/src/plugins/defaults/memory-v3-shadow/__tests__/health.test.ts +0 -219
- package/src/plugins/defaults/memory-v3-shadow/__tests__/needle.test.ts +0 -107
- package/src/plugins/defaults/memory-v3-shadow/__tests__/provider-blocks.test.ts +0 -13
- package/src/plugins/defaults/memory-v3-shadow/__tests__/reconcile.test.ts +0 -274
- package/src/plugins/defaults/memory-v3-shadow/__tests__/router.test.ts +0 -337
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selector.test.ts +0 -470
- package/src/plugins/defaults/memory-v3-shadow/__tests__/snapshot.test.ts +0 -168
- package/src/plugins/defaults/memory-v3-shadow/__tests__/tree.test.ts +0 -192
- package/src/plugins/defaults/memory-v3-shadow/assign.ts +0 -272
- package/src/plugins/defaults/memory-v3-shadow/core.ts +0 -26
- package/src/plugins/defaults/memory-v3-shadow/health.ts +0 -0
- package/src/plugins/defaults/memory-v3-shadow/needle.ts +0 -115
- package/src/plugins/defaults/memory-v3-shadow/provider-blocks.ts +0 -26
- package/src/plugins/defaults/memory-v3-shadow/reconcile.ts +0 -527
- package/src/plugins/defaults/memory-v3-shadow/router.ts +0 -190
- package/src/plugins/defaults/memory-v3-shadow/selector.ts +0 -226
- package/src/plugins/defaults/memory-v3-shadow/snapshot.ts +0 -209
- package/src/plugins/defaults/memory-v3-shadow/tree.ts +0 -174
- package/src/util/map-limit.ts +0 -27
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for `
|
|
2
|
+
* Tests for `shadow-plugin.ts` (section-lane pipeline).
|
|
3
3
|
*
|
|
4
4
|
* The v3 plugin is flag-gated. These tests assert:
|
|
5
5
|
* - both flags OFF → orchestrate is never called and no DB rows are written;
|
|
6
6
|
* - either flag ON → orchestrate runs and selection rows land in
|
|
7
|
-
* `memory_v3_selections
|
|
7
|
+
* `memory_v3_selections` with the new lane source tags;
|
|
8
8
|
* - shadow-only (live off) → the injector returns `null` (never mutates the
|
|
9
9
|
* turn) but still logs;
|
|
10
10
|
* - live on → the injector returns the rendered `<memory>` block;
|
|
11
11
|
* - an empty selection under live → `null`;
|
|
12
|
-
* - lazy-init runs the
|
|
12
|
+
* - lazy-init runs the lane builders only once across multiple turns, and
|
|
13
|
+
* `invalidateLanes` forces exactly one rebuild;
|
|
14
|
+
* - `initLanes` feeds synthetic capability pages (skills / CLI commands) into
|
|
15
|
+
* the section index via `renderCapabilityContent`, so the needle lane ranks
|
|
16
|
+
* them like any other page (they are no longer always-added to the pool).
|
|
13
17
|
*
|
|
14
18
|
* All heavy dependencies (config, flag resolver, conversation reads, v2 page
|
|
15
|
-
* index + page store,
|
|
16
|
-
* plugin so the module observes them at load time. A real
|
|
17
|
-
* backs the write assertions via the
|
|
19
|
+
* index + page store, the lane builders, orchestrate) are mocked BEFORE
|
|
20
|
+
* importing the plugin so the module observes them at load time. A real
|
|
21
|
+
* in-memory SQLite DB backs the write assertions via the
|
|
22
|
+
* `memory_v3_selections` migration.
|
|
18
23
|
*/
|
|
19
24
|
|
|
20
25
|
import { Database } from "bun:sqlite";
|
|
@@ -24,22 +29,25 @@ import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
|
24
29
|
|
|
25
30
|
import { migrateAddMemoryV3Selections } from "../../../../memory/migrations/268-add-memory-v3-selections.js";
|
|
26
31
|
import * as schema from "../../../../memory/schema.js";
|
|
27
|
-
import type {
|
|
32
|
+
import type { CandidateLane, SectionIndex, SelectionSource } from "../types.js";
|
|
28
33
|
|
|
29
34
|
// `mock.module` is process-global and, in Bun, neither `mock.restore()` nor a
|
|
30
35
|
// re-mock in `afterAll` reverts it for files that load LATER in the same
|
|
31
|
-
// `bun test src/memory
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
+
// `bun test src/plugins/defaults/memory-v3-shadow/` run. Sibling files import
|
|
37
|
+
// these same modules for real, so an unconditional stub here would leak in and
|
|
38
|
+
// break them. Instead each stub below DELEGATES to the real implementation
|
|
39
|
+
// unless this test is actively running (`shadowMockActive`, toggled in
|
|
40
|
+
// beforeEach/afterAll).
|
|
36
41
|
//
|
|
37
42
|
// Snapshot the real exports into plain objects NOW: a module namespace object
|
|
38
|
-
// is a live view, so reading
|
|
39
|
-
//
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
+
// is a live view, so reading the real export *after* the stub is installed
|
|
44
|
+
// would resolve back to the stub (infinite recursion).
|
|
45
|
+
const realSections = { ...(await import("../sections.js")) };
|
|
46
|
+
const realSectionNeedle = { ...(await import("../section-needle.js")) };
|
|
47
|
+
const realEdge = { ...(await import("../edge.js")) };
|
|
48
|
+
const realSectionDenseStore = {
|
|
49
|
+
...(await import("../section-dense-store.js")),
|
|
50
|
+
};
|
|
43
51
|
const realOrchestrate = { ...(await import("../orchestrate.js")) };
|
|
44
52
|
const realPlatform = { ...(await import("../../../../util/platform.js")) };
|
|
45
53
|
const realPageStore = {
|
|
@@ -48,33 +56,65 @@ const realPageStore = {
|
|
|
48
56
|
const realConversationCrud = {
|
|
49
57
|
...(await import("../../../../memory/conversation-crud.js")),
|
|
50
58
|
};
|
|
59
|
+
const realSkillStore = {
|
|
60
|
+
...(await import("../../../../memory/v2/skill-store.js")),
|
|
61
|
+
};
|
|
62
|
+
const realCliCommandStore = {
|
|
63
|
+
...(await import("../../../../memory/v2/cli-command-store.js")),
|
|
64
|
+
};
|
|
51
65
|
|
|
52
66
|
let shadowMockActive = false;
|
|
53
67
|
|
|
54
68
|
// ─── mutable test state, read by the mocks below ────────────────────────────
|
|
55
69
|
|
|
56
|
-
// Per-flag toggles. The plugin reads `memory-v3-live` and `memory-v3-shadow`
|
|
57
|
-
// independently, so the mock resolves by flag key rather than a single boolean.
|
|
58
70
|
let liveEnabled = false;
|
|
59
71
|
let shadowEnabled = false;
|
|
60
72
|
let messages: Array<{ role: string; content: string }> = [];
|
|
73
|
+
|
|
74
|
+
// A synthetic skill capability slug the page index carries. Its rendered
|
|
75
|
+
// content holds a distinctive term ("kumquat") so the real needle, built over
|
|
76
|
+
// the section index `initLanes` feeds, ranks it. Generic placeholder only.
|
|
77
|
+
const CAPABILITY_SLUG = "skills/example";
|
|
78
|
+
const CAPABILITY_CONTENT = "use the kumquat skill to do the thing";
|
|
79
|
+
|
|
80
|
+
// The orchestrate result the spy returns. `laneBySlug` records the lane that
|
|
81
|
+
// surfaced each pooled slug: page-1 → "needle", page-2 → "dense", page-3 →
|
|
82
|
+
// "edge"; `attributeSelections` reads it directly. `carried` is a carry-forward
|
|
83
|
+
// slug not re-selected this turn. `sectionBySlug` carries the matched section
|
|
84
|
+
// for the slugs that had one (page-1/page-2) — consumed by the live injector's
|
|
85
|
+
// progressive disclosure, independent of source attribution.
|
|
61
86
|
const orchestrateSpy = mock(async () => ({
|
|
62
|
-
openedLeaves: [],
|
|
63
87
|
currentSelections: [
|
|
64
|
-
{ slug: "
|
|
65
|
-
{ slug: "
|
|
88
|
+
{ slug: "page-1", pinned: true },
|
|
89
|
+
{ slug: "page-2", pinned: false },
|
|
90
|
+
{ slug: "page-3", pinned: false },
|
|
66
91
|
],
|
|
67
|
-
workingSetUnion: new Set<string>(["
|
|
68
|
-
finalInjection: ["
|
|
92
|
+
workingSetUnion: new Set<string>(["page-1", "page-2", "page-3"]),
|
|
93
|
+
finalInjection: ["page-1", "page-2", "page-3", "carried"],
|
|
94
|
+
sectionBySlug: new Map([
|
|
95
|
+
["page-1", { article: "page-1", title: "", text: "x", ordinal: 0 }],
|
|
96
|
+
["page-2", { article: "page-2", title: "", text: "y", ordinal: 0 }],
|
|
97
|
+
]),
|
|
98
|
+
laneBySlug: new Map<string, CandidateLane>([
|
|
99
|
+
["page-1", "needle"],
|
|
100
|
+
["page-2", "dense"],
|
|
101
|
+
["page-3", "edge"],
|
|
102
|
+
]),
|
|
69
103
|
}));
|
|
70
|
-
|
|
71
|
-
let
|
|
104
|
+
|
|
105
|
+
let sectionBuilds = 0;
|
|
72
106
|
let needleBuilds = 0;
|
|
73
|
-
let
|
|
107
|
+
let edgeBuilds = 0;
|
|
108
|
+
let ensureCollectionCalls = 0;
|
|
109
|
+
let ensureCollectionThrows = false;
|
|
110
|
+
|
|
111
|
+
// The `pageBody` resolver `initLanes` passes to `buildSectionIndex` (its second
|
|
112
|
+
// arg), captured by the stub below so a test can drive it directly: a capability
|
|
113
|
+
// slug must resolve to its rendered capability content, an on-disk slug to its
|
|
114
|
+
// page body.
|
|
115
|
+
let capturedPageBody: ((slug: string) => Promise<string>) | null = null;
|
|
74
116
|
|
|
75
|
-
// Shared in-memory DB so writes are observable from the test.
|
|
76
|
-
// sqlite handle alongside the drizzle wrapper so the test can both read rows
|
|
77
|
-
// directly and feed the same handle through the mocked `getSqliteFrom`.
|
|
117
|
+
// Shared in-memory DB so writes are observable from the test.
|
|
78
118
|
let testSqlite: Database;
|
|
79
119
|
let testDb = makeDb();
|
|
80
120
|
function makeDb() {
|
|
@@ -85,15 +125,10 @@ function makeDb() {
|
|
|
85
125
|
return db;
|
|
86
126
|
}
|
|
87
127
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
["domain-a", { members: ["domain-a/page-1", "domain-a/carried"] }],
|
|
93
|
-
["domain-b", { members: ["domain-b/page-2"] }],
|
|
94
|
-
]),
|
|
95
|
-
byPage: new Map(),
|
|
96
|
-
} as unknown as LeafTree;
|
|
128
|
+
const FAKE_SECTION_INDEX: SectionIndex = {
|
|
129
|
+
sections: [],
|
|
130
|
+
byArticle: new Map(),
|
|
131
|
+
};
|
|
97
132
|
|
|
98
133
|
// ─── module mocks (installed before the plugin import) ──────────────────────
|
|
99
134
|
|
|
@@ -111,16 +146,17 @@ mock.module("../../../../config/loader.js", () => ({
|
|
|
111
146
|
memory: {
|
|
112
147
|
v3: {
|
|
113
148
|
workingSet: { maxPages: 150, evictWindow: 5 },
|
|
114
|
-
|
|
149
|
+
needleK: 100,
|
|
150
|
+
denseK: 100,
|
|
151
|
+
edge: { hubDegree: 30, seedCount: 18, perSeed: 6, cap: 45 },
|
|
115
152
|
},
|
|
153
|
+
qdrant: { vectorSize: 8, onDisk: false },
|
|
116
154
|
},
|
|
117
155
|
}),
|
|
118
156
|
}));
|
|
119
157
|
|
|
120
158
|
// Spread the real module so every export the live path transitively imports
|
|
121
|
-
//
|
|
122
|
-
// whole module with a bare `{ getMessages }` made those consumers fail to load.
|
|
123
|
-
// Only `getMessages` is overridden, since that's the one the plugin reads.
|
|
159
|
+
// stays present; only `getMessages` is overridden.
|
|
124
160
|
mock.module("../../../../memory/conversation-crud.js", () => ({
|
|
125
161
|
...realConversationCrud,
|
|
126
162
|
getMessages: () => messages.map((m, i) => ({ ...m, id: `m${i}` })),
|
|
@@ -131,13 +167,42 @@ mock.module("../../../../memory/db-connection.js", () => ({
|
|
|
131
167
|
getSqliteFrom: () => testSqlite,
|
|
132
168
|
}));
|
|
133
169
|
|
|
134
|
-
mock.module("
|
|
135
|
-
getPageIndex: async () => ({
|
|
170
|
+
mock.module("../../../../memory/v2/page-index.js", () => ({
|
|
171
|
+
getPageIndex: async () => ({
|
|
172
|
+
entries: [
|
|
173
|
+
{
|
|
174
|
+
slug: "page-1",
|
|
175
|
+
id: 1,
|
|
176
|
+
summary: "",
|
|
177
|
+
edges: [],
|
|
178
|
+
leaves: [],
|
|
179
|
+
modifiedAt: 0,
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
slug: "page-2",
|
|
183
|
+
id: 2,
|
|
184
|
+
summary: "",
|
|
185
|
+
edges: [],
|
|
186
|
+
leaves: [],
|
|
187
|
+
modifiedAt: 0,
|
|
188
|
+
},
|
|
189
|
+
// A synthetic capability row — same shape the v2 page index appends for
|
|
190
|
+
// skills/CLI commands. `initLanes` must route it through the capability
|
|
191
|
+
// resolver, not an on-disk read.
|
|
192
|
+
{
|
|
193
|
+
slug: CAPABILITY_SLUG,
|
|
194
|
+
id: 3,
|
|
195
|
+
summary: "",
|
|
196
|
+
edges: [],
|
|
197
|
+
leaves: [],
|
|
198
|
+
modifiedAt: 0,
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
bySlug: new Map(),
|
|
202
|
+
}),
|
|
136
203
|
}));
|
|
137
204
|
|
|
138
205
|
// `pageContent` (live mode) reads the full page via `readPage`/`renderPageContent`.
|
|
139
|
-
// Stub them to return a deterministic body per slug so the rendered `<memory>`
|
|
140
|
-
// block is assertable without touching the filesystem.
|
|
141
206
|
mock.module("../../../../memory/v2/page-store.js", () => ({
|
|
142
207
|
...realPageStore,
|
|
143
208
|
readPage: async (workspaceDir: string, slug: string) =>
|
|
@@ -160,41 +225,74 @@ mock.module("../../../../util/platform.js", () => ({
|
|
|
160
225
|
: realPlatform.getWorkspaceDir(),
|
|
161
226
|
}));
|
|
162
227
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
228
|
+
// Capability stores: `renderCapabilityContent` (reached from `initLanes`' pageBody
|
|
229
|
+
// and from the live injector) resolves synthetic slugs through these. Spread the
|
|
230
|
+
// real module so the prefix predicates (`isSkillSlug`/`isCliCommandSlug`) stay
|
|
231
|
+
// intact; override only the content lookup so the capability slug resolves.
|
|
232
|
+
mock.module("../../../../memory/v2/skill-store.js", () => ({
|
|
233
|
+
...realSkillStore,
|
|
234
|
+
getSkillCapability: (idOrSlug: string) =>
|
|
235
|
+
shadowMockActive
|
|
236
|
+
? idOrSlug === CAPABILITY_SLUG
|
|
237
|
+
? { id: "example", content: CAPABILITY_CONTENT }
|
|
238
|
+
: null
|
|
239
|
+
: realSkillStore.getSkillCapability(idOrSlug),
|
|
240
|
+
}));
|
|
241
|
+
|
|
242
|
+
mock.module("../../../../memory/v2/cli-command-store.js", () => ({
|
|
243
|
+
...realCliCommandStore,
|
|
244
|
+
getCliCommandCapability: (idOrSlug: string) =>
|
|
173
245
|
shadowMockActive
|
|
174
|
-
?
|
|
175
|
-
|
|
176
|
-
: realTree.membersOf(tree, leaf),
|
|
246
|
+
? null
|
|
247
|
+
: realCliCommandStore.getCliCommandCapability(idOrSlug),
|
|
177
248
|
}));
|
|
178
249
|
|
|
179
|
-
mock.module("../
|
|
180
|
-
...
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
return
|
|
250
|
+
mock.module("../sections.js", () => ({
|
|
251
|
+
...realSections,
|
|
252
|
+
buildSectionIndex: async (
|
|
253
|
+
...args: Parameters<typeof realSections.buildSectionIndex>
|
|
254
|
+
) => {
|
|
255
|
+
if (!shadowMockActive) return realSections.buildSectionIndex(...args);
|
|
256
|
+
sectionBuilds++;
|
|
257
|
+
// Capture the `pageBody` resolver so a test can exercise the capability
|
|
258
|
+
// branch directly. Returning the FAKE index keeps the other tests cheap.
|
|
259
|
+
capturedPageBody = args[1];
|
|
260
|
+
return FAKE_SECTION_INDEX;
|
|
185
261
|
},
|
|
186
262
|
}));
|
|
187
263
|
|
|
188
|
-
mock.module("../needle.js", () => ({
|
|
189
|
-
...
|
|
190
|
-
|
|
191
|
-
...args: Parameters<typeof
|
|
264
|
+
mock.module("../section-needle.js", () => ({
|
|
265
|
+
...realSectionNeedle,
|
|
266
|
+
buildSectionNeedle: (
|
|
267
|
+
...args: Parameters<typeof realSectionNeedle.buildSectionNeedle>
|
|
192
268
|
) => {
|
|
193
|
-
if (!shadowMockActive) return
|
|
269
|
+
if (!shadowMockActive) return realSectionNeedle.buildSectionNeedle(...args);
|
|
194
270
|
needleBuilds++;
|
|
195
|
-
return { query: () => []
|
|
196
|
-
|
|
197
|
-
|
|
271
|
+
return { query: () => [], bestSection: () => -1 };
|
|
272
|
+
},
|
|
273
|
+
}));
|
|
274
|
+
|
|
275
|
+
mock.module("../edge.js", () => ({
|
|
276
|
+
...realEdge,
|
|
277
|
+
buildEdgeGraph: async (
|
|
278
|
+
...args: Parameters<typeof realEdge.buildEdgeGraph>
|
|
279
|
+
) => {
|
|
280
|
+
if (!shadowMockActive) return realEdge.buildEdgeGraph(...args);
|
|
281
|
+
edgeBuilds++;
|
|
282
|
+
return { adjacency: new Map(), hubs: new Set(), slugs: new Set() };
|
|
283
|
+
},
|
|
284
|
+
}));
|
|
285
|
+
|
|
286
|
+
mock.module("../section-dense-store.js", () => ({
|
|
287
|
+
...realSectionDenseStore,
|
|
288
|
+
ensureSectionCollection: async (
|
|
289
|
+
...args: Parameters<typeof realSectionDenseStore.ensureSectionCollection>
|
|
290
|
+
) => {
|
|
291
|
+
if (!shadowMockActive) {
|
|
292
|
+
return realSectionDenseStore.ensureSectionCollection(...args);
|
|
293
|
+
}
|
|
294
|
+
ensureCollectionCalls++;
|
|
295
|
+
if (ensureCollectionThrows) throw new Error("qdrant unavailable");
|
|
198
296
|
},
|
|
199
297
|
}));
|
|
200
298
|
|
|
@@ -211,9 +309,6 @@ const { runShadowObservation, resetShadowLanesForTests, invalidateLanes } =
|
|
|
211
309
|
await import("../shadow-plugin.js");
|
|
212
310
|
const { memoryV3Injector } = await import("../injector.js");
|
|
213
311
|
|
|
214
|
-
// The module stubs above stay installed for the rest of the process (Bun can't
|
|
215
|
-
// reliably uninstall them), but `shadowMockActive` gates their fake behavior to
|
|
216
|
-
// this file's tests only, so later-loaded sibling files see real behavior.
|
|
217
312
|
afterAll(() => {
|
|
218
313
|
shadowMockActive = false;
|
|
219
314
|
});
|
|
@@ -237,10 +332,12 @@ beforeEach(() => {
|
|
|
237
332
|
},
|
|
238
333
|
];
|
|
239
334
|
orchestrateSpy.mockClear();
|
|
240
|
-
|
|
241
|
-
coreLoads = 0;
|
|
335
|
+
sectionBuilds = 0;
|
|
242
336
|
needleBuilds = 0;
|
|
243
|
-
|
|
337
|
+
edgeBuilds = 0;
|
|
338
|
+
ensureCollectionCalls = 0;
|
|
339
|
+
ensureCollectionThrows = false;
|
|
340
|
+
capturedPageBody = null;
|
|
244
341
|
testDb = makeDb();
|
|
245
342
|
resetShadowLanesForTests();
|
|
246
343
|
});
|
|
@@ -260,22 +357,27 @@ describe("memory-v3 shadow plugin", () => {
|
|
|
260
357
|
shadowEnabled = false;
|
|
261
358
|
await runShadowObservation("conv-1", 0);
|
|
262
359
|
expect(orchestrateSpy).not.toHaveBeenCalled();
|
|
263
|
-
expect(
|
|
360
|
+
expect(sectionBuilds).toBe(0);
|
|
264
361
|
expect(readRows()).toHaveLength(0);
|
|
265
362
|
});
|
|
266
363
|
|
|
267
|
-
test("shadow flag ON → orchestrate runs and rows are written", async () => {
|
|
364
|
+
test("shadow flag ON → orchestrate runs and rows are written with per-lane sources", async () => {
|
|
268
365
|
shadowEnabled = true;
|
|
269
366
|
await runShadowObservation("conv-1", 2);
|
|
270
367
|
|
|
271
368
|
expect(orchestrateSpy).toHaveBeenCalledTimes(1);
|
|
272
369
|
const rows = readRows();
|
|
370
|
+
// Each selection is attributed to the lane that surfaced it
|
|
371
|
+
// (`result.laneBySlug`), not re-derived from section presence — so the
|
|
372
|
+
// dense-only page-2 logs "dense", not "needle".
|
|
273
373
|
expect(rows).toEqual([
|
|
274
|
-
{ slug: "
|
|
275
|
-
// page-1
|
|
276
|
-
{ slug: "
|
|
277
|
-
// page-2
|
|
278
|
-
{ slug: "
|
|
374
|
+
{ slug: "carried", source: "carry-forward", pinned: 0 },
|
|
375
|
+
// page-1 was surfaced by the needle lane → "needle", pinned.
|
|
376
|
+
{ slug: "page-1", source: "needle", pinned: 1 },
|
|
377
|
+
// page-2 was surfaced by the dense lane → "dense".
|
|
378
|
+
{ slug: "page-2", source: "dense", pinned: 0 },
|
|
379
|
+
// page-3 was surfaced by the edge lane → "edge".
|
|
380
|
+
{ slug: "page-3", source: "edge", pinned: 0 },
|
|
279
381
|
]);
|
|
280
382
|
});
|
|
281
383
|
|
|
@@ -294,16 +396,21 @@ describe("memory-v3 shadow plugin", () => {
|
|
|
294
396
|
expect(turn.currentMessage).toBe("hello world");
|
|
295
397
|
});
|
|
296
398
|
|
|
297
|
-
test("orchestrate receives the
|
|
399
|
+
test("orchestrate receives the lane deps", async () => {
|
|
298
400
|
shadowEnabled = true;
|
|
299
|
-
configL2Concurrency = 9;
|
|
300
401
|
await runShadowObservation("conv-1", 0);
|
|
301
402
|
const deps = (
|
|
302
403
|
orchestrateSpy.mock.calls as unknown as unknown[][]
|
|
303
404
|
)[0]![1] as {
|
|
304
|
-
|
|
405
|
+
sectionIndex?: unknown;
|
|
406
|
+
needle?: unknown;
|
|
407
|
+
edgeGraph?: unknown;
|
|
408
|
+
workingSet?: unknown;
|
|
305
409
|
};
|
|
306
|
-
expect(deps.
|
|
410
|
+
expect(deps.sectionIndex).toBeDefined();
|
|
411
|
+
expect(deps.needle).toBeDefined();
|
|
412
|
+
expect(deps.edgeGraph).toBeDefined();
|
|
413
|
+
expect(deps.workingSet).toBeDefined();
|
|
307
414
|
});
|
|
308
415
|
|
|
309
416
|
test("both flags OFF → produce returns null, no orchestrate, no writes", async () => {
|
|
@@ -332,9 +439,12 @@ describe("memory-v3 shadow plugin", () => {
|
|
|
332
439
|
expect(block!.placement).toBe("after-memory-prefix");
|
|
333
440
|
expect(block!.text.startsWith("<memory>\n")).toBe(true);
|
|
334
441
|
expect(block!.text.endsWith("\n</memory>")).toBe(true);
|
|
335
|
-
//
|
|
336
|
-
|
|
337
|
-
expect(block!.text).toContain("
|
|
442
|
+
// Progressive disclosure: a slug with a matched section renders that
|
|
443
|
+
// section's text (page-1 → "x"), NOT the full page body.
|
|
444
|
+
expect(block!.text).toContain("# memory/concepts/page-1.md\nx");
|
|
445
|
+
expect(block!.text).not.toContain("body for page-1");
|
|
446
|
+
// A slug with no matched section (carry-forward) falls back to the full body.
|
|
447
|
+
expect(block!.text).toContain("body for carried");
|
|
338
448
|
// Selections are still logged in live mode.
|
|
339
449
|
expect(readRows().length).toBeGreaterThan(0);
|
|
340
450
|
});
|
|
@@ -343,76 +453,87 @@ describe("memory-v3 shadow plugin", () => {
|
|
|
343
453
|
liveEnabled = true;
|
|
344
454
|
shadowEnabled = false;
|
|
345
455
|
orchestrateSpy.mockImplementationOnce(async () => ({
|
|
346
|
-
openedLeaves: [],
|
|
347
456
|
currentSelections: [],
|
|
348
457
|
workingSetUnion: new Set<string>(),
|
|
349
458
|
finalInjection: [],
|
|
459
|
+
sectionBySlug: new Map(),
|
|
460
|
+
laneBySlug: new Map(),
|
|
350
461
|
}));
|
|
351
462
|
const block = await produce("conv-1", 0);
|
|
352
463
|
expect(block).toBeNull();
|
|
353
|
-
// Orchestration still ran (and logged nothing, since there were no rows).
|
|
354
464
|
expect(orchestrateSpy).toHaveBeenCalledTimes(1);
|
|
355
465
|
expect(readRows()).toHaveLength(0);
|
|
356
466
|
});
|
|
357
467
|
|
|
358
|
-
test("lazy-init runs the
|
|
468
|
+
test("lazy-init runs the lane builders only once across turns", async () => {
|
|
359
469
|
shadowEnabled = true;
|
|
360
470
|
await runShadowObservation("conv-1", 0);
|
|
361
471
|
await runShadowObservation("conv-1", 1);
|
|
362
472
|
await runShadowObservation("conv-1", 2);
|
|
363
|
-
expect(
|
|
364
|
-
expect(coreLoads).toBe(1);
|
|
473
|
+
expect(sectionBuilds).toBe(1);
|
|
365
474
|
expect(needleBuilds).toBe(1);
|
|
475
|
+
expect(edgeBuilds).toBe(1);
|
|
476
|
+
expect(ensureCollectionCalls).toBe(1);
|
|
366
477
|
expect(orchestrateSpy).toHaveBeenCalledTimes(3);
|
|
367
478
|
});
|
|
368
479
|
|
|
480
|
+
test("a Qdrant section-collection failure does not disable the in-memory lanes", async () => {
|
|
481
|
+
shadowEnabled = true;
|
|
482
|
+
ensureCollectionThrows = true;
|
|
483
|
+
// initLanes still builds the needle + edge lanes and orchestrate runs — a
|
|
484
|
+
// Qdrant outage degrades only the dense lane, it does not take down all of v3
|
|
485
|
+
// (and does not poison the memoized lanes by rejecting init).
|
|
486
|
+
await runShadowObservation("conv-1", 0);
|
|
487
|
+
expect(needleBuilds).toBe(1);
|
|
488
|
+
expect(edgeBuilds).toBe(1);
|
|
489
|
+
expect(ensureCollectionCalls).toBe(1);
|
|
490
|
+
expect(orchestrateSpy).toHaveBeenCalledTimes(1);
|
|
491
|
+
});
|
|
492
|
+
|
|
369
493
|
test("invalidateLanes forces a one-time rebuild on the next turn", async () => {
|
|
370
494
|
shadowEnabled = true;
|
|
371
495
|
await runShadowObservation("conv-1", 0);
|
|
372
496
|
await runShadowObservation("conv-1", 1);
|
|
373
|
-
|
|
374
|
-
expect(treeLoads).toBe(1);
|
|
497
|
+
expect(sectionBuilds).toBe(1);
|
|
375
498
|
expect(needleBuilds).toBe(1);
|
|
376
499
|
|
|
377
500
|
invalidateLanes();
|
|
378
501
|
|
|
379
|
-
// The next turn rebuilds (fresh tree load + fresh needle index)...
|
|
380
502
|
await runShadowObservation("conv-1", 2);
|
|
381
|
-
expect(
|
|
382
|
-
expect(coreLoads).toBe(2);
|
|
503
|
+
expect(sectionBuilds).toBe(2);
|
|
383
504
|
expect(needleBuilds).toBe(2);
|
|
505
|
+
expect(edgeBuilds).toBe(2);
|
|
384
506
|
|
|
385
507
|
// ...and the rebuild is memoized again — no further builds until the next
|
|
386
508
|
// invalidation.
|
|
387
509
|
await runShadowObservation("conv-1", 3);
|
|
388
|
-
expect(
|
|
510
|
+
expect(sectionBuilds).toBe(2);
|
|
389
511
|
expect(needleBuilds).toBe(2);
|
|
390
512
|
});
|
|
391
513
|
|
|
392
514
|
test("resetShadowLanesForTests invalidates like invalidateLanes", async () => {
|
|
393
515
|
shadowEnabled = true;
|
|
394
516
|
await runShadowObservation("conv-1", 0);
|
|
395
|
-
expect(
|
|
517
|
+
expect(sectionBuilds).toBe(1);
|
|
396
518
|
|
|
397
519
|
resetShadowLanesForTests();
|
|
398
520
|
|
|
399
521
|
await runShadowObservation("conv-1", 1);
|
|
400
|
-
expect(
|
|
522
|
+
expect(sectionBuilds).toBe(2);
|
|
401
523
|
});
|
|
402
524
|
|
|
403
525
|
test("concurrent first turns after invalidation share a single build", async () => {
|
|
404
526
|
shadowEnabled = true;
|
|
405
527
|
await runShadowObservation("conv-1", 0);
|
|
406
|
-
expect(
|
|
528
|
+
expect(sectionBuilds).toBe(1);
|
|
407
529
|
|
|
408
530
|
invalidateLanes();
|
|
409
531
|
|
|
410
|
-
// Both turns race the rebuild; memoization must collapse them to one build.
|
|
411
532
|
await Promise.all([
|
|
412
533
|
runShadowObservation("conv-1", 1),
|
|
413
534
|
runShadowObservation("conv-1", 2),
|
|
414
535
|
]);
|
|
415
|
-
expect(
|
|
536
|
+
expect(sectionBuilds).toBe(2);
|
|
416
537
|
expect(needleBuilds).toBe(2);
|
|
417
538
|
});
|
|
418
539
|
|
|
@@ -428,4 +549,44 @@ describe("memory-v3 shadow plugin", () => {
|
|
|
428
549
|
expect(orchestrateSpy).not.toHaveBeenCalled();
|
|
429
550
|
expect(readRows()).toHaveLength(0);
|
|
430
551
|
});
|
|
552
|
+
|
|
553
|
+
describe("initLanes feeds synthetic capability pages into the section index", () => {
|
|
554
|
+
test("the pageBody resolver returns capability content for a synthetic slug and disk body otherwise", async () => {
|
|
555
|
+
shadowEnabled = true;
|
|
556
|
+
await runShadowObservation("conv-1", 0);
|
|
557
|
+
|
|
558
|
+
// `initLanes` ran the real pageBody-building closure and handed it to
|
|
559
|
+
// `buildSectionIndex`; the stub captured it.
|
|
560
|
+
expect(capturedPageBody).not.toBeNull();
|
|
561
|
+
const pageBody = capturedPageBody!;
|
|
562
|
+
|
|
563
|
+
// A capability slug resolves to its rendered capability content (NOT an
|
|
564
|
+
// on-disk read, which would miss), so the needle can rank it.
|
|
565
|
+
const capBody = await pageBody(CAPABILITY_SLUG);
|
|
566
|
+
expect(capBody).toContain("kumquat");
|
|
567
|
+
// On-disk slugs still resolve to their page body.
|
|
568
|
+
expect(await pageBody("page-1")).toBe("body for page-1");
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
test("a synthetic slug's capability content yields a section the needle ranks", async () => {
|
|
572
|
+
shadowEnabled = true;
|
|
573
|
+
await runShadowObservation("conv-1", 0);
|
|
574
|
+
const pageBody = capturedPageBody!;
|
|
575
|
+
|
|
576
|
+
// Feed the captured resolver through the REAL section builder + needle:
|
|
577
|
+
// the capability slug yields ≥1 section and is ranked on a term from its
|
|
578
|
+
// capability content — the path that makes synthetic pages lane-rankable.
|
|
579
|
+
const index = await realSections.buildSectionIndex(
|
|
580
|
+
[CAPABILITY_SLUG],
|
|
581
|
+
pageBody,
|
|
582
|
+
);
|
|
583
|
+
expect(index.byArticle.get(CAPABILITY_SLUG)?.length ?? 0).toBeGreaterThan(
|
|
584
|
+
0,
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
const needle = realSectionNeedle.buildSectionNeedle(index);
|
|
588
|
+
const hits = needle.query("kumquat", 5);
|
|
589
|
+
expect(hits.map((h) => h.article)).toContain(CAPABILITY_SLUG);
|
|
590
|
+
});
|
|
591
|
+
});
|
|
431
592
|
});
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { expect, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
|
-
LeafFrontmatter,
|
|
5
|
-
LeafNode,
|
|
6
|
-
LeafPath,
|
|
7
|
-
LeafTree,
|
|
8
4
|
MemoryRoutingTurn,
|
|
9
5
|
SelectionSource,
|
|
10
6
|
Slug,
|
|
@@ -12,24 +8,8 @@ import type {
|
|
|
12
8
|
} from "../types.js";
|
|
13
9
|
|
|
14
10
|
test("v3 core types instantiate", () => {
|
|
15
|
-
const path: LeafPath = "domain/topic/leaf";
|
|
16
11
|
const slug: Slug = "page-123";
|
|
17
12
|
|
|
18
|
-
const frontmatter: LeafFrontmatter = { path, in_core: true };
|
|
19
|
-
|
|
20
|
-
const node: LeafNode = {
|
|
21
|
-
path,
|
|
22
|
-
frontmatter,
|
|
23
|
-
description: "an example leaf node",
|
|
24
|
-
members: [slug],
|
|
25
|
-
domain: "example",
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const tree: LeafTree = {
|
|
29
|
-
leaves: new Map<LeafPath, LeafNode>([[path, node]]),
|
|
30
|
-
byPage: new Map<Slug, LeafPath[]>([[slug, [path]]]),
|
|
31
|
-
};
|
|
32
|
-
|
|
33
13
|
const entry: WorkingSetEntry = {
|
|
34
14
|
slug,
|
|
35
15
|
selectedAtTurn: 1,
|
|
@@ -46,8 +26,6 @@ test("v3 core types instantiate", () => {
|
|
|
46
26
|
|
|
47
27
|
const source: SelectionSource = "carry-forward";
|
|
48
28
|
|
|
49
|
-
expect(tree.leaves.get(path)).toBe(node);
|
|
50
|
-
expect(tree.byPage.get(slug)).toEqual([path]);
|
|
51
29
|
expect(entry.slug).toBe(slug);
|
|
52
30
|
expect(turnContext.turnNumber).toBe(3);
|
|
53
31
|
expect(source).toBe("carry-forward");
|