@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.
- package/dist/access-cli.js +6 -6
- package/dist/access-http.js +6 -6
- package/dist/access-mcp.js +5 -5
- package/dist/access-service.js +4 -4
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +2 -2
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory-graph.js +1 -1
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-ELKI4BB6.js → chunk-2ESBDLT5.js} +3 -3
- package/dist/{chunk-WZA5Y6AC.js → chunk-2QANQKSQ.js} +3 -3
- package/dist/{chunk-BDCCWRHR.js → chunk-5RPTH6AU.js} +3 -3
- package/dist/{chunk-JKV57BTN.js → chunk-7V2SGZ3H.js} +2 -2
- package/dist/{chunk-D4KJ74JJ.js → chunk-EWC6W6AB.js} +2 -2
- package/dist/{chunk-V67GWXM2.js → chunk-IP73YCZP.js} +1 -1
- package/dist/{chunk-KDUVQU6Y.js → chunk-MTGOAU7A.js} +4 -4
- package/dist/{chunk-65JSA4MP.js → chunk-RUYYYLDT.js} +7 -7
- package/dist/{chunk-CL3MWNNQ.js → chunk-TH67Q46T.js} +3 -3
- package/dist/{chunk-ZZYF3BUL.js → chunk-TQOU3VAT.js} +1 -1
- package/dist/{chunk-4JRRISLU.js → chunk-XL7FK7PJ.js} +61 -43
- package/dist/chunk-XL7FK7PJ.js.map +1 -0
- package/dist/cli.js +8 -8
- package/dist/compounding/engine.js +1 -1
- package/dist/{graph-edge-decay-MUP5J7CC.js → graph-edge-decay-5ZOK7ZNO.js} +2 -2
- package/dist/graph-snapshot.js +2 -2
- package/dist/graph.js +1 -1
- package/dist/index.js +403 -116
- package/dist/index.js.map +1 -1
- package/dist/operator-toolkit.js +2 -2
- package/dist/orchestrator.js +4 -4
- package/package.json +1 -1
- package/src/graph.test.ts +76 -11
- package/src/graph.ts +101 -88
- package/src/spaces/index.test.ts +205 -18
- package/src/spaces/index.ts +453 -139
- package/dist/chunk-4JRRISLU.js.map +0 -1
- /package/dist/{chunk-ELKI4BB6.js.map → chunk-2ESBDLT5.js.map} +0 -0
- /package/dist/{chunk-WZA5Y6AC.js.map → chunk-2QANQKSQ.js.map} +0 -0
- /package/dist/{chunk-BDCCWRHR.js.map → chunk-5RPTH6AU.js.map} +0 -0
- /package/dist/{chunk-JKV57BTN.js.map → chunk-7V2SGZ3H.js.map} +0 -0
- /package/dist/{chunk-D4KJ74JJ.js.map → chunk-EWC6W6AB.js.map} +0 -0
- /package/dist/{chunk-V67GWXM2.js.map → chunk-IP73YCZP.js.map} +0 -0
- /package/dist/{chunk-KDUVQU6Y.js.map → chunk-MTGOAU7A.js.map} +0 -0
- /package/dist/{chunk-65JSA4MP.js.map → chunk-RUYYYLDT.js.map} +0 -0
- /package/dist/{chunk-CL3MWNNQ.js.map → chunk-TH67Q46T.js.map} +0 -0
- /package/dist/{chunk-ZZYF3BUL.js.map → chunk-TQOU3VAT.js.map} +0 -0
- /package/dist/{graph-edge-decay-MUP5J7CC.js.map → graph-edge-decay-5ZOK7ZNO.js.map} +0 -0
package/dist/operator-toolkit.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
summarizeMemoryWorthLegacyCounters,
|
|
12
12
|
summarizeObservationThroughput,
|
|
13
13
|
summarizeTierDistribution
|
|
14
|
-
} from "./chunk-
|
|
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-
|
|
47
|
+
import "./chunk-XL7FK7PJ.js";
|
|
48
48
|
import "./chunk-2LSZVONP.js";
|
|
49
49
|
import "./chunk-DEUNUKTD.js";
|
|
50
50
|
import "./chunk-YFS5OEKO.js";
|
package/dist/orchestrator.js
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
sanitizeSessionKeyForFilename,
|
|
27
27
|
shouldFilterLifecycleRecallCandidate,
|
|
28
28
|
summarizeGraphShadowComparison
|
|
29
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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
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
|
|
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(
|
|
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(
|
|
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 {
|
|
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)
|
|
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" &&
|
|
247
|
-
|
|
248
|
-
typeof edge.
|
|
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(
|
|
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(
|
|
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<
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
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 =
|
|
547
|
-
? 0
|
|
548
|
-
|
|
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
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
for (const
|
|
595
|
-
const
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
const
|
|
605
|
-
|
|
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
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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);
|