rag-memory-epf-mcp 5.2.0 → 5.3.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 CHANGED
@@ -10,9 +10,9 @@ A **project-local RAG memory** MCP server — knowledge graph + multilingual vec
10
10
  ## Key Features
11
11
 
12
12
  - **Project-local isolation** — each project gets its own `.memory/rag-memory.db`. Multiple projects run simultaneously without interference.
13
- - **3-signal hybrid search** — vector similarity (bge-m3, 1024-dim) + FTS5 BM25 keyword matching + knowledge graph re-ranking, combined via Reciprocal Rank Fusion
13
+ - **Hybrid search** — vector similarity (bge-m3, 1024-dim) + FTS5 BM25 keyword matching (RRF-fused). Knowledge-graph re-ranking is an **opt-in legacy/experimental** signal since v5.3.0 (measured to hurt known-item retrieval)
14
14
  - **100+ languages** — Korean, Chinese, Japanese, Arabic, and more. Cross-lingual search works out of the box.
15
- - **Graph-aware scoring** — per-entity geometric decay (0.5^i) with hard cap prevents any single document from dominating results
15
+ - **Graph re-ranker (opt-in)** — per-entity geometric decay (0.5^i) with a hard cap 0.4; the cap bounds the boost but was measured (2026-08-17) to let heavily-linked chunks saturate it and outrank the exact chunk — hence off by default
16
16
  - **38 MCP tools** — knowledge graph CRUD, observation lifecycle (correct / retract / history), document pipeline, hybrid search, multi-hop traversal, graph analytics (centrality / community detection / structure), export/import, temporal queries
17
17
  - **Observations that hold their history** — corrections supersede instead of overwrite, search returns only current facts, and every revision keeps its provenance
18
18
  - **Structure-anchored chunking (c1, v5)** — boundaries anchor to markdown structure (fence-aware, H1–H4 first, block-greedy, exact-token fallback), so editing the top of a file no longer re-embeds the whole document: unchanged text reuses its stored vectors at sync time. Chunk offsets are Unicode codepoints, language-neutral across SQL `substr`, Python slicing, and JS `[...str]` iteration; a publish-time invariant gate locks the gap-free partition. `overlap` is retired (omit or 0).
@@ -77,7 +77,7 @@ Place this `.mcp.json` in each project folder with its own `DB_FILE_PATH`. Each
77
77
  ### Search & Retrieval (9)
78
78
  | Tool | Description | Annotation |
79
79
  |------|------------|------------|
80
- | `hybridSearch` | Vector + FTS5 BM25 + graph traversal (3-signal). Degrades to FTS5-only (`search_mode`) when the embedding model is down | readOnly |
80
+ | `hybridSearch` | Vector + FTS5 BM25, plus an **opt-in** graph re-ranker (`useGraph: true`; default off since v5.3.0). Degrades to FTS5-only (`search_mode`) when the embedding model is down | readOnly |
81
81
  | `searchNodes` | Semantic entity search with `since`/`until` temporal filtering | readOnly |
82
82
  | `openNodes` | Retrieve specific entities by name | readOnly |
83
83
  | `readGraph` | Get complete knowledge graph | readOnly |
@@ -153,6 +153,11 @@ storeDocument(id, content, metadata)
153
153
 
154
154
  ## Changelog
155
155
 
156
+ ### v5.3.0
157
+ - (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
+ - **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`.
159
+ - (v5.0.0–v5.2.0 notes live in the git tags / `docs/UPDATING.md`.)
160
+
156
161
  ### v4.0.0
157
162
 
158
163
  **Observation lifecycle (schema v13).** Observations used to be a JSON array of strings on the
package/dist/index.js CHANGED
@@ -2923,7 +2923,19 @@ export class RAGKnowledgeGraphManager {
2923
2923
  this.coordinator?.kick();
2924
2924
  return { imported, skipped, observation_order_remap: remapReport };
2925
2925
  }
2926
- async hybridSearch(query, limit = 5, useGraph = true) {
2926
+ // v5.3.0: the graph re-ranker is OPT-IN (harm-reduced default, not a validated improvement).
2927
+ // Measured 2026-08-17 on three real corpora (self-retrieval, usable samples 120/117/120,
2928
+ // summaries off): with the additive graph boost on, the known-item chunk got WORSE in
2929
+ // 46/49/52 samples and BETTER in 3/2/0 (sign test p < 7e-11 per corpus); 106 targets left the
2930
+ // top-10 entirely. Reproduced on the summaries-on product path (HAL, 20 paired samples:
2931
+ // hit@1 10 -> 7, hit@5 18 -> 13, worse 8 / better 3). Mechanism: only query-matched or
2932
+ // connected entities score, but the per-entity boost (0.5^i decay, cap 0.4) saturates fast, so
2933
+ // heavily-linked chunks get more chances to match and reach the cap — they can outrank the
2934
+ // exact chunk even at vector_similarity 0. Note the graph does not generate candidates: it only
2935
+ // re-orders the vector/FTS candidate pool, so useGraph:true is a legacy/experimental re-ranker
2936
+ // (backward compatibility, controlled evaluation), not a relationship-exploration path — that
2937
+ // contract is openNodes -> getNeighbors. The boost path itself is unchanged.
2938
+ async hybridSearch(query, limit = 5, useGraph = false) {
2927
2939
  if (!this.db)
2928
2940
  throw new Error('Database not initialized');
2929
2941
  if (!this.encoding)
@@ -3870,7 +3882,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3870
3882
  return { content: [{ type: "text", text: JSON.stringify(await ragKgManager.linkEntitiesToDocument(validatedArgs.documentId, validatedArgs.entityNames), null, 2) }] };
3871
3883
  case "hybridSearch":
3872
3884
  const limit = typeof validatedArgs.limit === 'number' ? validatedArgs.limit : 5;
3873
- const useGraph = validatedArgs.useGraph !== false;
3885
+ // v5.3.0: graph is opt-in — only an explicit true enables the re-ranker (schema default false).
3886
+ const useGraph = validatedArgs.useGraph === true;
3874
3887
  return { content: [{ type: "text", text: JSON.stringify(await ragKgManager.hybridSearch(validatedArgs.query, limit, useGraph), null, 2) }] };
3875
3888
  case "getDetailedContext":
3876
3889
  return { content: [{ type: "text", text: JSON.stringify(await ragKgManager.getDetailedContext(validatedArgs.chunkId, validatedArgs.includeSurrounding !== false), null, 2) }] };
@@ -9,7 +9,16 @@ export const migrations = [
9
9
  version: 1,
10
10
  description: 'Complete RAG Knowledge Graph schema - all tables and features',
11
11
  up: (db) => {
12
- // Disable foreign key enforcement to make deletions easier
12
+ // 줄은 **아무 일도 하지 않는다** (2026-08-11 실측). migration-manager 가 각
13
+ // 마이그레이션을 `db.transaction(...)` 으로 감싸는데, SQLite 에서 `PRAGMA foreign_keys`
14
+ // 는 **트랜잭션 안에서 no-op** 이기 때문이다. 실측 = 신규 DB 에 13개 마이그레이션을
15
+ // 전부 적용한 뒤에도 `foreign_keys = 1`.
16
+ //
17
+ // **고치지 말 것.** 이 줄이 실제로 동작하게 만들면(트랜잭션 밖으로 빼는 등) FK 는
18
+ // 마이그레이션 이후 **그 프로세스 수명 내내 꺼진 채로 남는다** — 부팅 게이트(index.ts)
19
+ // 는 `runMigrations()` **앞**에서 판정하므로 그걸 못 잡는다. 그 상태에서 entity 를
20
+ // 지우면 observation 계열이 CASCADE 되지 않아 고아·FK 위반이 쌓인다.
21
+ // 회귀 잠금 = test/observation-cascade.test.mjs T25(신규 DB 부팅 후 FK==1).
13
22
  db.pragma('foreign_keys = OFF');
14
23
  // Original entities table (enhanced)
15
24
  db.exec(`
@@ -292,22 +292,24 @@ const hybridSearchCapability = {
292
292
  },
293
293
  useGraph: {
294
294
  type: 'boolean',
295
- description: 'Whether to enhance results with knowledge graph connections',
296
- default: true
295
+ description: 'Opt-in legacy/experimental graph re-ranker (default false since v5.3.0 — measured to hurt known-item retrieval). It only re-orders the vector/FTS candidate pool; for relationship exploration use openNodes -> getNeighbors instead',
296
+ default: false
297
297
  }
298
298
  },
299
299
  required: ['query'],
300
300
  },
301
301
  };
302
302
  const hybridSearchDescription = () => `<description>
303
- Perform sophisticated hybrid search that combines vector similarity with knowledge graph traversal for superior results.
304
- **The most powerful search tool in the system** - leverages both semantic similarity and structural relationships.
305
- Perfect for complex queries that benefit from both content matching and conceptual connections.
303
+ Hybrid document search: vector similarity + FTS5 BM25 over document chunks, with an **opt-in** knowledge-graph re-ranker.
304
+ Default (useGraph false) is the harm-reduced default for finding a fact you know exists (known-item retrieval).
305
+ useGraph: true is a legacy/experimental re-ranker kept for backward compatibility and controlled evaluation — it does not
306
+ add candidates, it only re-orders the vector/FTS pool, and measured on three real corpora (2026-08-17) it pushed the exact
307
+ chunk out of the top-5 in ~40% of known-item queries. For relationship exploration use openNodes -> getNeighbors.
306
308
  </description>
307
309
 
308
310
  <importantNotes>
309
- - (!important!) **Hybrid approach is more powerful** than pure vector or graph search alone
310
- - (!important!) Graph enhancement finds related concepts even if not directly mentioned
311
+ - (!important!) **Graph re-ranking is opt-in (default false since v5.3.0)** it re-orders candidates by matched/connected entity links, which is measured to hurt known-item retrieval; enable it only for backward compatibility or controlled evaluation
312
+ - (!important!) Graph re-ranking does not generate candidates for "what is connected to X" use openNodes -> getNeighbors
311
313
  - (!important!) Results include similarity scores, graph boost, and hybrid rankings
312
314
  - (!important!) **Best results when knowledge graph is well-populated** with entities and relationships
313
315
  </importantNotes>
@@ -332,7 +334,7 @@ Perfect for complex queries that benefit from both content matching and conceptu
332
334
 
333
335
  <bestPractices>
334
336
  - Use natural language queries rather than keywords
335
- - Enable graph enhancement for better conceptual coverage
337
+ - Keep graph re-ranking off for "find the fact I know exists"; for "what is connected to this" use openNodes -> getNeighbors, not useGraph
336
338
  - Start with broader queries, then narrow down based on results
337
339
  - Review entity associations to understand why results were selected
338
340
  - Use appropriate limits based on your analysis needs
@@ -342,19 +344,18 @@ Perfect for complex queries that benefit from both content matching and conceptu
342
344
  <parameters>
343
345
  - query: Natural language search query (string, required)
344
346
  - limit: Maximum results to return, default 5 (number, optional)
345
- - useGraph: Enable knowledge graph enhancement, default true (boolean, optional)
347
+ - useGraph: Enable knowledge graph re-ranking, default false / opt-in (boolean, optional)
346
348
  </parameters>
347
349
 
348
350
  <examples>
349
- - Conceptual search: {"query": "machine learning applications in healthcare", "limit": 10}
350
- - Technical research: {"query": "React performance optimization techniques", "useGraph": true}
351
- - Discovery mode: {"query": "Einstein's contributions to modern physics", "limit": 15}
352
- - Quick lookup: {"query": "quantum computing advantages", "limit": 3, "useGraph": false}
351
+ - Known-item lookup (default, graph off): {"query": "machine learning applications in healthcare", "limit": 10}
352
+ - Legacy re-ranker (opt-in, evaluation/back-compat only): {"query": "React performance optimization techniques", "useGraph": true}
353
+ - Discovery / relationship exploration: use openNodes then getNeighbors on the entity, not this tool's useGraph
353
354
  </examples>`;
354
355
  const hybridSearchSchema = {
355
356
  query: z.string().describe('The search query to find relevant information'),
356
357
  limit: z.number().optional().default(5).describe('Maximum number of results to return'),
357
- useGraph: z.boolean().optional().default(true).describe('Whether to enhance results with knowledge graph connections'),
358
+ useGraph: z.boolean().optional().default(false).describe('Opt-in legacy/experimental graph re-ranker (default false since v5.3.0; measured to hurt known-item retrieval; for exploration use openNodes -> getNeighbors)'),
358
359
  };
359
360
  export const hybridSearchTool = {
360
361
  capability: hybridSearchCapability,
package/docs/UPDATING.md CHANGED
@@ -108,6 +108,30 @@ 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
+ ## v5.3.0 (schema v14, unchanged): `hybridSearch` graph re-ranking is opt-in
112
+
113
+ **What changed.** `useGraph` defaults to `false` (was `true`) — in the manager signature, the tool JSON
114
+ schema, the zod schema (`validateToolArgs` fills `false`) and the MCP dispatch (`=== true`). Nothing else
115
+ in the scoring path moved: `useGraph: true` runs exactly the pre-5.3 graph boost.
116
+
117
+ **Why.** Measured 2026-08-17 on three real corpora (self-retrieval, usable samples 120/117/120, summaries
118
+ off): with the additive graph boost on, the known-item chunk got WORSE in 46/49/52 samples and BETTER in
119
+ 3/2/0 (paired sign test p < 7e-11 per corpus); 106 targets fell out of the top-10 entirely. Reproduced on
120
+ the summaries-on product path (HAL, 20 paired samples: hit@1 10→7, hit@5 18→13). Only query-matched or
121
+ connected entities score, but the per-entity boost (0.5^i decay, cap 0.4) saturates fast, so heavily-linked
122
+ chunks can outrank the exact chunk even at `vector_similarity` 0. This is a reversible harm mitigation, not
123
+ a validated graph improvement — the graph's role (candidate generation vs. re-ranking, explicit mode) is
124
+ still open and will be decided on a graph-required query suite. Note that `useGraph: true` does not add
125
+ candidates (it re-orders the vector/FTS pool): for relationship exploration use `openNodes` → `getNeighbors`.
126
+
127
+ **Fleet rollout.** No migration, no schema change. Published first as `5.3.0-rc.1` on the `next`
128
+ dist-tag (omitting an argument changes behavior, so it got a canary before `latest`): the published rc was
129
+ installed fresh and run against a real project database — default call had no `graph_boost` and matched
130
+ explicit `useGraph:false`, the known-item probe returned the right chunk at rank 1, opt-in `true` kept
131
+ `graph_boost`, schema/MCP defaults read `false` — then `5.3.0` was published to `latest`. A caller that
132
+ relied on graph re-ranking by default must now pass `useGraph: true`. Callers that already choose per query
133
+ see no change. Regression lock: `test/search-graph-default.test.mjs`.
134
+
111
135
  ## v5.1.0 (schema v14, unchanged): destructive-replace reporting + `excludePattern`
112
136
 
113
137
  **What changes on upgrade**: nothing you have to do. No migration, no schema
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rag-memory-epf-mcp",
3
- "version": "5.2.0",
3
+ "version": "5.3.0",
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/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",
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",
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
  },
@@ -1,10 +0,0 @@
1
- import type { Tiktoken } from 'tiktoken';
2
- export interface ChunkSegment {
3
- text: string;
4
- start_pos: number | null;
5
- end_pos: number | null;
6
- start_token: number;
7
- end_token: number;
8
- }
9
- export declare function trimIncompleteUtf8(bytes: Uint8Array, trimHead: boolean, trimTail: boolean): Uint8Array;
10
- export declare function chunkText(text: string, encoding: Tiktoken, maxTokens?: number, overlap?: number): ChunkSegment[];
@@ -1,104 +0,0 @@
1
- // Tokenize and chunk text using a BPE encoder while reporting both token-space
2
- // and char-space (Unicode codepoint) offsets back into the original string.
3
- //
4
- // BPE tokenizers (cl100k_base) split multi-byte UTF-8 sequences across tokens.
5
- // Slicing token arrays at arbitrary boundaries can leave incomplete UTF-8
6
- // prefix/suffix bytes, which TextDecoder replaces with U+FFFD (�). We trim the
7
- // incomplete sequences at chunk boundaries; overlap covers the removed bytes.
8
- //
9
- // Each chunk records both token-space offsets (start_token/end_token from the
10
- // BPE encoder loop) and char-space offsets (start_pos/end_pos into the original
11
- // text). Char offsets are Unicode codepoint counts — language-neutral, so SQL
12
- // substr, Python str slicing, and JS [...str] iteration all line up. JS's
13
- // native UTF-16 indexing differs for supplementary characters (emoji, rare CJK),
14
- // so the function maintains parallel UTF-16 and codepoint cursors and reports
15
- // codepoint offsets. On a coincidental indexOf miss the char offsets are NULL.
16
- //
17
- // Extracted to a standalone module so publish-time invariant tests can exercise
18
- // the algorithm directly without booting the full RAG-Memory stack.
19
- // trimIncompleteUtf8: strip incomplete UTF-8 sequences from the head/tail of a
20
- // byte buffer produced by decoding an arbitrary token slice. A multi-byte
21
- // codepoint that begins or ends on the cut edge belongs to an adjacent chunk
22
- // and must be removed so TextDecoder does not emit U+FFFD. Pass
23
- // trimHead/trimTail=false to preserve head/tail bytes (first/last chunks).
24
- export function trimIncompleteUtf8(bytes, trimHead, trimTail) {
25
- let start = 0;
26
- let end = bytes.length;
27
- if (trimHead) {
28
- while (start < end && (bytes[start] & 0xC0) === 0x80)
29
- start++;
30
- }
31
- if (trimTail) {
32
- let i = end - 1;
33
- while (i >= start && (bytes[i] & 0xC0) === 0x80)
34
- i--;
35
- if (i >= start) {
36
- const lead = bytes[i];
37
- let needed = 1;
38
- if ((lead & 0x80) === 0)
39
- needed = 1;
40
- else if ((lead & 0xE0) === 0xC0)
41
- needed = 2;
42
- else if ((lead & 0xF0) === 0xE0)
43
- needed = 3;
44
- else if ((lead & 0xF8) === 0xF0)
45
- needed = 4;
46
- if (end - i < needed)
47
- end = i;
48
- }
49
- }
50
- return bytes.subarray(start, end);
51
- }
52
- export function chunkText(text, encoding, maxTokens = 800, overlap = 160) {
53
- const tokens = encoding.encode(text);
54
- const segments = [];
55
- let utf16Cursor = 0;
56
- let cpCursor = 0;
57
- for (let i = 0; i < tokens.length; i += maxTokens - overlap) {
58
- const chunkTokens = tokens.slice(i, i + maxTokens);
59
- const decodedBytes = encoding.decode(chunkTokens);
60
- const isFirst = i === 0;
61
- const isLast = i + chunkTokens.length >= tokens.length;
62
- const safeBytes = trimIncompleteUtf8(decodedBytes, !isFirst, !isLast);
63
- const chunkTextStr = new TextDecoder('utf-8').decode(safeBytes);
64
- let startPos;
65
- let endPos;
66
- if (isFirst) {
67
- startPos = 0;
68
- endPos = [...chunkTextStr].length;
69
- utf16Cursor = 0;
70
- cpCursor = 0;
71
- }
72
- else if (chunkTextStr.length === 0) {
73
- startPos = null;
74
- endPos = null;
75
- }
76
- else {
77
- const utfIdx = text.indexOf(chunkTextStr, utf16Cursor);
78
- if (utfIdx >= 0) {
79
- // Advance cpCursor by codepoints between the previous cursor and the
80
- // new chunk's start (handles overlap by anchoring at the previous
81
- // chunk's start, not its end).
82
- if (utfIdx > utf16Cursor) {
83
- cpCursor += [...text.slice(utf16Cursor, utfIdx)].length;
84
- utf16Cursor = utfIdx;
85
- }
86
- const cpLen = [...chunkTextStr].length;
87
- startPos = cpCursor;
88
- endPos = cpCursor + cpLen;
89
- }
90
- else {
91
- startPos = null;
92
- endPos = null;
93
- }
94
- }
95
- segments.push({
96
- text: chunkTextStr,
97
- start_pos: startPos,
98
- end_pos: endPos,
99
- start_token: i,
100
- end_token: i + chunkTokens.length
101
- });
102
- }
103
- return segments;
104
- }