rag-memory-epf-mcp 5.3.1 → 6.0.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 +58 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +174 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -153,6 +153,64 @@ 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
|
+
|
|
190
|
+
### v6.0.0
|
|
191
|
+
|
|
192
|
+
- **Breaking — observation-derived alias links are now gated.** `autoLinkEntities` used to take every
|
|
193
|
+
`stem.ext` token appearing anywhere in an entity's observations and link that entity to every chunk
|
|
194
|
+
containing the token as a substring. Measured on a live 2,891-chunk / 604-entity corpus: 65,388 of
|
|
195
|
+
66,841 `chunk_entities` rows (97.8%) existed only because of such a hit, and one token — `agents.md`,
|
|
196
|
+
held by 88 entities — accounted for 39,248 of the 80,096 distinct (chunk, entity) pairs any alias
|
|
197
|
+
could reach (49.0%). The regex also accepted things that are not filenames at all (`v3.3`,
|
|
198
|
+
`gpt-5.6`, `1.7mb`, `github.com`, `os.path`).
|
|
199
|
+
A token now has to (a) look like a filename — extension whitelist, stem ≥ 3 chars, non-numeric
|
|
200
|
+
stem — and (b) be held by at most 3 entities, and it must match on token boundaries so `foo.py` no
|
|
201
|
+
longer matches inside `notfoo.pyc`. Effect on the same corpus: alias links 100% → 4.0%. Filtering by
|
|
202
|
+
extension alone leaves 92.3%, so the owner cap is what does the work. The cap is a judgement, not a
|
|
203
|
+
discovered boundary — the sweep is smooth (`owners<=1` 1.6% … `<=10` 19.9%) — and it is a cap, not a
|
|
204
|
+
ban: a filename named by one to three records still links, which is what the alias path was for.
|
|
205
|
+
- **What this means for existing databases.** Nothing is rewritten on upgrade: rows already in
|
|
206
|
+
`chunk_entities` stay, and new ingests simply link far less. Links have always been a function of
|
|
207
|
+
when a document was last processed (nothing re-links older documents when entities are added), and
|
|
208
|
+
`chunk_entities` has no provenance column, so old alias rows cannot be told apart from name matches
|
|
209
|
+
after the fact. If you want the old noise gone you have to clean it offline, before or after
|
|
210
|
+
upgrading. Callers that assumed dense `chunk_entities` coverage will see sparser graphs.
|
|
211
|
+
- Regression: `test/alias-link-gate.test.mjs` (registered in `verify:engine`), verified to fail
|
|
212
|
+
without the gate.
|
|
213
|
+
|
|
156
214
|
### v5.3.0
|
|
157
215
|
- (Published first as `5.3.0-rc.1` on the `next` dist-tag; promoted to `latest` after a canary run of the published artifact against a real project database: default call carries no `graph_boost` and equals explicit `useGraph:false`, the known-item probe from the 2026-08-17 measurement returns the correct gotcha at rank 1, opt-in `true` still exposes `graph_boost`, schema/MCP defaults read `false`.)
|
|
158
216
|
- **Behavior change — `hybridSearch` graph re-ranking is now opt-in** (`useGraph` default `true` → `false`; tool schema, MCP exposure and the manager signature agree). Omitting the argument now means "no graph re-ranking" — a behavior change for callers that relied on the old default, hence a release-candidate first (`next` dist-tag, fleet canary) before stable. Measured 2026-08-17 on three real corpora (self-retrieval, usable samples 120/117/120, summaries off): with the additive graph boost on, the known-item chunk got worse in 46/49/52 samples and better in 3/2/0 (sign test p < 7e-11 per corpus), 106 targets left the top-10 entirely; reproduced on the summaries-on product path (HAL, 20 paired samples: hit@1 10→7, hit@5 18→13). Mechanism: only query-matched/connected entities score, but the per-entity boost saturates the cap quickly, so heavily-linked chunks can outrank the exact chunk even at `vector_similarity` 0. This is a harm-reduced default, not a validated graph improvement: the boost path is unchanged for `useGraph: true` (legacy/experimental re-ranker for back-compat and evaluation; the graph does not generate candidates — for relationship exploration use `openNodes` → `getNeighbors`). Regression lock: `test/search-graph-default.test.mjs`.
|
package/dist/index.d.ts
CHANGED
|
@@ -279,6 +279,19 @@ 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;
|
|
293
|
+
private static readonly ALIAS_FILE_EXT;
|
|
294
|
+
private looksLikeFilename;
|
|
282
295
|
private buildEntityRangeFinder;
|
|
283
296
|
private buildEntityMatcher;
|
|
284
297
|
private autoLinkEntities;
|
package/dist/index.js
CHANGED
|
@@ -2241,6 +2241,70 @@ export class RAGKnowledgeGraphManager {
|
|
|
2241
2241
|
hasCJK(text) {
|
|
2242
2242
|
return /[\u3000-\u9fff\uac00-\ud7af\uff00-\uffef]/.test(text);
|
|
2243
2243
|
}
|
|
2244
|
+
// `\b` asserts a transition between a word char and a non-word char. When the name itself
|
|
2245
|
+
// *ends* (or starts) with a non-word char — and ours routinely do, e.g. "… Review (2026-05-27)"
|
|
2246
|
+
// — there is no transition to assert, so `\bname\b` can never match however the text reads.
|
|
2247
|
+
// Measured 2026-08-23 on a live 2,913-chunk corpus: 23 standalone occurrences across 13 names
|
|
2248
|
+
// were invisible to the regex. This is not a widening: we still require both neighbours to be
|
|
2249
|
+
// non-word, so "Data" continues not to match inside "Database". It is what `\b` was reaching
|
|
2250
|
+
// for, stated in a way that survives a name whose own edges are punctuation.
|
|
2251
|
+
static WORD_CH = /[A-Za-z0-9_]/;
|
|
2252
|
+
/**
|
|
2253
|
+
* Index of an occurrence of `name` in `text` with non-word neighbours, or -1.
|
|
2254
|
+
*
|
|
2255
|
+
* Runs on the ORIGINAL text, not a lowercased copy. Lowercasing can change length —
|
|
2256
|
+
* 'İ' folds to two units — so an index taken from the folded string does not address the
|
|
2257
|
+
* same character in the original, and buildEntityRangeFinder hands these indices straight
|
|
2258
|
+
* to the codepoint table. (r9-1 made the same call for the regex path; a first cut of this
|
|
2259
|
+
* helper folded first and the İ coordinate test caught it.)
|
|
2260
|
+
*/
|
|
2261
|
+
standaloneIndex(text, name, from = 0) {
|
|
2262
|
+
let re;
|
|
2263
|
+
try {
|
|
2264
|
+
re = new RegExp(name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
|
|
2265
|
+
}
|
|
2266
|
+
catch {
|
|
2267
|
+
return -1;
|
|
2268
|
+
}
|
|
2269
|
+
re.lastIndex = from;
|
|
2270
|
+
let m;
|
|
2271
|
+
while ((m = re.exec(text)) !== null) {
|
|
2272
|
+
const before = m.index === 0 ? '' : text[m.index - 1];
|
|
2273
|
+
const after = text[m.index + m[0].length] ?? '';
|
|
2274
|
+
if (!RAGKnowledgeGraphManager.WORD_CH.test(before)
|
|
2275
|
+
&& !RAGKnowledgeGraphManager.WORD_CH.test(after))
|
|
2276
|
+
return m.index;
|
|
2277
|
+
if (re.lastIndex === m.index)
|
|
2278
|
+
re.lastIndex++;
|
|
2279
|
+
}
|
|
2280
|
+
return -1;
|
|
2281
|
+
}
|
|
2282
|
+
// The alias regex in autoLinkEntities matches anything shaped like "stem.ext", which
|
|
2283
|
+
// includes version strings ("v3.3", "gpt-5.6"), measurements ("1.7mb", "0.465") and
|
|
2284
|
+
// domains/module paths ("github.com", "os.path"). Those are not filenames and linking
|
|
2285
|
+
// on them is pure noise. Whitelist the extensions we actually ship and store.
|
|
2286
|
+
static ALIAS_FILE_EXT = new Set([
|
|
2287
|
+
'md', 'py', 'js', 'mjs', 'cjs', 'ts', 'tsx', 'jsx', 'json',
|
|
2288
|
+
'sh', 'bash', 'zsh', 'toml', 'yaml', 'yml', 'ini', 'cfg', 'conf',
|
|
2289
|
+
'txt', 'log', 'csv', 'tsv', 'sql', 'db', 'zip', 'gz', 'tar',
|
|
2290
|
+
'css', 'scss', 'html', 'htm', 'svg', 'png', 'jpg', 'jpeg', 'gif',
|
|
2291
|
+
'pdf', 'docx', 'pptx', 'xlsx', 'hwp', 'hwpx', 'lock', 'bak',
|
|
2292
|
+
]);
|
|
2293
|
+
// Deliberately absent: any 5+ character extension (jsonl, ipynb, scss is 4 so it stays).
|
|
2294
|
+
// The extractor regex is `\w{1,4}`, so a longer extension never reaches this set — listing
|
|
2295
|
+
// one would be a dead entry that reads as support. Extend the regex first if that changes.
|
|
2296
|
+
looksLikeFilename(token) {
|
|
2297
|
+
const i = token.lastIndexOf('.');
|
|
2298
|
+
if (i < 1)
|
|
2299
|
+
return false;
|
|
2300
|
+
const stem = token.slice(0, i);
|
|
2301
|
+
const ext = token.slice(i + 1);
|
|
2302
|
+
if (stem.length < 3)
|
|
2303
|
+
return false; // "d.ts" is a fragment, not a file
|
|
2304
|
+
if (/^\d+$/.test(stem))
|
|
2305
|
+
return false; // "2026.md" style numeric stems
|
|
2306
|
+
return RAGKnowledgeGraphManager.ALIAS_FILE_EXT.has(ext);
|
|
2307
|
+
}
|
|
2244
2308
|
// spec §5.4 (r7-2·r8-1·r9): primary name 의 본문 occurrence range [sCp, eCp).
|
|
2245
2309
|
// 의미 = buildEntityMatcher 와 동일 (CJK substring / Latin word-boundary) — 여기서
|
|
2246
2310
|
// 어긋나면 'Data' 가 'Database' 에 새로 링크되는 식으로 의미가 확장된다.
|
|
@@ -2301,6 +2365,18 @@ export class RAGKnowledgeGraphManager {
|
|
|
2301
2365
|
if (re.lastIndex === m.index)
|
|
2302
2366
|
re.lastIndex++;
|
|
2303
2367
|
}
|
|
2368
|
+
// Same correction as buildEntityMatcher — the two must agree or a name links at chunk
|
|
2369
|
+
// level but not at range level. Union, deduped: a hit found by both must not be pushed
|
|
2370
|
+
// twice or a chunk gets counted once per path.
|
|
2371
|
+
const seen = new Set(out.map(r => `${r.s}:${r.e}`));
|
|
2372
|
+
for (let at = this.standaloneIndex(content, name); at >= 0; at = this.standaloneIndex(content, name, at + 1)) {
|
|
2373
|
+
const s2 = origCpAt(at), e2 = origCpAt(at + name.length); // 대소문자 차이는 길이를 바꾸지 않는다
|
|
2374
|
+
const key = `${s2}:${e2}`;
|
|
2375
|
+
if (!seen.has(key)) {
|
|
2376
|
+
seen.add(key);
|
|
2377
|
+
out.push({ s: s2, e: e2 });
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2304
2380
|
}
|
|
2305
2381
|
catch {
|
|
2306
2382
|
pushAllSubstr();
|
|
@@ -2319,7 +2395,8 @@ export class RAGKnowledgeGraphManager {
|
|
|
2319
2395
|
try {
|
|
2320
2396
|
const escaped = lower.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
2321
2397
|
const re = new RegExp(`\\b${escaped}\\b`, 'i');
|
|
2322
|
-
|
|
2398
|
+
// See standaloneIndex: `\b` cannot fire when the name's own edge is punctuation.
|
|
2399
|
+
return (text) => re.test(text) || this.standaloneIndex(text, name) >= 0;
|
|
2323
2400
|
}
|
|
2324
2401
|
catch {
|
|
2325
2402
|
return (text) => text.toLowerCase().includes(lower);
|
|
@@ -2342,6 +2419,73 @@ export class RAGKnowledgeGraphManager {
|
|
|
2342
2419
|
// Minimum name length: 2 for CJK (e.g. "할랄"), 4 for Latin (avoid "API", "Bug")
|
|
2343
2420
|
const MIN_LEN_CJK = 2;
|
|
2344
2421
|
const MIN_LEN_LATIN = 4;
|
|
2422
|
+
// Observation-derived aliases are only useful when the token identifies few entities.
|
|
2423
|
+
// Measured on a 2,891-chunk / 604-entity corpus (2026-08-22). Denominators matter here,
|
|
2424
|
+
// so both are stated: 66,841 rows in chunk_entities, of which 65,388 (97.8%) exist only
|
|
2425
|
+
// because of an alias hit; counting distinct (chunk, entity) pairs reachable by any alias
|
|
2426
|
+
// gives 80,096. Against that 80,096, "agents.md" alone accounts for 39,248 (49.0%), and
|
|
2427
|
+
// 35,099 (43.8%) have no other alias reason at all. It is held by 88 entities. A filename
|
|
2428
|
+
// that dozens of entities mention is a stopword, not an identifier.
|
|
2429
|
+
//
|
|
2430
|
+
// The owner cap is what does the work: the extension whitelist alone still leaves 92.3%
|
|
2431
|
+
// of alias links. But the cap value is a judgement call, not a discovered boundary — the
|
|
2432
|
+
// sweep is smooth, with no natural knee (share of the 80,096 that survives):
|
|
2433
|
+
// owners<=1 1.6% · <=2 3.7% · <=3 5.2% · <=4 7.0% · <=5 8.3% · <=8 14.7% · <=10 19.9%
|
|
2434
|
+
// 3 was chosen to keep the intended behaviour (a file named by one or two records, plus
|
|
2435
|
+
// some slack) while cutting the stopword tail. Raising it is cheap and reversible.
|
|
2436
|
+
//
|
|
2437
|
+
// A chunk-frequency cap was measured (a further 1.4pp) and rejected on COST: it needs a
|
|
2438
|
+
// full-corpus scan on every ingest. Note the honest caveat — it was first rejected for
|
|
2439
|
+
// depending on ingest order, but this owner cap has that same property, and so does the
|
|
2440
|
+
// engine as a whole: autoLinkEntities only ever sees the entities that exist at ingest
|
|
2441
|
+
// time, and nothing re-links older documents when entities are added (see
|
|
2442
|
+
// createEntities / addObservations — neither calls this). Links are a function of when a
|
|
2443
|
+
// document was last processed. That predates this gate; it is not introduced by it.
|
|
2444
|
+
const MAX_ALIAS_OWNERS = 3;
|
|
2445
|
+
const aliasOwners = new Map();
|
|
2446
|
+
// Owners is only half the mapping. It asks "does this token point at one entity?" and says
|
|
2447
|
+
// nothing about "does this entity point at one token?" — so an entity that mentions a common
|
|
2448
|
+
// filename ONCE in its observations attaches to every chunk containing that filename, and
|
|
2449
|
+
// because it is the only owner, the cap waves it through. The cap stopped the explosion
|
|
2450
|
+
// (tens of thousands of rows) but not the magnet (one entity on dozens of chunks).
|
|
2451
|
+
// Measured 2026-08-23 after the 6.0.0 cleanup: 2,014 alias-only links remained and 34
|
|
2452
|
+
// entities held 68.1% of them; the biggest had the entity name appearing in ZERO of its
|
|
2453
|
+
// chunks — pulled in entirely by a filename someone mentioned in passing.
|
|
2454
|
+
// So require the mapping in both directions: the token must identify the entity (owners)
|
|
2455
|
+
// AND the entity must identify the token (the token, or its stem, appears in the name).
|
|
2456
|
+
// This is a structural condition, not a threshold — there is no knee to tune and it keeps
|
|
2457
|
+
// meaning the same as the corpus grows. Chunk-frequency caps were measured instead
|
|
2458
|
+
// (585ms full scan, so cost was NOT the objection) and rejected because the sweep is smooth
|
|
2459
|
+
// and every cut also removed legitimate links.
|
|
2460
|
+
const aliasNamesTheEntity = (entityName, token) => {
|
|
2461
|
+
const lower = entityName.toLowerCase();
|
|
2462
|
+
if (lower.includes(token))
|
|
2463
|
+
return true;
|
|
2464
|
+
const dot = token.lastIndexOf('.');
|
|
2465
|
+
const stem = dot > 0 ? token.slice(0, dot) : token;
|
|
2466
|
+
return stem.length >= 4 && lower.includes(stem);
|
|
2467
|
+
};
|
|
2468
|
+
for (const e of entities) {
|
|
2469
|
+
if (!e.observations)
|
|
2470
|
+
continue;
|
|
2471
|
+
let obs;
|
|
2472
|
+
try {
|
|
2473
|
+
obs = JSON.parse(e.observations);
|
|
2474
|
+
}
|
|
2475
|
+
catch {
|
|
2476
|
+
continue;
|
|
2477
|
+
}
|
|
2478
|
+
const seen = new Set();
|
|
2479
|
+
for (const ob of obs) {
|
|
2480
|
+
const pm = String(ob).match(/[\w\-]+\.\w{1,4}\b/g);
|
|
2481
|
+
if (pm)
|
|
2482
|
+
for (const p of pm)
|
|
2483
|
+
if (p.length >= 4)
|
|
2484
|
+
seen.add(p.toLowerCase());
|
|
2485
|
+
}
|
|
2486
|
+
for (const t of seen)
|
|
2487
|
+
aliasOwners.set(t, (aliasOwners.get(t) ?? 0) + 1);
|
|
2488
|
+
}
|
|
2345
2489
|
const insertStmt = this.db.prepare(`
|
|
2346
2490
|
INSERT OR IGNORE INTO chunk_entities (chunk_rowid, entity_id) VALUES (?, ?)
|
|
2347
2491
|
`);
|
|
@@ -2351,7 +2495,9 @@ export class RAGKnowledgeGraphManager {
|
|
|
2351
2495
|
if (entity.name.length < minLen)
|
|
2352
2496
|
continue;
|
|
2353
2497
|
const nameMatcher = this.buildEntityMatcher(entity.name);
|
|
2354
|
-
// Also collect observation-derived aliases (short keywords from observations)
|
|
2498
|
+
// Also collect observation-derived aliases (short keywords from observations).
|
|
2499
|
+
// Gated: the token must look like a filename AND identify at most MAX_ALIAS_OWNERS
|
|
2500
|
+
// entities. Ungated, "agents.md" linked 88 entities to every chunk that mentioned it.
|
|
2355
2501
|
const aliases = [];
|
|
2356
2502
|
if (entity.observations) {
|
|
2357
2503
|
let obs;
|
|
@@ -2366,9 +2512,32 @@ export class RAGKnowledgeGraphManager {
|
|
|
2366
2512
|
const pathMatch = ob.match(/[\w\-]+\.\w{1,4}\b/g);
|
|
2367
2513
|
if (pathMatch) {
|
|
2368
2514
|
for (const p of pathMatch) {
|
|
2369
|
-
if (p.length
|
|
2370
|
-
|
|
2371
|
-
|
|
2515
|
+
if (p.length < 4)
|
|
2516
|
+
continue;
|
|
2517
|
+
const tok = p.toLowerCase();
|
|
2518
|
+
if (!this.looksLikeFilename(tok))
|
|
2519
|
+
continue; // "v3.3", "gpt-5.6", "0.465"
|
|
2520
|
+
if ((aliasOwners.get(tok) ?? 0) > MAX_ALIAS_OWNERS)
|
|
2521
|
+
continue; // shared = identifies nothing
|
|
2522
|
+
if (!aliasNamesTheEntity(entity.name, tok))
|
|
2523
|
+
continue; // mentioned in passing = magnet
|
|
2524
|
+
// Bare substring matching links "foo.py" to a chunk saying "notfoo.pyc".
|
|
2525
|
+
// Require the token to stand alone: no filename character on either side.
|
|
2526
|
+
const boundary = /[\w\-.]/;
|
|
2527
|
+
aliases.push((text) => {
|
|
2528
|
+
const hay = text.toLowerCase();
|
|
2529
|
+
let from = 0;
|
|
2530
|
+
for (;;) {
|
|
2531
|
+
const i = hay.indexOf(tok, from);
|
|
2532
|
+
if (i < 0)
|
|
2533
|
+
return false;
|
|
2534
|
+
const before = i === 0 ? '' : hay[i - 1];
|
|
2535
|
+
const after = hay[i + tok.length] ?? '';
|
|
2536
|
+
if (!boundary.test(before) && !boundary.test(after))
|
|
2537
|
+
return true;
|
|
2538
|
+
from = i + 1;
|
|
2539
|
+
}
|
|
2540
|
+
});
|
|
2372
2541
|
}
|
|
2373
2542
|
}
|
|
2374
2543
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rag-memory-epf-mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.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/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/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",
|
|
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/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
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
|
},
|