@yugenlab/vaayu 0.1.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 (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +365 -0
  3. package/chunks/chunk-E5A3SCDJ.js +246 -0
  4. package/chunks/chunk-G5VYCA6O.js +69 -0
  5. package/chunks/chunk-H76V36OF.js +1029 -0
  6. package/chunks/chunk-HAPVUJ6A.js +238 -0
  7. package/chunks/chunk-IEKAYVA3.js +137 -0
  8. package/chunks/chunk-IGKYKEKT.js +43 -0
  9. package/chunks/chunk-IIET2K6D.js +7728 -0
  10. package/chunks/chunk-ITIVYGUG.js +347 -0
  11. package/chunks/chunk-JAWZ7ANC.js +208 -0
  12. package/chunks/chunk-JZU37VQ5.js +714 -0
  13. package/chunks/chunk-KC6NRZ7U.js +198 -0
  14. package/chunks/chunk-KDRROLVN.js +433 -0
  15. package/chunks/chunk-L7JICQBW.js +1006 -0
  16. package/chunks/chunk-MINFB5LT.js +1479 -0
  17. package/chunks/chunk-MJ74G5RB.js +5816 -0
  18. package/chunks/chunk-S4TBVCL2.js +2158 -0
  19. package/chunks/chunk-SMVJRPAH.js +2753 -0
  20. package/chunks/chunk-U6OLJ36B.js +438 -0
  21. package/chunks/chunk-URGEODS5.js +752 -0
  22. package/chunks/chunk-YSU3BWV6.js +123 -0
  23. package/chunks/consolidation-indexer-TOTTDZXW.js +21 -0
  24. package/chunks/day-consolidation-NKO63HZQ.js +24 -0
  25. package/chunks/graphrag-ZI2FSU7S.js +13 -0
  26. package/chunks/hierarchical-temporal-search-ZD46UMKR.js +8 -0
  27. package/chunks/hybrid-search-ZVLZVGFS.js +19 -0
  28. package/chunks/memory-store-KNJPMBLQ.js +17 -0
  29. package/chunks/periodic-consolidation-BPKOZDGB.js +10 -0
  30. package/chunks/postgres-3ZXBYTPC.js +8 -0
  31. package/chunks/recall-GMVHWQWW.js +20 -0
  32. package/chunks/search-7HZETVMZ.js +18 -0
  33. package/chunks/session-store-XKPGKXUS.js +44 -0
  34. package/chunks/sqlite-JPF5TICX.js +152 -0
  35. package/chunks/src-6GVZTUH6.js +12 -0
  36. package/chunks/src-QAXOD5SB.js +273 -0
  37. package/chunks/suncalc-NOHGYHDU.js +186 -0
  38. package/chunks/tree-RSHKDTCR.js +10 -0
  39. package/gateway.js +61944 -0
  40. package/package.json +51 -0
  41. package/pair-cli.js +133 -0
@@ -0,0 +1,2158 @@
1
+ import {
2
+ ALPHA,
3
+ BETA,
4
+ EmbeddingService,
5
+ GAMMA,
6
+ STOP_WORDS,
7
+ cosineSimilarity,
8
+ textMatchScore
9
+ } from "./chunk-JAWZ7ANC.js";
10
+ import {
11
+ DatabaseManager,
12
+ initGraphSchema
13
+ } from "./chunk-U6OLJ36B.js";
14
+ import {
15
+ getChitraguptaHome
16
+ } from "./chunk-KC6NRZ7U.js";
17
+
18
+ // ../chitragupta/packages/smriti/src/graphrag.ts
19
+ import fs from "fs";
20
+ import path from "path";
21
+
22
+ // ../chitragupta/packages/smriti/src/graphrag-pagerank.ts
23
+ var PAGERANK_DAMPING = 0.85;
24
+ var PAGERANK_EPSILON = 1e-4;
25
+ var PAGERANK_MAX_ITERATIONS = 100;
26
+ function computePageRank(graph) {
27
+ const N = graph.nodes.length;
28
+ if (N === 0) return /* @__PURE__ */ new Map();
29
+ const ranks = /* @__PURE__ */ new Map();
30
+ const nodeIds = /* @__PURE__ */ new Set();
31
+ for (const node of graph.nodes) {
32
+ ranks.set(node.id, 1 / N);
33
+ nodeIds.add(node.id);
34
+ }
35
+ const outDegree = /* @__PURE__ */ new Map();
36
+ const inLinks = /* @__PURE__ */ new Map();
37
+ for (const node of graph.nodes) {
38
+ outDegree.set(node.id, 0);
39
+ inLinks.set(node.id, []);
40
+ }
41
+ for (const edge of graph.edges) {
42
+ if (nodeIds.has(edge.source) && nodeIds.has(edge.target)) {
43
+ outDegree.set(edge.source, (outDegree.get(edge.source) ?? 0) + 1);
44
+ inLinks.get(edge.target).push(edge.source);
45
+ }
46
+ }
47
+ const baseFactor = (1 - PAGERANK_DAMPING) / N;
48
+ for (let iteration = 0; iteration < PAGERANK_MAX_ITERATIONS; iteration++) {
49
+ const newRanks = /* @__PURE__ */ new Map();
50
+ let maxDelta = 0;
51
+ let danglingRank = 0;
52
+ for (const node of graph.nodes) {
53
+ if ((outDegree.get(node.id) ?? 0) === 0) {
54
+ danglingRank += ranks.get(node.id) ?? 0;
55
+ }
56
+ }
57
+ for (const node of graph.nodes) {
58
+ let incomingRank = 0;
59
+ const sources = inLinks.get(node.id) ?? [];
60
+ for (const source of sources) {
61
+ const sourceRank = ranks.get(source) ?? 0;
62
+ const sourceOut = outDegree.get(source) ?? 1;
63
+ incomingRank += sourceRank / sourceOut;
64
+ }
65
+ const rank = baseFactor + PAGERANK_DAMPING * (incomingRank + danglingRank / N);
66
+ newRanks.set(node.id, rank);
67
+ const delta = Math.abs(rank - (ranks.get(node.id) ?? 0));
68
+ if (delta > maxDelta) maxDelta = delta;
69
+ }
70
+ for (const [id, rank] of newRanks) {
71
+ ranks.set(id, rank);
72
+ }
73
+ if (maxDelta < PAGERANK_EPSILON) break;
74
+ }
75
+ return ranks;
76
+ }
77
+
78
+ // ../chitragupta/packages/smriti/src/graphrag-pagerank-personalized.ts
79
+ function buildTopicBias(nodeIds, nodeContents, topic) {
80
+ const bias = /* @__PURE__ */ new Map();
81
+ const n = nodeIds.length;
82
+ if (!topic || n === 0) {
83
+ const uniform = 1 / Math.max(n, 1);
84
+ for (const id of nodeIds) bias.set(id, uniform);
85
+ return bias;
86
+ }
87
+ const queryTerms = tokenizeSimple(topic);
88
+ const queryTf = termFrequency(queryTerms);
89
+ let totalSim = 0;
90
+ const similarities = new Array(n);
91
+ for (let i = 0; i < n; i++) {
92
+ const content = nodeContents.get(nodeIds[i]) ?? "";
93
+ const docTerms = tokenizeSimple(content);
94
+ const docTf = termFrequency(docTerms);
95
+ const sim = tfCosineSimilarity(queryTf, docTf);
96
+ similarities[i] = sim;
97
+ totalSim += sim;
98
+ }
99
+ if (totalSim === 0) {
100
+ const uniform = 1 / n;
101
+ for (const id of nodeIds) bias.set(id, uniform);
102
+ return bias;
103
+ }
104
+ for (let i = 0; i < n; i++) {
105
+ bias.set(nodeIds[i], similarities[i] / totalSim);
106
+ }
107
+ return bias;
108
+ }
109
+ function tokenizeSimple(text) {
110
+ return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 2);
111
+ }
112
+ function termFrequency(tokens) {
113
+ const tf = /* @__PURE__ */ new Map();
114
+ for (const t of tokens) {
115
+ tf.set(t, (tf.get(t) ?? 0) + 1);
116
+ }
117
+ return tf;
118
+ }
119
+ function tfCosineSimilarity(a, b) {
120
+ let dot = 0;
121
+ let normA = 0;
122
+ let normB = 0;
123
+ for (const [term, freqA] of a) {
124
+ normA += freqA * freqA;
125
+ const freqB = b.get(term);
126
+ if (freqB !== void 0) dot += freqA * freqB;
127
+ }
128
+ for (const freqB of b.values()) {
129
+ normB += freqB * freqB;
130
+ }
131
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
132
+ return denom === 0 ? 0 : dot / denom;
133
+ }
134
+ function computePersonalizedPageRank(graph, topicBias, opts) {
135
+ const damping = opts?.damping ?? 0.85;
136
+ const epsilon = opts?.epsilon ?? 1e-6;
137
+ const maxIter = opts?.maxIterations ?? 150;
138
+ const useGS = opts?.useGaussSeidel ?? true;
139
+ const N = graph.nodes.length;
140
+ if (N === 0) return /* @__PURE__ */ new Map();
141
+ const nodeIds = graph.nodes.map((n) => n.id);
142
+ const nodeIdSet = new Set(nodeIds);
143
+ const outDegree = /* @__PURE__ */ new Map();
144
+ const inLinks = /* @__PURE__ */ new Map();
145
+ for (const id of nodeIds) {
146
+ outDegree.set(id, 0);
147
+ inLinks.set(id, []);
148
+ }
149
+ for (const edge of graph.edges) {
150
+ if (nodeIdSet.has(edge.source) && nodeIdSet.has(edge.target)) {
151
+ outDegree.set(edge.source, (outDegree.get(edge.source) ?? 0) + 1);
152
+ inLinks.get(edge.target).push(edge.source);
153
+ }
154
+ }
155
+ let bias;
156
+ if (typeof topicBias === "string") {
157
+ const nodeContents = /* @__PURE__ */ new Map();
158
+ for (const node of graph.nodes) {
159
+ nodeContents.set(node.id, node.content);
160
+ }
161
+ bias = buildTopicBias(nodeIds, nodeContents, topicBias);
162
+ } else if (topicBias instanceof Map) {
163
+ bias = topicBias;
164
+ } else {
165
+ const uniform = 1 / N;
166
+ bias = new Map(nodeIds.map((id) => [id, uniform]));
167
+ }
168
+ const ranks = /* @__PURE__ */ new Map();
169
+ for (const id of nodeIds) {
170
+ ranks.set(id, 1 / N);
171
+ }
172
+ const danglingNodes = [];
173
+ for (const id of nodeIds) {
174
+ if ((outDegree.get(id) ?? 0) === 0) danglingNodes.push(id);
175
+ }
176
+ for (let iter = 0; iter < maxIter; iter++) {
177
+ let danglingSum = 0;
178
+ for (const id of danglingNodes) {
179
+ danglingSum += ranks.get(id) ?? 0;
180
+ }
181
+ const danglingContrib = damping * danglingSum / N;
182
+ let maxDelta = 0;
183
+ if (useGS) {
184
+ for (const id of nodeIds) {
185
+ const biasVal = bias.get(id) ?? 1 / N;
186
+ let incomingSum = 0;
187
+ for (const src of inLinks.get(id) ?? []) {
188
+ const srcRank = ranks.get(src) ?? 0;
189
+ const srcOut = outDegree.get(src) ?? 1;
190
+ incomingSum += srcRank / srcOut;
191
+ }
192
+ const newRank = (1 - damping) * biasVal + damping * incomingSum + danglingContrib;
193
+ const oldRank = ranks.get(id) ?? 0;
194
+ const delta = Math.abs(newRank - oldRank);
195
+ if (delta > maxDelta) maxDelta = delta;
196
+ ranks.set(id, newRank);
197
+ }
198
+ } else {
199
+ const newRanks = /* @__PURE__ */ new Map();
200
+ for (const id of nodeIds) {
201
+ const biasVal = bias.get(id) ?? 1 / N;
202
+ let incomingSum = 0;
203
+ for (const src of inLinks.get(id) ?? []) {
204
+ const srcRank = ranks.get(src) ?? 0;
205
+ const srcOut = outDegree.get(src) ?? 1;
206
+ incomingSum += srcRank / srcOut;
207
+ }
208
+ const newRank = (1 - damping) * biasVal + damping * incomingSum + danglingContrib;
209
+ newRanks.set(id, newRank);
210
+ const delta = Math.abs(newRank - (ranks.get(id) ?? 0));
211
+ if (delta > maxDelta) maxDelta = delta;
212
+ }
213
+ for (const [id, rank] of newRanks) ranks.set(id, rank);
214
+ }
215
+ if (maxDelta < epsilon) break;
216
+ }
217
+ return ranks;
218
+ }
219
+ var IncrementalPageRank = class {
220
+ ranks = /* @__PURE__ */ new Map();
221
+ outDegree = /* @__PURE__ */ new Map();
222
+ inLinks = /* @__PURE__ */ new Map();
223
+ /** Forward adjacency list: source → Set<target>. Avoids O(E) reverse lookup in getOutNeighbors(). */
224
+ outLinks = /* @__PURE__ */ new Map();
225
+ nodeSet = /* @__PURE__ */ new Set();
226
+ damping;
227
+ epsilon;
228
+ constructor(damping = 0.85, epsilon = 1e-6) {
229
+ this.damping = damping;
230
+ this.epsilon = epsilon;
231
+ }
232
+ /**
233
+ * Initialize from a knowledge graph. Computes full PageRank once.
234
+ *
235
+ * @param graph - Initial knowledge graph.
236
+ */
237
+ initialize(graph) {
238
+ this.nodeSet.clear();
239
+ this.outDegree.clear();
240
+ this.inLinks.clear();
241
+ this.outLinks.clear();
242
+ for (const node of graph.nodes) {
243
+ this.nodeSet.add(node.id);
244
+ this.outDegree.set(node.id, 0);
245
+ this.inLinks.set(node.id, /* @__PURE__ */ new Set());
246
+ this.outLinks.set(node.id, /* @__PURE__ */ new Set());
247
+ }
248
+ for (const edge of graph.edges) {
249
+ if (this.nodeSet.has(edge.source) && this.nodeSet.has(edge.target)) {
250
+ this.outDegree.set(edge.source, (this.outDegree.get(edge.source) ?? 0) + 1);
251
+ this.inLinks.get(edge.target).add(edge.source);
252
+ this.outLinks.get(edge.source).add(edge.target);
253
+ }
254
+ }
255
+ this.ranks = computePersonalizedPageRank(graph, void 0, {
256
+ damping: this.damping,
257
+ epsilon: this.epsilon
258
+ });
259
+ }
260
+ /**
261
+ * Incrementally add an edge and update ranks via residual propagation.
262
+ *
263
+ * When edge (u -> v) is added:
264
+ * - Old contribution from u to each of its neighbors was PR(u)/L_old(u)
265
+ * - New contribution is PR(u)/L_new(u) where L_new = L_old + 1
266
+ * - Delta at v = +d * PR(u) / L_new(u)
267
+ * - Delta at each old neighbor w = -d * PR(u) * (1/L_new - 1/L_old)
268
+ *
269
+ * These deltas are injected as residuals and propagated.
270
+ *
271
+ * @param source - Source node ID.
272
+ * @param target - Target node ID.
273
+ */
274
+ addEdge(source, target) {
275
+ if (!this.nodeSet.has(source)) {
276
+ this.nodeSet.add(source);
277
+ this.outDegree.set(source, 0);
278
+ this.inLinks.set(source, /* @__PURE__ */ new Set());
279
+ this.outLinks.set(source, /* @__PURE__ */ new Set());
280
+ this.ranks.set(source, 1 / this.nodeSet.size);
281
+ }
282
+ if (!this.nodeSet.has(target)) {
283
+ this.nodeSet.add(target);
284
+ this.outDegree.set(target, 0);
285
+ this.inLinks.set(target, /* @__PURE__ */ new Set());
286
+ this.outLinks.set(target, /* @__PURE__ */ new Set());
287
+ this.ranks.set(target, 1 / this.nodeSet.size);
288
+ }
289
+ const oldDeg = this.outDegree.get(source) ?? 0;
290
+ const newDeg = oldDeg + 1;
291
+ this.outDegree.set(source, newDeg);
292
+ this.inLinks.get(target).add(source);
293
+ this.outLinks.get(source).add(target);
294
+ const prU = this.ranks.get(source) ?? 0;
295
+ const residuals = /* @__PURE__ */ new Map();
296
+ residuals.set(target, this.damping * prU / newDeg);
297
+ if (oldDeg > 0) {
298
+ const shareDelta = prU * (1 / newDeg - 1 / oldDeg);
299
+ for (const neighbor of this.getOutNeighbors(source)) {
300
+ if (neighbor === target) continue;
301
+ const prev = residuals.get(neighbor) ?? 0;
302
+ residuals.set(neighbor, prev + this.damping * shareDelta);
303
+ }
304
+ }
305
+ this.propagateResiduals(residuals);
306
+ }
307
+ /**
308
+ * Incrementally remove an edge and update ranks via residual propagation.
309
+ *
310
+ * @param source - Source node ID.
311
+ * @param target - Target node ID.
312
+ */
313
+ removeEdge(source, target) {
314
+ if (!this.nodeSet.has(source) || !this.nodeSet.has(target)) return;
315
+ const inSet = this.inLinks.get(target);
316
+ if (!inSet || !inSet.has(source)) return;
317
+ const oldDeg = this.outDegree.get(source) ?? 0;
318
+ if (oldDeg <= 0) return;
319
+ const newDeg = oldDeg - 1;
320
+ this.outDegree.set(source, newDeg);
321
+ inSet.delete(source);
322
+ this.outLinks.get(source)?.delete(target);
323
+ const prU = this.ranks.get(source) ?? 0;
324
+ const residuals = /* @__PURE__ */ new Map();
325
+ residuals.set(target, -this.damping * prU / oldDeg);
326
+ if (newDeg > 0) {
327
+ const shareDelta = prU * (1 / newDeg - 1 / oldDeg);
328
+ for (const neighbor of this.getOutNeighbors(source)) {
329
+ const prev = residuals.get(neighbor) ?? 0;
330
+ residuals.set(neighbor, prev + this.damping * shareDelta);
331
+ }
332
+ }
333
+ this.propagateResiduals(residuals);
334
+ }
335
+ /** Get current rank scores. */
336
+ getRanks() {
337
+ return new Map(this.ranks);
338
+ }
339
+ // ─── Private ──────────────────────────────────────────────────────
340
+ /**
341
+ * Propagate residuals through the graph until all are below epsilon.
342
+ *
343
+ * Push-forward scheme: the node with largest absolute residual pushes
344
+ * its residual to its rank and propagates damped fractions to neighbors.
345
+ *
346
+ * Convergence: guaranteed for damping < 1, with total work O(1/epsilon).
347
+ */
348
+ propagateResiduals(residuals) {
349
+ const maxPropagations = this.nodeSet.size * 20;
350
+ let propagations = 0;
351
+ while (propagations < maxPropagations) {
352
+ let maxNode = null;
353
+ let maxResidual = 0;
354
+ for (const [node, res2] of residuals) {
355
+ const absRes = Math.abs(res2);
356
+ if (absRes > maxResidual) {
357
+ maxResidual = absRes;
358
+ maxNode = node;
359
+ }
360
+ }
361
+ if (maxNode === null || maxResidual < this.epsilon) break;
362
+ const res = residuals.get(maxNode);
363
+ this.ranks.set(maxNode, (this.ranks.get(maxNode) ?? 0) + res);
364
+ residuals.delete(maxNode);
365
+ const outDeg = this.outDegree.get(maxNode) ?? 0;
366
+ if (outDeg > 0) {
367
+ const propagated = this.damping * res / outDeg;
368
+ for (const neighbor of this.getOutNeighbors(maxNode)) {
369
+ const prevRes = residuals.get(neighbor) ?? 0;
370
+ residuals.set(neighbor, prevRes + propagated);
371
+ }
372
+ }
373
+ propagations++;
374
+ }
375
+ }
376
+ /**
377
+ * Get outgoing neighbors of a node.
378
+ * Uses forward adjacency list for O(degree) lookup instead of O(E).
379
+ */
380
+ getOutNeighbors(nodeId) {
381
+ return [...this.outLinks.get(nodeId) ?? []];
382
+ }
383
+ };
384
+
385
+ // ../chitragupta/packages/smriti/src/graphrag-extraction.ts
386
+ var CHUNK_TARGET_TOKENS = 350;
387
+ var CHUNK_MIN_TOKENS = 200;
388
+ var CHUNK_MAX_TOKENS = 500;
389
+ var CHUNK_OVERLAP_TOKENS = 50;
390
+ function splitSentences(text) {
391
+ const raw = text.split(/(?<=[.!?])\s+(?=[A-Z])|(?<=[.!?])\s*$/);
392
+ return raw.map((s) => s.trim()).filter((s) => s.length > 0);
393
+ }
394
+ function estimateTokens(text) {
395
+ return text.split(/\s+/).filter((w) => w.length > 0).length;
396
+ }
397
+ function semanticChunk(text) {
398
+ const sentences = splitSentences(text);
399
+ if (sentences.length === 0) return [];
400
+ const totalTokens = estimateTokens(text);
401
+ if (totalTokens <= CHUNK_MAX_TOKENS) {
402
+ return [{ text, startSentence: 0, endSentence: sentences.length - 1 }];
403
+ }
404
+ const chunks = [];
405
+ let startIdx = 0;
406
+ while (startIdx < sentences.length) {
407
+ let currentTokens = 0;
408
+ let endIdx = startIdx;
409
+ while (endIdx < sentences.length && currentTokens < CHUNK_TARGET_TOKENS) {
410
+ currentTokens += estimateTokens(sentences[endIdx]);
411
+ endIdx++;
412
+ }
413
+ if (currentTokens < CHUNK_MIN_TOKENS && endIdx < sentences.length) {
414
+ while (endIdx < sentences.length && currentTokens < CHUNK_MIN_TOKENS) {
415
+ currentTokens += estimateTokens(sentences[endIdx]);
416
+ endIdx++;
417
+ }
418
+ }
419
+ const chunkText = sentences.slice(startIdx, endIdx).join(" ");
420
+ chunks.push({ text: chunkText, startSentence: startIdx, endSentence: endIdx - 1 });
421
+ let overlapTokens = 0;
422
+ let overlapStart = endIdx;
423
+ while (overlapStart > startIdx && overlapTokens < CHUNK_OVERLAP_TOKENS) {
424
+ overlapStart--;
425
+ overlapTokens += estimateTokens(sentences[overlapStart]);
426
+ }
427
+ startIdx = Math.max(overlapStart, startIdx + 1);
428
+ if (startIdx >= endIdx) break;
429
+ }
430
+ return chunks;
431
+ }
432
+ async function llmExtractEntities(text, endpoint, generationModel) {
433
+ const truncated = text.slice(0, 4e3);
434
+ const prompt = `Extract key entities, concepts, and topics from the following text. Return ONLY a valid JSON array of objects, each with "name" (string), "type" (one of: "person", "technology", "concept", "organization", "file", "tool", "decision", "topic"), and "description" (brief string). Do not include any text outside the JSON array.
435
+
436
+ Text:
437
+ ${truncated}
438
+
439
+ JSON:`;
440
+ const response = await fetch(`${endpoint}/api/generate`, {
441
+ method: "POST",
442
+ headers: { "Content-Type": "application/json" },
443
+ body: JSON.stringify({
444
+ model: generationModel,
445
+ prompt,
446
+ stream: false,
447
+ options: {
448
+ temperature: 0.1,
449
+ num_predict: 2048
450
+ }
451
+ }),
452
+ signal: AbortSignal.timeout(6e4)
453
+ });
454
+ if (!response.ok) {
455
+ throw new Error(`Ollama generation error: ${response.status}`);
456
+ }
457
+ const data = await response.json();
458
+ const responseText = data.response.trim();
459
+ const jsonMatch = responseText.match(/\[[\s\S]*\]/);
460
+ if (!jsonMatch) {
461
+ throw new Error("LLM response did not contain a JSON array");
462
+ }
463
+ const parsed = JSON.parse(jsonMatch[0]);
464
+ const entities = [];
465
+ for (const item of parsed) {
466
+ if (typeof item === "object" && item !== null && "name" in item && typeof item.name === "string") {
467
+ const obj = item;
468
+ entities.push({
469
+ name: String(obj.name).toLowerCase().trim(),
470
+ type: typeof obj.type === "string" ? obj.type : "concept",
471
+ description: typeof obj.description === "string" ? obj.description : ""
472
+ });
473
+ }
474
+ }
475
+ return entities;
476
+ }
477
+ function keywordExtractEntities(text) {
478
+ const words = text.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((w) => w.length > 4 && !STOP_WORDS.has(w));
479
+ const freq = /* @__PURE__ */ new Map();
480
+ for (const word of words) {
481
+ freq.set(word, (freq.get(word) ?? 0) + 1);
482
+ }
483
+ const entities = [];
484
+ const sorted = [...freq.entries()].sort((a, b) => b[1] - a[1]);
485
+ for (const [word, count] of sorted) {
486
+ if (count < 2) break;
487
+ if (entities.length >= 20) break;
488
+ entities.push({
489
+ name: word,
490
+ type: "concept",
491
+ description: `Keyword appearing ${count} times`
492
+ });
493
+ }
494
+ return entities;
495
+ }
496
+
497
+ // ../chitragupta/packages/smriti/src/ner-extractor.ts
498
+ var DEFAULT_ENDPOINT = "http://localhost:8501";
499
+ var DEFAULT_MODEL = "gliner-large-v2.1";
500
+ var DEFAULT_MIN_CONFIDENCE = 0.5;
501
+ var DEFAULT_MAX_ENTITIES = 50;
502
+ var GLINER_TIMEOUT_MS = 1e4;
503
+ var GLINER_PROBE_TIMEOUT_MS = 3e3;
504
+ var ALL_ENTITY_TYPES = [
505
+ "person",
506
+ "tool",
507
+ "file",
508
+ "concept",
509
+ "decision",
510
+ "error",
511
+ "technology",
512
+ "action",
513
+ "location",
514
+ "organization"
515
+ ];
516
+ var GLINER_LABEL_MAP = {
517
+ // Standard NER tags
518
+ per: "person",
519
+ person: "person",
520
+ org: "organization",
521
+ organization: "organization",
522
+ loc: "location",
523
+ location: "location",
524
+ gpe: "location",
525
+ // Our custom labels (sent as GLiNER request labels)
526
+ tool: "tool",
527
+ file: "file",
528
+ concept: "concept",
529
+ decision: "decision",
530
+ error: "error",
531
+ technology: "technology",
532
+ action: "action"
533
+ };
534
+ var HEURISTIC_CONFIDENCE = 0.6;
535
+ var FILE_PATTERN = /(?:^|[\s"'`(])([./][\w/.-]+\.\w{1,10})\b/g;
536
+ var TECHNOLOGY_WORDS = [
537
+ "angular",
538
+ "aws",
539
+ "azure",
540
+ "bash",
541
+ "bun",
542
+ "c\\+\\+",
543
+ "css",
544
+ "deno",
545
+ "docker",
546
+ "elasticsearch",
547
+ "express",
548
+ "fastify",
549
+ "firebase",
550
+ "git",
551
+ "go",
552
+ "golang",
553
+ "graphql",
554
+ "html",
555
+ "java",
556
+ "javascript",
557
+ "jest",
558
+ "kafka",
559
+ "kotlin",
560
+ "kubernetes",
561
+ "linux",
562
+ "mongodb",
563
+ "mysql",
564
+ "nest",
565
+ "next\\.js",
566
+ "nextjs",
567
+ "node",
568
+ "node\\.js",
569
+ "nodejs",
570
+ "npm",
571
+ "ollama",
572
+ "postgres",
573
+ "postgresql",
574
+ "prisma",
575
+ "python",
576
+ "react",
577
+ "redis",
578
+ "rust",
579
+ "sql",
580
+ "svelte",
581
+ "swift",
582
+ "terraform",
583
+ "typescript",
584
+ "vite",
585
+ "vitest",
586
+ "vue",
587
+ "webpack",
588
+ "yarn",
589
+ "zod"
590
+ ];
591
+ var TECHNOLOGY_PATTERN = new RegExp(
592
+ `\\b(${TECHNOLOGY_WORDS.join("|")})\\b`,
593
+ "gi"
594
+ );
595
+ var TOOL_WORDS = [
596
+ "read",
597
+ "write",
598
+ "edit",
599
+ "bash",
600
+ "grep",
601
+ "find",
602
+ "ls",
603
+ "diff",
604
+ "watch",
605
+ "glob",
606
+ "notebookedit",
607
+ "webfetch",
608
+ "websearch"
609
+ ];
610
+ var TOOL_PATTERN = new RegExp(
611
+ `\\b(${TOOL_WORDS.join("|")})\\b`,
612
+ "gi"
613
+ );
614
+ var ERROR_PATTERN = /\b((?:[A-Z]\w*Error)\b|(?:ENOENT|EACCES|ECONNREFUSED|ETIMEDOUT|EPERM)\b|(?:failed\s+to\s+\w+)|(?:error:\s*\S+))/gi;
615
+ var DECISION_PATTERN = /\b((?:decided|chose|selected|opted|switched|migrated|moved)\s+to\s+\w[\w\s]{0,30})/gi;
616
+ var ACTION_PATTERN = /\b((?:created|deleted|removed|modified|updated|installed|uninstalled|deployed|fixed|refactored|renamed|merged|rebased|reverted|committed|pushed|pulled)\s+\w[\w\s]{0,30})/gi;
617
+ var CONCEPT_PATTERN = /\b([A-Z][a-z]+(?:\s+[A-Z][a-z]+){1,3})\b/g;
618
+ var NERExtractor = class {
619
+ endpoint;
620
+ model;
621
+ entityTypes;
622
+ minConfidence;
623
+ maxEntities;
624
+ useHeuristic;
625
+ /** Cached GLiNER2 availability probe result (null = not yet probed). */
626
+ glinerAvailable = null;
627
+ constructor(config = {}) {
628
+ this.endpoint = config.glinerEndpoint ?? DEFAULT_ENDPOINT;
629
+ this.model = config.glinerModel ?? DEFAULT_MODEL;
630
+ this.entityTypes = config.entityTypes ?? ALL_ENTITY_TYPES;
631
+ this.minConfidence = config.minConfidence ?? DEFAULT_MIN_CONFIDENCE;
632
+ this.maxEntities = config.maxEntities ?? DEFAULT_MAX_ENTITIES;
633
+ this.useHeuristic = config.useHeuristic ?? true;
634
+ }
635
+ // ─── Public API ──────────────────────────────────────────────────────────
636
+ /**
637
+ * Extract entities from text.
638
+ * Tries GLiNER2 first; falls back to heuristic if GLiNER2 is unavailable
639
+ * and `useHeuristic` is enabled.
640
+ */
641
+ async extract(text) {
642
+ if (!text || text.trim().length === 0) return [];
643
+ const glinerUp = await this.isGLiNERAvailable();
644
+ if (glinerUp) {
645
+ try {
646
+ const entities = await this.extractViaGLiNER(text);
647
+ return this.postProcess(entities);
648
+ } catch {
649
+ }
650
+ }
651
+ if (this.useHeuristic) {
652
+ const entities = this.extractViaHeuristic(text);
653
+ return this.postProcess(entities);
654
+ }
655
+ return [];
656
+ }
657
+ /**
658
+ * Extract entities from multiple texts in batch.
659
+ * Each text is processed independently; results are returned in order.
660
+ */
661
+ async extractBatch(texts) {
662
+ return Promise.all(texts.map((t) => this.extract(t)));
663
+ }
664
+ /**
665
+ * Probe whether GLiNER2 is reachable at the configured endpoint.
666
+ * Caches the result after the first successful probe.
667
+ */
668
+ async isGLiNERAvailable() {
669
+ if (this.glinerAvailable !== null) return this.glinerAvailable;
670
+ try {
671
+ const response = await fetch(this.endpoint, {
672
+ method: "GET",
673
+ signal: AbortSignal.timeout(GLINER_PROBE_TIMEOUT_MS)
674
+ });
675
+ this.glinerAvailable = response.ok;
676
+ } catch {
677
+ this.glinerAvailable = false;
678
+ }
679
+ return this.glinerAvailable;
680
+ }
681
+ // ─── GLiNER2 Integration ─────────────────────────────────────────────────
682
+ /**
683
+ * Call GLiNER2's /predict endpoint to extract entities.
684
+ *
685
+ * Request format:
686
+ * POST /predict { text, labels, model }
687
+ *
688
+ * Response format:
689
+ * { entities: [{ text, label, score, start, end }] }
690
+ */
691
+ async extractViaGLiNER(text) {
692
+ const response = await fetch(`${this.endpoint}/predict`, {
693
+ method: "POST",
694
+ headers: { "Content-Type": "application/json" },
695
+ body: JSON.stringify({
696
+ text,
697
+ labels: this.entityTypes,
698
+ model: this.model
699
+ }),
700
+ signal: AbortSignal.timeout(GLINER_TIMEOUT_MS)
701
+ });
702
+ if (!response.ok) {
703
+ throw new Error(`GLiNER2 prediction error: ${response.status}`);
704
+ }
705
+ const data = await response.json();
706
+ if (!data.entities || !Array.isArray(data.entities)) {
707
+ return [];
708
+ }
709
+ return data.entities.map((e) => ({
710
+ text: e.text,
711
+ type: this.mapGLiNERLabel(e.label),
712
+ confidence: e.score,
713
+ span: [e.start, e.end]
714
+ }));
715
+ }
716
+ /**
717
+ * Map a GLiNER2 label string to our EntityType enum.
718
+ * Falls back to "concept" for unknown labels.
719
+ */
720
+ mapGLiNERLabel(label) {
721
+ const normalized = label.toLowerCase().trim();
722
+ return GLINER_LABEL_MAP[normalized] ?? "concept";
723
+ }
724
+ // ─── Heuristic Fallback ──────────────────────────────────────────────────
725
+ /**
726
+ * Regex-based NER extraction.
727
+ * Each pattern extracts entities of a specific type.
728
+ * All heuristic matches receive a fixed confidence of 0.6.
729
+ */
730
+ extractViaHeuristic(text) {
731
+ const entities = [];
732
+ const coveredSpans = [];
733
+ const addMatch = (match, type, groupIndex = 1) => {
734
+ const captured = match[groupIndex];
735
+ if (!captured) return;
736
+ const start = match.index + match[0].indexOf(captured);
737
+ const end = start + captured.length;
738
+ if (this.spanOverlaps([start, end], coveredSpans)) return;
739
+ coveredSpans.push([start, end]);
740
+ entities.push({
741
+ text: captured.trim(),
742
+ type,
743
+ confidence: HEURISTIC_CONFIDENCE,
744
+ span: [start, end]
745
+ });
746
+ };
747
+ this.execAll(FILE_PATTERN, text, (m) => addMatch(m, "file"));
748
+ this.execAll(ERROR_PATTERN, text, (m) => addMatch(m, "error"));
749
+ this.execAll(TECHNOLOGY_PATTERN, text, (m) => addMatch(m, "technology"));
750
+ this.execAll(TOOL_PATTERN, text, (m) => addMatch(m, "tool"));
751
+ this.execAll(DECISION_PATTERN, text, (m) => addMatch(m, "decision"));
752
+ this.execAll(ACTION_PATTERN, text, (m) => addMatch(m, "action"));
753
+ this.execAll(CONCEPT_PATTERN, text, (m) => addMatch(m, "concept"));
754
+ return entities;
755
+ }
756
+ // ─── Utilities ───────────────────────────────────────────────────────────
757
+ /**
758
+ * Execute a global regex against text, calling `fn` for each match.
759
+ * Resets the regex lastIndex before and after execution.
760
+ */
761
+ execAll(pattern, text, fn) {
762
+ pattern.lastIndex = 0;
763
+ let match;
764
+ while ((match = pattern.exec(text)) !== null) {
765
+ fn(match);
766
+ }
767
+ pattern.lastIndex = 0;
768
+ }
769
+ /**
770
+ * Check if a candidate span overlaps with any existing span.
771
+ */
772
+ spanOverlaps(candidate, existing) {
773
+ const [cs, ce] = candidate;
774
+ return existing.some(([es, ee]) => cs < ee && ce > es);
775
+ }
776
+ /**
777
+ * Post-process extracted entities:
778
+ * 1. Filter by requested entity types
779
+ * 2. Filter by minimum confidence
780
+ * 3. Deduplicate by normalized text
781
+ * 4. Sort by confidence descending
782
+ * 5. Cap at maxEntities
783
+ */
784
+ postProcess(entities) {
785
+ const typeSet = new Set(this.entityTypes);
786
+ let filtered = entities.filter(
787
+ (e) => typeSet.has(e.type) && e.confidence >= this.minConfidence
788
+ );
789
+ const seen = /* @__PURE__ */ new Map();
790
+ for (const entity of filtered) {
791
+ const key = `${entity.type}::${entity.text.toLowerCase().trim()}`;
792
+ const existing = seen.get(key);
793
+ if (!existing || entity.confidence > existing.confidence) {
794
+ seen.set(key, entity);
795
+ }
796
+ }
797
+ filtered = [...seen.values()];
798
+ filtered.sort((a, b) => {
799
+ if (b.confidence !== a.confidence) return b.confidence - a.confidence;
800
+ return a.span[0] - b.span[0];
801
+ });
802
+ return filtered.slice(0, this.maxEntities);
803
+ }
804
+ };
805
+
806
+ // ../chitragupta/packages/smriti/src/bitemporal.ts
807
+ function nowISO() {
808
+ return (/* @__PURE__ */ new Date()).toISOString();
809
+ }
810
+ var EPOCH_ISO = "1970-01-01T00:00:00.000Z";
811
+ function createEdge(source, target, relationship, weight, validFrom) {
812
+ const now = nowISO();
813
+ return {
814
+ source,
815
+ target,
816
+ relationship,
817
+ weight,
818
+ validFrom: validFrom ?? now,
819
+ recordedAt: now
820
+ };
821
+ }
822
+ function supersedEdge(existingEdge, newWeight, newRelationship) {
823
+ const now = nowISO();
824
+ const superseded = {
825
+ ...existingEdge,
826
+ supersededAt: now
827
+ };
828
+ const replacement = {
829
+ source: existingEdge.source,
830
+ target: existingEdge.target,
831
+ relationship: newRelationship ?? existingEdge.relationship,
832
+ weight: newWeight ?? existingEdge.weight,
833
+ validFrom: now,
834
+ recordedAt: now
835
+ };
836
+ return [superseded, replacement];
837
+ }
838
+ function expireEdge(edge, validUntil) {
839
+ return {
840
+ ...edge,
841
+ validUntil: validUntil ?? nowISO()
842
+ };
843
+ }
844
+ function queryEdgesAtTime(edges, asOfValid, asOfRecord) {
845
+ return edges.filter((e) => {
846
+ const vFrom = e.validFrom ?? EPOCH_ISO;
847
+ if (vFrom > asOfValid) return false;
848
+ if (e.validUntil !== void 0 && e.validUntil <= asOfValid) return false;
849
+ if (asOfRecord !== void 0) {
850
+ const rAt = e.recordedAt ?? EPOCH_ISO;
851
+ if (rAt > asOfRecord) return false;
852
+ if (e.supersededAt !== void 0 && e.supersededAt <= asOfRecord) return false;
853
+ } else {
854
+ if (e.supersededAt !== void 0) return false;
855
+ }
856
+ return true;
857
+ });
858
+ }
859
+ function getEdgeHistory(edges, source, target) {
860
+ return edges.filter((e) => e.source === source && e.target === target).sort((a, b) => (a.recordedAt ?? EPOCH_ISO).localeCompare(b.recordedAt ?? EPOCH_ISO));
861
+ }
862
+ function temporalDecay(edge, now, halfLifeMs) {
863
+ const referenceTime = edge.validUntil !== void 0 ? new Date(edge.validUntil).getTime() : new Date(edge.validFrom ?? EPOCH_ISO).getTime();
864
+ const elapsed = now - referenceTime;
865
+ if (elapsed <= 0) return edge.weight;
866
+ const decay = Math.exp(-Math.LN2 * elapsed / halfLifeMs);
867
+ return edge.weight * decay;
868
+ }
869
+ function compactEdges(edges, retentionMs) {
870
+ const cutoff = Date.now() - retentionMs;
871
+ return edges.filter((e) => {
872
+ if (e.supersededAt === void 0) return true;
873
+ return new Date(e.supersededAt).getTime() > cutoff;
874
+ });
875
+ }
876
+
877
+ // ../chitragupta/packages/smriti/src/graphrag-builder.ts
878
+ function scopeToId(scope) {
879
+ switch (scope.type) {
880
+ case "global":
881
+ return "memory-global";
882
+ case "project":
883
+ return `memory-project-${scope.path}`;
884
+ case "agent":
885
+ return `memory-agent-${scope.agentId}`;
886
+ case "session":
887
+ return `memory-session-${scope.sessionId}`;
888
+ }
889
+ }
890
+ function scopeToLabel(scope) {
891
+ switch (scope.type) {
892
+ case "global":
893
+ return "Global Memory";
894
+ case "project":
895
+ return `Project Memory: ${scope.path}`;
896
+ case "agent":
897
+ return `Agent Memory: ${scope.agentId}`;
898
+ case "session":
899
+ return `Session Memory: ${scope.sessionId}`;
900
+ }
901
+ }
902
+ async function createSessionNode(ctx, session) {
903
+ const summaryText = `${session.meta.title} - ${session.meta.tags.join(", ")}`;
904
+ return {
905
+ id: session.meta.id,
906
+ type: "session",
907
+ label: session.meta.title,
908
+ content: summaryText,
909
+ embedding: await ctx.getEmbedding(summaryText),
910
+ metadata: {
911
+ agent: session.meta.agent,
912
+ model: session.meta.model,
913
+ project: session.meta.project,
914
+ created: session.meta.created,
915
+ turnCount: session.turns.length
916
+ }
917
+ };
918
+ }
919
+ async function extractConceptsFromNodes(ctx, nodes, edges) {
920
+ const entityIndex = /* @__PURE__ */ new Map();
921
+ for (const node of nodes) {
922
+ if (!node.content || node.type === "concept") continue;
923
+ const entities = await ctx.extractEntities(node.content);
924
+ for (const entity of entities) {
925
+ const key = entity.name.toLowerCase();
926
+ const existing = entityIndex.get(key);
927
+ if (existing) {
928
+ existing.sourceIds.push(node.id);
929
+ if (entity.description.length > existing.entity.description.length) {
930
+ existing.entity = entity;
931
+ }
932
+ } else {
933
+ entityIndex.set(key, { entity, sourceIds: [node.id] });
934
+ }
935
+ }
936
+ }
937
+ for (const [key, data] of entityIndex) {
938
+ if (data.sourceIds.length < 2) continue;
939
+ const conceptId = `concept-${key.replace(/\s+/g, "-")}`;
940
+ if (nodes.some((n) => n.id === conceptId)) continue;
941
+ const conceptNode = {
942
+ id: conceptId,
943
+ type: "concept",
944
+ label: data.entity.name,
945
+ content: data.entity.description || data.entity.name,
946
+ metadata: {
947
+ entityType: data.entity.type,
948
+ sourceCount: data.sourceIds.length
949
+ }
950
+ };
951
+ nodes.push(conceptNode);
952
+ for (const sourceId of data.sourceIds) {
953
+ edges.push(createEdge(sourceId, conceptId, "mentions_concept", 0.5));
954
+ }
955
+ }
956
+ }
957
+ async function indexSessionTurns(ctx, session, nodes, edges) {
958
+ for (const turn of session.turns) {
959
+ const chunks = semanticChunk(turn.content);
960
+ for (let chunkIdx = 0; chunkIdx < chunks.length; chunkIdx++) {
961
+ const chunk = chunks[chunkIdx];
962
+ const chunkId = `${session.meta.id}-turn-${turn.turnNumber}-chunk-${chunkIdx}`;
963
+ const chunkNode = {
964
+ id: chunkId,
965
+ type: "session",
966
+ label: `Turn ${turn.turnNumber} (${turn.role}) chunk ${chunkIdx + 1}/${chunks.length}`,
967
+ content: chunk.text,
968
+ embedding: await ctx.getEmbedding(chunk.text),
969
+ metadata: {
970
+ sessionId: session.meta.id,
971
+ turnNumber: turn.turnNumber,
972
+ role: turn.role,
973
+ chunkIndex: chunkIdx,
974
+ totalChunks: chunks.length,
975
+ startSentence: chunk.startSentence,
976
+ endSentence: chunk.endSentence
977
+ }
978
+ };
979
+ nodes.push(chunkNode);
980
+ edges.push(createEdge(session.meta.id, chunkId, "contains_chunk", 1));
981
+ if (chunkIdx > 0) {
982
+ const prevChunkId = `${session.meta.id}-turn-${turn.turnNumber}-chunk-${chunkIdx - 1}`;
983
+ edges.push(createEdge(prevChunkId, chunkId, "followed_by", 0.7));
984
+ }
985
+ }
986
+ if (turn.toolCalls) {
987
+ for (let i = 0; i < turn.toolCalls.length; i++) {
988
+ const tc = turn.toolCalls[i];
989
+ const toolNodeId = `${session.meta.id}-turn-${turn.turnNumber}-tool-${i}`;
990
+ const toolNode = {
991
+ id: toolNodeId,
992
+ type: "file",
993
+ label: `Tool: ${tc.name}`,
994
+ content: tc.input.slice(0, 500),
995
+ metadata: {
996
+ toolName: tc.name,
997
+ isError: tc.isError ?? false
998
+ }
999
+ };
1000
+ nodes.push(toolNode);
1001
+ const firstChunkId = `${session.meta.id}-turn-${turn.turnNumber}-chunk-0`;
1002
+ const linkTarget = nodes.some((n) => n.id === firstChunkId) ? firstChunkId : session.meta.id;
1003
+ edges.push(createEdge(linkTarget, toolNodeId, "uses_tool", 0.8));
1004
+ }
1005
+ }
1006
+ }
1007
+ if (session.meta.parent) {
1008
+ edges.push(createEdge(session.meta.parent, session.meta.id, "branched_to", 0.9));
1009
+ }
1010
+ }
1011
+ async function buildMemoryNodes(ctx, memory, nodes, edges) {
1012
+ const memoryId = scopeToId(memory.scope);
1013
+ const chunks = semanticChunk(memory.content);
1014
+ if (chunks.length <= 1) {
1015
+ const memoryNode = {
1016
+ id: memoryId,
1017
+ type: "memory",
1018
+ label: scopeToLabel(memory.scope),
1019
+ content: memory.content,
1020
+ embedding: await ctx.getEmbedding(memory.content),
1021
+ metadata: { scope: memory.scope }
1022
+ };
1023
+ nodes.push(memoryNode);
1024
+ } else {
1025
+ const memoryNode = {
1026
+ id: memoryId,
1027
+ type: "memory",
1028
+ label: scopeToLabel(memory.scope),
1029
+ content: chunks[0].text,
1030
+ embedding: await ctx.getEmbedding(memory.content.slice(0, 500)),
1031
+ metadata: { scope: memory.scope, totalChunks: chunks.length }
1032
+ };
1033
+ nodes.push(memoryNode);
1034
+ for (let i = 0; i < chunks.length; i++) {
1035
+ const chunkId = `${memoryId}-chunk-${i}`;
1036
+ const chunkNode = {
1037
+ id: chunkId,
1038
+ type: "memory",
1039
+ label: `${scopeToLabel(memory.scope)} chunk ${i + 1}/${chunks.length}`,
1040
+ content: chunks[i].text,
1041
+ embedding: await ctx.getEmbedding(chunks[i].text),
1042
+ metadata: {
1043
+ scope: memory.scope,
1044
+ chunkIndex: i,
1045
+ totalChunks: chunks.length,
1046
+ startSentence: chunks[i].startSentence,
1047
+ endSentence: chunks[i].endSentence
1048
+ }
1049
+ };
1050
+ nodes.push(chunkNode);
1051
+ edges.push(createEdge(memoryId, chunkId, "contains_chunk", 1));
1052
+ }
1053
+ }
1054
+ }
1055
+ async function buildMemoryNodesFromContent(ctx, scope, content, nodes, edges) {
1056
+ const memoryId = scopeToId(scope);
1057
+ const chunks = semanticChunk(content);
1058
+ if (chunks.length <= 1) {
1059
+ const memoryNode = {
1060
+ id: memoryId,
1061
+ type: "memory",
1062
+ label: scopeToLabel(scope),
1063
+ content,
1064
+ embedding: await ctx.getEmbedding(content),
1065
+ metadata: { scope }
1066
+ };
1067
+ nodes.push(memoryNode);
1068
+ } else {
1069
+ const memoryNode = {
1070
+ id: memoryId,
1071
+ type: "memory",
1072
+ label: scopeToLabel(scope),
1073
+ content: chunks[0].text,
1074
+ embedding: await ctx.getEmbedding(content.slice(0, 500)),
1075
+ metadata: { scope, totalChunks: chunks.length }
1076
+ };
1077
+ nodes.push(memoryNode);
1078
+ for (let i = 0; i < chunks.length; i++) {
1079
+ const chunkId = `${memoryId}-chunk-${i}`;
1080
+ const chunkNode = {
1081
+ id: chunkId,
1082
+ type: "memory",
1083
+ label: `${scopeToLabel(scope)} chunk ${i + 1}/${chunks.length}`,
1084
+ content: chunks[i].text,
1085
+ embedding: await ctx.getEmbedding(chunks[i].text),
1086
+ metadata: {
1087
+ scope,
1088
+ chunkIndex: i,
1089
+ totalChunks: chunks.length,
1090
+ startSentence: chunks[i].startSentence,
1091
+ endSentence: chunks[i].endSentence
1092
+ }
1093
+ };
1094
+ nodes.push(chunkNode);
1095
+ edges.push(createEdge(memoryId, chunkId, "contains_chunk", 1));
1096
+ }
1097
+ }
1098
+ }
1099
+ function removeSessionFromGraph(graph, sessionId) {
1100
+ const nodeIdsToRemove = /* @__PURE__ */ new Set();
1101
+ for (const node of graph.nodes) {
1102
+ if (node.id === sessionId || node.id.startsWith(`${sessionId}-turn-`)) {
1103
+ nodeIdsToRemove.add(node.id);
1104
+ }
1105
+ }
1106
+ graph.nodes = graph.nodes.filter((n) => !nodeIdsToRemove.has(n.id));
1107
+ graph.edges = graph.edges.filter(
1108
+ (e) => !nodeIdsToRemove.has(e.source) && !nodeIdsToRemove.has(e.target)
1109
+ );
1110
+ }
1111
+ function removeMemoryFromGraph(graph, scope) {
1112
+ const memoryId = scopeToId(scope);
1113
+ graph.nodes = graph.nodes.filter(
1114
+ (n) => n.id !== memoryId && !n.id.startsWith(`${memoryId}-chunk-`)
1115
+ );
1116
+ graph.edges = graph.edges.filter(
1117
+ (e) => e.source !== memoryId && e.target !== memoryId && !e.source.startsWith(`${memoryId}-chunk-`) && !e.target.startsWith(`${memoryId}-chunk-`)
1118
+ );
1119
+ }
1120
+
1121
+ // ../chitragupta/packages/smriti/src/graphrag-leiden.ts
1122
+ var DEFAULT_LEIDEN_CONFIG = {
1123
+ resolution: 1,
1124
+ maxIterations: 10,
1125
+ minModularityGain: 1e-6,
1126
+ seed: 42,
1127
+ minCommunitySize: 1
1128
+ };
1129
+ var Xorshift32 = class {
1130
+ state;
1131
+ constructor(seed) {
1132
+ this.state = seed | 0 || 1;
1133
+ }
1134
+ next() {
1135
+ let x = this.state;
1136
+ x ^= x << 13;
1137
+ x ^= x >> 17;
1138
+ x ^= x << 5;
1139
+ this.state = x;
1140
+ return (x >>> 0) / 4294967295;
1141
+ }
1142
+ };
1143
+ var AdjacencyGraph = class {
1144
+ /** nodeIndex → Map<neighborIndex, weight> */
1145
+ adj;
1146
+ /** nodeIndex → weighted degree (sum of edge weights) */
1147
+ degree;
1148
+ /** Total edge weight (2m). */
1149
+ totalWeight;
1150
+ /** Number of nodes. */
1151
+ n;
1152
+ /** Node ID → index mapping. */
1153
+ idToIndex;
1154
+ /** Index → node ID mapping. */
1155
+ indexToId;
1156
+ constructor(graph) {
1157
+ const nodeIds = graph.nodes.map((n) => n.id);
1158
+ this.n = nodeIds.length;
1159
+ this.idToIndex = new Map(nodeIds.map((id, i) => [id, i]));
1160
+ this.indexToId = nodeIds;
1161
+ this.adj = new Array(this.n);
1162
+ this.degree = new Array(this.n).fill(0);
1163
+ for (let i = 0; i < this.n; i++) {
1164
+ this.adj[i] = /* @__PURE__ */ new Map();
1165
+ }
1166
+ let totalW = 0;
1167
+ for (const edge of graph.edges) {
1168
+ const s = this.idToIndex.get(edge.source);
1169
+ const t = this.idToIndex.get(edge.target);
1170
+ if (s === void 0 || t === void 0) continue;
1171
+ if (s === t) continue;
1172
+ const w = edge.weight ?? 1;
1173
+ this.adj[s].set(t, (this.adj[s].get(t) ?? 0) + w);
1174
+ this.adj[t].set(s, (this.adj[t].get(s) ?? 0) + w);
1175
+ this.degree[s] += w;
1176
+ this.degree[t] += w;
1177
+ totalW += 2 * w;
1178
+ }
1179
+ this.totalWeight = totalW;
1180
+ }
1181
+ };
1182
+ function computeModularity(g, assignment, resolution) {
1183
+ const twoM = g.totalWeight;
1184
+ if (twoM === 0) return 0;
1185
+ const internalWeight = /* @__PURE__ */ new Map();
1186
+ const communityDegree = /* @__PURE__ */ new Map();
1187
+ for (let i = 0; i < g.n; i++) {
1188
+ const ci = assignment[i];
1189
+ communityDegree.set(ci, (communityDegree.get(ci) ?? 0) + g.degree[i]);
1190
+ for (const [j, w] of g.adj[i]) {
1191
+ if (assignment[j] === ci && j > i) {
1192
+ internalWeight.set(ci, (internalWeight.get(ci) ?? 0) + w);
1193
+ }
1194
+ }
1195
+ }
1196
+ let Q = 0;
1197
+ for (const c of communityDegree.keys()) {
1198
+ const ec = internalWeight.get(c) ?? 0;
1199
+ const ac = communityDegree.get(c) ?? 0;
1200
+ Q += 2 * ec / twoM - resolution * (ac / twoM) ** 2;
1201
+ }
1202
+ return Q;
1203
+ }
1204
+ function modularityGain(g, i, cNew, assignment, communityDegree, resolution) {
1205
+ const twoM = g.totalWeight;
1206
+ if (twoM === 0) return 0;
1207
+ const cOld = assignment[i];
1208
+ if (cOld === cNew) return 0;
1209
+ const ki = g.degree[i];
1210
+ let kInNew = 0;
1211
+ let kInOld = 0;
1212
+ for (const [j, w] of g.adj[i]) {
1213
+ if (assignment[j] === cNew) kInNew += w;
1214
+ if (assignment[j] === cOld) kInOld += w;
1215
+ }
1216
+ const sigmaTotNew = communityDegree.get(cNew) ?? 0;
1217
+ const sigmaTotOld = communityDegree.get(cOld) ?? 0;
1218
+ const removeGain = -kInOld / twoM + resolution * ki * (sigmaTotOld - ki) / (twoM * twoM);
1219
+ const addGain = kInNew / twoM - resolution * ki * sigmaTotNew / (twoM * twoM);
1220
+ return removeGain + addGain;
1221
+ }
1222
+ function localNodeMoving(g, assignment, resolution, rng) {
1223
+ const communityDegree = /* @__PURE__ */ new Map();
1224
+ for (let i = 0; i < g.n; i++) {
1225
+ const ci = assignment[i];
1226
+ communityDegree.set(ci, (communityDegree.get(ci) ?? 0) + g.degree[i]);
1227
+ }
1228
+ const order = Array.from({ length: g.n }, (_, i) => i);
1229
+ for (let i = order.length - 1; i > 0; i--) {
1230
+ const j = Math.floor(rng.next() * (i + 1));
1231
+ [order[i], order[j]] = [order[j], order[i]];
1232
+ }
1233
+ let improved = false;
1234
+ for (const i of order) {
1235
+ const cOld = assignment[i];
1236
+ const ki = g.degree[i];
1237
+ const neighborCommunities = /* @__PURE__ */ new Set();
1238
+ for (const [j] of g.adj[i]) {
1239
+ neighborCommunities.add(assignment[j]);
1240
+ }
1241
+ let bestCommunity = cOld;
1242
+ let bestGain = 0;
1243
+ for (const cNew of neighborCommunities) {
1244
+ if (cNew === cOld) continue;
1245
+ const gain = modularityGain(g, i, cNew, assignment, communityDegree, resolution);
1246
+ if (gain > bestGain) {
1247
+ bestGain = gain;
1248
+ bestCommunity = cNew;
1249
+ }
1250
+ }
1251
+ if (bestCommunity !== cOld) {
1252
+ communityDegree.set(cOld, (communityDegree.get(cOld) ?? 0) - ki);
1253
+ communityDegree.set(bestCommunity, (communityDegree.get(bestCommunity) ?? 0) + ki);
1254
+ assignment[i] = bestCommunity;
1255
+ improved = true;
1256
+ }
1257
+ }
1258
+ return improved;
1259
+ }
1260
+ function refineCommunities(g, assignment) {
1261
+ const communityNodes = /* @__PURE__ */ new Map();
1262
+ for (let i = 0; i < g.n; i++) {
1263
+ const c = assignment[i];
1264
+ if (!communityNodes.has(c)) communityNodes.set(c, []);
1265
+ communityNodes.get(c).push(i);
1266
+ }
1267
+ let nextCommunityId = Math.max(...assignment) + 1;
1268
+ for (const [_communityId, nodes] of communityNodes) {
1269
+ if (nodes.length <= 1) continue;
1270
+ const nodeSet = new Set(nodes);
1271
+ const visited = /* @__PURE__ */ new Set();
1272
+ const components = [];
1273
+ for (const start of nodes) {
1274
+ if (visited.has(start)) continue;
1275
+ const component = [];
1276
+ const queue = [start];
1277
+ visited.add(start);
1278
+ while (queue.length > 0) {
1279
+ const node = queue.shift();
1280
+ component.push(node);
1281
+ for (const [neighbor] of g.adj[node]) {
1282
+ if (nodeSet.has(neighbor) && !visited.has(neighbor)) {
1283
+ visited.add(neighbor);
1284
+ queue.push(neighbor);
1285
+ }
1286
+ }
1287
+ }
1288
+ components.push(component);
1289
+ }
1290
+ if (components.length > 1) {
1291
+ components.sort((a, b) => b.length - a.length);
1292
+ for (let k = 1; k < components.length; k++) {
1293
+ const newId = nextCommunityId++;
1294
+ for (const node of components[k]) {
1295
+ assignment[node] = newId;
1296
+ }
1297
+ }
1298
+ }
1299
+ }
1300
+ }
1301
+ function compactCommunities(assignment, minSize) {
1302
+ const sizes = /* @__PURE__ */ new Map();
1303
+ for (const c of assignment) {
1304
+ sizes.set(c, (sizes.get(c) ?? 0) + 1);
1305
+ }
1306
+ let largestCommunity = 0;
1307
+ let largestSize = 0;
1308
+ for (const [c, s] of sizes) {
1309
+ if (s > largestSize) {
1310
+ largestSize = s;
1311
+ largestCommunity = c;
1312
+ }
1313
+ }
1314
+ const remap = /* @__PURE__ */ new Map();
1315
+ let nextId = 0;
1316
+ for (const [c, s] of sizes) {
1317
+ if (s < minSize) {
1318
+ remap.set(c, remap.get(largestCommunity) ?? nextId);
1319
+ if (!remap.has(largestCommunity)) {
1320
+ remap.set(largestCommunity, nextId++);
1321
+ }
1322
+ } else {
1323
+ if (!remap.has(c)) {
1324
+ remap.set(c, nextId++);
1325
+ }
1326
+ }
1327
+ }
1328
+ return assignment.map((c) => remap.get(c) ?? 0);
1329
+ }
1330
+ function leiden(graph, config) {
1331
+ const cfg = { ...DEFAULT_LEIDEN_CONFIG, ...config };
1332
+ if (graph.nodes.length === 0) {
1333
+ return {
1334
+ communities: /* @__PURE__ */ new Map(),
1335
+ communityList: [],
1336
+ modularity: 0,
1337
+ iterations: 0,
1338
+ levels: 0
1339
+ };
1340
+ }
1341
+ const g = new AdjacencyGraph(graph);
1342
+ const rng = new Xorshift32(cfg.seed ?? Date.now() | 1);
1343
+ const assignment = Array.from({ length: g.n }, (_, i) => i);
1344
+ let prevModularity = -1;
1345
+ let iterations = 0;
1346
+ for (let iter = 0; iter < cfg.maxIterations; iter++) {
1347
+ iterations++;
1348
+ const improved = localNodeMoving(g, assignment, cfg.resolution, rng);
1349
+ refineCommunities(g, assignment);
1350
+ const currentModularity = computeModularity(g, assignment, cfg.resolution);
1351
+ if (!improved || currentModularity - prevModularity < cfg.minModularityGain) {
1352
+ break;
1353
+ }
1354
+ prevModularity = currentModularity;
1355
+ }
1356
+ const compacted = compactCommunities(assignment, cfg.minCommunitySize);
1357
+ const communities = /* @__PURE__ */ new Map();
1358
+ for (let i = 0; i < g.n; i++) {
1359
+ communities.set(g.indexToId[i], compacted[i]);
1360
+ }
1361
+ const finalModularity = computeModularity(g, compacted, cfg.resolution);
1362
+ const communityNodes = /* @__PURE__ */ new Map();
1363
+ for (let i = 0; i < g.n; i++) {
1364
+ const c = compacted[i];
1365
+ if (!communityNodes.has(c)) communityNodes.set(c, []);
1366
+ communityNodes.get(c).push(g.indexToId[i]);
1367
+ }
1368
+ const communityList = [];
1369
+ for (const [id, members] of communityNodes) {
1370
+ let internalEdges = 0;
1371
+ let internalWeight = 0;
1372
+ const memberSet = new Set(members.map((m) => g.idToIndex.get(m)));
1373
+ for (const mi of memberSet) {
1374
+ for (const [j, w] of g.adj[mi]) {
1375
+ if (memberSet.has(j) && j > mi) {
1376
+ internalEdges++;
1377
+ internalWeight += w;
1378
+ }
1379
+ }
1380
+ }
1381
+ const possibleEdges = members.length * (members.length - 1) / 2;
1382
+ const density = possibleEdges > 0 ? internalEdges / possibleEdges : 0;
1383
+ let ac = 0;
1384
+ for (const mi of memberSet) {
1385
+ ac += g.degree[mi];
1386
+ }
1387
+ const twoM = g.totalWeight;
1388
+ const communityMod = twoM > 0 ? 2 * internalWeight / twoM - cfg.resolution * (ac / twoM) ** 2 : 0;
1389
+ communityList.push({
1390
+ id,
1391
+ members,
1392
+ modularity: communityMod,
1393
+ internalDensity: density,
1394
+ level: 0
1395
+ });
1396
+ }
1397
+ communityList.sort((a, b) => b.members.length - a.members.length);
1398
+ return {
1399
+ communities,
1400
+ communityList,
1401
+ modularity: finalModularity,
1402
+ iterations,
1403
+ levels: 1
1404
+ };
1405
+ }
1406
+ function annotateCommunities(graph, result) {
1407
+ for (const node of graph.nodes) {
1408
+ const communityId = result.communities.get(node.id);
1409
+ if (communityId !== void 0) {
1410
+ node.metadata.communityId = communityId;
1411
+ }
1412
+ }
1413
+ }
1414
+ function communitySummary(graph, communityId, maxLabels = 5) {
1415
+ const members = graph.nodes.filter(
1416
+ (n) => n.metadata.communityId === communityId
1417
+ );
1418
+ const typeCount = {};
1419
+ for (const node of members) {
1420
+ typeCount[node.type] = (typeCount[node.type] ?? 0) + 1;
1421
+ }
1422
+ const labels = members.filter((n) => n.type === "concept" || n.type === "session").slice(0, maxLabels).map((n) => n.label);
1423
+ return {
1424
+ communityId,
1425
+ labels,
1426
+ nodeTypes: typeCount,
1427
+ size: members.length
1428
+ };
1429
+ }
1430
+ function filterByCommunity(nodes, communityId) {
1431
+ return nodes.filter((n) => n.metadata.communityId === communityId);
1432
+ }
1433
+ function findBridgeNodes(graph, result, minCommunities = 2) {
1434
+ const touchedCommunities = /* @__PURE__ */ new Map();
1435
+ for (const node of graph.nodes) {
1436
+ const ownC = result.communities.get(node.id);
1437
+ if (ownC !== void 0) {
1438
+ touchedCommunities.set(node.id, /* @__PURE__ */ new Set([ownC]));
1439
+ }
1440
+ }
1441
+ for (const edge of graph.edges) {
1442
+ const sc = result.communities.get(edge.source);
1443
+ const tc = result.communities.get(edge.target);
1444
+ if (sc === void 0 || tc === void 0) continue;
1445
+ if (sc === tc) continue;
1446
+ touchedCommunities.get(edge.source)?.add(tc);
1447
+ touchedCommunities.get(edge.target)?.add(sc);
1448
+ }
1449
+ const bridges = [];
1450
+ for (const node of graph.nodes) {
1451
+ const communities = touchedCommunities.get(node.id);
1452
+ if (communities && communities.size >= minCommunities) {
1453
+ bridges.push(node);
1454
+ }
1455
+ }
1456
+ return bridges;
1457
+ }
1458
+
1459
+ // ../chitragupta/packages/smriti/src/graphrag.ts
1460
+ var DEFAULT_CONFIG = {
1461
+ endpoint: process.env.OLLAMA_HOST ?? "http://localhost:11434",
1462
+ model: "nomic-embed-text",
1463
+ generationModel: "llama3.2"
1464
+ };
1465
+ function getGraphDir() {
1466
+ return path.join(getChitraguptaHome(), "graphrag");
1467
+ }
1468
+ function getGraphPath() {
1469
+ return path.join(getGraphDir(), "graph.json");
1470
+ }
1471
+ function getEmbeddingsPath() {
1472
+ return path.join(getGraphDir(), "embeddings.json");
1473
+ }
1474
+ function getPageRankPath() {
1475
+ return path.join(getGraphDir(), "pagerank.json");
1476
+ }
1477
+ var GraphRAGEngine = class {
1478
+ config;
1479
+ graph;
1480
+ embeddingCache;
1481
+ pageRankScores;
1482
+ maxEmbeddingCacheSize;
1483
+ ollamaAvailable = null;
1484
+ embeddingService;
1485
+ /** Push-based incremental PageRank engine — avoids full recompute on edge changes. */
1486
+ incrementalPR = null;
1487
+ /** Whether the graph.db schema has been initialized in this process. */
1488
+ _graphDbInitialized = false;
1489
+ constructor(config) {
1490
+ this.config = { ...DEFAULT_CONFIG, ...config };
1491
+ this.graph = { nodes: [], edges: [] };
1492
+ this.embeddingCache = /* @__PURE__ */ new Map();
1493
+ this.pageRankScores = /* @__PURE__ */ new Map();
1494
+ this.maxEmbeddingCacheSize = config?.maxEmbeddingCacheSize ?? 1e4;
1495
+ this.embeddingService = config?.embeddingService ?? new EmbeddingService();
1496
+ this.loadFromDisk();
1497
+ }
1498
+ // ─── Ollama Availability (for entity extraction) ──────────────────
1499
+ async checkOllamaAvailability() {
1500
+ if (this.ollamaAvailable !== null) return this.ollamaAvailable;
1501
+ try {
1502
+ const response = await fetch(`${this.config.endpoint}/api/version`, {
1503
+ method: "GET",
1504
+ signal: AbortSignal.timeout(3e3)
1505
+ });
1506
+ this.ollamaAvailable = response.ok;
1507
+ } catch {
1508
+ this.ollamaAvailable = false;
1509
+ }
1510
+ return this.ollamaAvailable;
1511
+ }
1512
+ async getEmbedding(text) {
1513
+ const cached = this.embeddingCache.get(text);
1514
+ if (cached) return cached;
1515
+ const vector = await this.embeddingService.getEmbedding(text);
1516
+ this.embeddingCache.set(text, vector);
1517
+ if (this.embeddingCache.size > this.maxEmbeddingCacheSize) {
1518
+ const oldest = this.embeddingCache.keys().next().value;
1519
+ if (oldest !== void 0) {
1520
+ this.embeddingCache.delete(oldest);
1521
+ }
1522
+ }
1523
+ return vector;
1524
+ }
1525
+ // ─── Cosine Similarity (delegate) ──────────────────────────────────
1526
+ cosineSimilarity(a, b) {
1527
+ return cosineSimilarity(a, b);
1528
+ }
1529
+ // ─── Entity Extraction (delegates) ─────────────────────────────────
1530
+ async extractEntities(text) {
1531
+ const isAvailable = await this.checkOllamaAvailability();
1532
+ let baseEntities;
1533
+ if (isAvailable) {
1534
+ try {
1535
+ baseEntities = await llmExtractEntities(text, this.config.endpoint, this.config.generationModel);
1536
+ } catch {
1537
+ baseEntities = keywordExtractEntities(text);
1538
+ }
1539
+ } else {
1540
+ baseEntities = keywordExtractEntities(text);
1541
+ }
1542
+ try {
1543
+ const nerExtractor = new NERExtractor({ useHeuristic: true });
1544
+ const nerEntities = await nerExtractor.extract(text);
1545
+ const nerTypeMap = {
1546
+ file: "file",
1547
+ technology: "concept",
1548
+ tool: "tool",
1549
+ error: "concept",
1550
+ decision: "concept",
1551
+ action: "concept",
1552
+ person: "person",
1553
+ organization: "organization",
1554
+ location: "concept",
1555
+ concept: "concept"
1556
+ };
1557
+ const seen = new Set(baseEntities.map((e) => e.name.toLowerCase()));
1558
+ for (const ne of nerEntities) {
1559
+ const normalized = ne.text.toLowerCase().trim();
1560
+ if (!seen.has(normalized) && normalized.length > 0) {
1561
+ baseEntities.push({
1562
+ name: normalized,
1563
+ type: nerTypeMap[ne.type] ?? "concept",
1564
+ description: `NER-detected ${ne.type} (confidence: ${ne.confidence.toFixed(2)})`
1565
+ });
1566
+ seen.add(normalized);
1567
+ }
1568
+ }
1569
+ } catch {
1570
+ }
1571
+ return baseEntities;
1572
+ }
1573
+ // ─── Incremental PageRank ────────────────────────────────────────
1574
+ /**
1575
+ * Initialize (or reinitialize) the incremental PageRank engine from
1576
+ * the current graph state. Performs one full Gauss-Seidel computation
1577
+ * internally, then subsequent edge changes are O(1/ε) each via
1578
+ * push-based residual propagation.
1579
+ */
1580
+ initIncrementalPR() {
1581
+ this.incrementalPR = new IncrementalPageRank();
1582
+ this.incrementalPR.initialize(this.graph);
1583
+ this.pageRankScores = this.incrementalPR.getRanks();
1584
+ }
1585
+ /**
1586
+ * Feed removed edges into the incremental PR engine so it can adjust
1587
+ * scores via residual propagation rather than a full recompute.
1588
+ */
1589
+ applyEdgeRemovals(edges) {
1590
+ if (!this.incrementalPR || edges.length === 0) return;
1591
+ for (const edge of edges) {
1592
+ this.incrementalPR.removeEdge(edge.source, edge.target);
1593
+ }
1594
+ this.pageRankScores = this.incrementalPR.getRanks();
1595
+ }
1596
+ /**
1597
+ * Feed newly added edges into the incremental PR engine.
1598
+ */
1599
+ applyEdgeAdditions(edges) {
1600
+ if (!this.incrementalPR || edges.length === 0) return;
1601
+ for (const edge of edges) {
1602
+ this.incrementalPR.addEdge(edge.source, edge.target);
1603
+ }
1604
+ this.pageRankScores = this.incrementalPR.getRanks();
1605
+ }
1606
+ // ─── PageRank (delegates) ──────────────────────────────────────────
1607
+ /**
1608
+ * Compute PageRank for the given graph (or the internal graph if none provided).
1609
+ *
1610
+ * When called with an explicit graph argument, uses the full power-iteration
1611
+ * algorithm (useful for one-off computations on external graphs and tests).
1612
+ * When called without arguments, initializes the incremental PageRank engine
1613
+ * on the internal graph.
1614
+ */
1615
+ computePageRank(graph) {
1616
+ if (graph) {
1617
+ this.pageRankScores = computePageRank(graph);
1618
+ return this.pageRankScores;
1619
+ }
1620
+ this.initIncrementalPR();
1621
+ return this.pageRankScores;
1622
+ }
1623
+ getPageRank(nodeId) {
1624
+ return this.pageRankScores.get(nodeId) ?? 0;
1625
+ }
1626
+ /**
1627
+ * Force a full PageRank recompute from the current graph state.
1628
+ * Reinitializes the incremental engine from scratch. Useful after
1629
+ * graph compaction or any structural change that bypasses the
1630
+ * normal indexSession/indexMemory paths.
1631
+ */
1632
+ recomputePageRank() {
1633
+ this.initIncrementalPR();
1634
+ this.saveToDisk();
1635
+ }
1636
+ // ─── Graph Building ───────────────────────────────────────────────
1637
+ async buildGraph(sessions, memories) {
1638
+ const nodes = [];
1639
+ const edges = [];
1640
+ for (const session of sessions) {
1641
+ const sessionNode = await createSessionNode(this, session);
1642
+ nodes.push(sessionNode);
1643
+ await indexSessionTurns(this, session, nodes, edges);
1644
+ }
1645
+ for (const memory of memories) {
1646
+ await buildMemoryNodes(this, memory, nodes, edges);
1647
+ }
1648
+ await extractConceptsFromNodes(this, nodes, edges);
1649
+ this.graph = { nodes, edges };
1650
+ if (this.graph.nodes.length > 0 && this.graph.edges.length > 0) {
1651
+ const leidenResult = leiden(this.graph);
1652
+ annotateCommunities(this.graph, leidenResult);
1653
+ }
1654
+ this.initIncrementalPR();
1655
+ this.saveToDisk();
1656
+ return this.graph;
1657
+ }
1658
+ // ─── Hybrid Search ───────────────────────────────────────────────
1659
+ async search(query, graph, topK = 10) {
1660
+ const searchGraph = graph ?? this.graph;
1661
+ if (searchGraph.nodes.length === 0) return [];
1662
+ const queryEmbedding = await this.getEmbedding(query);
1663
+ if (this.pageRankScores.size === 0) {
1664
+ this.computePageRank(searchGraph);
1665
+ }
1666
+ let maxPageRank = 0;
1667
+ for (const node of searchGraph.nodes) {
1668
+ const pr = this.pageRankScores.get(node.id) ?? 0;
1669
+ if (pr > maxPageRank) maxPageRank = pr;
1670
+ }
1671
+ const scored = [];
1672
+ for (const node of searchGraph.nodes) {
1673
+ let cosineSim = 0;
1674
+ if (node.embedding && node.embedding.length > 0) {
1675
+ cosineSim = cosineSimilarity(queryEmbedding, node.embedding);
1676
+ cosineSim = Math.max(0, cosineSim);
1677
+ }
1678
+ const rawPageRank = this.pageRankScores.get(node.id) ?? 0;
1679
+ const normalizedPageRank = maxPageRank > 0 ? rawPageRank / maxPageRank : 0;
1680
+ const textScore = textMatchScore(query, node.content + " " + node.label);
1681
+ const finalScore = ALPHA * cosineSim + BETA * normalizedPageRank + GAMMA * textScore;
1682
+ if (finalScore > 0) {
1683
+ scored.push({ node, score: finalScore });
1684
+ }
1685
+ }
1686
+ scored.sort((a, b) => b.score - a.score);
1687
+ return scored.slice(0, topK).map((s) => s.node);
1688
+ }
1689
+ // ─── Incremental Indexing ─────────────────────────────────────────
1690
+ async indexSession(session) {
1691
+ const edgesBefore = [...this.graph.edges];
1692
+ removeSessionFromGraph(this.graph, session.meta.id);
1693
+ const edgesAfterRemoval = this.graph.edges;
1694
+ const edgesAfterSet = new Set(edgesAfterRemoval);
1695
+ const removedEdges = edgesBefore.filter((e) => !edgesAfterSet.has(e));
1696
+ const edgeCountBeforeAdd = this.graph.edges.length;
1697
+ const sessionNode = await createSessionNode(this, session);
1698
+ this.graph.nodes.push(sessionNode);
1699
+ await indexSessionTurns(this, session, this.graph.nodes, this.graph.edges);
1700
+ const newEdges = this.graph.edges.slice(edgeCountBeforeAdd);
1701
+ if (!this.incrementalPR) {
1702
+ this.initIncrementalPR();
1703
+ } else {
1704
+ this.applyEdgeRemovals(removedEdges);
1705
+ this.applyEdgeAdditions(newEdges);
1706
+ }
1707
+ this.saveToDisk();
1708
+ }
1709
+ async indexMemory(scope, content) {
1710
+ const edgesBefore = [...this.graph.edges];
1711
+ removeMemoryFromGraph(this.graph, scope);
1712
+ const edgesAfterRemoval = this.graph.edges;
1713
+ const edgesAfterSet = new Set(edgesAfterRemoval);
1714
+ const removedEdges = edgesBefore.filter((e) => !edgesAfterSet.has(e));
1715
+ const edgeCountBeforeAdd = this.graph.edges.length;
1716
+ await buildMemoryNodesFromContent(this, scope, content, this.graph.nodes, this.graph.edges);
1717
+ const newEdges = this.graph.edges.slice(edgeCountBeforeAdd);
1718
+ if (!this.incrementalPR) {
1719
+ this.initIncrementalPR();
1720
+ } else {
1721
+ this.applyEdgeRemovals(removedEdges);
1722
+ this.applyEdgeAdditions(newEdges);
1723
+ }
1724
+ this.saveToDisk();
1725
+ }
1726
+ // ─── Pramana Lookup ──────────────────────────────────────────────
1727
+ /**
1728
+ * Look up the dominant pramana type for a given node ID.
1729
+ *
1730
+ * Queries the edges table for edges involving this node (as source or target)
1731
+ * and returns the most common non-null pramana value. Falls back to 'shabda'
1732
+ * (testimony/documentation) if no pramana data is found.
1733
+ *
1734
+ * @param nodeId - The graph node ID to look up.
1735
+ * @returns The dominant PramanaType for this node.
1736
+ */
1737
+ lookupPramana(nodeId) {
1738
+ const db = this.getGraphDb();
1739
+ if (!db) return "shabda";
1740
+ try {
1741
+ const row = db.prepare(`
1742
+ SELECT pramana, COUNT(*) as cnt
1743
+ FROM edges
1744
+ WHERE (source = ? OR target = ?) AND pramana IS NOT NULL
1745
+ GROUP BY pramana
1746
+ ORDER BY cnt DESC
1747
+ LIMIT 1
1748
+ `).get(nodeId, nodeId);
1749
+ if (row?.pramana) return row.pramana;
1750
+ } catch {
1751
+ }
1752
+ return "shabda";
1753
+ }
1754
+ /**
1755
+ * Batch look up pramana types for multiple node IDs.
1756
+ *
1757
+ * More efficient than calling lookupPramana() individually for each node.
1758
+ * Returns a Map from node ID to PramanaType. Missing entries default to 'shabda'.
1759
+ *
1760
+ * @param nodeIds - Array of node IDs to look up.
1761
+ * @returns Map from node ID to its dominant PramanaType.
1762
+ */
1763
+ lookupPramanaBatch(nodeIds) {
1764
+ const result = /* @__PURE__ */ new Map();
1765
+ if (nodeIds.length === 0) return result;
1766
+ const db = this.getGraphDb();
1767
+ if (!db) {
1768
+ for (const id of nodeIds) result.set(id, "shabda");
1769
+ return result;
1770
+ }
1771
+ try {
1772
+ const placeholders = nodeIds.map(() => "?").join(",");
1773
+ const rows = db.prepare(`
1774
+ SELECT node_id, pramana FROM (
1775
+ SELECT source AS node_id, pramana, COUNT(*) AS cnt
1776
+ FROM edges
1777
+ WHERE source IN (${placeholders}) AND pramana IS NOT NULL
1778
+ GROUP BY source, pramana
1779
+ UNION ALL
1780
+ SELECT target AS node_id, pramana, COUNT(*) AS cnt
1781
+ FROM edges
1782
+ WHERE target IN (${placeholders}) AND pramana IS NOT NULL
1783
+ GROUP BY target, pramana
1784
+ )
1785
+ GROUP BY node_id
1786
+ HAVING cnt = MAX(cnt)
1787
+ `).all(...nodeIds, ...nodeIds);
1788
+ for (const row of rows) {
1789
+ result.set(row.node_id, row.pramana);
1790
+ }
1791
+ } catch {
1792
+ }
1793
+ for (const id of nodeIds) {
1794
+ if (!result.has(id)) result.set(id, "shabda");
1795
+ }
1796
+ return result;
1797
+ }
1798
+ // ─── SQLite Persistence Helpers ─────────────────────────────────
1799
+ /**
1800
+ * Get the graph database handle, initializing the schema on first access.
1801
+ * Returns null if the database layer is unavailable (e.g. mocked fs in tests).
1802
+ */
1803
+ getGraphDb() {
1804
+ try {
1805
+ const dbm = DatabaseManager.instance();
1806
+ if (!this._graphDbInitialized) {
1807
+ initGraphSchema(dbm);
1808
+ this._graphDbInitialized = true;
1809
+ }
1810
+ return dbm.get("graph");
1811
+ } catch {
1812
+ return null;
1813
+ }
1814
+ }
1815
+ /** Insert or replace a node in the SQLite nodes table. */
1816
+ upsertNode(db, node) {
1817
+ db.prepare(`
1818
+ INSERT OR REPLACE INTO nodes (id, type, label, content, metadata, created_at, updated_at)
1819
+ VALUES (?, ?, ?, ?, ?, ?, ?)
1820
+ `).run(
1821
+ node.id,
1822
+ node.type,
1823
+ node.label,
1824
+ node.content,
1825
+ JSON.stringify(node.metadata),
1826
+ Date.now(),
1827
+ Date.now()
1828
+ );
1829
+ }
1830
+ /** Insert an edge into the SQLite edges table (bi-temporal fields mapped). */
1831
+ insertEdge(db, edge) {
1832
+ db.prepare(`
1833
+ INSERT OR IGNORE INTO edges (source, target, relationship, weight, pramana, viveka,
1834
+ valid_from, valid_until, recorded_at, superseded_at)
1835
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1836
+ `).run(
1837
+ edge.source,
1838
+ edge.target,
1839
+ edge.relationship,
1840
+ edge.weight,
1841
+ null,
1842
+ // pramana — not yet used
1843
+ null,
1844
+ // viveka — not yet used
1845
+ edge.validFrom ? new Date(edge.validFrom).getTime() : Date.now(),
1846
+ edge.validUntil ? new Date(edge.validUntil).getTime() : null,
1847
+ edge.recordedAt ? new Date(edge.recordedAt).getTime() : Date.now(),
1848
+ edge.supersededAt ? new Date(edge.supersededAt).getTime() : null
1849
+ );
1850
+ }
1851
+ // ─── Persistence ──────────────────────────────────────────────────
1852
+ saveToDisk() {
1853
+ try {
1854
+ const db = this.getGraphDb();
1855
+ if (db) {
1856
+ db.transaction(() => {
1857
+ db.prepare("DELETE FROM pagerank").run();
1858
+ db.prepare("DELETE FROM edges").run();
1859
+ db.prepare("DELETE FROM nodes").run();
1860
+ for (const node of this.graph.nodes) {
1861
+ this.upsertNode(db, node);
1862
+ }
1863
+ for (const edge of this.graph.edges) {
1864
+ this.insertEdge(db, edge);
1865
+ }
1866
+ for (const [nodeId, score] of this.pageRankScores) {
1867
+ db.prepare(
1868
+ "INSERT OR REPLACE INTO pagerank (node_id, score, updated_at) VALUES (?, ?, ?)"
1869
+ ).run(nodeId, score, Date.now());
1870
+ }
1871
+ })();
1872
+ this.saveEmbeddingsJson();
1873
+ return;
1874
+ }
1875
+ } catch {
1876
+ }
1877
+ this.saveToDiskJson();
1878
+ }
1879
+ loadFromDisk() {
1880
+ let loadedFromSqlite = false;
1881
+ try {
1882
+ const db = this.getGraphDb();
1883
+ if (db) {
1884
+ const nodeCount = db.prepare("SELECT COUNT(*) as cnt FROM nodes").get().cnt;
1885
+ if (nodeCount > 0) {
1886
+ const nodeRows = db.prepare("SELECT * FROM nodes").all();
1887
+ this.graph.nodes = nodeRows.map((row) => ({
1888
+ id: row.id,
1889
+ type: row.type,
1890
+ label: row.label,
1891
+ content: row.content ?? "",
1892
+ metadata: (() => {
1893
+ try {
1894
+ return JSON.parse(row.metadata ?? "{}");
1895
+ } catch {
1896
+ return {};
1897
+ }
1898
+ })()
1899
+ // Note: embedding not stored in nodes table — it's in vectors.db (Phase 0.6)
1900
+ }));
1901
+ const edgeRows = db.prepare("SELECT * FROM edges").all();
1902
+ this.graph.edges = edgeRows.map((row) => ({
1903
+ source: row.source,
1904
+ target: row.target,
1905
+ relationship: row.relationship,
1906
+ weight: row.weight,
1907
+ validFrom: row.valid_from ? new Date(row.valid_from).toISOString() : void 0,
1908
+ validUntil: row.valid_until ? new Date(row.valid_until).toISOString() : void 0,
1909
+ recordedAt: row.recorded_at ? new Date(row.recorded_at).toISOString() : void 0,
1910
+ supersededAt: row.superseded_at ? new Date(row.superseded_at).toISOString() : void 0
1911
+ }));
1912
+ const prRows = db.prepare("SELECT * FROM pagerank").all();
1913
+ for (const row of prRows) {
1914
+ this.pageRankScores.set(row.node_id, row.score);
1915
+ }
1916
+ loadedFromSqlite = true;
1917
+ } else {
1918
+ this.loadFromDiskJson();
1919
+ if (this.graph.nodes.length > 0) {
1920
+ try {
1921
+ db.transaction(() => {
1922
+ for (const node of this.graph.nodes) {
1923
+ this.upsertNode(db, node);
1924
+ }
1925
+ for (const edge of this.graph.edges) {
1926
+ this.insertEdge(db, edge);
1927
+ }
1928
+ for (const [nodeId, score] of this.pageRankScores) {
1929
+ db.prepare(
1930
+ "INSERT OR REPLACE INTO pagerank (node_id, score, updated_at) VALUES (?, ?, ?)"
1931
+ ).run(nodeId, score, Date.now());
1932
+ }
1933
+ })();
1934
+ } catch {
1935
+ }
1936
+ }
1937
+ loadedFromSqlite = true;
1938
+ }
1939
+ }
1940
+ } catch {
1941
+ }
1942
+ if (!loadedFromSqlite) {
1943
+ this.loadFromDiskJson();
1944
+ }
1945
+ this.loadEmbeddingsJson();
1946
+ if (this.graph.edges.length > 0) {
1947
+ this.initIncrementalPR();
1948
+ }
1949
+ }
1950
+ // ─── JSON Fallback Persistence (legacy / migration) ───────────────
1951
+ saveToDiskJson() {
1952
+ try {
1953
+ const dir = getGraphDir();
1954
+ fs.mkdirSync(dir, { recursive: true });
1955
+ fs.writeFileSync(getGraphPath(), JSON.stringify(this.graph, null, " "), "utf-8");
1956
+ this.saveEmbeddingsJson();
1957
+ const pageRankObj = {};
1958
+ for (const [key, value] of this.pageRankScores) {
1959
+ pageRankObj[key] = value;
1960
+ }
1961
+ fs.writeFileSync(getPageRankPath(), JSON.stringify(pageRankObj, null, " "), "utf-8");
1962
+ } catch {
1963
+ }
1964
+ }
1965
+ saveEmbeddingsJson() {
1966
+ try {
1967
+ const dir = getGraphDir();
1968
+ fs.mkdirSync(dir, { recursive: true });
1969
+ const embeddings = {};
1970
+ for (const [key, value] of this.embeddingCache) {
1971
+ embeddings[key] = value;
1972
+ }
1973
+ fs.writeFileSync(getEmbeddingsPath(), JSON.stringify(embeddings, null, " "), "utf-8");
1974
+ } catch {
1975
+ }
1976
+ }
1977
+ loadFromDiskJson() {
1978
+ try {
1979
+ const graphPath = getGraphPath();
1980
+ if (fs.existsSync(graphPath)) {
1981
+ const raw = fs.readFileSync(graphPath, "utf-8");
1982
+ this.graph = JSON.parse(raw);
1983
+ }
1984
+ } catch {
1985
+ this.graph = { nodes: [], edges: [] };
1986
+ }
1987
+ try {
1988
+ const prPath = getPageRankPath();
1989
+ if (fs.existsSync(prPath)) {
1990
+ const raw = fs.readFileSync(prPath, "utf-8");
1991
+ const ranks = JSON.parse(raw);
1992
+ for (const [key, value] of Object.entries(ranks)) {
1993
+ this.pageRankScores.set(key, value);
1994
+ }
1995
+ }
1996
+ } catch {
1997
+ this.pageRankScores = /* @__PURE__ */ new Map();
1998
+ }
1999
+ }
2000
+ loadEmbeddingsJson() {
2001
+ try {
2002
+ const embPath = getEmbeddingsPath();
2003
+ if (fs.existsSync(embPath)) {
2004
+ const raw = fs.readFileSync(embPath, "utf-8");
2005
+ const embeddings = JSON.parse(raw);
2006
+ for (const [key, value] of Object.entries(embeddings)) {
2007
+ this.embeddingCache.set(key, value);
2008
+ }
2009
+ while (this.embeddingCache.size > this.maxEmbeddingCacheSize) {
2010
+ const oldest = this.embeddingCache.keys().next().value;
2011
+ if (oldest !== void 0) {
2012
+ this.embeddingCache.delete(oldest);
2013
+ }
2014
+ }
2015
+ }
2016
+ } catch {
2017
+ this.embeddingCache = /* @__PURE__ */ new Map();
2018
+ }
2019
+ }
2020
+ getGraph() {
2021
+ return this.graph;
2022
+ }
2023
+ async clear() {
2024
+ this.graph = { nodes: [], edges: [] };
2025
+ this.embeddingCache = /* @__PURE__ */ new Map();
2026
+ this.pageRankScores = /* @__PURE__ */ new Map();
2027
+ this.incrementalPR = null;
2028
+ try {
2029
+ const db = this.getGraphDb();
2030
+ if (db) {
2031
+ db.exec("DELETE FROM pagerank; DELETE FROM edges; DELETE FROM nodes;");
2032
+ }
2033
+ } catch {
2034
+ }
2035
+ this.saveToDisk();
2036
+ }
2037
+ resetAvailability() {
2038
+ this.ollamaAvailable = null;
2039
+ this.embeddingService.resetAvailability();
2040
+ }
2041
+ // ─── Graph Neighbor Queries ───────────────────────────────────────
2042
+ /**
2043
+ * Get edges connected to a node, optionally filtered by direction.
2044
+ * Uses the in-memory graph for fast access.
2045
+ */
2046
+ getNeighbors(nodeId, direction = "both") {
2047
+ return this.graph.edges.filter((edge) => {
2048
+ if (direction === "out") return edge.source === nodeId;
2049
+ if (direction === "in") return edge.target === nodeId;
2050
+ return edge.source === nodeId || edge.target === nodeId;
2051
+ });
2052
+ }
2053
+ };
2054
+ function migrateGraphJson() {
2055
+ const graphPath = getGraphPath();
2056
+ const prPath = getPageRankPath();
2057
+ if (!fs.existsSync(graphPath)) {
2058
+ return { nodes: 0, edges: 0 };
2059
+ }
2060
+ try {
2061
+ const raw = fs.readFileSync(graphPath, "utf-8");
2062
+ const graph = JSON.parse(raw);
2063
+ if (!graph.nodes || graph.nodes.length === 0) {
2064
+ return { nodes: 0, edges: 0 };
2065
+ }
2066
+ const pageRankScores = /* @__PURE__ */ new Map();
2067
+ try {
2068
+ if (fs.existsSync(prPath)) {
2069
+ const prRaw = fs.readFileSync(prPath, "utf-8");
2070
+ const ranks = JSON.parse(prRaw);
2071
+ for (const [key, value] of Object.entries(ranks)) {
2072
+ pageRankScores.set(key, value);
2073
+ }
2074
+ }
2075
+ } catch {
2076
+ }
2077
+ const dbm = DatabaseManager.instance();
2078
+ initGraphSchema(dbm);
2079
+ const db = dbm.get("graph");
2080
+ db.transaction(() => {
2081
+ const upsertNodeStmt = db.prepare(`
2082
+ INSERT OR REPLACE INTO nodes (id, type, label, content, metadata, created_at, updated_at)
2083
+ VALUES (?, ?, ?, ?, ?, ?, ?)
2084
+ `);
2085
+ const insertEdgeStmt = db.prepare(`
2086
+ INSERT OR IGNORE INTO edges (source, target, relationship, weight, pramana, viveka,
2087
+ valid_from, valid_until, recorded_at, superseded_at)
2088
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2089
+ `);
2090
+ const upsertPRStmt = db.prepare(
2091
+ "INSERT OR REPLACE INTO pagerank (node_id, score, updated_at) VALUES (?, ?, ?)"
2092
+ );
2093
+ const now = Date.now();
2094
+ for (const node of graph.nodes) {
2095
+ upsertNodeStmt.run(
2096
+ node.id,
2097
+ node.type,
2098
+ node.label,
2099
+ node.content,
2100
+ JSON.stringify(node.metadata),
2101
+ now,
2102
+ now
2103
+ );
2104
+ }
2105
+ for (const edge of graph.edges) {
2106
+ insertEdgeStmt.run(
2107
+ edge.source,
2108
+ edge.target,
2109
+ edge.relationship,
2110
+ edge.weight,
2111
+ null,
2112
+ null,
2113
+ edge.validFrom ? new Date(edge.validFrom).getTime() : now,
2114
+ edge.validUntil ? new Date(edge.validUntil).getTime() : null,
2115
+ edge.recordedAt ? new Date(edge.recordedAt).getTime() : now,
2116
+ edge.supersededAt ? new Date(edge.supersededAt).getTime() : null
2117
+ );
2118
+ }
2119
+ for (const [nodeId, score] of pageRankScores) {
2120
+ upsertPRStmt.run(nodeId, score, now);
2121
+ }
2122
+ })();
2123
+ try {
2124
+ fs.renameSync(graphPath, graphPath + ".bak");
2125
+ } catch {
2126
+ }
2127
+ try {
2128
+ if (fs.existsSync(prPath)) {
2129
+ fs.renameSync(prPath, prPath + ".bak");
2130
+ }
2131
+ } catch {
2132
+ }
2133
+ return { nodes: graph.nodes.length, edges: graph.edges.length };
2134
+ } catch {
2135
+ return { nodes: 0, edges: 0 };
2136
+ }
2137
+ }
2138
+
2139
+ export {
2140
+ computePersonalizedPageRank,
2141
+ IncrementalPageRank,
2142
+ NERExtractor,
2143
+ createEdge,
2144
+ supersedEdge,
2145
+ expireEdge,
2146
+ queryEdgesAtTime,
2147
+ getEdgeHistory,
2148
+ temporalDecay,
2149
+ compactEdges,
2150
+ leiden,
2151
+ annotateCommunities,
2152
+ communitySummary,
2153
+ filterByCommunity,
2154
+ findBridgeNodes,
2155
+ GraphRAGEngine,
2156
+ migrateGraphJson
2157
+ };
2158
+ //# sourceMappingURL=chunk-S4TBVCL2.js.map