md-semantic-search 1.0.3 → 2.0.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.
Files changed (75) hide show
  1. package/README.md +132 -16
  2. package/RESEARCH.md +207 -3
  3. package/bin/cli.mjs +449 -13
  4. package/dist/binary-format.js +9 -0
  5. package/dist/completions.js +11 -1
  6. package/dist/core.d.ts +42 -4
  7. package/dist/core.js +208 -14
  8. package/dist/federation.d.ts +16 -1
  9. package/dist/federation.js +33 -16
  10. package/dist/filter.d.ts +7 -0
  11. package/dist/filter.js +13 -0
  12. package/dist/hnsw.d.ts +96 -0
  13. package/dist/hnsw.js +326 -0
  14. package/dist/index-format.js +8 -1
  15. package/dist/index.d.ts +4 -4
  16. package/dist/index.js +4 -4
  17. package/dist/indexer.d.ts +18 -1
  18. package/dist/indexer.js +92 -18
  19. package/dist/ivf.d.ts +14 -7
  20. package/dist/ivf.js +126 -18
  21. package/dist/lexical.d.ts +10 -0
  22. package/dist/lexical.js +98 -15
  23. package/dist/markdown-parser.d.ts +62 -0
  24. package/dist/markdown-parser.js +227 -30
  25. package/dist/mcp.d.ts +229 -7
  26. package/dist/mcp.js +743 -44
  27. package/dist/models.d.ts +8 -2
  28. package/dist/models.js +84 -32
  29. package/dist/providers.d.ts +56 -0
  30. package/dist/providers.js +305 -0
  31. package/dist/quantization.d.ts +12 -0
  32. package/dist/quantization.js +54 -0
  33. package/dist/rag.d.ts +132 -7
  34. package/dist/rag.js +314 -38
  35. package/dist/repair.d.ts +1 -1
  36. package/dist/repair.js +53 -2
  37. package/dist/rerank.d.ts +51 -6
  38. package/dist/rerank.js +69 -10
  39. package/dist/search.d.ts +49 -3
  40. package/dist/search.js +387 -78
  41. package/dist/serve.d.ts +35 -0
  42. package/dist/serve.js +574 -58
  43. package/dist/tui.d.ts +14 -0
  44. package/dist/tui.js +132 -15
  45. package/dist/watcher.js +23 -12
  46. package/dist/webui.d.ts +3 -3
  47. package/dist/webui.js +225 -5
  48. package/dist/wikilinks.d.ts +31 -0
  49. package/dist/wikilinks.js +185 -1
  50. package/package.json +3 -3
  51. package/src/binary-format.ts +13 -0
  52. package/src/completions.ts +11 -1
  53. package/src/core.ts +249 -13
  54. package/src/federation.ts +51 -17
  55. package/src/filter.ts +14 -0
  56. package/src/hnsw.ts +414 -0
  57. package/src/index-format.ts +7 -1
  58. package/src/index.ts +7 -2
  59. package/src/indexer.ts +112 -19
  60. package/src/ivf.ts +157 -20
  61. package/src/lexical.ts +91 -13
  62. package/src/markdown-parser.ts +279 -37
  63. package/src/mcp.ts +820 -47
  64. package/src/models.ts +97 -35
  65. package/src/providers.ts +345 -0
  66. package/src/quantization.ts +61 -0
  67. package/src/rag.ts +451 -34
  68. package/src/repair.ts +51 -2
  69. package/src/rerank.ts +121 -14
  70. package/src/search.ts +424 -82
  71. package/src/serve.ts +589 -66
  72. package/src/tui.ts +142 -16
  73. package/src/watcher.ts +21 -10
  74. package/src/webui.ts +225 -5
  75. package/src/wikilinks.ts +220 -1
package/src/hnsw.ts ADDED
@@ -0,0 +1,414 @@
1
+ /**
2
+ * Pure JS/TS HNSW approximate nearest neighbor index (issue #201).
3
+ *
4
+ * Hierarchical Navigable Small World graphs (Malkov & Yashunin 2016): no
5
+ * training phase, incremental inserts, ~99% recall with sublinear search —
6
+ * the counterpart of the k-means IVF in `ivf.ts`, sharing its contract:
7
+ * build → serialize → persist → search as a candidate pre-filter in front
8
+ * of the exact sweep. Cosine distance (1 − dot on normalized vectors) is
9
+ * computed through an injected distance function so int8 zero-copy vectors
10
+ * work unchanged. Deleted nodes are tombstones — searches skip them, which
11
+ * is what lets the incremental watch rebuild reuse a persisted graph.
12
+ */
13
+
14
+ /** Deterministic PRNG for level assignment (same family as ivf.ts). */
15
+ function mulberry32(seed: number): () => number {
16
+ let s = seed >>> 0;
17
+ return () => {
18
+ s = (s + 0x6d2b79f5) >>> 0;
19
+ let t = Math.imul(s ^ (s >>> 15), 1 | s);
20
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
21
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
22
+ };
23
+ }
24
+
25
+ export interface HnswIndex {
26
+ dim: number;
27
+ /** Max links per node on layers > 0. */
28
+ M: number;
29
+ /** Random level seed — rebuilds from the same insertion order are identical. */
30
+ seed: number;
31
+ /** Node index of the graph entry (top layer). */
32
+ entryPoint: number;
33
+ /** Highest layer in the graph. */
34
+ maxLevel: number;
35
+ /** levels[i] = top layer of node i. */
36
+ levels: number[];
37
+ /** neighbors[i][l] = link list of node i on layer l (l ≤ levels[i]). */
38
+ neighbors: number[][][];
39
+ /** Tombstoned node indices — skipped by search, kept for graph connectivity. */
40
+ deleted: Set<number>;
41
+ }
42
+
43
+ export interface SerializedHnsw {
44
+ format: 'hnsw-v1';
45
+ dim: number;
46
+ M: number;
47
+ seed: number;
48
+ entryPoint: number;
49
+ maxLevel: number;
50
+ count: number;
51
+ levels: number[];
52
+ neighbors: number[][][];
53
+ deleted: number[];
54
+ }
55
+
56
+ export interface BuildHnswOptions {
57
+ M?: number;
58
+ seed?: number;
59
+ /**
60
+ * Distance between the query/node vector at `a` and the stored node `b` —
61
+ * LOWER is closer (cosine distance: `1 − dot(a, vectors[b])`). Injected so
62
+ * int8 zero-copy and norm-cached sweeps work unchanged.
63
+ */
64
+ distance: (a: Float32Array | Int8Array | number[], b: number) => number;
65
+ vectors: Array<Float32Array | Int8Array | number[]>;
66
+ }
67
+
68
+ export const DEFAULT_HNSW_M = 16;
69
+ export const DEFAULT_HNSW_EF = 64;
70
+
71
+ export function buildHnsw(
72
+ vectors: Array<Float32Array | Int8Array | number[]>,
73
+ options: BuildHnswOptions
74
+ ): HnswIndex {
75
+ const hnsw: HnswIndex = {
76
+ dim: vectors[0]?.length ?? 0,
77
+ M: options.M ?? DEFAULT_HNSW_M,
78
+ seed: options.seed ?? 42,
79
+ entryPoint: -1,
80
+ maxLevel: -1,
81
+ levels: [],
82
+ neighbors: [],
83
+ deleted: new Set(),
84
+ };
85
+ for (let i = 0; i < vectors.length; i++) insertHnsw(hnsw, i, options.distance, options.vectors);
86
+ return hnsw;
87
+ }
88
+
89
+ /**
90
+ * Insert node `idx` (vector index) into the graph. Incremental — new indices
91
+ * attach to the existing structure without a rebuild; vectors for nodes NOT
92
+ * yet inserted are read from `vectors` when needed, so the caller can insert
93
+ * lazily. Tombstoned indices can be re-inserted (undeletes).
94
+ */
95
+ export function insertHnsw(
96
+ hnsw: HnswIndex,
97
+ idx: number,
98
+ distance: (a: Float32Array | Int8Array | number[], b: number) => number,
99
+ vectors: Array<Float32Array | Int8Array | number[]>
100
+ ): void {
101
+ const vec = vectors[idx];
102
+ if (!vec) throw new Error(`hnsw insert: no vector at index ${idx}`);
103
+ if (hnsw.levels[idx] !== undefined && !hnsw.deleted.has(idx)) {
104
+ throw new Error(`hnsw insert: node ${idx} already present`);
105
+ }
106
+ hnsw.deleted.delete(idx);
107
+
108
+ const M = hnsw.M;
109
+ const M0 = M * 2;
110
+ const mL = 1 / Math.log(M);
111
+ const rng = mulberry32(hnsw.seed + idx * 2654435761);
112
+
113
+ const u = Math.max(rng(), Number.MIN_VALUE);
114
+ const level = Math.min(Math.floor(-Math.log(u) * mL), 64);
115
+ hnsw.levels[idx] = level;
116
+ hnsw.neighbors[idx] = Array.from({ length: level + 1 }, () => []);
117
+
118
+ if (hnsw.entryPoint < 0) {
119
+ hnsw.entryPoint = idx;
120
+ hnsw.maxLevel = level;
121
+ return;
122
+ }
123
+
124
+ const qDist = (node: number) => distance(vec, node);
125
+
126
+ // 1. Greedy descent through layers above the node's level
127
+ let ep = hnsw.entryPoint;
128
+ let epDist = qDist(ep);
129
+ for (let l = hnsw.maxLevel; l > level; l--) {
130
+ let changed = true;
131
+ while (changed) {
132
+ changed = false;
133
+ for (const nb of hnsw.neighbors[ep][l] ?? []) {
134
+ const d = qDist(nb);
135
+ if (d < epDist) {
136
+ epDist = d;
137
+ ep = nb;
138
+ changed = true;
139
+ }
140
+ }
141
+ }
142
+ }
143
+
144
+ // 2. Insert on each layer from min(level, maxLevel) down to 0
145
+ const pairwise = (a: number, b: number) => distance(vectors[a], b);
146
+ for (let l = Math.min(level, hnsw.maxLevel); l >= 0; l--) {
147
+ const efC = Math.max(M0, DEFAULT_HNSW_EF);
148
+ const candidates = searchLayer(hnsw, qDist, ep, epDist, l, efC);
149
+ const cap = l === 0 ? M0 : M;
150
+ // Algorithm 4: diversity-aware neighbor selection keeps the graph
151
+ // navigable — a candidate is skipped only when it is CLOSER to an
152
+ // already-selected neighbor than to the query
153
+ const selected = selectNeighborsHeuristic(candidates, cap, pairwise);
154
+ hnsw.neighbors[idx][l] = selected.map((c) => c.node);
155
+ for (const { node } of selected) {
156
+ const links = hnsw.neighbors[node][l];
157
+ if (!links.includes(idx)) links.push(idx);
158
+ if (links.length > cap) {
159
+ // re-select the neighbor's own links with the same heuristic — the
160
+ // diversity rule preserves connectivity instead of blind top-M,
161
+ // which used to sever the just-added edge and strand whole regions
162
+ const pruned = selectNeighborsHeuristic(
163
+ links.map((n) => ({ node: n, dist: pairwise(node, n) })),
164
+ cap,
165
+ pairwise,
166
+ );
167
+ hnsw.neighbors[node][l] = pruned.map((c) => c.node);
168
+ }
169
+ }
170
+ if (candidates.length > 0) {
171
+ ep = candidates[0].node;
172
+ epDist = candidates[0].dist;
173
+ }
174
+ }
175
+
176
+ if (level > hnsw.maxLevel) {
177
+ hnsw.maxLevel = level;
178
+ hnsw.entryPoint = idx;
179
+ }
180
+ }
181
+
182
+ /** Tombstone a node — searches skip it; the graph keeps it for connectivity. */
183
+ export function deleteHnsw(hnsw: HnswIndex, idx: number): void {
184
+ if (hnsw.levels[idx] !== undefined) hnsw.deleted.add(idx);
185
+ }
186
+
187
+ /** ef-search one layer, returning the closest `ef` live candidates (closest first). */
188
+ function searchLayer(
189
+ hnsw: HnswIndex,
190
+ qDist: (node: number) => number,
191
+ ep: number,
192
+ epDist: number,
193
+ layer: number,
194
+ ef: number
195
+ ): Array<{ node: number; dist: number }> {
196
+ const visited = new Set<number>([ep]);
197
+ // max-heap by dist via simple sorted insertion (candidate lists are small)
198
+ const candidates: Array<{ node: number; dist: number }> = [{ node: ep, dist: epDist }];
199
+ const results: Array<{ node: number; dist: number }> =
200
+ hnsw.deleted.has(ep) ? [] : [{ node: ep, dist: epDist }];
201
+
202
+ while (candidates.length > 0) {
203
+ const current = candidates.shift()!;
204
+ const worst = results.length >= ef ? results[results.length - 1].dist : Infinity;
205
+ if (current.dist > worst) break;
206
+
207
+ for (const nb of hnsw.neighbors[current.node][layer] ?? []) {
208
+ if (visited.has(nb)) continue;
209
+ visited.add(nb);
210
+ const d = qDist(nb);
211
+ const worstNow = results.length >= ef ? results[results.length - 1].dist : Infinity;
212
+ if (d < worstNow || results.length < ef) {
213
+ candidates.push({ node: nb, dist: d });
214
+ candidates.sort((a, b) => a.dist - b.dist);
215
+ if (!hnsw.deleted.has(nb)) {
216
+ results.push({ node: nb, dist: d });
217
+ results.sort((a, b) => a.dist - b.dist);
218
+ if (results.length > ef) results.pop();
219
+ }
220
+ }
221
+ }
222
+ }
223
+ return results;
224
+ }
225
+
226
+ /**
227
+ * Algorithm 4 of the HNSW paper (Malkov & Yashunin 2016): candidates sorted
228
+ * by distance to the query enter the result in order; a candidate is skipped
229
+ * when it is closer to some already-selected neighbor than to the query
230
+ * (redundant direction). `pairwise(a, b)` measures node-to-node distance.
231
+ * If fewer than `m` survive (disconnected candidate set), the closest
232
+ * leftovers backfill so the link count stays useful.
233
+ */
234
+ export function selectNeighborsHeuristic(
235
+ candidates: Array<{ node: number; dist: number }>,
236
+ m: number,
237
+ pairwise: (a: number, b: number) => number
238
+ ): Array<{ node: number; dist: number }> {
239
+ const sorted = [...candidates].sort((a, b) => a.dist - b.dist);
240
+ const result: Array<{ node: number; dist: number }> = [];
241
+ const skipped: Array<{ node: number; dist: number }> = [];
242
+ for (const cand of sorted) {
243
+ if (result.length >= m) break;
244
+ let dominated = false;
245
+ for (const chosen of result) {
246
+ if (pairwise(cand.node, chosen.node) < cand.dist) {
247
+ dominated = true;
248
+ break;
249
+ }
250
+ }
251
+ if (dominated) skipped.push(cand);
252
+ else result.push(cand);
253
+ }
254
+ for (const cand of skipped) {
255
+ if (result.length >= m) break;
256
+ result.push(cand);
257
+ }
258
+ return result;
259
+ }
260
+
261
+ /**
262
+ * ANN candidate pre-filter: ef-search from the entry point, returning LIVE
263
+ * candidate chunk indices (tombstones excluded). Mirrors
264
+ * `searchIVFCandidates` — the result feeds the same probed-candidate set.
265
+ */
266
+ export function searchHnswCandidates(
267
+ queryVec: Float32Array | Int8Array | number[],
268
+ hnsw: HnswIndex,
269
+ vectors: Array<Float32Array | Int8Array | number[]>,
270
+ options: {
271
+ ef?: number;
272
+ /** Lower-is-closer distance; defaults to cosine distance via `cosine`. */
273
+ distance?: (a: Float32Array | Int8Array | number[], b: number) => number;
274
+ } = {}
275
+ ): number[] {
276
+ if (hnsw.entryPoint < 0 || hnsw.levels.length === 0) return [];
277
+ const distance =
278
+ options.distance ??
279
+ ((a: Float32Array | Int8Array | number[], b: number) => 1 - dotSimilarity(a, vectors[b]));
280
+ const qDist = (node: number) => distance(queryVec, node);
281
+
282
+ let ep = hnsw.entryPoint;
283
+ let epDist = qDist(ep);
284
+ for (let l = hnsw.maxLevel; l > 0; l--) {
285
+ let changed = true;
286
+ while (changed) {
287
+ changed = false;
288
+ for (const nb of hnsw.neighbors[ep][l] ?? []) {
289
+ const d = qDist(nb);
290
+ if (d < epDist) {
291
+ epDist = d;
292
+ ep = nb;
293
+ changed = true;
294
+ }
295
+ }
296
+ }
297
+ }
298
+
299
+ const ef = Math.max(options.ef ?? DEFAULT_HNSW_EF, 1);
300
+ const found = searchLayer(hnsw, qDist, ep, epDist, 0, ef);
301
+ return found.map((f) => f.node);
302
+ }
303
+
304
+ /** Dot product that tolerates int8 storage (norm handling mirrors the exact sweep). */
305
+ function dotSimilarity(a: Float32Array | Int8Array | number[], b: Float32Array | Int8Array | number[]): number {
306
+ let s = 0;
307
+ const n = Math.min(a.length, b.length);
308
+ for (let i = 0; i < n; i++) s += a[i] * (b as ArrayLike<number>)[i];
309
+ return s;
310
+ }
311
+
312
+ export function serializeHnsw(hnsw: HnswIndex): SerializedHnsw {
313
+ return {
314
+ format: 'hnsw-v1',
315
+ dim: hnsw.dim,
316
+ M: hnsw.M,
317
+ seed: hnsw.seed,
318
+ entryPoint: hnsw.entryPoint,
319
+ maxLevel: hnsw.maxLevel,
320
+ count: hnsw.levels.length,
321
+ levels: hnsw.levels,
322
+ neighbors: hnsw.neighbors,
323
+ deleted: [...hnsw.deleted].sort((a, b) => a - b),
324
+ };
325
+ }
326
+
327
+ /**
328
+ * Hardened deserialization — every structural invariant is checked with the
329
+ * same discipline as `deserializeIVF`; any violation throws (the loader
330
+ * falls back to brute force).
331
+ */
332
+ export function deserializeHnsw(raw: unknown, expectedChunksCount?: number): HnswIndex {
333
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
334
+ throw new Error('invalid HNSW payload: expected JSON object — run `mdss index` to rebuild');
335
+ }
336
+ const obj = raw as Record<string, unknown>;
337
+ if (obj.format !== 'hnsw-v1') {
338
+ throw new Error(`invalid HNSW payload: format must be "hnsw-v1", got ${JSON.stringify(obj.format)} — run \`mdss index\` to rebuild`);
339
+ }
340
+ const { dim, M, seed, entryPoint, maxLevel, count } = obj as Record<string, unknown>;
341
+ if (
342
+ typeof dim !== 'number' || !Number.isSafeInteger(dim) || dim <= 0 ||
343
+ typeof M !== 'number' || !Number.isSafeInteger(M) || M <= 0 || M > 128 ||
344
+ typeof seed !== 'number' || !Number.isSafeInteger(seed) ||
345
+ typeof entryPoint !== 'number' || !Number.isSafeInteger(entryPoint) || entryPoint < 0 ||
346
+ typeof maxLevel !== 'number' || !Number.isSafeInteger(maxLevel) || maxLevel < 0 ||
347
+ typeof count !== 'number' || !Number.isSafeInteger(count) || count <= 0
348
+ ) {
349
+ throw new Error('invalid HNSW payload: invalid header — run `mdss index` to rebuild');
350
+ }
351
+ if (!Array.isArray(obj.levels) || obj.levels.length !== count) {
352
+ throw new Error(`invalid HNSW payload: levels must be an array of ${count} — run \`mdss index\` to rebuild`);
353
+ }
354
+ const levels: number[] = [];
355
+ for (let i = 0; i < count; i++) {
356
+ const l = (obj.levels as unknown[])[i];
357
+ if (typeof l !== 'number' || !Number.isSafeInteger(l) || l < 0 || l > maxLevel) {
358
+ throw new Error(`invalid HNSW payload: level of node ${i} must be an integer in [0, ${maxLevel}] — run \`mdss index\` to rebuild`);
359
+ }
360
+ levels.push(l);
361
+ }
362
+ if (levels[entryPoint] !== maxLevel) {
363
+ throw new Error(`invalid HNSW payload: entry point ${entryPoint} must sit on the top layer ${maxLevel} — run \`mdss index\` to rebuild`);
364
+ }
365
+ if (!Array.isArray(obj.neighbors) || obj.neighbors.length !== count) {
366
+ throw new Error(`invalid HNSW payload: neighbors must be an array of ${count} — run \`mdss index\` to rebuild`);
367
+ }
368
+ const neighbors: number[][][] = new Array(count);
369
+ const m0 = M * 2;
370
+ for (let i = 0; i < count; i++) {
371
+ const perLevel = (obj.neighbors as unknown[])[i];
372
+ if (!Array.isArray(perLevel) || perLevel.length !== levels[i] + 1) {
373
+ throw new Error(`invalid HNSW payload: node ${i} must have ${levels[i] + 1} link lists — run \`mdss index\` to rebuild`);
374
+ }
375
+ const node: number[][] = new Array(levels[i] + 1);
376
+ for (let l = 0; l <= levels[i]; l++) {
377
+ const list = perLevel[l];
378
+ if (!Array.isArray(list)) {
379
+ throw new Error(`invalid HNSW payload: node ${i} layer ${l} must be an array — run \`mdss index\` to rebuild`);
380
+ }
381
+ if (list.length > (l === 0 ? m0 : M)) {
382
+ throw new Error(`invalid HNSW payload: node ${i} layer ${l} exceeds the link cap — run \`mdss index\` to rebuild`);
383
+ }
384
+ const seen = new Set<number>();
385
+ for (const nb of list) {
386
+ if (typeof nb !== 'number' || !Number.isSafeInteger(nb) || nb < 0 || nb >= count || nb === i) {
387
+ throw new Error(`invalid HNSW payload: node ${i} layer ${l} has invalid link ${JSON.stringify(nb)} — run \`mdss index\` to rebuild`);
388
+ }
389
+ if (seen.has(nb)) {
390
+ throw new Error(`invalid HNSW payload: duplicate link ${nb} on node ${i} layer ${l} — run \`mdss index\` to rebuild`);
391
+ }
392
+ seen.add(nb);
393
+ }
394
+ node[l] = list as number[];
395
+ }
396
+ neighbors[i] = node;
397
+ }
398
+ let deleted = new Set<number>();
399
+ if (obj.deleted !== undefined) {
400
+ if (!Array.isArray(obj.deleted)) {
401
+ throw new Error('invalid HNSW payload: deleted must be an array — run `mdss index` to rebuild');
402
+ }
403
+ for (const idx of obj.deleted) {
404
+ if (typeof idx !== 'number' || !Number.isSafeInteger(idx) || idx < 0 || idx >= count) {
405
+ throw new Error(`invalid HNSW payload: deleted index ${JSON.stringify(idx)} out of range — run \`mdss index\` to rebuild`);
406
+ }
407
+ deleted.add(idx);
408
+ }
409
+ }
410
+ if (expectedChunksCount !== undefined && count > expectedChunksCount) {
411
+ throw new Error(`invalid HNSW payload: ${count} nodes exceed ${expectedChunksCount} chunks — run \`mdss index\` to rebuild`);
412
+ }
413
+ return { dim, M, seed, entryPoint, maxLevel, levels, neighbors, deleted };
414
+ }
@@ -114,7 +114,13 @@ export function validateIndexEnvelope(
114
114
  } else if (modelName !== undefined && modelName !== null && typeof modelName !== 'string') {
115
115
  throw new Error(`${location} model metadata must be a string${REBUILD}`);
116
116
  }
117
- const model = resolveModel(modelName as string | undefined);
117
+ let model: ModelAdapter;
118
+ try {
119
+ const candidateDim = typeof index.dim === 'number' && index.dim > 0 ? index.dim : undefined;
120
+ model = resolveModel(modelName as string | undefined, candidateDim);
121
+ } catch {
122
+ model = resolveModel(modelName as string | undefined);
123
+ }
118
124
  const dim = resolveIndexDimension(index.dim, model.dim);
119
125
  if (schema < 3) {
120
126
  return { index, schema, model, dim };
package/src/index.ts CHANGED
@@ -37,14 +37,16 @@ export {
37
37
  } from './rerank.js';
38
38
 
39
39
  export {
40
- embed, getExtractor, cosine,
40
+ embed, getExtractor, cosine, truncateAndNormalizeVector,
41
41
  walkMarkdown, parseFile, chunkMarkdown,
42
42
  splitFrontmatter, extractTitle, globToRegExp,
43
43
  encodeVec, decodeVec, isBinaryIndex,
44
+ getDocLines, GetDocLinesOptions, GetDocLinesResult,
44
45
  } from './core.js';
45
46
 
46
47
  export {
47
- MCP_TOOLS, handleMcpRequest, startMcpServer,
48
+ MCP_TOOLS, MCP_RESOURCE_TEMPLATES, MCP_PROMPTS,
49
+ handleMcpRequest, startMcpServer,
48
50
  } from './mcp.js';
49
51
 
50
52
  export {
@@ -55,6 +57,7 @@ export {
55
57
  export {
56
58
  extractLinks, resolveLinks, buildRelationshipGraph, getRelatedNotes,
57
59
  computePageRank, expandGraphNeighborhood,
60
+ findRelatedNotes, FindRelatedNotesOptions, RelatedNoteHit, RelatedNoteResult,
58
61
  } from './wikilinks.js';
59
62
 
60
63
  export {
@@ -81,7 +84,9 @@ export {
81
84
 
82
85
  export {
83
86
  askQuestion, synthesizeAnswer, extractAnswerFallback,
87
+ chatTurn, loadChatSession, saveChatSession, listChatSessions, getSessionsDir,
84
88
  AskOptions, SynthesisResult, Citation, DEFAULT_RAG_SYSTEM_PROMPT,
89
+ ChatSession, ChatTurn, ChatSourceManifestItem, ChatTurnOptions,
85
90
  } from './rag.js';
86
91
 
87
92
  export {