@symerian/symi 3.0.18 → 3.0.19
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/build-info.json +3 -3
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/package.json +1 -1
- package/extensions/copilot-proxy/README.md +0 -24
- package/extensions/copilot-proxy/index.ts +0 -154
- package/extensions/copilot-proxy/node_modules/.bin/symi +0 -21
- package/extensions/copilot-proxy/package.json +0 -15
- package/extensions/copilot-proxy/symi.plugin.json +0 -9
- package/extensions/device-pair/index.ts +0 -642
- package/extensions/device-pair/symi.plugin.json +0 -20
- package/extensions/diagnostics-otel/index.ts +0 -15
- package/extensions/diagnostics-otel/node_modules/.bin/acorn +0 -21
- package/extensions/diagnostics-otel/node_modules/.bin/symi +0 -21
- package/extensions/diagnostics-otel/package.json +0 -27
- package/extensions/diagnostics-otel/src/service.test.ts +0 -290
- package/extensions/diagnostics-otel/src/service.ts +0 -666
- package/extensions/diagnostics-otel/symi.plugin.json +0 -8
- package/extensions/google-antigravity-auth/README.md +0 -24
- package/extensions/google-antigravity-auth/index.ts +0 -424
- package/extensions/google-antigravity-auth/node_modules/.bin/symi +0 -21
- package/extensions/google-antigravity-auth/package.json +0 -15
- package/extensions/google-antigravity-auth/symi.plugin.json +0 -9
- package/extensions/google-gemini-cli-auth/README.md +0 -35
- package/extensions/google-gemini-cli-auth/index.ts +0 -75
- package/extensions/google-gemini-cli-auth/node_modules/.bin/symi +0 -21
- package/extensions/google-gemini-cli-auth/oauth.test.ts +0 -162
- package/extensions/google-gemini-cli-auth/oauth.ts +0 -636
- package/extensions/google-gemini-cli-auth/package.json +0 -15
- package/extensions/google-gemini-cli-auth/symi.plugin.json +0 -9
- package/extensions/learning-loop/index.ts +0 -159
- package/extensions/learning-loop/node_modules/.bin/symi +0 -21
- package/extensions/learning-loop/package.json +0 -18
- package/extensions/learning-loop/src/analytics/gateway-methods.ts +0 -230
- package/extensions/learning-loop/src/analytics/metrics-aggregator.ts +0 -153
- package/extensions/learning-loop/src/capture/run-tracker.ts +0 -181
- package/extensions/learning-loop/src/capture/serializer.ts +0 -74
- package/extensions/learning-loop/src/db.ts +0 -583
- package/extensions/learning-loop/src/feedback/explicit-feedback.ts +0 -58
- package/extensions/learning-loop/src/feedback/implicit-signals.ts +0 -89
- package/extensions/learning-loop/src/graph/edge-inference.ts +0 -189
- package/extensions/learning-loop/src/graph/graph-retrieval.ts +0 -144
- package/extensions/learning-loop/src/graph/graph-store.ts +0 -183
- package/extensions/learning-loop/src/hooks.ts +0 -244
- package/extensions/learning-loop/src/injection/cache.ts +0 -73
- package/extensions/learning-loop/src/injection/context-injector.ts +0 -104
- package/extensions/learning-loop/src/injection/prompt-builder.ts +0 -43
- package/extensions/learning-loop/src/learning/embedding-bridge.ts +0 -54
- package/extensions/learning-loop/src/learning/learning-extractor.ts +0 -217
- package/extensions/learning-loop/src/learning/learning-store.ts +0 -158
- package/extensions/learning-loop/src/learning/retrieval.ts +0 -87
- package/extensions/learning-loop/src/math/confidence-intervals.ts +0 -62
- package/extensions/learning-loop/src/math/ewma.ts +0 -51
- package/extensions/learning-loop/src/math/weighted-scorer.ts +0 -42
- package/extensions/learning-loop/src/schema.ts +0 -176
- package/extensions/learning-loop/src/scoring/normalization.ts +0 -32
- package/extensions/learning-loop/src/scoring/quality-engine.ts +0 -78
- package/extensions/learning-loop/src/scoring/signal-extractors.ts +0 -155
- package/extensions/learning-loop/src/test/context-injector.test.ts +0 -142
- package/extensions/learning-loop/src/test/fixes.test.ts +0 -1286
- package/extensions/learning-loop/src/test/graph.test.ts +0 -711
- package/extensions/learning-loop/src/test/integration.test.ts +0 -312
- package/extensions/learning-loop/src/test/learning-store.test.ts +0 -191
- package/extensions/learning-loop/src/test/math.test.ts +0 -148
- package/extensions/learning-loop/src/test/quality-engine.test.ts +0 -231
- package/extensions/learning-loop/src/test/run-tracker.test.ts +0 -143
- package/extensions/learning-loop/src/types.ts +0 -281
- package/extensions/learning-loop/symi.plugin.json +0 -46
- package/extensions/llm-task/README.md +0 -97
- package/extensions/llm-task/index.ts +0 -6
- package/extensions/llm-task/package.json +0 -12
- package/extensions/llm-task/src/llm-task-tool.test.ts +0 -138
- package/extensions/llm-task/src/llm-task-tool.ts +0 -249
- package/extensions/llm-task/symi.plugin.json +0 -21
- package/extensions/memory-lancedb/config.ts +0 -161
- package/extensions/memory-lancedb/index.test.ts +0 -330
- package/extensions/memory-lancedb/index.ts +0 -670
- package/extensions/memory-lancedb/node_modules/.bin/arrow2csv +0 -21
- package/extensions/memory-lancedb/node_modules/.bin/openai +0 -21
- package/extensions/memory-lancedb/node_modules/.bin/symi +0 -21
- package/extensions/memory-lancedb/package.json +0 -20
- package/extensions/memory-lancedb/symi.plugin.json +0 -71
- package/extensions/minimax-portal-auth/README.md +0 -33
- package/extensions/minimax-portal-auth/index.ts +0 -161
- package/extensions/minimax-portal-auth/node_modules/.bin/symi +0 -21
- package/extensions/minimax-portal-auth/oauth.ts +0 -247
- package/extensions/minimax-portal-auth/package.json +0 -15
- package/extensions/minimax-portal-auth/symi.plugin.json +0 -9
- package/extensions/model-equalizer/index.ts +0 -80
- package/extensions/model-equalizer/skills/model-equalizer/SKILL.md +0 -58
- package/extensions/model-equalizer/src/detection.ts +0 -62
- package/extensions/model-equalizer/src/enhancer.ts +0 -63
- package/extensions/model-equalizer/src/test/detection.test.ts +0 -218
- package/extensions/model-equalizer/src/test/enhancer.test.ts +0 -137
- package/extensions/model-equalizer/src/test/integration.test.ts +0 -185
- package/extensions/model-equalizer/src/types.ts +0 -24
- package/extensions/model-equalizer/symi.plugin.json +0 -12
- package/extensions/phone-control/index.ts +0 -421
- package/extensions/phone-control/symi.plugin.json +0 -10
- package/extensions/pipeline/README.md +0 -75
- package/extensions/pipeline/SKILL.md +0 -97
- package/extensions/pipeline/index.ts +0 -18
- package/extensions/pipeline/package.json +0 -11
- package/extensions/pipeline/src/pipeline-tool.test.ts +0 -345
- package/extensions/pipeline/src/pipeline-tool.ts +0 -266
- package/extensions/pipeline/src/windows-spawn.test.ts +0 -148
- package/extensions/pipeline/src/windows-spawn.ts +0 -193
- package/extensions/pipeline/symi.plugin.json +0 -10
- package/extensions/qwen-portal-auth/README.md +0 -24
- package/extensions/qwen-portal-auth/index.ts +0 -134
- package/extensions/qwen-portal-auth/oauth.ts +0 -190
- package/extensions/qwen-portal-auth/symi.plugin.json +0 -9
- package/extensions/talk-voice/index.ts +0 -150
- package/extensions/talk-voice/symi.plugin.json +0 -10
- package/extensions/thread-ownership/index.test.ts +0 -180
- package/extensions/thread-ownership/index.ts +0 -133
- package/extensions/thread-ownership/symi.plugin.json +0 -28
|
@@ -1,711 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
5
|
-
import { createDatabaseManager } from "../db.js";
|
|
6
|
-
import { createEdgeInference } from "../graph/edge-inference.js";
|
|
7
|
-
import { createGraphRetrieval } from "../graph/graph-retrieval.js";
|
|
8
|
-
import { createGraphStore } from "../graph/graph-store.js";
|
|
9
|
-
import { createLearningStore } from "../learning/learning-store.js";
|
|
10
|
-
import type { RetrievalResult } from "../learning/retrieval.js";
|
|
11
|
-
import type { LearningLoopConfig, CompletedRun, QualityScore, LearningRecord } from "../types.js";
|
|
12
|
-
|
|
13
|
-
const TEST_CONFIG: LearningLoopConfig = {
|
|
14
|
-
capture: { embedPrompts: false, maxRuns: 1000 },
|
|
15
|
-
scoring: {
|
|
16
|
-
weights: {
|
|
17
|
-
taskCompletion: 0.35,
|
|
18
|
-
toolEfficiency: 0.25,
|
|
19
|
-
responseAppropriateLength: 0.1,
|
|
20
|
-
latencyRelative: 0.1,
|
|
21
|
-
userFeedback: 0.2,
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
injection: { maxLearnings: 5, minRelevance: 0.3, maxTokens: 500, cacheTtlMs: 60000 },
|
|
25
|
-
decay: { halfLifeDays: 30 },
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const logger = {
|
|
29
|
-
info: () => {},
|
|
30
|
-
warn: () => {},
|
|
31
|
-
error: () => {},
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
function makeRun(id: string): CompletedRun {
|
|
35
|
-
return {
|
|
36
|
-
runId: id,
|
|
37
|
-
sessionId: "sess-1",
|
|
38
|
-
sessionKey: "sk-1",
|
|
39
|
-
agentId: "agent-1",
|
|
40
|
-
provider: "openai",
|
|
41
|
-
model: "gpt-4",
|
|
42
|
-
promptHash: "hash-" + id,
|
|
43
|
-
promptLength: 100,
|
|
44
|
-
responseLength: 200,
|
|
45
|
-
responseToolCallCount: 0,
|
|
46
|
-
usage: { input: 50, output: 100, total: 150 },
|
|
47
|
-
toolCalls: [],
|
|
48
|
-
success: true,
|
|
49
|
-
error: null,
|
|
50
|
-
durationMs: 1000,
|
|
51
|
-
startedAt: Date.now() - 1000,
|
|
52
|
-
completedAt: Date.now(),
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const testScore: QualityScore = {
|
|
57
|
-
score: 0.85,
|
|
58
|
-
signals: [],
|
|
59
|
-
algorithmVersion: 1,
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
// Generate a simple normalized embedding of specified dimension
|
|
63
|
-
function makeEmbedding(seed: number, dim: number = 8): number[] {
|
|
64
|
-
const emb: number[] = [];
|
|
65
|
-
for (let i = 0; i < dim; i++) {
|
|
66
|
-
emb.push(Math.sin(seed + i * 0.7));
|
|
67
|
-
}
|
|
68
|
-
// Normalize
|
|
69
|
-
const norm = Math.sqrt(emb.reduce((s, v) => s + v * v, 0));
|
|
70
|
-
return emb.map((v) => v / norm);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Create an embedding with a target cosine similarity to the base.
|
|
75
|
-
* Uses deterministic rotation: result = cos(angle)*base + sin(angle)*orthogonal
|
|
76
|
-
* where angle = arccos(targetSimilarity).
|
|
77
|
-
*/
|
|
78
|
-
function makeEmbeddingWithSimilarity(base: number[], targetSimilarity: number): number[] {
|
|
79
|
-
const angle = Math.acos(Math.min(1, Math.max(-1, targetSimilarity)));
|
|
80
|
-
// Create an orthogonal vector by shifting and normalizing
|
|
81
|
-
const shifted = base.map((_, i) => Math.sin(i * 2.3 + 1.7));
|
|
82
|
-
// Gram-Schmidt: orth = shifted - (shifted·base)*base
|
|
83
|
-
const dot = shifted.reduce((s, v, i) => s + v * base[i]!, 0);
|
|
84
|
-
const orthRaw = shifted.map((v, i) => v - dot * base[i]!);
|
|
85
|
-
const orthNorm = Math.sqrt(orthRaw.reduce((s, v) => s + v * v, 0));
|
|
86
|
-
const orth = orthRaw.map((v) => v / orthNorm);
|
|
87
|
-
// Combine
|
|
88
|
-
const result = base.map((v, i) => Math.cos(angle) * v + Math.sin(angle) * orth[i]!);
|
|
89
|
-
const resultNorm = Math.sqrt(result.reduce((s, v) => s + v * v, 0));
|
|
90
|
-
return result.map((v) => v / resultNorm);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
describe("Graph Store", () => {
|
|
94
|
-
let tmpDir: string;
|
|
95
|
-
let db: ReturnType<typeof createDatabaseManager>;
|
|
96
|
-
let graphStore: ReturnType<typeof createGraphStore>;
|
|
97
|
-
let learningStore: ReturnType<typeof createLearningStore>;
|
|
98
|
-
|
|
99
|
-
beforeEach(() => {
|
|
100
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "graph-test-"));
|
|
101
|
-
db = createDatabaseManager({ stateDir: tmpDir, config: TEST_CONFIG, logger });
|
|
102
|
-
graphStore = createGraphStore({ db });
|
|
103
|
-
learningStore = createLearningStore({ db, graphStore });
|
|
104
|
-
|
|
105
|
-
// Insert runs for FK constraints
|
|
106
|
-
db.insertRun(makeRun("run-1"), testScore);
|
|
107
|
-
db.insertRun(makeRun("run-2"), testScore);
|
|
108
|
-
db.insertRun(makeRun("run-3"), testScore);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
afterEach(() => {
|
|
112
|
-
db.close();
|
|
113
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
// Test 1: Edge CRUD
|
|
117
|
-
describe("Edge CRUD", () => {
|
|
118
|
-
it("should add and retrieve edges", () => {
|
|
119
|
-
graphStore.addEdge("lrn-a", "lrn-b", "S", 0.8);
|
|
120
|
-
graphStore.addEdge("lrn-a", "lrn-c", "T", 0.5);
|
|
121
|
-
|
|
122
|
-
const edges = graphStore.getEdges("lrn-a");
|
|
123
|
-
expect(edges).toHaveLength(2);
|
|
124
|
-
expect(edges.map((e) => e.edgeType).sort()).toEqual(["S", "T"]);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it("should upsert edge weight", () => {
|
|
128
|
-
graphStore.addEdge("lrn-a", "lrn-b", "R", 0.3);
|
|
129
|
-
graphStore.upsertEdge("lrn-a", "lrn-b", "R", 0.5);
|
|
130
|
-
|
|
131
|
-
const edges = graphStore.getEdges("lrn-a", { typeFilter: ["R"] });
|
|
132
|
-
expect(edges).toHaveLength(1);
|
|
133
|
-
expect(edges[0]!.weight).toBeCloseTo(0.5);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it("should filter edges by type", () => {
|
|
137
|
-
graphStore.addEdge("lrn-a", "lrn-b", "S", 0.8);
|
|
138
|
-
graphStore.addEdge("lrn-a", "lrn-c", "T", 0.5);
|
|
139
|
-
graphStore.addEdge("lrn-a", "lrn-d", "C", 0.6);
|
|
140
|
-
|
|
141
|
-
const semantic = graphStore.getEdges("lrn-a", { typeFilter: ["S"] });
|
|
142
|
-
expect(semantic).toHaveLength(1);
|
|
143
|
-
expect(semantic[0]!.edgeType).toBe("S");
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it("should remove edges for a learning", () => {
|
|
147
|
-
graphStore.addEdge("lrn-a", "lrn-b", "S", 0.8);
|
|
148
|
-
graphStore.addEdge("lrn-a", "lrn-c", "T", 0.5);
|
|
149
|
-
graphStore.addEdge("lrn-d", "lrn-a", "C", 0.6);
|
|
150
|
-
|
|
151
|
-
graphStore.removeEdgesFor("lrn-a");
|
|
152
|
-
|
|
153
|
-
expect(graphStore.getEdges("lrn-a")).toHaveLength(0);
|
|
154
|
-
// Edges that referenced lrn-a from other nodes are also gone
|
|
155
|
-
expect(graphStore.getEdges("lrn-d")).toHaveLength(0);
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
// Test 2: Centrality
|
|
160
|
-
describe("Centrality", () => {
|
|
161
|
-
it("should track incremental centrality on edge add", () => {
|
|
162
|
-
graphStore.addEdge("lrn-a", "lrn-b", "S", 0.8);
|
|
163
|
-
|
|
164
|
-
const centralityA = graphStore.getCentrality("lrn-a");
|
|
165
|
-
const centralityB = graphStore.getCentrality("lrn-b");
|
|
166
|
-
|
|
167
|
-
const expected = Math.log2(1 + 0.8);
|
|
168
|
-
expect(centralityA).toBeCloseTo(expected);
|
|
169
|
-
expect(centralityB).toBeCloseTo(expected);
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it("should accumulate centrality across multiple edges", () => {
|
|
173
|
-
graphStore.addEdge("lrn-a", "lrn-b", "S", 0.8);
|
|
174
|
-
graphStore.addEdge("lrn-a", "lrn-c", "T", 0.5);
|
|
175
|
-
graphStore.addEdge("lrn-a", "lrn-d", "C", 0.6);
|
|
176
|
-
|
|
177
|
-
const centralityA = graphStore.getCentrality("lrn-a");
|
|
178
|
-
const expected = Math.log2(1.8) + Math.log2(1.5) + Math.log2(1.6);
|
|
179
|
-
expect(centralityA).toBeCloseTo(expected, 5);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it("should update centrality on edge upsert", () => {
|
|
183
|
-
graphStore.addEdge("lrn-a", "lrn-b", "R", 0.3);
|
|
184
|
-
const before = graphStore.getCentrality("lrn-a");
|
|
185
|
-
|
|
186
|
-
graphStore.upsertEdge("lrn-a", "lrn-b", "R", 0.7);
|
|
187
|
-
const after = graphStore.getCentrality("lrn-a");
|
|
188
|
-
|
|
189
|
-
expect(after).toBeGreaterThan(before);
|
|
190
|
-
expect(after).toBeCloseTo(Math.log2(1.7));
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it("should recompute neighbors on removeEdgesFor", () => {
|
|
194
|
-
graphStore.addEdge("lrn-a", "lrn-b", "S", 0.8);
|
|
195
|
-
graphStore.addEdge("lrn-b", "lrn-c", "T", 0.5);
|
|
196
|
-
|
|
197
|
-
// lrn-b has centrality from both edges
|
|
198
|
-
const beforeB = graphStore.getCentrality("lrn-b");
|
|
199
|
-
expect(beforeB).toBeCloseTo(Math.log2(1.8) + Math.log2(1.5));
|
|
200
|
-
|
|
201
|
-
// Remove lrn-a edges
|
|
202
|
-
graphStore.removeEdgesFor("lrn-a");
|
|
203
|
-
|
|
204
|
-
// lrn-b should only have centrality from edge to lrn-c
|
|
205
|
-
const afterB = graphStore.getCentrality("lrn-b");
|
|
206
|
-
expect(afterB).toBeCloseTo(Math.log2(1.5));
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
// Test 3: Neighbors / hop expansion
|
|
211
|
-
describe("Neighbors", () => {
|
|
212
|
-
it("should get 1-hop neighbors", () => {
|
|
213
|
-
graphStore.addEdge("lrn-a", "lrn-b", "S", 0.8);
|
|
214
|
-
graphStore.addEdge("lrn-a", "lrn-c", "T", 0.5);
|
|
215
|
-
|
|
216
|
-
const neighbors = graphStore.getNeighbors("lrn-a", 1);
|
|
217
|
-
expect(neighbors.size).toBe(2);
|
|
218
|
-
expect(neighbors.has("lrn-b")).toBe(true);
|
|
219
|
-
expect(neighbors.has("lrn-c")).toBe(true);
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it("should get 2-hop neighbors", () => {
|
|
223
|
-
graphStore.addEdge("lrn-a", "lrn-b", "S", 0.8);
|
|
224
|
-
graphStore.addEdge("lrn-b", "lrn-c", "T", 0.5);
|
|
225
|
-
|
|
226
|
-
const neighbors = graphStore.getNeighbors("lrn-a", 2);
|
|
227
|
-
expect(neighbors.size).toBe(2);
|
|
228
|
-
expect(neighbors.has("lrn-b")).toBe(true);
|
|
229
|
-
expect(neighbors.has("lrn-c")).toBe(true);
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
it("should not include self in neighbors", () => {
|
|
233
|
-
graphStore.addEdge("lrn-a", "lrn-b", "S", 0.8);
|
|
234
|
-
const neighbors = graphStore.getNeighbors("lrn-a", 1);
|
|
235
|
-
expect(neighbors.has("lrn-a")).toBe(false);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it("should filter by edge type", () => {
|
|
239
|
-
graphStore.addEdge("lrn-a", "lrn-b", "S", 0.8);
|
|
240
|
-
graphStore.addEdge("lrn-a", "lrn-c", "T", 0.5);
|
|
241
|
-
|
|
242
|
-
const neighbors = graphStore.getNeighbors("lrn-a", 1, ["S"]);
|
|
243
|
-
expect(neighbors.size).toBe(1);
|
|
244
|
-
expect(neighbors.has("lrn-b")).toBe(true);
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
// Test 4: Temporal inference
|
|
249
|
-
describe("Edge Inference - Temporal", () => {
|
|
250
|
-
it("should create T-edges for learnings from same run", () => {
|
|
251
|
-
const edgeInference = createEdgeInference({ graphStore, learningStore });
|
|
252
|
-
|
|
253
|
-
const id1 = learningStore.addLearning({
|
|
254
|
-
runId: "run-1",
|
|
255
|
-
category: "tool_pattern",
|
|
256
|
-
content: "Pattern alpha",
|
|
257
|
-
embedding: null,
|
|
258
|
-
confidence: 0.8,
|
|
259
|
-
})!;
|
|
260
|
-
|
|
261
|
-
const id2 = learningStore.addLearning({
|
|
262
|
-
runId: "run-1",
|
|
263
|
-
category: "error_recovery",
|
|
264
|
-
content: "Recovery beta",
|
|
265
|
-
embedding: null,
|
|
266
|
-
confidence: 0.7,
|
|
267
|
-
})!;
|
|
268
|
-
|
|
269
|
-
const learning2 = learningStore.getLearning(id2)!;
|
|
270
|
-
edgeInference.onLearningCreated(learning2, "run-1");
|
|
271
|
-
|
|
272
|
-
const edges = graphStore.getEdges(id2, { typeFilter: ["T"] });
|
|
273
|
-
expect(edges.length).toBeGreaterThanOrEqual(1);
|
|
274
|
-
// Should have T-edge connecting id1 and id2
|
|
275
|
-
const hasTemporalEdge = edges.some(
|
|
276
|
-
(e) =>
|
|
277
|
-
(e.sourceId === id1 && e.targetId === id2) || (e.sourceId === id2 && e.targetId === id1),
|
|
278
|
-
);
|
|
279
|
-
expect(hasTemporalEdge).toBe(true);
|
|
280
|
-
});
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
// Test 5: Semantic inference
|
|
284
|
-
describe("Edge Inference - Semantic", () => {
|
|
285
|
-
it("should create S-edges for similar embeddings", () => {
|
|
286
|
-
const edgeInference = createEdgeInference({ graphStore, learningStore });
|
|
287
|
-
const baseEmb = makeEmbedding(1);
|
|
288
|
-
|
|
289
|
-
const id1 = learningStore.addLearning({
|
|
290
|
-
runId: "run-1",
|
|
291
|
-
category: "tool_pattern",
|
|
292
|
-
content: "Use grep for code searching patterns",
|
|
293
|
-
embedding: baseEmb,
|
|
294
|
-
confidence: 0.8,
|
|
295
|
-
})!;
|
|
296
|
-
|
|
297
|
-
// Cosine ~0.75: above 0.6 threshold, below 0.92 dedup
|
|
298
|
-
const similarEmb = makeEmbeddingWithSimilarity(baseEmb, 0.75);
|
|
299
|
-
const id2 = learningStore.addLearning({
|
|
300
|
-
runId: "run-2",
|
|
301
|
-
category: "tool_pattern",
|
|
302
|
-
content: "Grep is useful for finding code patterns",
|
|
303
|
-
embedding: similarEmb,
|
|
304
|
-
confidence: 0.7,
|
|
305
|
-
})!;
|
|
306
|
-
|
|
307
|
-
expect(id2).not.toBeNull();
|
|
308
|
-
const learning2 = learningStore.getLearning(id2)!;
|
|
309
|
-
edgeInference.onLearningCreated(learning2, "run-2");
|
|
310
|
-
|
|
311
|
-
const edges = graphStore.getEdges(id2, { typeFilter: ["S"] });
|
|
312
|
-
expect(edges.length).toBeGreaterThanOrEqual(1);
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
it("should not create S-edges for dissimilar embeddings", () => {
|
|
316
|
-
const edgeInference = createEdgeInference({ graphStore, learningStore });
|
|
317
|
-
|
|
318
|
-
const id1 = learningStore.addLearning({
|
|
319
|
-
runId: "run-1",
|
|
320
|
-
category: "tool_pattern",
|
|
321
|
-
content: "Pattern about databases",
|
|
322
|
-
embedding: makeEmbedding(1),
|
|
323
|
-
confidence: 0.8,
|
|
324
|
-
})!;
|
|
325
|
-
|
|
326
|
-
// Very different embedding
|
|
327
|
-
const id2 = learningStore.addLearning({
|
|
328
|
-
runId: "run-2",
|
|
329
|
-
category: "error_recovery",
|
|
330
|
-
content: "Totally different topic about UI rendering",
|
|
331
|
-
embedding: makeEmbedding(100),
|
|
332
|
-
confidence: 0.7,
|
|
333
|
-
})!;
|
|
334
|
-
|
|
335
|
-
const learning2 = learningStore.getLearning(id2)!;
|
|
336
|
-
edgeInference.onLearningCreated(learning2, "run-2");
|
|
337
|
-
|
|
338
|
-
const edges = graphStore.getEdges(id2, { typeFilter: ["S"] });
|
|
339
|
-
// Low cosine similarity should produce no S-edge
|
|
340
|
-
expect(edges).toHaveLength(0);
|
|
341
|
-
});
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
// Test 6: Supersession
|
|
345
|
-
describe("Edge Inference - Supersession", () => {
|
|
346
|
-
it("should create U-edge when new learning supersedes old one", () => {
|
|
347
|
-
const edgeInference = createEdgeInference({ graphStore, learningStore });
|
|
348
|
-
const baseEmb = makeEmbedding(42);
|
|
349
|
-
|
|
350
|
-
const id1 = learningStore.addLearning({
|
|
351
|
-
runId: "run-1",
|
|
352
|
-
category: "tool_pattern",
|
|
353
|
-
content: "Tool X works well with param A",
|
|
354
|
-
embedding: baseEmb,
|
|
355
|
-
confidence: 0.5,
|
|
356
|
-
})!;
|
|
357
|
-
|
|
358
|
-
// Same category, cosine ~0.85 (>= 0.8 for supersession, < 0.92 for dedup), higher confidence
|
|
359
|
-
const id2 = learningStore.addLearning({
|
|
360
|
-
runId: "run-2",
|
|
361
|
-
category: "tool_pattern",
|
|
362
|
-
content: "Tool X works even better with param A and B",
|
|
363
|
-
embedding: makeEmbeddingWithSimilarity(baseEmb, 0.85),
|
|
364
|
-
confidence: 0.9,
|
|
365
|
-
})!;
|
|
366
|
-
|
|
367
|
-
expect(id2).not.toBeNull();
|
|
368
|
-
const learning2 = learningStore.getLearning(id2)!;
|
|
369
|
-
edgeInference.onLearningCreated(learning2, "run-2");
|
|
370
|
-
|
|
371
|
-
const edges = graphStore.getEdges(id1, { typeFilter: ["U"] });
|
|
372
|
-
const hasSupersession = edges.some(
|
|
373
|
-
(e) => e.sourceId === id1 && e.targetId === id2 && e.weight === 1.0,
|
|
374
|
-
);
|
|
375
|
-
expect(hasSupersession).toBe(true);
|
|
376
|
-
});
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
// Test 7: Contradiction
|
|
380
|
-
describe("Edge Inference - Contradiction", () => {
|
|
381
|
-
it("should detect contradiction via negation density", () => {
|
|
382
|
-
const edgeInference = createEdgeInference({ graphStore, learningStore });
|
|
383
|
-
|
|
384
|
-
const id1 = learningStore.addLearning({
|
|
385
|
-
runId: "run-1",
|
|
386
|
-
category: "tool_pattern",
|
|
387
|
-
content: "Tool grep works effectively for searching code",
|
|
388
|
-
embedding: null,
|
|
389
|
-
confidence: 0.8,
|
|
390
|
-
})!;
|
|
391
|
-
|
|
392
|
-
const id2 = learningStore.addLearning({
|
|
393
|
-
runId: "run-2",
|
|
394
|
-
category: "tool_pattern",
|
|
395
|
-
content: "Tool grep does not work effectively for searching code",
|
|
396
|
-
embedding: null,
|
|
397
|
-
confidence: 0.7,
|
|
398
|
-
})!;
|
|
399
|
-
|
|
400
|
-
const learning2 = learningStore.getLearning(id2)!;
|
|
401
|
-
edgeInference.onLearningCreated(learning2, "run-2");
|
|
402
|
-
|
|
403
|
-
const contradictions = graphStore.getContradictions(id2);
|
|
404
|
-
expect(contradictions.length).toBeGreaterThanOrEqual(1);
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
it("should detect contradiction via category opposition", () => {
|
|
408
|
-
const edgeInference = createEdgeInference({ graphStore, learningStore });
|
|
409
|
-
const baseEmb = makeEmbedding(5);
|
|
410
|
-
|
|
411
|
-
const id1 = learningStore.addLearning({
|
|
412
|
-
runId: "run-1",
|
|
413
|
-
category: "tool_pattern",
|
|
414
|
-
content: "Tool X is reliable and fast",
|
|
415
|
-
embedding: baseEmb,
|
|
416
|
-
confidence: 0.8,
|
|
417
|
-
})!;
|
|
418
|
-
|
|
419
|
-
// Cosine ~0.78: above 0.7 for contradiction detection, below 0.92 for dedup
|
|
420
|
-
const id2 = learningStore.addLearning({
|
|
421
|
-
runId: "run-2",
|
|
422
|
-
category: "anti_pattern",
|
|
423
|
-
content: "Tool X is unreliable and slow",
|
|
424
|
-
embedding: makeEmbeddingWithSimilarity(baseEmb, 0.78),
|
|
425
|
-
confidence: 0.7,
|
|
426
|
-
})!;
|
|
427
|
-
|
|
428
|
-
expect(id2).not.toBeNull();
|
|
429
|
-
const learning2 = learningStore.getLearning(id2)!;
|
|
430
|
-
edgeInference.onLearningCreated(learning2, "run-2");
|
|
431
|
-
|
|
432
|
-
const contradictions = graphStore.getContradictions();
|
|
433
|
-
expect(contradictions.length).toBeGreaterThanOrEqual(1);
|
|
434
|
-
});
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
// Test 8: Reinforcement
|
|
438
|
-
describe("Edge Inference - Reinforcement", () => {
|
|
439
|
-
it("should create R-edges for co-applied learnings", () => {
|
|
440
|
-
const edgeInference = createEdgeInference({ graphStore, learningStore });
|
|
441
|
-
|
|
442
|
-
const id1 = learningStore.addLearning({
|
|
443
|
-
runId: "run-1",
|
|
444
|
-
category: "tool_pattern",
|
|
445
|
-
content: "Learning A",
|
|
446
|
-
embedding: null,
|
|
447
|
-
confidence: 0.8,
|
|
448
|
-
})!;
|
|
449
|
-
const id2 = learningStore.addLearning({
|
|
450
|
-
runId: "run-1",
|
|
451
|
-
category: "error_recovery",
|
|
452
|
-
content: "Learning B",
|
|
453
|
-
embedding: null,
|
|
454
|
-
confidence: 0.7,
|
|
455
|
-
})!;
|
|
456
|
-
|
|
457
|
-
edgeInference.onRunScored("run-2", [id1, id2], []);
|
|
458
|
-
|
|
459
|
-
const [source, target] = id1 < id2 ? [id1, id2] : [id2, id1];
|
|
460
|
-
const edges = graphStore.getEdges(source, { typeFilter: ["R"] });
|
|
461
|
-
expect(edges).toHaveLength(1);
|
|
462
|
-
expect(edges[0]!.weight).toBeCloseTo(0.3);
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
it("should increment R-edge weight on repeated co-application", () => {
|
|
466
|
-
const edgeInference = createEdgeInference({ graphStore, learningStore });
|
|
467
|
-
|
|
468
|
-
const id1 = learningStore.addLearning({
|
|
469
|
-
runId: "run-1",
|
|
470
|
-
category: "tool_pattern",
|
|
471
|
-
content: "Learning C",
|
|
472
|
-
embedding: null,
|
|
473
|
-
confidence: 0.8,
|
|
474
|
-
})!;
|
|
475
|
-
const id2 = learningStore.addLearning({
|
|
476
|
-
runId: "run-1",
|
|
477
|
-
category: "error_recovery",
|
|
478
|
-
content: "Learning D",
|
|
479
|
-
embedding: null,
|
|
480
|
-
confidence: 0.7,
|
|
481
|
-
})!;
|
|
482
|
-
|
|
483
|
-
edgeInference.onRunScored("run-2", [id1, id2], []);
|
|
484
|
-
edgeInference.onRunScored("run-3", [id1, id2], []);
|
|
485
|
-
|
|
486
|
-
const [source, target] = id1 < id2 ? [id1, id2] : [id2, id1];
|
|
487
|
-
const edges = graphStore.getEdges(source, { typeFilter: ["R"] });
|
|
488
|
-
expect(edges).toHaveLength(1);
|
|
489
|
-
expect(edges[0]!.weight).toBeCloseTo(0.4); // 0.3 + 0.1
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
it("should create C-edges from injected to extracted learnings", () => {
|
|
493
|
-
const edgeInference = createEdgeInference({ graphStore, learningStore });
|
|
494
|
-
|
|
495
|
-
const injectedId = learningStore.addLearning({
|
|
496
|
-
runId: "run-1",
|
|
497
|
-
category: "tool_pattern",
|
|
498
|
-
content: "Injected learning",
|
|
499
|
-
embedding: null,
|
|
500
|
-
confidence: 0.8,
|
|
501
|
-
})!;
|
|
502
|
-
const extractedId = learningStore.addLearning({
|
|
503
|
-
runId: "run-2",
|
|
504
|
-
category: "error_recovery",
|
|
505
|
-
content: "Extracted learning",
|
|
506
|
-
embedding: null,
|
|
507
|
-
confidence: 0.7,
|
|
508
|
-
})!;
|
|
509
|
-
|
|
510
|
-
edgeInference.onRunScored("run-2", [injectedId], [extractedId]);
|
|
511
|
-
|
|
512
|
-
const edges = graphStore.getEdges(injectedId, { typeFilter: ["C"] });
|
|
513
|
-
const hasCausal = edges.some((e) => e.sourceId === injectedId && e.targetId === extractedId);
|
|
514
|
-
expect(hasCausal).toBe(true);
|
|
515
|
-
expect(edges.find((e) => e.targetId === extractedId)!.weight).toBeCloseTo(0.6);
|
|
516
|
-
});
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
// Test 9: Hop expansion in retrieval
|
|
520
|
-
describe("Graph Retrieval", () => {
|
|
521
|
-
it("should expand seeds to include graph neighbors", () => {
|
|
522
|
-
const graphRetrieval = createGraphRetrieval({
|
|
523
|
-
graphStore,
|
|
524
|
-
learningStore,
|
|
525
|
-
config: TEST_CONFIG,
|
|
526
|
-
});
|
|
527
|
-
|
|
528
|
-
// Create learnings
|
|
529
|
-
const id1 = learningStore.addLearning({
|
|
530
|
-
runId: "run-1",
|
|
531
|
-
category: "tool_pattern",
|
|
532
|
-
content: "Seed learning about tool usage",
|
|
533
|
-
embedding: null,
|
|
534
|
-
confidence: 0.8,
|
|
535
|
-
})!;
|
|
536
|
-
const id2 = learningStore.addLearning({
|
|
537
|
-
runId: "run-1",
|
|
538
|
-
category: "error_recovery",
|
|
539
|
-
content: "Neighbor learning about error handling",
|
|
540
|
-
embedding: null,
|
|
541
|
-
confidence: 0.7,
|
|
542
|
-
})!;
|
|
543
|
-
|
|
544
|
-
// Create S-edge between them
|
|
545
|
-
graphStore.addEdge(id1, id2, "S", 0.8);
|
|
546
|
-
|
|
547
|
-
const learning1 = learningStore.getLearning(id1)!;
|
|
548
|
-
const seeds: RetrievalResult[] = [{ learning: learning1, score: 0.9, decayedScore: 0.85 }];
|
|
549
|
-
|
|
550
|
-
const results = graphRetrieval.expandWithGraph(seeds, 5);
|
|
551
|
-
expect(results.length).toBe(2);
|
|
552
|
-
|
|
553
|
-
const neighborResult = results.find((r) => r.learning.id === id2);
|
|
554
|
-
expect(neighborResult).toBeDefined();
|
|
555
|
-
expect(neighborResult!.decayedScore).toBeLessThan(0.85); // Decayed by hop
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
it("should apply hop decay correctly", () => {
|
|
559
|
-
const graphRetrieval = createGraphRetrieval({
|
|
560
|
-
graphStore,
|
|
561
|
-
learningStore,
|
|
562
|
-
config: TEST_CONFIG,
|
|
563
|
-
});
|
|
564
|
-
|
|
565
|
-
const id1 = learningStore.addLearning({
|
|
566
|
-
runId: "run-1",
|
|
567
|
-
category: "tool_pattern",
|
|
568
|
-
content: "Seed learning",
|
|
569
|
-
embedding: null,
|
|
570
|
-
confidence: 0.8,
|
|
571
|
-
})!;
|
|
572
|
-
const id2 = learningStore.addLearning({
|
|
573
|
-
runId: "run-1",
|
|
574
|
-
category: "error_recovery",
|
|
575
|
-
content: "1-hop neighbor",
|
|
576
|
-
embedding: null,
|
|
577
|
-
confidence: 0.7,
|
|
578
|
-
})!;
|
|
579
|
-
|
|
580
|
-
graphStore.addEdge(id1, id2, "C", 0.9); // Causal edge, multiplier = 1.0
|
|
581
|
-
|
|
582
|
-
const learning1 = learningStore.getLearning(id1)!;
|
|
583
|
-
const seeds: RetrievalResult[] = [{ learning: learning1, score: 1.0, decayedScore: 1.0 }];
|
|
584
|
-
|
|
585
|
-
const results = graphRetrieval.expandWithGraph(seeds, 5);
|
|
586
|
-
const neighbor = results.find((r) => r.learning.id === id2)!;
|
|
587
|
-
|
|
588
|
-
// Score = seedScore * hopDecay * typeMultiplier(C=1.0) * centralityBoost
|
|
589
|
-
// centralityBoost = 1.0 + 0.1 * log2(1 + centrality(id2))
|
|
590
|
-
const centrality = graphStore.getCentrality(id2);
|
|
591
|
-
const expectedCentralityBoost = 1.0 + 0.1 * Math.log2(1 + centrality);
|
|
592
|
-
const expectedScore = 1.0 * 0.6 * 1.0 * expectedCentralityBoost;
|
|
593
|
-
expect(neighbor.decayedScore).toBeCloseTo(expectedScore, 2);
|
|
594
|
-
});
|
|
595
|
-
|
|
596
|
-
it("should annotate contested results with X-edges", () => {
|
|
597
|
-
const graphRetrieval = createGraphRetrieval({
|
|
598
|
-
graphStore,
|
|
599
|
-
learningStore,
|
|
600
|
-
config: TEST_CONFIG,
|
|
601
|
-
});
|
|
602
|
-
|
|
603
|
-
const id1 = learningStore.addLearning({
|
|
604
|
-
runId: "run-1",
|
|
605
|
-
category: "tool_pattern",
|
|
606
|
-
content: "Contested seed learning",
|
|
607
|
-
embedding: null,
|
|
608
|
-
confidence: 0.8,
|
|
609
|
-
})!;
|
|
610
|
-
const id2 = learningStore.addLearning({
|
|
611
|
-
runId: "run-2",
|
|
612
|
-
category: "anti_pattern",
|
|
613
|
-
content: "Contradicting learning",
|
|
614
|
-
embedding: null,
|
|
615
|
-
confidence: 0.7,
|
|
616
|
-
})!;
|
|
617
|
-
|
|
618
|
-
// Create X-edge (contradiction)
|
|
619
|
-
graphStore.addEdge(id1, id2, "X", 0.8);
|
|
620
|
-
|
|
621
|
-
const learning1 = learningStore.getLearning(id1)!;
|
|
622
|
-
const seeds: RetrievalResult[] = [{ learning: learning1, score: 0.9, decayedScore: 0.85 }];
|
|
623
|
-
|
|
624
|
-
const results = graphRetrieval.expandWithGraph(seeds, 5);
|
|
625
|
-
const contestedResult = results.find((r) => r.learning.id === id1);
|
|
626
|
-
expect(contestedResult).toBeDefined();
|
|
627
|
-
expect(contestedResult!.contested).toBe(true);
|
|
628
|
-
|
|
629
|
-
// Contradiction neighbor should NOT be expanded (X multiplier = 0.0)
|
|
630
|
-
const contradictionNeighbor = results.find((r) => r.learning.id === id2);
|
|
631
|
-
expect(contradictionNeighbor).toBeUndefined();
|
|
632
|
-
});
|
|
633
|
-
});
|
|
634
|
-
|
|
635
|
-
// Test 10: Integration -- full cycle
|
|
636
|
-
describe("Integration", () => {
|
|
637
|
-
it("should handle full cycle: create learnings, infer edges, retrieve with expansion", () => {
|
|
638
|
-
const edgeInference = createEdgeInference({ graphStore, learningStore });
|
|
639
|
-
const graphRetrieval = createGraphRetrieval({
|
|
640
|
-
graphStore,
|
|
641
|
-
learningStore,
|
|
642
|
-
config: TEST_CONFIG,
|
|
643
|
-
});
|
|
644
|
-
|
|
645
|
-
// Create learnings from same run
|
|
646
|
-
const id1 = learningStore.addLearning({
|
|
647
|
-
runId: "run-1",
|
|
648
|
-
category: "tool_pattern",
|
|
649
|
-
content: "Grep is efficient for finding patterns in code",
|
|
650
|
-
embedding: null,
|
|
651
|
-
confidence: 0.8,
|
|
652
|
-
})!;
|
|
653
|
-
const learning1 = learningStore.getLearning(id1)!;
|
|
654
|
-
edgeInference.onLearningCreated(learning1, "run-1");
|
|
655
|
-
|
|
656
|
-
const id2 = learningStore.addLearning({
|
|
657
|
-
runId: "run-1",
|
|
658
|
-
category: "error_recovery",
|
|
659
|
-
content: "When grep fails use broader search terms",
|
|
660
|
-
embedding: null,
|
|
661
|
-
confidence: 0.7,
|
|
662
|
-
})!;
|
|
663
|
-
const learning2 = learningStore.getLearning(id2)!;
|
|
664
|
-
edgeInference.onLearningCreated(learning2, "run-1");
|
|
665
|
-
|
|
666
|
-
// Verify T-edges were created (same run)
|
|
667
|
-
const edges = graphStore.getEdges(id1, { typeFilter: ["T"] });
|
|
668
|
-
expect(edges.length).toBeGreaterThanOrEqual(1);
|
|
669
|
-
|
|
670
|
-
// Simulate run scoring with both applied
|
|
671
|
-
edgeInference.onRunScored("run-2", [id1, id2], []);
|
|
672
|
-
|
|
673
|
-
// Verify R-edges were created
|
|
674
|
-
const rEdges = graphStore.getEdges(id1, { typeFilter: ["R"] });
|
|
675
|
-
expect(rEdges.length).toBeGreaterThanOrEqual(1);
|
|
676
|
-
|
|
677
|
-
// Retrieve with graph expansion
|
|
678
|
-
const seeds: RetrievalResult[] = [{ learning: learning1, score: 0.9, decayedScore: 0.85 }];
|
|
679
|
-
|
|
680
|
-
const results = graphRetrieval.expandWithGraph(seeds, 5);
|
|
681
|
-
expect(results.length).toBe(2); // Seed + neighbor
|
|
682
|
-
expect(results.map((r) => r.learning.id).sort()).toEqual([id1, id2].sort());
|
|
683
|
-
});
|
|
684
|
-
|
|
685
|
-
it("should clean up graph edges when learning is removed", () => {
|
|
686
|
-
const id1 = learningStore.addLearning({
|
|
687
|
-
runId: "run-1",
|
|
688
|
-
category: "tool_pattern",
|
|
689
|
-
content: "Removable learning",
|
|
690
|
-
embedding: null,
|
|
691
|
-
confidence: 0.8,
|
|
692
|
-
})!;
|
|
693
|
-
const id2 = learningStore.addLearning({
|
|
694
|
-
runId: "run-1",
|
|
695
|
-
category: "error_recovery",
|
|
696
|
-
content: "Connected learning",
|
|
697
|
-
embedding: null,
|
|
698
|
-
confidence: 0.7,
|
|
699
|
-
})!;
|
|
700
|
-
|
|
701
|
-
graphStore.addEdge(id1, id2, "S", 0.8);
|
|
702
|
-
expect(graphStore.getEdges(id1)).toHaveLength(1);
|
|
703
|
-
|
|
704
|
-
// Remove learning -- should also clean up edges
|
|
705
|
-
learningStore.removeLearning(id1);
|
|
706
|
-
|
|
707
|
-
expect(graphStore.getEdges(id1)).toHaveLength(0);
|
|
708
|
-
expect(graphStore.getEdges(id2, { typeFilter: ["S"] })).toHaveLength(0);
|
|
709
|
-
});
|
|
710
|
-
});
|
|
711
|
-
});
|