rag-memory-epf-mcp 6.3.2 → 6.4.0-rc.1

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/README.md CHANGED
@@ -154,6 +154,26 @@ storeDocument(id, content, metadata)
154
154
 
155
155
  ## Changelog
156
156
 
157
+ ### v6.4.0 (2026-10-03)
158
+
159
+ - (Release candidate `6.4.0-rc.1` on the `next` dist-tag; `latest` stays 6.3.2 until the published artifact passes a
160
+ canary against a real project database.)
161
+
162
+ - **Changed** `hybridSearch` default ranking = reciprocal rank fusion of the vector and FTS5 candidate lists
163
+ (k=60). A chunk that only FTS5 finds — typically an exact identifier, filename or setting key — can now reach
164
+ the top; before, it scored at most 0.0164 and sat under every vector candidate in the measured corpora.
165
+ `relevance_score` on the default call is a rank score (at most 2/61, comparable only within one call). Measured on frozen snapshots: exact-identifier hit@10 7/20 → 18/20; worse on 0 of
166
+ 203 queries across three corpora.
167
+ - **Changed** the per-result summary path is opt-in (`RAG_MEMORY_SEARCH_SUMMARIES=on`). It embedded every
168
+ sentence fragment of every candidate (median 121 s per search) and its context boost pushed vector-0 chunks
169
+ to rank 1. `off` keeps working. Legacy paths (`summaries=on`, `useGraph: true`) keep the 6.3.2 formula.
170
+ - **Changed** with the default model (bge-m3) queries are embedded as raw text: the bge-v1.5 instruction the loader
171
+ prepended to every query is gone (bge-m3 needs none; stored vectors never had it). A custom `EMBEDDING_MODEL` keeps
172
+ the previous query instruction. Paraphrase hit@10 7 → 9 of 20, identifier hit@1 7 → 8,
173
+ document known-item (`hybridSearch`) unchanged or +1; entity known-item (`searchNodes`) hit@10 unchanged,
174
+ hit@1 -1 / -1 / +1 (hub / uap / hal). No re-index.
175
+ - Details and rollout: `docs/UPDATING.md`, `specs/changes/search-fusion-rrf/proposal.md`.
176
+
157
177
  ### v6.3.2 (2026-09-25)
158
178
 
159
179
  - **Changed — on macOS the engine takes `-wal` and `-shm` out of cloud sync.** It sets
package/dist/index.d.ts CHANGED
@@ -3,6 +3,11 @@ import { type SourceInput } from './src/observations/lifecycle.js';
3
3
  import { EmbeddingGate } from './src/embeddingGate.js';
4
4
  import type { EmbedPriority } from './src/embeddingGate.js';
5
5
  import { BackfillCoordinator } from './src/backfillCoordinator.js';
6
+ export declare const LEGACY_QUERY_INSTRUCTION = "Represent this sentence for searching relevant passages: ";
7
+ export declare function embeddingInput(text: string, isQuery: boolean, defaultModel?: boolean): string;
8
+ export declare function makeEmbedFn(model: (input: string, opts: any) => Promise<{
9
+ data: any;
10
+ }>, defaultModel: boolean): (text: string, dims: number, isQuery: boolean) => Promise<Float32Array<ArrayBuffer>>;
6
11
  export declare function compileFtsLiteralQuery(q: string): string | null;
7
12
  export declare function resolveDbFilePath(envValue: string | undefined, cwd: string, serverDir: string): string;
8
13
  export declare function parseMmapSize(raw: string | undefined): number;
package/dist/index.js CHANGED
@@ -67,6 +67,24 @@ function printBanner(opts) {
67
67
  // operators (every term is double-quoted; special characters stripped exactly
68
68
  // as the pre-3.6 hybridSearch sanitizer did). Returns null when nothing
69
69
  // searchable remains (contract: caller returns empty results + warning).
70
+ // search-fusion-rrf (query input): what the model sees for a text. For the DEFAULT model (bge-m3) queries are
71
+ // embedded as raw text — the model card: "the BGE-M3 model no longer requires adding instructions to the
72
+ // queries"; measured on the frozen hub snapshot (93 queries, full-corpus vector rank): 36 better, 6 worse, 51 same.
73
+ // A CUSTOM model (EMBEDDING_MODEL set to something else) keeps the pre-change query instruction: the evidence
74
+ // above is for bge-m3 only, and some models (e.g. bge v1.5) still recommend an instruction on queries.
75
+ // Passages are always raw. `isQuery` stays in the signature and in the embedding cache key.
76
+ export const LEGACY_QUERY_INSTRUCTION = 'Represent this sentence for searching relevant passages: ';
77
+ export function embeddingInput(text, isQuery, defaultModel = true) {
78
+ return isQuery && !defaultModel ? `${LEGACY_QUERY_INSTRUCTION}${text}` : text;
79
+ }
80
+ // The embed function handed to the gate. Exported so the input wiring is testable with a stub model.
81
+ export function makeEmbedFn(model, defaultModel) {
82
+ return async (text, dims, isQuery) => {
83
+ const input = embeddingInput(text, isQuery, defaultModel);
84
+ const r = await model(input, { pooling: 'cls', normalize: true });
85
+ return new Float32Array(r.data.slice(0, dims));
86
+ };
87
+ }
70
88
  export function compileFtsLiteralQuery(q) {
71
89
  const sanitized = q.replace(/["\*\(\)\-]/g, ' ').trim();
72
90
  if (!sanitized)
@@ -335,8 +353,12 @@ export class RAGKnowledgeGraphManager {
335
353
  .get(EMBEDDING_MODEL, MODEL_REVISION, MODEL_DTYPE, dims, 'cls', 1);
336
354
  this.db.prepare(`INSERT INTO server_meta(key,value) VALUES('current_profile_id',?)
337
355
  ON CONFLICT(key) DO UPDATE SET value=excluded.value`).run(String(row.id));
338
- this.db.prepare(`INSERT INTO server_meta(key,value) VALUES('query_prefix_version','1')
339
- ON CONFLICT(key) DO NOTHING`).run();
356
+ // query_prefix_version: diagnostic marker of the query-input convention of the engine that last INITIALISED
357
+ // this DB — 1 = instruction prepended to queries (<= 6.3.2, and custom models now), 2 = raw queries (default
358
+ // bge-m3 since search-fusion-rrf). It is written at init, not per query: it does not prove what the last query
359
+ // used, which engines are attached, or vector compatibility. Nothing reads it for logic.
360
+ this.db.prepare(`INSERT INTO server_meta(key,value) VALUES('query_prefix_version',?)
361
+ ON CONFLICT(key) DO UPDATE SET value=excluded.value`).run(IS_DEFAULT_MODEL_CONFIG ? '2' : '1');
340
362
  return row.id;
341
363
  }
342
364
  // Real model loader used by the gate: version-independent cache dir with a
@@ -374,11 +396,7 @@ export class RAGKnowledgeGraphManager {
374
396
  if (role === 'owner')
375
397
  lock.markComplete();
376
398
  console.error(`✅ ${EMBEDDING_MODEL} model loaded (${MODEL_DTYPE})`);
377
- return async (text, dims, isQuery) => {
378
- const input = isQuery ? `Represent this sentence for searching relevant passages: ${text}` : text;
379
- const r = await model(input, { pooling: 'cls', normalize: true });
380
- return new Float32Array(r.data.slice(0, dims));
381
- };
399
+ return makeEmbedFn(model, IS_DEFAULT_MODEL_CONFIG);
382
400
  }
383
401
  catch (e) {
384
402
  // Cache policy by CAUSE and ROLE (beta 2R B3 -> 4R M1 -> 5R M1), unit-
@@ -3549,6 +3567,9 @@ export class RAGKnowledgeGraphManager {
3549
3567
  throw new Error('Database not initialized');
3550
3568
  if (!this.encoding)
3551
3569
  throw new Error('Tokenizer not initialized');
3570
+ // search-fusion-rrf: summary mode is read ONCE, at call entry (before any await), and used for the
3571
+ // whole call. Opt-in since this change (was opt-out via =off in v5..6.3.2).
3572
+ const summariesOn = process.env.RAG_MEMORY_SEARCH_SUMMARIES === 'on';
3552
3573
  console.error(`🔍 Enhanced hybrid search: "${query}"`);
3553
3574
  // Parity with searchNodes (beta 1R supplement): an unsearchable query gets
3554
3575
  // an explicit warning instead of a silent empty envelope.
@@ -3612,6 +3633,11 @@ export class RAGKnowledgeGraphManager {
3612
3633
  }
3613
3634
  catch (embErr) {
3614
3635
  vectorDegraded = true;
3636
+ // search-fusion-rrf (D5): a failure on a LATER cross-lingual variant used to leave the earlier
3637
+ // variants' vector candidates in resultMap while the envelope said 'fts-only'. Drop them so the
3638
+ // degraded call really is FTS-only (no vector rank, no summary, no graph) in every mode.
3639
+ resultMap.clear();
3640
+ primaryQueryEmbedding = null;
3615
3641
  // 'inference_error' (not 'model_not_ready'): model_state may still read
3616
3642
  // 'ready' here — a contradictory reason pair confused callers (beta B6).
3617
3643
  degradationReason = this.degradationReason() ?? 'inference_error';
@@ -3619,6 +3645,12 @@ export class RAGKnowledgeGraphManager {
3619
3645
  }
3620
3646
  }
3621
3647
  const vectorResults = Array.from(resultMap.values()).sort((a, b) => a.distance - b.distance);
3648
+ // search-fusion-rrf: vector rank (1-based, best distance across variants) for rank fusion.
3649
+ // Taken here, before FTS5-only candidates are appended to vectorResults below.
3650
+ const vectorRankOf = new Map(vectorResults.map((r, i) => [r.chunk_id, i + 1]));
3651
+ // Default ranking = reciprocal rank fusion of the vector list and the FTS5 list (k=60).
3652
+ // The legacy formula stays for the opt-in diagnostics (summaries on, graph re-ranker on).
3653
+ const rankFusion = !useGraph && !summariesOn;
3622
3654
  // FTS5 full-text search as additional signal (Reciprocal Rank Fusion)
3623
3655
  const ftsBoostMap = new Map();
3624
3656
  try {
@@ -3807,12 +3839,12 @@ export class RAGKnowledgeGraphManager {
3807
3839
  graphBoost += Math.min(entityBoost, 0.4);
3808
3840
  }
3809
3841
  // Generate semantic summary (skip when degraded — no embeddings available).
3810
- // RAG_MEMORY_SEARCH_SUMMARIES=off: diagnostic escape hatch (v5) — the summary
3811
- // path embeds EVERY sentence of EVERY candidate (~100+ inferences per search,
3812
- // measured 90-120s cold). Off = preview slices + relevanceScore 0; ranking
3813
- // then rests on vectorSimilarity + boosts. Default unchanged.
3842
+ // search-fusion-rrf: the summary path is OPT-IN (RAG_MEMORY_SEARCH_SUMMARIES=on). It embeds
3843
+ // every sentence fragment of every candidate one at a time (~400 inferences per search on a
3844
+ // real corpus, median 121 s measured 2026-10-03) and its unbounded context boost let
3845
+ // vector-0 chunks take rank 1 (known-item hit@1 9/10 off vs 4/10 on). Default = preview slices.
3814
3846
  let summary, keyHighlight, relevanceScore;
3815
- if (vectorDegraded || !primaryQueryEmbedding || process.env.RAG_MEMORY_SEARCH_SUMMARIES === 'off') {
3847
+ if (vectorDegraded || !primaryQueryEmbedding || !summariesOn) {
3816
3848
  keyHighlight = result.text.slice(0, 150);
3817
3849
  summary = result.text.slice(0, 300);
3818
3850
  relevanceScore = 0;
@@ -3822,8 +3854,11 @@ export class RAGKnowledgeGraphManager {
3822
3854
  ));
3823
3855
  }
3824
3856
  const vectorSimilarity = Math.max(0, 1 - result.distance / 2);
3825
- const ftsBoost = ftsBoostMap.get(result.chunk_id) || 0;
3826
- const finalScore = Math.max(vectorSimilarity, relevanceScore) + graphBoost + ftsBoost;
3857
+ const ftsBoost = ftsBoostMap.get(result.chunk_id) || 0; // = 1/(60 + FTS rank), 0 if not an FTS hit
3858
+ const vectorRank = vectorRankOf.get(result.chunk_id);
3859
+ const finalScore = rankFusion
3860
+ ? (vectorRank !== undefined ? 1 / (60 + vectorRank) : 0) + ftsBoost
3861
+ : Math.max(vectorSimilarity, relevanceScore) + graphBoost + ftsBoost;
3827
3862
  // Determine document title and source ID
3828
3863
  let documentTitle;
3829
3864
  let sourceId;
@@ -277,7 +277,7 @@ export const addObservationsTool = {
277
277
  };
278
278
  // === HYBRID SEARCH TOOL ===
279
279
  const hybridSearchCapability = {
280
- description: 'Perform advanced hybrid search combining vector similarity with knowledge graph traversal',
280
+ description: 'Perform hybrid search over vector and FTS5 candidates with default reciprocal rank fusion and an optional legacy graph re-ranker',
281
281
  parameters: {
282
282
  type: 'object',
283
283
  properties: {
@@ -300,7 +300,8 @@ const hybridSearchCapability = {
300
300
  },
301
301
  };
302
302
  const hybridSearchDescription = () => `<description>
303
- Hybrid document search: vector similarity + FTS5 BM25 over document chunks, with an **opt-in** knowledge-graph re-ranker.
303
+ Hybrid document search: vector similarity + FTS5 BM25 over document chunks, fused by rank (reciprocal rank fusion,
304
+ k=60, so an exact identifier that only FTS5 finds can still enter the returned top results), with an **opt-in** knowledge-graph re-ranker.
304
305
  Default (useGraph false) is the harm-reduced default for finding a fact you know exists (known-item retrieval).
305
306
  useGraph: true is a legacy/experimental re-ranker kept for backward compatibility and controlled evaluation — it does not
306
307
  add candidates, it only re-orders the vector/FTS pool, and measured on three real corpora (2026-08-17) it pushed the exact
@@ -310,14 +311,14 @@ chunk out of the top-5 in ~40% of known-item queries. For relationship explorati
310
311
  <importantNotes>
311
312
  - (!important!) **Graph re-ranking is opt-in (default false since v5.3.0)** — it re-orders candidates by matched/connected entity links, which is measured to hurt known-item retrieval; enable it only for backward compatibility or controlled evaluation
312
313
  - (!important!) Graph re-ranking does not generate candidates — for "what is connected to X" use openNodes -> getNeighbors
313
- - (!important!) Results include similarity scores, graph boost, and hybrid rankings
314
+ - (!important!) relevance_score is a RANK score on the default call: 1/(60 + vector rank) + 1/(60 + FTS rank), at most 2/61; it is not a similarity or probability and is only comparable within one call (vector_similarity and fts_boost are reported alongside)
314
315
  - (!important!) **Best results when knowledge graph is well-populated** with entities and relationships
315
316
  </importantNotes>
316
317
 
317
318
  <whenToUseThisTool>
318
319
  - When you need comprehensive search across documents and knowledge
319
320
  - For complex queries requiring conceptual understanding
320
- - When exploring relationships between concepts
321
+ - Exact identifiers (file names, setting keys, function names, hashes) as well as natural-language questions
321
322
  - **Before making decisions** - to gather all relevant information
322
323
  - When researching topics that span multiple domains
323
324
  - For discovery of implicit connections and patterns
@@ -325,7 +326,8 @@ chunk out of the top-5 in ~40% of known-item queries. For relationship explorati
325
326
 
326
327
  <features>
327
328
  - Vector similarity search using sentence transformers
328
- - Knowledge graph traversal for conceptual enhancement
329
+ - Rank fusion of vector and FTS5 candidates: an exact term found only by FTS5 can enter the returned top results even when vector candidates are present
330
+ - Optional legacy graph re-ranker (re-orders only; no traversal — use openNodes -> getNeighbors)
329
331
  - Hybrid scoring combining multiple relevance signals
330
332
  - Entity association highlighting
331
333
  - Configurable result limits and graph usage
@@ -333,7 +335,7 @@ chunk out of the top-5 in ~40% of known-item queries. For relationship explorati
333
335
  </features>
334
336
 
335
337
  <bestPractices>
336
- - Use natural language queries rather than keywords
338
+ - Natural-language questions and exact identifiers both work; pass the identifier itself when you know it
337
339
  - Keep graph re-ranking off for "find the fact I know exists"; for "what is connected to this" use openNodes -> getNeighbors, not useGraph
338
340
  - Start with broader queries, then narrow down based on results
339
341
  - Review entity associations to understand why results were selected
@@ -342,7 +344,7 @@ chunk out of the top-5 in ~40% of known-item queries. For relationship explorati
342
344
  </bestPractices>
343
345
 
344
346
  <parameters>
345
- - query: Natural language search query (string, required)
347
+ - query: Search query — a natural-language question or an exact term/identifier (string, required)
346
348
  - limit: Maximum results to return, default 5 (number, optional)
347
349
  - useGraph: Enable knowledge graph re-ranking, default false / opt-in (boolean, optional)
348
350
  </parameters>
package/docs/UPDATING.md CHANGED
@@ -108,6 +108,52 @@ path and holder pid (e.g. `.download-<key>.lock`). Verify the holder process
108
108
  is genuinely gone or hung (`ps -p <pid>`), then remove the lock file manually;
109
109
  the next start becomes a clean download owner.
110
110
 
111
+ ## v6.4.0 (schema v14, unchanged): `hybridSearch` default = rank fusion, summaries opt-in
112
+
113
+ (Release candidate `6.4.0-rc.1` on the `next` dist-tag first; `latest` stays 6.3.2 until the published artifact
114
+ passes a canary against a real project database.)
115
+
116
+ **What changed.** (1) The per-result summary path runs only with `RAG_MEMORY_SEARCH_SUMMARIES=on` (it used to
117
+ run unless the variable was `off`; `off` still means off). (2) With summaries off and `useGraph: false` — the
118
+ default call — results are ranked by reciprocal rank fusion of the vector list and the FTS5 list:
119
+ `relevance_score = 1/(60 + vector rank) + 1/(60 + FTS rank)`, a list a chunk is absent from contributes 0.
120
+ Healthy-path candidate gathering and depth are unchanged. If vector search fails after an earlier query variant
121
+ succeeded, the call now discards those partial vector candidates and returns a true FTS-only result (the previous
122
+ version could keep them while reporting `fts-only`). `summaries=on` or `useGraph: true` keep the 6.3.2 formula
123
+ (`max(vector_similarity, summary relevance) + graph boost + FTS term`) unchanged.
124
+
125
+ **Why.** Measured 2026-10-03 on the frozen eval snapshots (spec `specs/changes/search-fusion-rrf/proposal.md`):
126
+ the summary path embedded ~400 sentence fragments per search (median 121 s) and its unbounded context boost let
127
+ vector-0 chunks take rank 1 (known-item hit@1 9/10 with summaries off vs 4/10 on). With summaries off, a chunk
128
+ found only by FTS scored at most 0.0164 against ~0.4-0.6 for every vector candidate, so exact-identifier
129
+ queries surfaced their target in the top 10 for 7/20 queries although FTS ranked it first for 17/20. Rank fusion:
130
+ 18/20, and worse than the old formula on 0 of 203 queries across three corpora.
131
+
132
+ **Query input (same release).** With the default model (bge-m3) queries are embedded as raw text; a custom
133
+ `EMBEDDING_MODEL` keeps the previous query instruction (the evidence below is for bge-m3 only). Up to 6.3.2 the loader prepended the bge-v1.5
134
+ instruction `Represent this sentence for searching relevant passages: ` to every query (hybridSearch, searchNodes,
135
+ graph seeds) while stored vectors were embedded without it; the bge-m3 model card says the model needs no query
136
+ instruction. Measured on the frozen snapshots: hybridSearch paraphrase hit@10 7 -> 9 / 20 and MRR 0.217 -> 0.275,
137
+ identifier hit@1 7 -> 8, known-item unchanged on hub / uap and hit@1 +1 on hal; searchNodes known-item (40 entities per
138
+ corpus) hit@10 unchanged, hit@1 -1 / -1 / +1. No re-index: only query vectors change; rolling back means a restart.
139
+ `server_meta.query_prefix_version` is a diagnostic marker written at init (2 = raw queries with the default model,
140
+ 1 = instruction); it does not prove what the last query used.
141
+
142
+ **What a caller notices.** On the default call `relevance_score` is a rank score — at most 2/61 ≈ 0.0328, no lower
143
+ bound (a chunk in one list at rank r gets 1/(60+r)), comparable only within one call; do not threshold it against
144
+ old values or across queries/modes. `vector_similarity` and `fts_boost` keep their meaning. `content_summary`
145
+ / `key_highlight` are preview slices unless summaries are turned on. The fragment-summary work is gone from default
146
+ searches: measured query-cached medians were 20 / 10 / 6 ms on the frozen hub / uap / hal runs; with the query
147
+ embedding cache cleared before every call (one Mac, final build) medians were about 220 / 228 /
148
+ 203 ms (max 888 ms). Other machines and corpora are unmeasured — this is not a universal millisecond guarantee. With
149
+ `useGraph: true` and the variable unset, summaries are now off; set `RAG_MEMORY_SEARCH_SUMMARIES=on` to reproduce
150
+ the previous summary-enabled graph path.
151
+
152
+ **Fleet rollout.** No migration. Recommended like v5.3.0: publish an rc on the `next` dist-tag first, run it
153
+ against a real project database (identifier query + known-item probe), then promote to `latest`.
154
+ Regression locks: `test/search-fusion-rrf.test.mjs`, `test/search-summaries-off.test.mjs`, and
155
+ `test/graph-context-explain.test.mjs` (legacy path byte-identical with summaries on).
156
+
111
157
  ## v5.3.0 (schema v14, unchanged): `hybridSearch` graph re-ranking is opt-in
112
158
 
113
159
  **What changed.** `useGraph` defaults to `false` (was `true`) — in the manager signature, the tool JSON
@@ -207,8 +253,10 @@ the real v14 (this is exactly how code-v8 never ran in production).
207
253
  **Diagnostic env (v5)**: `RAG_MEMORY_SEARCH_SUMMARIES=off` disables the per-result
208
254
  sentence-similarity summaries in `hybridSearch` (which embed every sentence of every
209
255
  candidate — 100+ inferences, 90-120s cold per search, measured). Off = preview-slice
210
- summaries, `relevance_score` 0, ranking rests on vector similarity + boosts. Default
211
- unchanged. The 3-arm release harness sets this uniformly across all arms.
256
+ summaries, the internal summary relevance is 0 (the response's `relevance_score` is still the final score) and
257
+ ranking rests on vector similarity + boosts. **Superseded by the next
258
+ release: summaries are off by default and opt-in with `=on`** (see the section above). The 3-arm release
259
+ harness sets this uniformly across all arms.
212
260
 
213
261
  ## v3.6 breaking response changes
214
262
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rag-memory-epf-mcp",
3
- "version": "6.3.2",
3
+ "version": "6.4.0-rc.1",
4
4
  "engines": {
5
5
  "node": ">=24"
6
6
  },
@@ -45,7 +45,7 @@
45
45
  "prepare": "npm run build",
46
46
  "watch": "tsc --watch",
47
47
  "verify:invariants": "node test/chunk-invariants.test.mjs",
48
- "verify:engine": "node test/engine-smoke.test.mjs && node test/launch-smoke.test.mjs && node test/sync-atomicity.test.mjs && node test/dedup.test.mjs && node test/search-degradation.test.mjs && node test/entity-embed-cap.test.mjs && node test/migration12.test.mjs && node test/model-cache.test.mjs && node test/embedding-gate.test.mjs && node test/lazy-boot.test.mjs && node test/reconciliation.test.mjs && node test/backfill.test.mjs && node test/fts-query.test.mjs && node test/search-contracts.test.mjs && node test/tool-contracts.test.mjs && node test/bounded-exit.test.mjs && node test/observation-schema.test.mjs && node test/search-graph-default.test.mjs && node test/observation-migration.test.mjs && node test/observation-lifecycle.test.mjs && node test/observation-contracts.test.mjs && node test/observation-search.test.mjs && node test/observation-cascade.test.mjs && node test/observation-realdata.test.mjs && node test/chunker-c.test.mjs && node test/migration14.test.mjs && node test/chunk-params-validation.test.mjs && node test/vector-reuse.test.mjs && node test/entity-range-linking.test.mjs && node test/alias-link-gate.test.mjs && node test/entity-name-boundary.test.mjs && node test/stats-chunking.test.mjs && node test/migration14-realdata.test.mjs && node test/migration14-realdata-sync.test.mjs && node test/search-summaries-off.test.mjs && node test/document-return-contracts.test.mjs && node test/observation-date-prefix.test.mjs && node test/delete-entities-cascade.test.mjs && node test/delete-entities-kg-hygiene.test.mjs && node test/backup-publish-portable.test.mjs && node test/graph-context-explain.test.mjs && node test/eval-graph-role-libs.test.mjs && node test/eval-graph-role-t5b.test.mjs && node --test test/eval-graph-role-t8-fix.test.mjs && node --test test/eval-graph-role-t7-upstream.test.mjs && node --test test/eval-graph-role-t11-decision.test.mjs && node --test test/eval-graph-role-prereq-fix.test.mjs && node test/db-path.test.mjs && node test/mmap-option.test.mjs && node test/wal-exit.test.mjs && node test/wal-sidecar-guard.test.mjs",
48
+ "verify:engine": "node test/engine-smoke.test.mjs && node test/launch-smoke.test.mjs && node test/sync-atomicity.test.mjs && node test/dedup.test.mjs && node test/search-degradation.test.mjs && node test/entity-embed-cap.test.mjs && node test/migration12.test.mjs && node test/model-cache.test.mjs && node test/embedding-gate.test.mjs && node test/lazy-boot.test.mjs && node test/reconciliation.test.mjs && node test/backfill.test.mjs && node test/fts-query.test.mjs && node test/search-contracts.test.mjs && node test/tool-contracts.test.mjs && node test/bounded-exit.test.mjs && node test/observation-schema.test.mjs && node test/search-graph-default.test.mjs && node test/observation-migration.test.mjs && node test/observation-lifecycle.test.mjs && node test/observation-contracts.test.mjs && node test/observation-search.test.mjs && node test/observation-cascade.test.mjs && node test/observation-realdata.test.mjs && node test/chunker-c.test.mjs && node test/migration14.test.mjs && node test/chunk-params-validation.test.mjs && node test/vector-reuse.test.mjs && node test/entity-range-linking.test.mjs && node test/alias-link-gate.test.mjs && node test/entity-name-boundary.test.mjs && node test/stats-chunking.test.mjs && node test/migration14-realdata.test.mjs && node test/migration14-realdata-sync.test.mjs && node test/search-summaries-off.test.mjs && node test/search-fusion-rrf.test.mjs && node test/query-input.test.mjs && node test/document-return-contracts.test.mjs && node test/observation-date-prefix.test.mjs && node test/delete-entities-cascade.test.mjs && node test/delete-entities-kg-hygiene.test.mjs && node test/backup-publish-portable.test.mjs && node test/graph-context-explain.test.mjs && node test/eval-graph-role-libs.test.mjs && node test/eval-graph-role-t5b.test.mjs && node --test test/eval-graph-role-t8-fix.test.mjs && node --test test/eval-graph-role-t7-upstream.test.mjs && node --test test/eval-graph-role-t11-decision.test.mjs && node --test test/eval-graph-role-prereq-fix.test.mjs && node test/db-path.test.mjs && node test/mmap-option.test.mjs && node test/wal-exit.test.mjs && node test/wal-sidecar-guard.test.mjs",
49
49
  "test": "npm run build && npm run verify:invariants && npm run verify:engine",
50
50
  "prepublishOnly": "npm run build && npm run verify:invariants && npm run verify:engine"
51
51
  },