rag-memory-epf-mcp 3.3.3 → 3.3.5
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/dist/index.js +23 -13
- package/dist/src/migrations/migrations.js +71 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1139,15 +1139,19 @@ class RAGKnowledgeGraphManager {
|
|
|
1139
1139
|
//
|
|
1140
1140
|
// Each chunk records both token-space offsets (start_token/end_token from the
|
|
1141
1141
|
// BPE encoder loop) and char-space offsets (start_pos/end_pos into the original
|
|
1142
|
-
// text). Char offsets are
|
|
1143
|
-
//
|
|
1144
|
-
//
|
|
1142
|
+
// text). Char offsets are Unicode codepoint counts — language-neutral, so SQL
|
|
1143
|
+
// substr, Python str slicing, and JS [...str] iteration all line up. JS's
|
|
1144
|
+
// native UTF-16 indexing differs for supplementary characters (emoji, rare
|
|
1145
|
+
// CJK), so the function maintains parallel UTF-16 and codepoint cursors and
|
|
1146
|
+
// reports codepoint offsets. On a coincidental indexOf miss the char offsets
|
|
1147
|
+
// are NULL.
|
|
1145
1148
|
chunkText(text, maxTokens = 800, overlap = 160) {
|
|
1146
1149
|
if (!this.encoding)
|
|
1147
1150
|
throw new Error('Tokenizer not initialized');
|
|
1148
1151
|
const tokens = this.encoding.encode(text);
|
|
1149
1152
|
const chunks = [];
|
|
1150
|
-
let
|
|
1153
|
+
let utf16Cursor = 0;
|
|
1154
|
+
let cpCursor = 0;
|
|
1151
1155
|
for (let i = 0; i < tokens.length; i += maxTokens - overlap) {
|
|
1152
1156
|
const chunkTokens = tokens.slice(i, i + maxTokens);
|
|
1153
1157
|
const decodedBytes = this.encoding.decode(chunkTokens);
|
|
@@ -1155,25 +1159,31 @@ class RAGKnowledgeGraphManager {
|
|
|
1155
1159
|
const isLast = i + chunkTokens.length >= tokens.length;
|
|
1156
1160
|
const safeBytes = trimIncompleteUtf8(decodedBytes, !isFirst, !isLast);
|
|
1157
1161
|
const chunkText = new TextDecoder('utf-8').decode(safeBytes);
|
|
1158
|
-
// Recover char-space offsets. First chunk is anchored at 0 because
|
|
1159
|
-
// trimIncompleteUtf8 leaves the leading bytes intact when isFirst is true.
|
|
1160
1162
|
let startPos;
|
|
1161
1163
|
let endPos;
|
|
1162
1164
|
if (isFirst) {
|
|
1163
1165
|
startPos = 0;
|
|
1164
|
-
endPos = chunkText.length;
|
|
1165
|
-
|
|
1166
|
+
endPos = [...chunkText].length;
|
|
1167
|
+
utf16Cursor = 0;
|
|
1168
|
+
cpCursor = 0;
|
|
1166
1169
|
}
|
|
1167
1170
|
else if (chunkText.length === 0) {
|
|
1168
1171
|
startPos = null;
|
|
1169
1172
|
endPos = null;
|
|
1170
1173
|
}
|
|
1171
1174
|
else {
|
|
1172
|
-
const
|
|
1173
|
-
if (
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1175
|
+
const utfIdx = text.indexOf(chunkText, utf16Cursor);
|
|
1176
|
+
if (utfIdx >= 0) {
|
|
1177
|
+
// Advance cpCursor by codepoints between the previous cursor and the
|
|
1178
|
+
// new chunk's start (handles overlap by anchoring at the previous
|
|
1179
|
+
// chunk's start, not its end).
|
|
1180
|
+
if (utfIdx > utf16Cursor) {
|
|
1181
|
+
cpCursor += [...text.slice(utf16Cursor, utfIdx)].length;
|
|
1182
|
+
utf16Cursor = utfIdx;
|
|
1183
|
+
}
|
|
1184
|
+
const cpLen = [...chunkText].length;
|
|
1185
|
+
startPos = cpCursor;
|
|
1186
|
+
endPos = cpCursor + cpLen;
|
|
1177
1187
|
}
|
|
1178
1188
|
else {
|
|
1179
1189
|
startPos = null;
|
|
@@ -428,7 +428,11 @@ export const migrations = [
|
|
|
428
428
|
db.exec(`DROP TABLE IF EXISTS chunks_fts`);
|
|
429
429
|
}
|
|
430
430
|
},
|
|
431
|
-
// Migration
|
|
431
|
+
// Migration 10: Separate token-space vs char-space chunk offsets.
|
|
432
|
+
// (Slot 9 is intentionally skipped — some user databases from early v3.x
|
|
433
|
+
// experiments have an unrelated migration recorded at version 9 (Ollama
|
|
434
|
+
// dimension swap). Reusing that slot would silently no-op against those
|
|
435
|
+
// databases. Version 10 ensures the migration runs everywhere.)
|
|
432
436
|
// Before this migration, chunk_metadata.start_pos/end_pos held *token* indices
|
|
433
437
|
// for document chunks (a leftover from the BPE tokenizer-based chunkText loop)
|
|
434
438
|
// but already held character lengths (0..text.length) for entity/relationship
|
|
@@ -442,13 +446,22 @@ export const migrations = [
|
|
|
442
446
|
// (already a valid 0..text.length char range against the chunk text itself);
|
|
443
447
|
// token columns stay NULL since these chunks have no token-space concept.
|
|
444
448
|
{
|
|
445
|
-
version:
|
|
449
|
+
version: 10,
|
|
446
450
|
description: 'Add start_token/end_token; reinterpret start_pos/end_pos as char offsets',
|
|
447
451
|
up: (db) => {
|
|
448
|
-
// 1) Add columns
|
|
449
|
-
|
|
450
|
-
db.
|
|
451
|
-
|
|
452
|
+
// 1) Add columns (idempotent — some databases may have been touched by a
|
|
453
|
+
// pre-release v9 attempt; tolerate the column already existing).
|
|
454
|
+
const cols = db.prepare(`PRAGMA table_info(chunk_metadata)`).all()
|
|
455
|
+
.map(c => c.name);
|
|
456
|
+
if (!cols.includes('start_token')) {
|
|
457
|
+
db.exec(`ALTER TABLE chunk_metadata ADD COLUMN start_token INTEGER`);
|
|
458
|
+
}
|
|
459
|
+
if (!cols.includes('end_token')) {
|
|
460
|
+
db.exec(`ALTER TABLE chunk_metadata ADD COLUMN end_token INTEGER`);
|
|
461
|
+
}
|
|
462
|
+
// 2) Move token data into new columns for document chunks (only if not
|
|
463
|
+
// already moved — guard against re-running in the rare case a prior
|
|
464
|
+
// partial run already touched some rows).
|
|
452
465
|
db.exec(`
|
|
453
466
|
UPDATE chunk_metadata
|
|
454
467
|
SET start_token = start_pos,
|
|
@@ -456,6 +469,8 @@ export const migrations = [
|
|
|
456
469
|
start_pos = NULL,
|
|
457
470
|
end_pos = NULL
|
|
458
471
|
WHERE chunk_type = 'document'
|
|
472
|
+
AND start_token IS NULL
|
|
473
|
+
AND start_pos IS NOT NULL
|
|
459
474
|
`);
|
|
460
475
|
// 3) Recompute char offsets via indexOf with a running cursor per document
|
|
461
476
|
const docRows = db.prepare(`
|
|
@@ -505,5 +520,55 @@ export const migrations = [
|
|
|
505
520
|
// start_token/end_token columns remain (no DROP COLUMN); they will be
|
|
506
521
|
// ignored by older code.
|
|
507
522
|
}
|
|
523
|
+
},
|
|
524
|
+
// Migration 11: Convert chunk_metadata.start_pos/end_pos from JS UTF-16 code
|
|
525
|
+
// unit indices to Unicode codepoint indices. v3.3.4 stored offsets in JS's
|
|
526
|
+
// native UTF-16 unit space, which mismatches SQL substr/length and Python
|
|
527
|
+
// string indexing for any document containing supplementary characters
|
|
528
|
+
// (emoji, rare CJK). Codepoints are language-neutral. Walks each document's
|
|
529
|
+
// chunks in chunk_index order, locating each chunk in the source via UTF-16
|
|
530
|
+
// indexOf and counting codepoints between cursor positions to derive the
|
|
531
|
+
// codepoint offsets. Idempotent — chunks where indexOf misses keep their
|
|
532
|
+
// existing values.
|
|
533
|
+
{
|
|
534
|
+
version: 11,
|
|
535
|
+
description: 'Convert chunk_metadata.start_pos/end_pos to Unicode codepoint indices',
|
|
536
|
+
up: (db) => {
|
|
537
|
+
const docRows = db.prepare(`SELECT DISTINCT document_id FROM chunk_metadata
|
|
538
|
+
WHERE chunk_type='document' AND document_id IS NOT NULL
|
|
539
|
+
AND start_pos IS NOT NULL AND end_pos IS NOT NULL`).all();
|
|
540
|
+
const getContent = db.prepare(`SELECT content FROM documents WHERE id = ?`);
|
|
541
|
+
const getChunks = db.prepare(`SELECT rowid, text FROM chunk_metadata
|
|
542
|
+
WHERE document_id=? AND chunk_type='document'
|
|
543
|
+
AND start_pos IS NOT NULL AND end_pos IS NOT NULL
|
|
544
|
+
ORDER BY chunk_index ASC`);
|
|
545
|
+
const upd = db.prepare(`UPDATE chunk_metadata SET start_pos=?, end_pos=? WHERE rowid=?`);
|
|
546
|
+
for (const { document_id } of docRows) {
|
|
547
|
+
const doc = getContent.get(document_id);
|
|
548
|
+
if (!doc)
|
|
549
|
+
continue;
|
|
550
|
+
const content = doc.content;
|
|
551
|
+
const chunks = getChunks.all(document_id);
|
|
552
|
+
let utf16Cursor = 0;
|
|
553
|
+
let cpCursor = 0;
|
|
554
|
+
for (const c of chunks) {
|
|
555
|
+
if (!c.text)
|
|
556
|
+
continue;
|
|
557
|
+
const utfIdx = content.indexOf(c.text, utf16Cursor);
|
|
558
|
+
if (utfIdx < 0)
|
|
559
|
+
continue; // miss: leave existing
|
|
560
|
+
if (utfIdx > utf16Cursor) {
|
|
561
|
+
cpCursor += [...content.slice(utf16Cursor, utfIdx)].length;
|
|
562
|
+
utf16Cursor = utfIdx;
|
|
563
|
+
}
|
|
564
|
+
const cpLen = [...c.text].length;
|
|
565
|
+
upd.run(cpCursor, cpCursor + cpLen, c.rowid);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
},
|
|
569
|
+
down: (_db) => {
|
|
570
|
+
// No clean reversal: would need the original document content to recompute
|
|
571
|
+
// UTF-16 indices, and we never recorded which chunks were touched. No-op.
|
|
572
|
+
}
|
|
508
573
|
}
|
|
509
574
|
];
|