rag-memory-epf-mcp 6.4.3 → 6.4.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/README.md +18 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +22 -10
- package/dist/src/backfillCoordinator.d.ts +2 -0
- package/dist/src/backfillCoordinator.js +36 -4
- package/dist/src/vecSimilarity.d.ts +17 -0
- package/dist/src/vecSimilarity.js +23 -0
- package/docs/UPDATING.md +8 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -154,6 +154,24 @@ storeDocument(id, content, metadata)
|
|
|
154
154
|
|
|
155
155
|
## Changelog
|
|
156
156
|
|
|
157
|
+
### v6.4.4 (2026-11-07)
|
|
158
|
+
|
|
159
|
+
- **Fixed — the similarity numbers are real cosine similarities.** The vec0 tables use the default L2 metric and the
|
|
160
|
+
embeddings are unit-normalized, so cosine similarity = 1 − d²/2. The engine used 1 − d/2 (L2 read as a cosine
|
|
161
|
+
distance): `searchNodes` `similarity` and the default `hybridSearch` `vector_similarity` were too low above cos 0.5
|
|
162
|
+
(cos 0.8 showed ≈0.684) and too high below it (orthogonal vectors showed ≈0.293 instead of 0). Ranking of the default
|
|
163
|
+
search is unchanged (rank fusion uses ranks only).
|
|
164
|
+
- **Unchanged on purpose — the legacy paths keep the 6.3.2 score.** `hybridSearch` with `useGraph: true` or with
|
|
165
|
+
summaries on, and the graph-seed threshold (`> 0.4`, about cosine 0.28), stay byte-identical to 6.3.2 for evaluation
|
|
166
|
+
comparability; that score is named `legacyL2AsCosineScore` and is not a cosine.
|
|
167
|
+
- **Fixed — chunk vectors without metadata are cleaned up.** Reconciliation now deletes searchable chunk vectors that
|
|
168
|
+
have no `chunk_metadata` row (same rule and batching as entity orphans; off mode only classifies it as deferred).
|
|
169
|
+
`importGraph` no longer creates them: a full replace (`merge: false`) and a same-id document replace delete that
|
|
170
|
+
document's chunk vectors first. Rowids past 2^53 are kept exact.
|
|
171
|
+
- **Added — `getKnowledgeGraphStats().server.coverage.{chunk,entity}.orphan_vectors`**: searchable vectors with no
|
|
172
|
+
metadata row (`null` when the slot map cannot be resolved). `missing` keeps its meaning.
|
|
173
|
+
- Not measured: Windows, Drive-synced DBs under two machines.
|
|
174
|
+
|
|
157
175
|
### v6.4.3 (2026-11-02)
|
|
158
176
|
|
|
159
177
|
- **Fixed — re-embedding no longer leaks vector slots.** sqlite-vec puts a new row only into a free slot of the latest
|
package/dist/index.d.ts
CHANGED
|
@@ -489,7 +489,7 @@ export declare class RAGKnowledgeGraphManager {
|
|
|
489
489
|
}>;
|
|
490
490
|
/**
|
|
491
491
|
* Diagnostic seam for the graph re-ranker (evaluation change graph-role-evaluation, R2).
|
|
492
|
-
* Returns the seed entities (query-vector matched,
|
|
492
|
+
* Returns the seed entities (query-vector matched, legacy score > 0.4 = cosine ≈0.28, top-10 per variant) and the
|
|
493
493
|
* 1-hop connected entities exactly as hybridSearch(useGraph:true) computes them, plus edge detail
|
|
494
494
|
* that hybridSearch itself does not use (edge id, type, direction, confidence). It never generates
|
|
495
495
|
* candidates and never changes ranking; hybridSearch consumes only the name sets.
|
package/dist/index.js
CHANGED
|
@@ -34,6 +34,7 @@ import { migrations } from './src/migrations/migrations.js';
|
|
|
34
34
|
import { EmbeddingGate, GateNotReadyError, GateDisabledError, TerminalConfigError } from './src/embeddingGate.js';
|
|
35
35
|
import { resolveModelCacheDir, preflightCacheDir, artifactKey, ModelDownloadLock, handleLoaderFailure } from './src/modelCache.js';
|
|
36
36
|
import { BackfillCoordinator } from './src/backfillCoordinator.js';
|
|
37
|
+
import { l2ToCosineSimilarity, legacyL2AsCosineScore } from './src/vecSimilarity.js';
|
|
37
38
|
import { writeVector, deleteVector, registerVecSlotFunctions, InvalidSlotError, slotSummary, rebuildVecTable, rebuildReason, DEFAULT_REBUILD_POLICY } from './src/vecSlots.js';
|
|
38
39
|
import { calendarDate, resolveCalendarTimeZone, stampDatePrefix, stripDatePrefix } from './src/observations/date-prefix.js';
|
|
39
40
|
import os from 'node:os';
|
|
@@ -1894,7 +1895,7 @@ export class RAGKnowledgeGraphManager {
|
|
|
1894
1895
|
name: result.name,
|
|
1895
1896
|
entityType: result.entityType,
|
|
1896
1897
|
observations: JSON.parse(result.observations),
|
|
1897
|
-
similarity:
|
|
1898
|
+
similarity: l2ToCosineSimilarity(result.distance) // vec0 L2 distance of unit vectors -> cosine similarity
|
|
1898
1899
|
}));
|
|
1899
1900
|
// hybrid-partial (spec §4): entities without vectors must not vanish from
|
|
1900
1901
|
// search while backfill catches up — merge lexical FTS hits for the gap.
|
|
@@ -3665,12 +3666,18 @@ export class RAGKnowledgeGraphManager {
|
|
|
3665
3666
|
this.db.exec(`DELETE FROM entity_observations`);
|
|
3666
3667
|
this.db.exec(`DELETE FROM observation_roots`);
|
|
3667
3668
|
this.db.exec(`DELETE FROM entities`);
|
|
3669
|
+
// documents → chunk_metadata is a CASCADE, but vec0 has no FK: delete the document chunk vectors first,
|
|
3670
|
+
// or they stay searchable-less orphans (IN-42 · same order as storeDocument's replace path).
|
|
3671
|
+
// rowids stay BigInt end to end (2_ r1 R1: a Number cast past 2^53 deletes the wrong vector)
|
|
3672
|
+
for (const c of this.db.prepare(`SELECT rowid FROM chunk_metadata WHERE document_id IS NOT NULL`).safeIntegers(true).all()) {
|
|
3673
|
+
deleteVector(this.db, 'chunks', c.rowid);
|
|
3674
|
+
}
|
|
3668
3675
|
this.db.exec(`DELETE FROM documents`);
|
|
3669
3676
|
// entities 를 지워도 파생 데이터는 따라오지 않는다: chunk_metadata 에는
|
|
3670
3677
|
// entities 로 가는 FK 가 없고 entity_embedding_metadata.entity_id 는 UNIQUE 일
|
|
3671
3678
|
// 뿐이다. 그래서 replace-import 뒤에 **사라진 entity 의 벡터와 KG chunk 가
|
|
3672
|
-
// 검색에 남았다**(advisor beta 발견 2). document chunk
|
|
3673
|
-
// CASCADE 로
|
|
3679
|
+
// 검색에 남았다**(advisor beta 발견 2). document chunk 메타는 documents 의
|
|
3680
|
+
// CASCADE 로 정리되고 그 벡터는 위에서 먼저 지웠다(IN-42) — 여기서는 entity·relationship chunk 만 지운다.
|
|
3674
3681
|
const orphanChunks = this.db.prepare(`SELECT rowid FROM chunk_metadata WHERE chunk_type IN ('entity','relationship')`)
|
|
3675
3682
|
.all();
|
|
3676
3683
|
for (const c of orphanChunks) {
|
|
@@ -3732,6 +3739,10 @@ export class RAGKnowledgeGraphManager {
|
|
|
3732
3739
|
// doc.id is a string here (checked above, before any write).
|
|
3733
3740
|
for (const r of collectEvidenceRowidsCitingDocument(this.db, doc.id))
|
|
3734
3741
|
citedEvidence.add(r);
|
|
3742
|
+
// REPLACE deletes an existing row → its chunk_metadata goes by CASCADE, the vec0 vectors would not (IN-42).
|
|
3743
|
+
for (const c of this.db.prepare(`SELECT rowid FROM chunk_metadata WHERE document_id = ?`).safeIntegers(true).all(doc.id)) {
|
|
3744
|
+
deleteVector(this.db, 'chunks', c.rowid);
|
|
3745
|
+
}
|
|
3735
3746
|
const result = stmt.run(doc.id, doc.content, JSON.stringify(doc.metadata || {}), doc.created_at || new Date().toISOString());
|
|
3736
3747
|
if (result.changes > 0) {
|
|
3737
3748
|
imported.documents++;
|
|
@@ -3901,7 +3912,7 @@ export class RAGKnowledgeGraphManager {
|
|
|
3901
3912
|
}
|
|
3902
3913
|
/**
|
|
3903
3914
|
* Diagnostic seam for the graph re-ranker (evaluation change graph-role-evaluation, R2).
|
|
3904
|
-
* Returns the seed entities (query-vector matched,
|
|
3915
|
+
* Returns the seed entities (query-vector matched, legacy score > 0.4 = cosine ≈0.28, top-10 per variant) and the
|
|
3905
3916
|
* 1-hop connected entities exactly as hybridSearch(useGraph:true) computes them, plus edge detail
|
|
3906
3917
|
* that hybridSearch itself does not use (edge id, type, direction, confidence). It never generates
|
|
3907
3918
|
* candidates and never changes ranking; hybridSearch consumes only the name sets.
|
|
@@ -3953,7 +3964,7 @@ export class RAGKnowledgeGraphManager {
|
|
|
3953
3964
|
WHERE r.source_entity = ? OR r.target_entity = ?
|
|
3954
3965
|
ORDER BY r.id`);
|
|
3955
3966
|
for (const entity of similar) {
|
|
3956
|
-
const similarity =
|
|
3967
|
+
const similarity = legacyL2AsCosineScore(entity.distance); // legacy seed score, pinned (4ac3155) — not a cosine
|
|
3957
3968
|
if (similarity > 0.4) {
|
|
3958
3969
|
seeds.push({ entity_id: entity.entity_id, name: entity.name, similarity });
|
|
3959
3970
|
for (const row of edgeStmt.all(entity.entity_id, entity.entity_id, entity.entity_id, entity.entity_id, entity.entity_id)) {
|
|
@@ -4300,7 +4311,8 @@ export class RAGKnowledgeGraphManager {
|
|
|
4300
4311
|
({ summary, keyHighlight, relevanceScore } = await this.generateContentSummary(result.text, primaryQueryEmbedding, chunkEntities, result.chunk_type === 'relationship' ? 1 : 2 // Shorter summary for relationships
|
|
4301
4312
|
));
|
|
4302
4313
|
}
|
|
4303
|
-
|
|
4314
|
+
// default (rank fusion) = real cosine · legacy paths (useGraph:true or summaries on) keep the pinned 6.3.2 score
|
|
4315
|
+
const vectorSimilarity = rankFusion ? l2ToCosineSimilarity(result.distance) : legacyL2AsCosineScore(result.distance);
|
|
4304
4316
|
const ftsBoost = ftsBoostMap.get(result.chunk_id) || 0; // = 1/(60 + FTS rank), 0 if not an FTS hit
|
|
4305
4317
|
const vectorRank = vectorRankOf.get(result.chunk_id);
|
|
4306
4318
|
const finalScore = rankFusion
|
|
@@ -4566,11 +4578,11 @@ export class RAGKnowledgeGraphManager {
|
|
|
4566
4578
|
coverage: cov ? {
|
|
4567
4579
|
// vec-slot-reuse D1c: an unresolvable slot map is reported as error with missing = null, not as a count.
|
|
4568
4580
|
chunk: cov.chunk.error
|
|
4569
|
-
? { total: cov.chunk.total, embedded: null, verified: null, legacy_assumed: null, missing: null, error: cov.chunk.error }
|
|
4570
|
-
: { total: cov.chunk.total, embedded: cov.chunk.embedded, verified: cov.chunk.verified, legacy_assumed: cov.chunk.legacy_assumed, missing: cov.chunk.total - cov.chunk.embedded },
|
|
4581
|
+
? { total: cov.chunk.total, embedded: null, verified: null, legacy_assumed: null, missing: null, orphan_vectors: null, error: cov.chunk.error }
|
|
4582
|
+
: { total: cov.chunk.total, embedded: cov.chunk.embedded, verified: cov.chunk.verified, legacy_assumed: cov.chunk.legacy_assumed, missing: cov.chunk.total - cov.chunk.embedded, orphan_vectors: cov.chunk.orphan_vectors ?? null },
|
|
4571
4583
|
entity: cov.entity.error
|
|
4572
|
-
? { total: cov.entity.total, embedded: null, verified: null, legacy_assumed: null, missing: null, error: cov.entity.error }
|
|
4573
|
-
: { total: cov.entity.total, embedded: cov.entity.embedded, verified: cov.entity.verified, legacy_assumed: cov.entity.legacy_assumed, missing: cov.entity.total - cov.entity.embedded },
|
|
4584
|
+
? { total: cov.entity.total, embedded: null, verified: null, legacy_assumed: null, missing: null, orphan_vectors: null, error: cov.entity.error }
|
|
4585
|
+
: { total: cov.entity.total, embedded: cov.entity.embedded, verified: cov.entity.verified, legacy_assumed: cov.entity.legacy_assumed, missing: cov.entity.total - cov.entity.embedded, orphan_vectors: cov.entity.orphan_vectors ?? null },
|
|
4574
4586
|
} : null,
|
|
4575
4587
|
// vec-slot-reuse: per vec0 table = slots, searchable, validity-0 rows, reclaimable chunks, why a rebuild
|
|
4576
4588
|
// is (not) due, and the last attempt of this process. Read-only: computing it writes nothing.
|
|
@@ -6,6 +6,7 @@ export interface CoverageSnapshot {
|
|
|
6
6
|
embedded: number;
|
|
7
7
|
verified: number;
|
|
8
8
|
legacy_assumed: number;
|
|
9
|
+
orphan_vectors?: number;
|
|
9
10
|
error?: string;
|
|
10
11
|
};
|
|
11
12
|
entity: {
|
|
@@ -13,6 +14,7 @@ export interface CoverageSnapshot {
|
|
|
13
14
|
embedded: number;
|
|
14
15
|
verified: number;
|
|
15
16
|
legacy_assumed: number;
|
|
17
|
+
orphan_vectors?: number;
|
|
16
18
|
error?: string;
|
|
17
19
|
};
|
|
18
20
|
}
|
|
@@ -92,6 +92,8 @@ export class BackfillCoordinator {
|
|
|
92
92
|
// vec-slot-reuse r3 M2: searchable orphans only (validity-0 rows belong to the rebuild); vectorless
|
|
93
93
|
// metadata counts only while still stamped (it is normalized to missing, not deleted, to keep its rowid).
|
|
94
94
|
+ q(`SELECT COUNT(*) c FROM entity_embeddings_rowids m WHERE m.rowid NOT IN (SELECT rowid FROM entity_embedding_metadata) AND ${searchableVectorSql('entity_embeddings', 'm.rowid')}`)
|
|
95
|
+
// IN-42: searchable chunk vectors without chunk_metadata (same rule as entity orphans)
|
|
96
|
+
+ q(`SELECT COUNT(*) c FROM chunks_rowids m WHERE m.rowid NOT IN (SELECT rowid FROM chunk_metadata) AND ${searchableVectorSql('chunks', 'm.rowid')}`)
|
|
95
97
|
+ q(`SELECT COUNT(*) c FROM entity_embedding_metadata WHERE provenance_state IS NOT NULL AND rowid NOT IN (SELECT rowid FROM entity_embeddings_rowids)`)
|
|
96
98
|
+ q(`SELECT COUNT(*) c FROM entity_embedding_metadata WHERE provenance_state IS NOT NULL AND profile_id IS NOT ?`, profileId)
|
|
97
99
|
+ q(`SELECT COUNT(*) c FROM chunk_metadata m WHERE m.provenance_state IS NOT NULL AND m.profile_id IS NOT ? AND m.rowid IN (SELECT rowid FROM chunks_rowids)`, profileId)
|
|
@@ -136,6 +138,22 @@ export class BackfillCoordinator {
|
|
|
136
138
|
break;
|
|
137
139
|
await new Promise(r => setImmediate(r));
|
|
138
140
|
}
|
|
141
|
+
// 1b. searchable chunk vectors without metadata -> delete (IN-42 · same batching and validity rule as 1)
|
|
142
|
+
for (;;) {
|
|
143
|
+
if (this.shuttingDown)
|
|
144
|
+
throw new Error('shutdown during sanitation');
|
|
145
|
+
const orphans = db.prepare(`SELECT m.rowid FROM chunks_rowids m
|
|
146
|
+
WHERE m.rowid NOT IN (SELECT rowid FROM chunk_metadata) AND ${searchableVectorSql('chunks', 'm.rowid')} LIMIT ${RECON_BATCH}`).safeIntegers(true).all();
|
|
147
|
+
if (orphans.length === 0)
|
|
148
|
+
break;
|
|
149
|
+
batchTx(() => { for (const r of orphans) {
|
|
150
|
+
deleteVector(db, 'chunks', r.rowid);
|
|
151
|
+
touched++;
|
|
152
|
+
} });
|
|
153
|
+
if (orphans.length < RECON_BATCH)
|
|
154
|
+
break;
|
|
155
|
+
await new Promise(r => setImmediate(r));
|
|
156
|
+
}
|
|
139
157
|
// 2. stamped entity metadata whose vector row is gone -> normalize to missing (was: delete). Keeping the
|
|
140
158
|
// row keeps its rowid, so the backfill re-inserts at the same rowid (r3 M2 case C: restart after repair).
|
|
141
159
|
await batchedRun(`UPDATE entity_embedding_metadata SET input_hash = NULL, profile_id = NULL, provenance_state = NULL
|
|
@@ -178,8 +196,10 @@ export class BackfillCoordinator {
|
|
|
178
196
|
});
|
|
179
197
|
await new Promise(r => setImmediate(r));
|
|
180
198
|
}
|
|
181
|
-
if (touched > 0)
|
|
182
|
-
|
|
199
|
+
if (touched > 0) {
|
|
200
|
+
this.snapshot = null; // cached coverage (incl. orphan_vectors) is stale — also on the legacy=0 early return (2_ r1 R2)
|
|
201
|
+
console.error(`🧾 provenance sanitation: ${touched} split-state/old-profile/orphan rows invalidated`);
|
|
202
|
+
}
|
|
183
203
|
return touched;
|
|
184
204
|
}
|
|
185
205
|
// Single-flight, idempotent. off mode defers (spec §9); fresh DBs go straight
|
|
@@ -612,6 +632,18 @@ export class BackfillCoordinator {
|
|
|
612
632
|
const entQ = byState(`SELECT m.provenance_state s, COUNT(*) c FROM entity_embedding_metadata m
|
|
613
633
|
WHERE ${searchableVectorSql('entity_embeddings', 'm.rowid')} GROUP BY m.provenance_state`);
|
|
614
634
|
const entBy = entQ.rows;
|
|
635
|
+
const orphanQ = (t, meta) => {
|
|
636
|
+
try {
|
|
637
|
+
return db.prepare(`SELECT COUNT(*) c FROM ${t}_rowids m WHERE m.rowid NOT IN (SELECT rowid FROM ${meta}) AND ${searchableVectorSql(t, 'm.rowid')}`).get().c;
|
|
638
|
+
}
|
|
639
|
+
catch (e) {
|
|
640
|
+
if (!(e instanceof VecSlotError) && !String(e.message).includes('vec0 slot'))
|
|
641
|
+
throw e;
|
|
642
|
+
return undefined;
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
const chunkOrphans = orphanQ('chunks', 'chunk_metadata');
|
|
646
|
+
const entOrphans = orphanQ('entity_embeddings', 'entity_embedding_metadata');
|
|
615
647
|
const pick = (rows, s) => rows.find(r => r.s === s)?.c ?? 0;
|
|
616
648
|
// Searchable = verified + legacy_assumed. NULL rows (pre-reconciliation or
|
|
617
649
|
// deferred in off mode) are NOT counted as embedded (6R note 4).
|
|
@@ -620,9 +652,9 @@ export class BackfillCoordinator {
|
|
|
620
652
|
const entVerified = pick(entBy, 'verified');
|
|
621
653
|
const entLegacy = pick(entBy, 'legacy_assumed');
|
|
622
654
|
this.snapshot = {
|
|
623
|
-
chunk: { total: chunkTotal, embedded: chunkVerified + chunkLegacy, verified: chunkVerified, legacy_assumed: chunkLegacy,
|
|
655
|
+
chunk: { total: chunkTotal, embedded: chunkVerified + chunkLegacy, verified: chunkVerified, legacy_assumed: chunkLegacy, orphan_vectors: chunkOrphans,
|
|
624
656
|
...(chunkQ.error ? { error: chunkQ.error } : {}) },
|
|
625
|
-
entity: { total: entTotal, embedded: entVerified + entLegacy, verified: entVerified, legacy_assumed: entLegacy,
|
|
657
|
+
entity: { total: entTotal, embedded: entVerified + entLegacy, verified: entVerified, legacy_assumed: entLegacy, orphan_vectors: entOrphans,
|
|
626
658
|
...(entQ.error ? { error: entQ.error } : {}) },
|
|
627
659
|
};
|
|
628
660
|
return this.snapshot;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vec0 tables (chunks · entity_embeddings) are created without `distance_metric`, so sqlite-vec returns the
|
|
3
|
+
* default L2 distance. Embeddings are unit-normalized (`normalize: true`), so for unit vectors
|
|
4
|
+
* ‖a − b‖² = 2 − 2·cos → cosine similarity = 1 − d²/2.
|
|
5
|
+
* The earlier conversion 1 − d/2 treated the L2 value as a cosine distance: right − old = (d − d²)/2, so it
|
|
6
|
+
* under-reported for d < 1 (cos > 0.5: cos 0.8 showed ≈0.684) and over-reported for d > 1 (orthogonal showed
|
|
7
|
+
* ≈0.293 instead of 0 · cos 0.3 showed ≈0.408 and passed the 0.4 graph-seed threshold). Clamped to [0, 1].
|
|
8
|
+
* Guarded by test/vec-similarity.test.mjs (also asserts the metric is still L2).
|
|
9
|
+
*/
|
|
10
|
+
export declare function l2ToCosineSimilarity(distance: number): number;
|
|
11
|
+
/**
|
|
12
|
+
* The 6.3.2 score, kept ONLY for the legacy paths that are pinned byte-for-byte (engine commit 4ac3155 · test
|
|
13
|
+
* graph-context-explain): hybridSearch with useGraph:true or summaries on, and the graph-seed `> 0.4` threshold of
|
|
14
|
+
* explainGraphContext. It is the historical 1 − d/2 conversion of an L2 distance and is NOT a cosine similarity
|
|
15
|
+
* (its 0.4 threshold equals a real cosine of about 0.28). Default search and searchNodes use l2ToCosineSimilarity.
|
|
16
|
+
*/
|
|
17
|
+
export declare function legacyL2AsCosineScore(distance: number): number;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vec0 tables (chunks · entity_embeddings) are created without `distance_metric`, so sqlite-vec returns the
|
|
3
|
+
* default L2 distance. Embeddings are unit-normalized (`normalize: true`), so for unit vectors
|
|
4
|
+
* ‖a − b‖² = 2 − 2·cos → cosine similarity = 1 − d²/2.
|
|
5
|
+
* The earlier conversion 1 − d/2 treated the L2 value as a cosine distance: right − old = (d − d²)/2, so it
|
|
6
|
+
* under-reported for d < 1 (cos > 0.5: cos 0.8 showed ≈0.684) and over-reported for d > 1 (orthogonal showed
|
|
7
|
+
* ≈0.293 instead of 0 · cos 0.3 showed ≈0.408 and passed the 0.4 graph-seed threshold). Clamped to [0, 1].
|
|
8
|
+
* Guarded by test/vec-similarity.test.mjs (also asserts the metric is still L2).
|
|
9
|
+
*/
|
|
10
|
+
export function l2ToCosineSimilarity(distance) {
|
|
11
|
+
if (!Number.isFinite(distance))
|
|
12
|
+
return 0;
|
|
13
|
+
return Math.min(1, Math.max(0, 1 - (distance * distance) / 2));
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The 6.3.2 score, kept ONLY for the legacy paths that are pinned byte-for-byte (engine commit 4ac3155 · test
|
|
17
|
+
* graph-context-explain): hybridSearch with useGraph:true or summaries on, and the graph-seed `> 0.4` threshold of
|
|
18
|
+
* explainGraphContext. It is the historical 1 − d/2 conversion of an L2 distance and is NOT a cosine similarity
|
|
19
|
+
* (its 0.4 threshold equals a real cosine of about 0.28). Default search and searchNodes use l2ToCosineSimilarity.
|
|
20
|
+
*/
|
|
21
|
+
export function legacyL2AsCosineScore(distance) {
|
|
22
|
+
return Math.max(0, 1 - distance / 2);
|
|
23
|
+
}
|
package/docs/UPDATING.md
CHANGED
|
@@ -108,6 +108,14 @@ 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.4 (schema v14, unchanged): real cosine similarity in default search, chunk orphan vectors cleaned
|
|
112
|
+
|
|
113
|
+
**What changes for an existing database.** Nothing is migrated and nothing is re-embedded. On the first reconciliation
|
|
114
|
+
after start, searchable chunk vectors that have no metadata row are deleted (the count shows as
|
|
115
|
+
`coverage.chunk.orphan_vectors` before, 0 after). Similarity numbers shown by `searchNodes` and the default
|
|
116
|
+
`hybridSearch` change to real cosine values; their order does not. `useGraph: true` and summaries-on results stay as in
|
|
117
|
+
6.4.3. An engine that is already running keeps the old behaviour until it restarts on 6.4.4.
|
|
118
|
+
|
|
111
119
|
## v6.4.3 (schema v14, unchanged): vector slots rewritten in place, idle rebuild, searchable-only coverage
|
|
112
120
|
|
|
113
121
|
**What changes for an existing database.** Nothing is migrated and nothing is re-embedded. Re-embeds stop leaving empty
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rag-memory-epf-mcp",
|
|
3
|
-
"version": "6.4.
|
|
3
|
+
"version": "6.4.4",
|
|
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 --no-concurrent-sparkplug 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 && node test/kg-evidence-core.test.mjs && node test/kg-delete-relation.test.mjs && node test/kg-confidence-copy.test.mjs && node test/kg-embed-cas.test.mjs && node test/kg-relation-evidence.test.mjs && node test/kg-detail-context.test.mjs && node test/kg-evidence-invalidation.test.mjs && node test/kg-evidence-cas.test.mjs && node test/kg-defaults.test.mjs && node test/chunker-oversize-cost.test.mjs && node test/chunker-structure-parity.test.mjs && node test/search-limit-depth.test.mjs && node test/kg-import-id-contract.test.mjs && node test/kg-evidence-guards.test.mjs && node test/model-session-options.test.mjs && node test/write-seq.test.mjs && node test/vec-slot-reuse.test.mjs && node test/vec-rebuild.test.mjs && node test/vec-slot-invalid.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 --no-concurrent-sparkplug 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 && node test/kg-evidence-core.test.mjs && node test/kg-delete-relation.test.mjs && node test/kg-confidence-copy.test.mjs && node test/kg-embed-cas.test.mjs && node test/kg-relation-evidence.test.mjs && node test/kg-detail-context.test.mjs && node test/kg-evidence-invalidation.test.mjs && node test/kg-evidence-cas.test.mjs && node test/kg-defaults.test.mjs && node test/chunker-oversize-cost.test.mjs && node test/chunker-structure-parity.test.mjs && node test/search-limit-depth.test.mjs && node test/kg-import-id-contract.test.mjs && node test/kg-evidence-guards.test.mjs && node test/model-session-options.test.mjs && node test/write-seq.test.mjs && node test/vec-slot-reuse.test.mjs && node test/vec-rebuild.test.mjs && node test/vec-slot-invalid.test.mjs && node test/vec-similarity.test.mjs && node test/chunk-orphan-vectors.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
|
},
|