@remnic/core 9.3.600 → 9.3.602

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 (48) hide show
  1. package/dist/access-cli.js +6 -6
  2. package/dist/access-http.js +6 -6
  3. package/dist/access-mcp.js +5 -5
  4. package/dist/access-service.js +4 -4
  5. package/dist/causal-behavior.js +2 -2
  6. package/dist/causal-chain.js +2 -2
  7. package/dist/causal-consolidation.js +2 -2
  8. package/dist/causal-retrieval.js +2 -2
  9. package/dist/causal-trajectory-graph.js +1 -1
  10. package/dist/causal-trajectory.js +1 -1
  11. package/dist/{chunk-ELKI4BB6.js → chunk-2ESBDLT5.js} +3 -3
  12. package/dist/{chunk-WZA5Y6AC.js → chunk-2QANQKSQ.js} +3 -3
  13. package/dist/{chunk-BDCCWRHR.js → chunk-5RPTH6AU.js} +3 -3
  14. package/dist/{chunk-JKV57BTN.js → chunk-7V2SGZ3H.js} +2 -2
  15. package/dist/{chunk-D4KJ74JJ.js → chunk-EWC6W6AB.js} +2 -2
  16. package/dist/{chunk-V67GWXM2.js → chunk-IP73YCZP.js} +1 -1
  17. package/dist/{chunk-KDUVQU6Y.js → chunk-MTGOAU7A.js} +4 -4
  18. package/dist/{chunk-65JSA4MP.js → chunk-RUYYYLDT.js} +7 -7
  19. package/dist/{chunk-CL3MWNNQ.js → chunk-TH67Q46T.js} +3 -3
  20. package/dist/{chunk-ZZYF3BUL.js → chunk-TQOU3VAT.js} +1 -1
  21. package/dist/{chunk-4JRRISLU.js → chunk-XL7FK7PJ.js} +61 -43
  22. package/dist/chunk-XL7FK7PJ.js.map +1 -0
  23. package/dist/cli.js +8 -8
  24. package/dist/compounding/engine.js +1 -1
  25. package/dist/{graph-edge-decay-MUP5J7CC.js → graph-edge-decay-5ZOK7ZNO.js} +2 -2
  26. package/dist/graph-snapshot.js +2 -2
  27. package/dist/graph.js +1 -1
  28. package/dist/index.js +403 -116
  29. package/dist/index.js.map +1 -1
  30. package/dist/operator-toolkit.js +2 -2
  31. package/dist/orchestrator.js +4 -4
  32. package/package.json +1 -1
  33. package/src/graph.test.ts +76 -11
  34. package/src/graph.ts +101 -88
  35. package/src/spaces/index.test.ts +205 -18
  36. package/src/spaces/index.ts +453 -139
  37. package/dist/chunk-4JRRISLU.js.map +0 -1
  38. /package/dist/{chunk-ELKI4BB6.js.map → chunk-2ESBDLT5.js.map} +0 -0
  39. /package/dist/{chunk-WZA5Y6AC.js.map → chunk-2QANQKSQ.js.map} +0 -0
  40. /package/dist/{chunk-BDCCWRHR.js.map → chunk-5RPTH6AU.js.map} +0 -0
  41. /package/dist/{chunk-JKV57BTN.js.map → chunk-7V2SGZ3H.js.map} +0 -0
  42. /package/dist/{chunk-D4KJ74JJ.js.map → chunk-EWC6W6AB.js.map} +0 -0
  43. /package/dist/{chunk-V67GWXM2.js.map → chunk-IP73YCZP.js.map} +0 -0
  44. /package/dist/{chunk-KDUVQU6Y.js.map → chunk-MTGOAU7A.js.map} +0 -0
  45. /package/dist/{chunk-65JSA4MP.js.map → chunk-RUYYYLDT.js.map} +0 -0
  46. /package/dist/{chunk-CL3MWNNQ.js.map → chunk-TH67Q46T.js.map} +0 -0
  47. /package/dist/{chunk-ZZYF3BUL.js.map → chunk-TQOU3VAT.js.map} +0 -0
  48. /package/dist/{graph-edge-decay-MUP5J7CC.js.map → graph-edge-decay-5ZOK7ZNO.js.map} +0 -0
@@ -11,7 +11,7 @@ import {
11
11
  summarizeMemoryWorthLegacyCounters,
12
12
  summarizeObservationThroughput,
13
13
  summarizeTierDistribution
14
- } from "./chunk-ELKI4BB6.js";
14
+ } from "./chunk-2ESBDLT5.js";
15
15
  import "./chunk-JTDRJQ3K.js";
16
16
  import "./chunk-LZ3VEOU5.js";
17
17
  import "./chunk-YBPYIAA5.js";
@@ -44,7 +44,7 @@ import "./chunk-Z5LAYHGJ.js";
44
44
  import "./chunk-PVGDJXVK.js";
45
45
  import "./chunk-OI27U2HT.js";
46
46
  import "./chunk-NNVTUXEB.js";
47
- import "./chunk-4JRRISLU.js";
47
+ import "./chunk-XL7FK7PJ.js";
48
48
  import "./chunk-2LSZVONP.js";
49
49
  import "./chunk-DEUNUKTD.js";
50
50
  import "./chunk-YFS5OEKO.js";
@@ -26,7 +26,7 @@ import {
26
26
  sanitizeSessionKeyForFilename,
27
27
  shouldFilterLifecycleRecallCandidate,
28
28
  summarizeGraphShadowComparison
29
- } from "./chunk-KDUVQU6Y.js";
29
+ } from "./chunk-MTGOAU7A.js";
30
30
  import "./chunk-5RIRL3XL.js";
31
31
  import "./chunk-KVEVLBKC.js";
32
32
  import "./chunk-BFBF3XEF.js";
@@ -40,7 +40,7 @@ import "./chunk-UWK5OXUJ.js";
40
40
  import "./chunk-T2PO5MUF.js";
41
41
  import "./chunk-4HP7HIE3.js";
42
42
  import "./chunk-7N4KAIGN.js";
43
- import "./chunk-V67GWXM2.js";
43
+ import "./chunk-IP73YCZP.js";
44
44
  import "./chunk-DRD2Q7HQ.js";
45
45
  import "./chunk-TECVW3JP.js";
46
46
  import "./chunk-W7L6HXUC.js";
@@ -166,7 +166,7 @@ import "./chunk-QY7YA7OL.js";
166
166
  import "./chunk-NNVTUXEB.js";
167
167
  import "./chunk-QDW3E4RD.js";
168
168
  import "./chunk-EUML3N6B.js";
169
- import "./chunk-4JRRISLU.js";
169
+ import "./chunk-XL7FK7PJ.js";
170
170
  import "./chunk-2LSZVONP.js";
171
171
  import "./chunk-DEUNUKTD.js";
172
172
  import "./chunk-YFS5OEKO.js";
@@ -183,7 +183,7 @@ import "./chunk-QSVPYQPG.js";
183
183
  import "./chunk-FVQJYWH7.js";
184
184
  import "./chunk-G7D6GZ5J.js";
185
185
  import "./chunk-ALEPI75L.js";
186
- import "./chunk-ZZYF3BUL.js";
186
+ import "./chunk-TQOU3VAT.js";
187
187
  import "./chunk-UZYLX7M6.js";
188
188
  import "./chunk-U3PN77QT.js";
189
189
  import "./chunk-4DJQYKMN.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/core",
3
- "version": "9.3.600",
3
+ "version": "9.3.602",
4
4
  "description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/graph.test.ts CHANGED
@@ -1,15 +1,10 @@
1
- import test from "node:test";
2
1
  import assert from "node:assert/strict";
2
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
- import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
5
+ import test from "node:test";
6
6
 
7
- import {
8
- GraphIndex,
9
- graphFilePath,
10
- readEdges,
11
- type GraphConfig,
12
- } from "./graph.js";
7
+ import { type GraphConfig, type GraphEdge, GraphIndex, graphFilePath, readEdges } from "./graph.js";
13
8
 
14
9
  function makeGraphConfig(): GraphConfig {
15
10
  return {
@@ -53,17 +48,87 @@ test("graph reads skip malformed JSON edge objects before traversal", async () =
53
48
  }),
54
49
  "",
55
50
  ].join("\n"),
56
- "utf-8",
51
+ "utf-8"
57
52
  );
58
53
 
59
54
  const edges = await readEdges(memoryDir, "entity");
60
- assert.deepEqual(edges.map((edge) => edge.to), ["c"]);
55
+ assert.deepEqual(
56
+ edges.map((edge) => edge.to),
57
+ ["c"]
58
+ );
61
59
 
62
60
  const graph = new GraphIndex(memoryDir, makeGraphConfig());
63
61
  const activated = await graph.spreadingActivation(["a"]);
64
- assert.deepEqual(activated.map((candidate) => candidate.path), ["c"]);
62
+ assert.deepEqual(
63
+ activated.map((candidate) => candidate.path),
64
+ ["c"]
65
+ );
65
66
  assert.equal(Number.isFinite(activated[0]?.score), true);
66
67
  } finally {
67
68
  await rm(memoryDir, { recursive: true, force: true });
68
69
  }
69
70
  });
71
+
72
+ test("spreadingActivation propagates accumulated activation from multiple seeds", async () => {
73
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-graph-multi-seed-"));
74
+ try {
75
+ await writeGraphEdges(memoryDir, [
76
+ makeEdge("seed-a", "shared"),
77
+ makeEdge("seed-b", "shared"),
78
+ makeEdge("shared", "downstream"),
79
+ ]);
80
+
81
+ const graph = new GraphIndex(memoryDir, makeGraphConfig());
82
+ const activated = await graph.spreadingActivation(["seed-a", "seed-b"]);
83
+ const scores = new Map(activated.map((candidate) => [candidate.path, candidate.score]));
84
+
85
+ assert.equal(scores.get("shared"), 1);
86
+ assert.equal(scores.get("downstream"), 0.5);
87
+ } finally {
88
+ await rm(memoryDir, { recursive: true, force: true });
89
+ }
90
+ });
91
+
92
+ test("spreadingActivation propagates same-depth alternate path activation", async () => {
93
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-graph-alt-path-"));
94
+ try {
95
+ await writeGraphEdges(memoryDir, [
96
+ makeEdge("seed", "left"),
97
+ makeEdge("seed", "right"),
98
+ makeEdge("left", "shared"),
99
+ makeEdge("right", "shared"),
100
+ makeEdge("shared", "downstream"),
101
+ ]);
102
+
103
+ const graph = new GraphIndex(memoryDir, {
104
+ ...makeGraphConfig(),
105
+ maxGraphTraversalSteps: 3,
106
+ });
107
+ const activated = await graph.spreadingActivation(["seed"]);
108
+ const scores = new Map(activated.map((candidate) => [candidate.path, candidate.score]));
109
+
110
+ assert.equal(scores.get("left"), 0.5);
111
+ assert.equal(scores.get("right"), 0.5);
112
+ assert.equal(scores.get("shared"), 0.5);
113
+ assert.equal(scores.get("downstream"), 0.25);
114
+ } finally {
115
+ await rm(memoryDir, { recursive: true, force: true });
116
+ }
117
+ });
118
+
119
+ function makeEdge(from: string, to: string): GraphEdge {
120
+ return {
121
+ from,
122
+ to,
123
+ type: "entity",
124
+ weight: 1,
125
+ label: "test",
126
+ ts: "2026-01-01T00:00:00.000Z",
127
+ };
128
+ }
129
+
130
+ async function writeGraphEdges(memoryDir: string, edges: GraphEdge[]): Promise<void> {
131
+ const filePath = graphFilePath(memoryDir, "entity");
132
+ await mkdir(path.dirname(filePath), { recursive: true });
133
+ await writeFile(filePath, `${edges.map((edge) => JSON.stringify(edge)).join("\n")}\n`, "utf-8");
134
+ }
package/src/graph.ts CHANGED
@@ -10,8 +10,8 @@
10
10
  * All writes are fail-open: errors are caught/logged, never thrown.
11
11
  */
12
12
 
13
- import { mkdir, appendFile, readFile } from "node:fs/promises";
14
- import * as path from "path";
13
+ import { appendFile, mkdir, readFile } from "node:fs/promises";
14
+ import * as path from "node:path";
15
15
 
16
16
  import { readEdgeConfidence } from "./graph-edge-reinforcement.js";
17
17
  import { emitGraphEvent } from "./graph-events.js";
@@ -66,14 +66,7 @@ export const DEFAULT_GRAPH_TRAVERSAL_CONFIDENCE_FLOOR = 0.2;
66
66
  export const DEFAULT_GRAPH_TRAVERSAL_PAGERANK_ITERATIONS = 8;
67
67
 
68
68
  // Causal signal phrases — order matters (most specific first)
69
- export const CAUSAL_PHRASES = [
70
- "as a result",
71
- "led to",
72
- "because of",
73
- "therefore",
74
- "caused",
75
- "because",
76
- ];
69
+ export const CAUSAL_PHRASES = ["as a result", "led to", "because of", "therefore", "caused", "because"];
77
70
 
78
71
  export function graphsDir(memoryDir: string): string {
79
72
  return path.join(memoryDir, "state", "graphs");
@@ -114,8 +107,8 @@ export function withGraphWriteLock<T>(filePath: string, fn: () => Promise<T>): P
114
107
  filePath,
115
108
  next.then(
116
109
  () => undefined,
117
- () => undefined,
118
- ),
110
+ () => undefined
111
+ )
119
112
  );
120
113
  return next;
121
114
  }
@@ -123,7 +116,7 @@ export function withGraphWriteLock<T>(filePath: string, fn: () => Promise<T>): P
123
116
  export async function appendEdge(memoryDir: string, edge: GraphEdge): Promise<void> {
124
117
  await ensureGraphsDir(memoryDir);
125
118
  const filePath = graphFilePath(memoryDir, edge.type);
126
- const line = JSON.stringify(edge) + "\n";
119
+ const line = `${JSON.stringify(edge)}\n`;
127
120
  await withGraphWriteLock(filePath, async () => {
128
121
  await appendFile(filePath, line, "utf8");
129
122
  });
@@ -205,7 +198,7 @@ export async function readEdgesStrict(memoryDir: string, type: GraphType): Promi
205
198
  */
206
199
  export async function readAllEdges(
207
200
  memoryDir: string,
208
- config: Pick<GraphConfig, "entityGraphEnabled" | "timeGraphEnabled" | "causalGraphEnabled">,
201
+ config: Pick<GraphConfig, "entityGraphEnabled" | "timeGraphEnabled" | "causalGraphEnabled">
209
202
  ): Promise<GraphEdge[]> {
210
203
  const parts: GraphEdge[][] = await Promise.all([
211
204
  config.entityGraphEnabled ? readEdges(memoryDir, "entity") : Promise.resolve([]),
@@ -243,9 +236,12 @@ function isValidGraphEdge(raw: unknown, expectedType: GraphType): raw is GraphEd
243
236
  const edge = raw as Record<string, unknown>;
244
237
  return (
245
238
  edge.type === expectedType &&
246
- typeof edge.from === "string" && edge.from.length > 0 &&
247
- typeof edge.to === "string" && edge.to.length > 0 &&
248
- typeof edge.weight === "number" && Number.isFinite(edge.weight) &&
239
+ typeof edge.from === "string" &&
240
+ edge.from.length > 0 &&
241
+ typeof edge.to === "string" &&
242
+ edge.to.length > 0 &&
243
+ typeof edge.weight === "number" &&
244
+ Number.isFinite(edge.weight) &&
249
245
  typeof edge.label === "string" &&
250
246
  typeof edge.ts === "string"
251
247
  );
@@ -258,7 +254,7 @@ export async function analyzeGraphHealth(
258
254
  timeGraphEnabled?: boolean;
259
255
  causalGraphEnabled?: boolean;
260
256
  includeRepairGuidance?: boolean;
261
- },
257
+ }
262
258
  ): Promise<GraphHealthReport> {
263
259
  const enabledTypes: GraphType[] = [];
264
260
  if (options?.entityGraphEnabled !== false) enabledTypes.push("entity");
@@ -324,7 +320,7 @@ export async function analyzeGraphHealth(
324
320
  validEdges: 0,
325
321
  corruptLines: 0,
326
322
  uniqueNodes: globalNodes.size,
327
- },
323
+ }
328
324
  );
329
325
  totals.uniqueNodes = globalNodes.size;
330
326
 
@@ -338,10 +334,14 @@ export async function analyzeGraphHealth(
338
334
  if (options?.includeRepairGuidance === true) {
339
335
  const guidance: string[] = [];
340
336
  if (totals.corruptLines > 0) {
341
- guidance.push("Corrupt graph lines detected: back up memory/state/graphs, then rebuild graphs from clean memory replay/extraction runs.");
337
+ guidance.push(
338
+ "Corrupt graph lines detected: back up memory/state/graphs, then rebuild graphs from clean memory replay/extraction runs."
339
+ );
342
340
  }
343
341
  if (totals.validEdges === 0) {
344
- guidance.push("No valid edges detected yet: run normal extraction traffic (or replay ingestion) to seed graph files.");
342
+ guidance.push(
343
+ "No valid edges detected yet: run normal extraction traffic (or replay ingestion) to seed graph files."
344
+ );
345
345
  }
346
346
  if (guidance.length > 0) report.repairGuidance = guidance;
347
347
  }
@@ -392,10 +392,7 @@ export class GraphIndex {
392
392
  }
393
393
 
394
394
  private async loadEdgesCached(): Promise<GraphEdge[]> {
395
- if (
396
- this.edgeCache &&
397
- Date.now() - this.edgeCache.loadedAt < GraphIndex.EDGE_CACHE_TTL_MS
398
- ) {
395
+ if (this.edgeCache && Date.now() - this.edgeCache.loadedAt < GraphIndex.EDGE_CACHE_TTL_MS) {
399
396
  return this.edgeCache.allEdges;
400
397
  }
401
398
  const allEdges = await readAllEdges(this.memoryDir, {
@@ -520,21 +517,23 @@ export class GraphIndex {
520
517
  * Default `false` (floor from config is applied).
521
518
  */
522
519
  includeLowConfidence?: boolean;
523
- },
524
- ): Promise<Array<{
525
- path: string;
526
- score: number;
527
- seed: string;
528
- hopDepth: number;
529
- decayedWeight: number;
530
- graphType: "entity" | "time" | "causal";
531
- /**
532
- * Confidence of the edge that produced this candidate's recorded
533
- * provenance (the strongest edge along the chosen entry path).
534
- * In `[0, 1]`. Legacy edges without `confidence` surface as 1.0.
535
- */
536
- edgeConfidence: number;
537
- }>> {
520
+ }
521
+ ): Promise<
522
+ Array<{
523
+ path: string;
524
+ score: number;
525
+ seed: string;
526
+ hopDepth: number;
527
+ decayedWeight: number;
528
+ graphType: "entity" | "time" | "causal";
529
+ /**
530
+ * Confidence of the edge that produced this candidate's recorded
531
+ * provenance (the strongest edge along the chosen entry path).
532
+ * In `[0, 1]`. Legacy edges without `confidence` surface as 1.0.
533
+ */
534
+ edgeConfidence: number;
535
+ }>
536
+ > {
538
537
  if (!this.cfg.multiGraphMemoryEnabled) return [];
539
538
  const steps = maxSteps ?? this.cfg.maxGraphTraversalSteps;
540
539
  const decay = this.cfg.graphActivationDecay;
@@ -543,12 +542,9 @@ export class GraphIndex {
543
542
  // Otherwise clamp the configured floor into [0, 1] so misconfiguration
544
543
  // cannot (a) admit edges with negative confidence or (b) reject every
545
544
  // edge.
546
- const floor = opts?.includeLowConfidence === true
547
- ? 0
548
- : clampConfidenceFloor(this.cfg.graphTraversalConfidenceFloor);
549
- const iterations = clampPageRankIterations(
550
- this.cfg.graphTraversalPageRankIterations,
551
- );
545
+ const floor =
546
+ opts?.includeLowConfidence === true ? 0 : clampConfidenceFloor(this.cfg.graphTraversalConfidenceFloor);
547
+ const iterations = clampPageRankIterations(this.cfg.graphTraversalPageRankIterations);
552
548
 
553
549
  try {
554
550
  const allEdges = await this.loadEdgesCached();
@@ -581,50 +577,67 @@ export class GraphIndex {
581
577
  edgeConfidence: number;
582
578
  }
583
579
  >();
584
- const visited = new Set<string>(seeds);
585
-
586
- // BFS queue: [nodePath, hop, seedPath]
587
- const queue: Array<[string, number, string]> = seeds.map((s) => [s, 0, s]);
588
-
589
- while (queue.length > 0) {
590
- const [node, hop, sourceSeed] = queue.shift()!;
591
- if (hop >= steps) continue;
592
-
593
- const edges = adj.get(node) ?? [];
594
- for (const edge of edges) {
595
- const neighbor = edge.to === node ? edge.from : edge.to;
596
- const conf = readEdgeConfidence(edge);
597
- // Defense in depth: the adjacency build already drops sub-floor
598
- // edges, but if a synthesized reverse edge ever bypassed that
599
- // path, this guard keeps spreading activation honest.
600
- if (conf < floor) continue;
601
- const score = edge.weight * conf * Math.pow(decay, hop + 1);
602
-
603
- if (!seedSet.has(neighbor)) {
604
- const existing = scores.get(neighbor) ?? 0;
605
- scores.set(neighbor, existing + score);
606
-
607
- const prev = provenance.get(neighbor);
608
- if (
609
- !prev ||
610
- hop + 1 < prev.hopDepth ||
611
- (hop + 1 === prev.hopDepth && score > prev.decayedWeight)
612
- ) {
613
- provenance.set(neighbor, {
614
- seed: sourceSeed,
615
- hopDepth: hop + 1,
616
- decayedWeight: score,
617
- graphType: edge.type,
618
- edgeConfidence: conf,
619
- });
580
+ let frontier = new Map<string, { node: string; seed: string; activation: number }>();
581
+ const reachedBySeed = new Map<string, Set<string>>();
582
+ for (const seed of seeds) {
583
+ frontier.set(`${seed}\0${seed}`, { node: seed, seed, activation: 1 });
584
+ reachedBySeed.set(seed, new Set([seed]));
585
+ }
586
+
587
+ for (let hop = 0; hop < steps && frontier.size > 0; hop++) {
588
+ const nextFrontier = new Map<string, { node: string; seed: string; activation: number }>();
589
+
590
+ for (const { node, seed: sourceSeed, activation } of frontier.values()) {
591
+ const edges = adj.get(node) ?? [];
592
+ for (const edge of edges) {
593
+ const neighbor = edge.to === node ? edge.from : edge.to;
594
+ const conf = readEdgeConfidence(edge);
595
+ // Defense in depth: the adjacency build already drops sub-floor
596
+ // edges, but if a synthesized reverse edge ever bypassed that
597
+ // path, this guard keeps spreading activation honest.
598
+ if (conf < floor) continue;
599
+ const score = activation * edge.weight * conf * decay;
600
+ const reachedForSeed = reachedBySeed.get(sourceSeed);
601
+ if (reachedForSeed?.has(neighbor)) {
602
+ continue;
620
603
  }
621
- }
622
604
 
623
- if (!visited.has(neighbor)) {
624
- visited.add(neighbor);
625
- queue.push([neighbor, hop + 1, sourceSeed]);
605
+ if (!seedSet.has(neighbor)) {
606
+ const existing = scores.get(neighbor) ?? 0;
607
+ scores.set(neighbor, existing + score);
608
+
609
+ const prev = provenance.get(neighbor);
610
+ if (!prev || hop + 1 < prev.hopDepth || (hop + 1 === prev.hopDepth && score > prev.decayedWeight)) {
611
+ provenance.set(neighbor, {
612
+ seed: sourceSeed,
613
+ hopDepth: hop + 1,
614
+ decayedWeight: score,
615
+ graphType: edge.type,
616
+ edgeConfidence: conf,
617
+ });
618
+ }
619
+
620
+ if (hop + 1 < steps) {
621
+ const frontierKey = `${sourceSeed}\0${neighbor}`;
622
+ const existingFrontier = nextFrontier.get(frontierKey);
623
+ if (existingFrontier) {
624
+ existingFrontier.activation += score;
625
+ } else {
626
+ nextFrontier.set(frontierKey, {
627
+ node: neighbor,
628
+ seed: sourceSeed,
629
+ activation: score,
630
+ });
631
+ }
632
+ }
633
+ }
626
634
  }
627
635
  }
636
+
637
+ for (const { node, seed } of nextFrontier.values()) {
638
+ reachedBySeed.get(seed)?.add(node);
639
+ }
640
+ frontier = nextFrontier;
628
641
  }
629
642
 
630
643
  // Issue #681 PR 3/3 — optional PageRank-style refinement.
@@ -712,7 +725,7 @@ export function clampPageRankIterations(raw: unknown): number {
712
725
  export function applyPageRankRefinement(
713
726
  scores: Map<string, number>,
714
727
  adj: Map<string, GraphEdge[]>,
715
- opts: { iterations: number; floor: number; damping: number },
728
+ opts: { iterations: number; floor: number; damping: number }
716
729
  ): void {
717
730
  const { iterations, floor, damping } = opts;
718
731
  if (iterations <= 0 || scores.size === 0) return;
@@ -793,7 +806,7 @@ export function applyPageRankRefinement(
793
806
  */
794
807
  export function applyLateralInhibition(
795
808
  scores: Map<string, number>,
796
- opts: { beta: number; topM: number },
809
+ opts: { beta: number; topM: number }
797
810
  ): Map<string, number> {
798
811
  const { beta, topM } = opts;
799
812
  if (beta === 0 || topM === 0) return new Map(scores);