rag-memory-epf-mcp 6.0.0 → 6.1.0
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 +34 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +139 -46
- package/dist/src/observations/projection.d.ts +1 -0
- package/dist/src/observations/projection.js +17 -0
- package/package.json +70 -70
package/README.md
CHANGED
|
@@ -153,6 +153,40 @@ storeDocument(id, content, metadata)
|
|
|
153
153
|
|
|
154
154
|
## Changelog
|
|
155
155
|
|
|
156
|
+
### v6.0.1
|
|
157
|
+
|
|
158
|
+
**Versioning note.** Both changes below stop links from being created that should never have been
|
|
159
|
+
created, and restore links that should have been. No tool signature, return shape or schema
|
|
160
|
+
changes, so this is a patch: **derived-link density is not part of the compatibility contract**.
|
|
161
|
+
Read the entries as "linking got more precise", not as an API break.
|
|
162
|
+
|
|
163
|
+
- **An observation alias must now also appear in the entity's own name.** 6.0.0 capped how many
|
|
164
|
+
entities may share a token. That answers "does this token point at one entity?" and says nothing
|
|
165
|
+
about the other direction, so an entity that mentions a common filename *once* still attached to
|
|
166
|
+
every chunk containing it — and, being the sole owner, sailed through the cap. The cap stopped
|
|
167
|
+
the explosion, not the magnet. Measured after the 6.0.0 cleanup on a live corpus: 2,014
|
|
168
|
+
alias-only links remained and **34 entities held 68.1%** of them; the largest had the entity name
|
|
169
|
+
in **zero** of its chunks. Requiring the token (or its stem) to appear in the name brings that to
|
|
170
|
+
6 entities / 42.1%, and what remains are entities that really are about that file. Expect far
|
|
171
|
+
fewer alias links on new ingests; existing rows are not rewritten.
|
|
172
|
+
- **A chunk-frequency cap was measured and rejected on the evidence, not on cost.** The full-corpus
|
|
173
|
+
scan is 585ms (731 tokens x 2,913 chunks). It was rejected because the sweep has no knee and every
|
|
174
|
+
cut also removed legitimate links — `log_coverage.py` occurs in 64 chunks and belongs to an entity
|
|
175
|
+
about exactly that file. The name condition is structural: no threshold, same meaning at any
|
|
176
|
+
corpus size.
|
|
177
|
+
- **Fixed — entity names whose own edge is punctuation never linked.** The Latin matcher used
|
|
178
|
+
`\b<name>\b`. `\b` asserts a transition between a word character and a non-word character, so when
|
|
179
|
+
the name itself starts or ends with punctuation — `Widget Review (2026-05-27)`, `--build-flag` —
|
|
180
|
+
there is no transition to assert and the pattern cannot match however the text reads. Measured on
|
|
181
|
+
a live 2,913-chunk / 312-name corpus: **23 standalone occurrences across 13 names** were invisible.
|
|
182
|
+
Both the chunk matcher and the document range finder now accept an occurrence whose neighbours on
|
|
183
|
+
both sides are non-word. **Not a widening**: both sides must still be non-word, so `Data` continues
|
|
184
|
+
not to match inside `Database`. The scan runs on the original text rather than a lowercased copy,
|
|
185
|
+
because folding can change length (`İ` becomes two units) and the range finder converts these
|
|
186
|
+
indices to codepoints.
|
|
187
|
+
- Regressions: `test/alias-link-gate.test.mjs` (magnet case) and `test/entity-name-boundary.test.mjs`,
|
|
188
|
+
both registered in `verify:engine` and both verified to fail without their fix.
|
|
189
|
+
|
|
156
190
|
### v6.0.0
|
|
157
191
|
|
|
158
192
|
- **Breaking — observation-derived alias links are now gated.** `autoLinkEntities` used to take every
|
package/dist/index.d.ts
CHANGED
|
@@ -279,6 +279,17 @@ export declare class RAGKnowledgeGraphManager {
|
|
|
279
279
|
errors?: string[];
|
|
280
280
|
}>;
|
|
281
281
|
private hasCJK;
|
|
282
|
+
private static readonly WORD_CH;
|
|
283
|
+
/**
|
|
284
|
+
* Index of an occurrence of `name` in `text` with non-word neighbours, or -1.
|
|
285
|
+
*
|
|
286
|
+
* Runs on the ORIGINAL text, not a lowercased copy. Lowercasing can change length —
|
|
287
|
+
* 'İ' folds to two units — so an index taken from the folded string does not address the
|
|
288
|
+
* same character in the original, and buildEntityRangeFinder hands these indices straight
|
|
289
|
+
* to the codepoint table. (r9-1 made the same call for the regex path; a first cut of this
|
|
290
|
+
* helper folded first and the İ coordinate test caught it.)
|
|
291
|
+
*/
|
|
292
|
+
private standaloneIndex;
|
|
282
293
|
private static readonly ALIAS_FILE_EXT;
|
|
283
294
|
private looksLikeFilename;
|
|
284
295
|
private buildEntityRangeFinder;
|
package/dist/index.js
CHANGED
|
@@ -22,7 +22,7 @@ import { getAllMCPTools, validateToolArgs, getSystemInfo } from './src/tools/too
|
|
|
22
22
|
// Import migration system
|
|
23
23
|
import { MigrationManager } from './src/migrations/migration-manager.js';
|
|
24
24
|
import { backupBeforeMigration } from './src/backup/preflight.js';
|
|
25
|
-
import { rebuildProjection, deleteStaleKgChunks } from './src/observations/projection.js';
|
|
25
|
+
import { rebuildProjection, deleteStaleKgChunks, deleteKgRelationshipChunks } from './src/observations/projection.js';
|
|
26
26
|
import { addRevision, correctRevision, transitionStatus, linkSources, nextProjectionOrder } from './src/observations/lifecycle.js';
|
|
27
27
|
import { getObservationHistory } from './src/observations/history.js';
|
|
28
28
|
// Import chunk text algorithm (extracted for publish-time invariant testing)
|
|
@@ -843,58 +843,76 @@ export class RAGKnowledgeGraphManager {
|
|
|
843
843
|
console.error(`🗑️ Deleting entities: ${entityNames.join(', ')}`);
|
|
844
844
|
for (const name of entityNames) {
|
|
845
845
|
const entityId = `entity_${name.toLowerCase().replace(/[^\p{L}\p{N}]/gu, '_')}`;
|
|
846
|
+
// One entity = one transaction. The four deletion steps are a single unit:
|
|
847
|
+
// a mid-sequence failure must roll back, not leave embeddings/links purged while
|
|
848
|
+
// the entity itself survives (2026-08-24 audit finding; regression =
|
|
849
|
+
// test/delete-entities-kg-hygiene.test.mjs ⓒ). Batch semantics are preserved —
|
|
850
|
+
// other entities still proceed when one fails.
|
|
846
851
|
try {
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
852
|
+
const deleted = this.db.transaction((eid) => {
|
|
853
|
+
// Check if entity exists first
|
|
854
|
+
const entityExists = this.db.prepare(`
|
|
855
|
+
SELECT id FROM entities WHERE id = ?
|
|
856
|
+
`).get(eid);
|
|
857
|
+
if (!entityExists)
|
|
858
|
+
return false;
|
|
859
|
+
// Step 0: Delete entity embeddings
|
|
860
|
+
const embeddingMetadata = this.db.prepare(`
|
|
861
|
+
SELECT rowid FROM entity_embedding_metadata WHERE entity_id = ?
|
|
862
|
+
`).get(eid);
|
|
863
|
+
if (embeddingMetadata) {
|
|
864
|
+
const embeddings = this.db.prepare(`
|
|
865
|
+
DELETE FROM entity_embeddings WHERE rowid = ?
|
|
866
|
+
`).run(embeddingMetadata.rowid);
|
|
867
|
+
const metadata = this.db.prepare(`
|
|
868
|
+
DELETE FROM entity_embedding_metadata WHERE entity_id = ?
|
|
869
|
+
`).run(eid);
|
|
870
|
+
if (embeddings.changes > 0 || metadata.changes > 0) {
|
|
871
|
+
console.error(` ├─ Removed entity embeddings for '${name}'`);
|
|
872
|
+
}
|
|
868
873
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
874
|
+
// Step 1: Delete chunk-entity associations
|
|
875
|
+
const chunkAssociations = this.db.prepare(`
|
|
876
|
+
DELETE FROM chunk_entities WHERE entity_id = ?
|
|
877
|
+
`).run(eid);
|
|
878
|
+
if (chunkAssociations.changes > 0) {
|
|
879
|
+
console.error(` ├─ Removed ${chunkAssociations.changes} chunk associations for '${name}'`);
|
|
880
|
+
}
|
|
881
|
+
// Step 2: Capture relationship ids BEFORE deleting the rows — KG relationship
|
|
882
|
+
// chunks are keyed by relationship_id, so after the DELETE they could no
|
|
883
|
+
// longer be found and would dangle forever.
|
|
884
|
+
const relIds = this.db.prepare(`
|
|
885
|
+
SELECT id FROM relationships
|
|
886
|
+
WHERE source_entity = ? OR target_entity = ?
|
|
887
|
+
`).all(eid, eid);
|
|
888
|
+
const relationships = this.db.prepare(`
|
|
889
|
+
DELETE FROM relationships
|
|
890
|
+
WHERE source_entity = ? OR target_entity = ?
|
|
891
|
+
`).run(eid, eid);
|
|
892
|
+
if (relationships.changes > 0) {
|
|
893
|
+
console.error(` ├─ Removed ${relationships.changes} relationships for '${name}'`);
|
|
894
|
+
}
|
|
895
|
+
// Step 2b: KG hygiene — sweep stale entity chunks and chunks of the captured
|
|
896
|
+
// relationships. The generation path is dormant today (generateKnowledgeGraphChunks
|
|
897
|
+
// is not tool-exposed), but once seeded these chunks stay vector-searchable after
|
|
898
|
+
// deletion unless swept here (same fail-closed rationale as deleteStaleKgChunks).
|
|
899
|
+
deleteStaleKgChunks(this.db, eid);
|
|
900
|
+
deleteKgRelationshipChunks(this.db, relIds.map(r => r.id));
|
|
901
|
+
// Step 3: Finally delete the entity itself (FK CASCADE takes observation lifecycle rows)
|
|
902
|
+
const entity = this.db.prepare(`
|
|
903
|
+
DELETE FROM entities WHERE id = ?
|
|
904
|
+
`).run(eid);
|
|
905
|
+
return entity.changes > 0;
|
|
906
|
+
})(entityId);
|
|
907
|
+
if (deleted) {
|
|
890
908
|
console.error(` └─ Deleted entity '${name}' successfully`);
|
|
891
909
|
}
|
|
892
910
|
else {
|
|
893
|
-
console.warn(
|
|
911
|
+
console.warn(`⚠️ Entity '${name}' not found, skipping`);
|
|
894
912
|
}
|
|
895
913
|
}
|
|
896
914
|
catch (error) {
|
|
897
|
-
console.error(`❌ Failed to delete entity '${name}':`, error);
|
|
915
|
+
console.error(`❌ Failed to delete entity '${name}' (transaction rolled back, continuing):`, error);
|
|
898
916
|
// Continue with other entities instead of failing completely
|
|
899
917
|
}
|
|
900
918
|
}
|
|
@@ -2241,6 +2259,44 @@ export class RAGKnowledgeGraphManager {
|
|
|
2241
2259
|
hasCJK(text) {
|
|
2242
2260
|
return /[\u3000-\u9fff\uac00-\ud7af\uff00-\uffef]/.test(text);
|
|
2243
2261
|
}
|
|
2262
|
+
// `\b` asserts a transition between a word char and a non-word char. When the name itself
|
|
2263
|
+
// *ends* (or starts) with a non-word char — and ours routinely do, e.g. "… Review (2026-05-27)"
|
|
2264
|
+
// — there is no transition to assert, so `\bname\b` can never match however the text reads.
|
|
2265
|
+
// Measured 2026-08-23 on a live 2,913-chunk corpus: 23 standalone occurrences across 13 names
|
|
2266
|
+
// were invisible to the regex. This is not a widening: we still require both neighbours to be
|
|
2267
|
+
// non-word, so "Data" continues not to match inside "Database". It is what `\b` was reaching
|
|
2268
|
+
// for, stated in a way that survives a name whose own edges are punctuation.
|
|
2269
|
+
static WORD_CH = /[A-Za-z0-9_]/;
|
|
2270
|
+
/**
|
|
2271
|
+
* Index of an occurrence of `name` in `text` with non-word neighbours, or -1.
|
|
2272
|
+
*
|
|
2273
|
+
* Runs on the ORIGINAL text, not a lowercased copy. Lowercasing can change length —
|
|
2274
|
+
* 'İ' folds to two units — so an index taken from the folded string does not address the
|
|
2275
|
+
* same character in the original, and buildEntityRangeFinder hands these indices straight
|
|
2276
|
+
* to the codepoint table. (r9-1 made the same call for the regex path; a first cut of this
|
|
2277
|
+
* helper folded first and the İ coordinate test caught it.)
|
|
2278
|
+
*/
|
|
2279
|
+
standaloneIndex(text, name, from = 0) {
|
|
2280
|
+
let re;
|
|
2281
|
+
try {
|
|
2282
|
+
re = new RegExp(name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
|
|
2283
|
+
}
|
|
2284
|
+
catch {
|
|
2285
|
+
return -1;
|
|
2286
|
+
}
|
|
2287
|
+
re.lastIndex = from;
|
|
2288
|
+
let m;
|
|
2289
|
+
while ((m = re.exec(text)) !== null) {
|
|
2290
|
+
const before = m.index === 0 ? '' : text[m.index - 1];
|
|
2291
|
+
const after = text[m.index + m[0].length] ?? '';
|
|
2292
|
+
if (!RAGKnowledgeGraphManager.WORD_CH.test(before)
|
|
2293
|
+
&& !RAGKnowledgeGraphManager.WORD_CH.test(after))
|
|
2294
|
+
return m.index;
|
|
2295
|
+
if (re.lastIndex === m.index)
|
|
2296
|
+
re.lastIndex++;
|
|
2297
|
+
}
|
|
2298
|
+
return -1;
|
|
2299
|
+
}
|
|
2244
2300
|
// The alias regex in autoLinkEntities matches anything shaped like "stem.ext", which
|
|
2245
2301
|
// includes version strings ("v3.3", "gpt-5.6"), measurements ("1.7mb", "0.465") and
|
|
2246
2302
|
// domains/module paths ("github.com", "os.path"). Those are not filenames and linking
|
|
@@ -2327,6 +2383,18 @@ export class RAGKnowledgeGraphManager {
|
|
|
2327
2383
|
if (re.lastIndex === m.index)
|
|
2328
2384
|
re.lastIndex++;
|
|
2329
2385
|
}
|
|
2386
|
+
// Same correction as buildEntityMatcher — the two must agree or a name links at chunk
|
|
2387
|
+
// level but not at range level. Union, deduped: a hit found by both must not be pushed
|
|
2388
|
+
// twice or a chunk gets counted once per path.
|
|
2389
|
+
const seen = new Set(out.map(r => `${r.s}:${r.e}`));
|
|
2390
|
+
for (let at = this.standaloneIndex(content, name); at >= 0; at = this.standaloneIndex(content, name, at + 1)) {
|
|
2391
|
+
const s2 = origCpAt(at), e2 = origCpAt(at + name.length); // 대소문자 차이는 길이를 바꾸지 않는다
|
|
2392
|
+
const key = `${s2}:${e2}`;
|
|
2393
|
+
if (!seen.has(key)) {
|
|
2394
|
+
seen.add(key);
|
|
2395
|
+
out.push({ s: s2, e: e2 });
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2330
2398
|
}
|
|
2331
2399
|
catch {
|
|
2332
2400
|
pushAllSubstr();
|
|
@@ -2345,7 +2413,8 @@ export class RAGKnowledgeGraphManager {
|
|
|
2345
2413
|
try {
|
|
2346
2414
|
const escaped = lower.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
2347
2415
|
const re = new RegExp(`\\b${escaped}\\b`, 'i');
|
|
2348
|
-
|
|
2416
|
+
// See standaloneIndex: `\b` cannot fire when the name's own edge is punctuation.
|
|
2417
|
+
return (text) => re.test(text) || this.standaloneIndex(text, name) >= 0;
|
|
2349
2418
|
}
|
|
2350
2419
|
catch {
|
|
2351
2420
|
return (text) => text.toLowerCase().includes(lower);
|
|
@@ -2392,6 +2461,28 @@ export class RAGKnowledgeGraphManager {
|
|
|
2392
2461
|
// document was last processed. That predates this gate; it is not introduced by it.
|
|
2393
2462
|
const MAX_ALIAS_OWNERS = 3;
|
|
2394
2463
|
const aliasOwners = new Map();
|
|
2464
|
+
// Owners is only half the mapping. It asks "does this token point at one entity?" and says
|
|
2465
|
+
// nothing about "does this entity point at one token?" — so an entity that mentions a common
|
|
2466
|
+
// filename ONCE in its observations attaches to every chunk containing that filename, and
|
|
2467
|
+
// because it is the only owner, the cap waves it through. The cap stopped the explosion
|
|
2468
|
+
// (tens of thousands of rows) but not the magnet (one entity on dozens of chunks).
|
|
2469
|
+
// Measured 2026-08-23 after the 6.0.0 cleanup: 2,014 alias-only links remained and 34
|
|
2470
|
+
// entities held 68.1% of them; the biggest had the entity name appearing in ZERO of its
|
|
2471
|
+
// chunks — pulled in entirely by a filename someone mentioned in passing.
|
|
2472
|
+
// So require the mapping in both directions: the token must identify the entity (owners)
|
|
2473
|
+
// AND the entity must identify the token (the token, or its stem, appears in the name).
|
|
2474
|
+
// This is a structural condition, not a threshold — there is no knee to tune and it keeps
|
|
2475
|
+
// meaning the same as the corpus grows. Chunk-frequency caps were measured instead
|
|
2476
|
+
// (585ms full scan, so cost was NOT the objection) and rejected because the sweep is smooth
|
|
2477
|
+
// and every cut also removed legitimate links.
|
|
2478
|
+
const aliasNamesTheEntity = (entityName, token) => {
|
|
2479
|
+
const lower = entityName.toLowerCase();
|
|
2480
|
+
if (lower.includes(token))
|
|
2481
|
+
return true;
|
|
2482
|
+
const dot = token.lastIndexOf('.');
|
|
2483
|
+
const stem = dot > 0 ? token.slice(0, dot) : token;
|
|
2484
|
+
return stem.length >= 4 && lower.includes(stem);
|
|
2485
|
+
};
|
|
2395
2486
|
for (const e of entities) {
|
|
2396
2487
|
if (!e.observations)
|
|
2397
2488
|
continue;
|
|
@@ -2446,6 +2537,8 @@ export class RAGKnowledgeGraphManager {
|
|
|
2446
2537
|
continue; // "v3.3", "gpt-5.6", "0.465"
|
|
2447
2538
|
if ((aliasOwners.get(tok) ?? 0) > MAX_ALIAS_OWNERS)
|
|
2448
2539
|
continue; // shared = identifies nothing
|
|
2540
|
+
if (!aliasNamesTheEntity(entity.name, tok))
|
|
2541
|
+
continue; // mentioned in passing = magnet
|
|
2449
2542
|
// Bare substring matching links "foo.py" to a chunk saying "notfoo.pyc".
|
|
2450
2543
|
// Require the token to stand alone: no filename character on either side.
|
|
2451
2544
|
const boundary = /[\w\-.]/;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type Database from 'better-sqlite3';
|
|
2
2
|
export declare function rebuildProjection(db: Database.Database, entityId: string): void;
|
|
3
3
|
export declare function deleteStaleKgChunks(db: Database.Database, entityId: string): number;
|
|
4
|
+
export declare function deleteKgRelationshipChunks(db: Database.Database, relationshipIds: string[]): number;
|
|
@@ -29,3 +29,20 @@ export function deleteStaleKgChunks(db, entityId) {
|
|
|
29
29
|
}
|
|
30
30
|
return n;
|
|
31
31
|
}
|
|
32
|
+
// relationship 청크는 relationship_id 키라 엔티티 삭제만으로는 잡을 수 없다 —
|
|
33
|
+
// 호출자가 relationships 행을 지우기 전에 id 를 캡처해서 넘겨야 한다(deleteEntities 계약).
|
|
34
|
+
// 넘어온 id 중 이미 없는 것은 무시한다(멱등). 벡터 행을 먼저 지우는 것은 deleteStaleKgChunks 와 같다.
|
|
35
|
+
export function deleteKgRelationshipChunks(db, relationshipIds) {
|
|
36
|
+
if (relationshipIds.length === 0)
|
|
37
|
+
return 0;
|
|
38
|
+
let n = 0;
|
|
39
|
+
for (const rid of relationshipIds) {
|
|
40
|
+
const chunks = db.prepare(`SELECT rowid FROM chunk_metadata WHERE chunk_type = 'relationship' AND relationship_id = ?`).all(rid);
|
|
41
|
+
for (const c of chunks) {
|
|
42
|
+
db.exec(`DELETE FROM chunks WHERE rowid = ${Number(c.rowid)}`);
|
|
43
|
+
db.prepare(`DELETE FROM chunk_metadata WHERE rowid = ?`).run(c.rowid);
|
|
44
|
+
n++;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return n;
|
|
48
|
+
}
|
package/package.json
CHANGED
|
@@ -1,71 +1,71 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
2
|
+
"name": "rag-memory-epf-mcp",
|
|
3
|
+
"version": "6.1.0",
|
|
4
|
+
"engines": {
|
|
5
|
+
"node": ">=24"
|
|
6
|
+
},
|
|
7
|
+
"description": "Project-local RAG memory MCP server \u2014 knowledge graph + multilingual vector + FTS5 in a single SQLite file. Per-project isolation, 38 MCP tools, codepoint-safe chunking (Korean/CJK/emoji).",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"mcp",
|
|
10
|
+
"model-context-protocol",
|
|
11
|
+
"rag",
|
|
12
|
+
"knowledge-graph",
|
|
13
|
+
"vector-search",
|
|
14
|
+
"fts5",
|
|
15
|
+
"sqlite",
|
|
16
|
+
"embeddings",
|
|
17
|
+
"bge-m3",
|
|
18
|
+
"multilingual",
|
|
19
|
+
"korean",
|
|
20
|
+
"claude-code",
|
|
21
|
+
"gemini-cli",
|
|
22
|
+
"codex-cli",
|
|
23
|
+
"memory",
|
|
24
|
+
"agent-memory"
|
|
25
|
+
],
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"author": "bripin123",
|
|
28
|
+
"homepage": "https://github.com/bripin123/rag-memory-epf-mcp",
|
|
29
|
+
"bugs": "https://github.com/bripin123/rag-memory-epf-mcp/issues",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/bripin123/rag-memory-epf-mcp.git"
|
|
33
|
+
},
|
|
34
|
+
"type": "module",
|
|
35
|
+
"main": "dist/index.js",
|
|
36
|
+
"bin": {
|
|
37
|
+
"rag-memory-mcp": "dist/index.js"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"dist",
|
|
41
|
+
"docs/UPDATING.md"
|
|
42
|
+
],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsc && shx chmod +x dist/*.js",
|
|
45
|
+
"prepare": "npm run build",
|
|
46
|
+
"watch": "tsc --watch",
|
|
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",
|
|
49
|
+
"test": "npm run build && npm run verify:invariants && npm run verify:engine",
|
|
50
|
+
"prepublishOnly": "npm run build && npm run verify:invariants && npm run verify:engine"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@huggingface/transformers": "^3.5.1",
|
|
54
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
55
|
+
"better-sqlite3": "^12.8.0",
|
|
56
|
+
"graphology": "^0.26.0",
|
|
57
|
+
"graphology-communities-louvain": "^2.0.2",
|
|
58
|
+
"graphology-metrics": "^2.4.0",
|
|
59
|
+
"graphology-shortest-path": "^2.1.0",
|
|
60
|
+
"graphology-types": "^0.24.8",
|
|
61
|
+
"sqlite-vec": "^0.1.7",
|
|
62
|
+
"tiktoken": "^1.0.17",
|
|
63
|
+
"zod": "^3.25.28"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@types/better-sqlite3": "^7.6.12",
|
|
67
|
+
"@types/node": "^24",
|
|
68
|
+
"shx": "^0.3.4",
|
|
69
|
+
"typescript": "^5.6.2"
|
|
70
|
+
}
|
|
71
|
+
}
|