rag-memory-epf-mcp 3.3.4 → 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 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 recovered with indexOf using a running cursor at the
1143
- // previous chunk's start, so substr(text, start_pos, end_pos - start_pos)
1144
- // equals chunk.text. On a coincidental indexOf miss the char offsets are NULL.
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 charCursor = 0;
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
- charCursor = 0;
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 idx = text.indexOf(chunkText, charCursor);
1173
- if (idx >= 0) {
1174
- startPos = idx;
1175
- endPos = idx + chunkText.length;
1176
- charCursor = idx;
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;
@@ -520,5 +520,55 @@ export const migrations = [
520
520
  // start_token/end_token columns remain (no DROP COLUMN); they will be
521
521
  // ignored by older code.
522
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
+ }
523
573
  }
524
574
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rag-memory-epf-mcp",
3
- "version": "3.3.4",
3
+ "version": "3.3.5",
4
4
  "description": "MCP server for project-local RAG memory with knowledge graph and multilingual vector search",
5
5
  "license": "MIT",
6
6
  "author": "bripin123",