@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.
Files changed (116) hide show
  1. package/dist/build-info.json +3 -3
  2. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  3. package/package.json +1 -1
  4. package/extensions/copilot-proxy/README.md +0 -24
  5. package/extensions/copilot-proxy/index.ts +0 -154
  6. package/extensions/copilot-proxy/node_modules/.bin/symi +0 -21
  7. package/extensions/copilot-proxy/package.json +0 -15
  8. package/extensions/copilot-proxy/symi.plugin.json +0 -9
  9. package/extensions/device-pair/index.ts +0 -642
  10. package/extensions/device-pair/symi.plugin.json +0 -20
  11. package/extensions/diagnostics-otel/index.ts +0 -15
  12. package/extensions/diagnostics-otel/node_modules/.bin/acorn +0 -21
  13. package/extensions/diagnostics-otel/node_modules/.bin/symi +0 -21
  14. package/extensions/diagnostics-otel/package.json +0 -27
  15. package/extensions/diagnostics-otel/src/service.test.ts +0 -290
  16. package/extensions/diagnostics-otel/src/service.ts +0 -666
  17. package/extensions/diagnostics-otel/symi.plugin.json +0 -8
  18. package/extensions/google-antigravity-auth/README.md +0 -24
  19. package/extensions/google-antigravity-auth/index.ts +0 -424
  20. package/extensions/google-antigravity-auth/node_modules/.bin/symi +0 -21
  21. package/extensions/google-antigravity-auth/package.json +0 -15
  22. package/extensions/google-antigravity-auth/symi.plugin.json +0 -9
  23. package/extensions/google-gemini-cli-auth/README.md +0 -35
  24. package/extensions/google-gemini-cli-auth/index.ts +0 -75
  25. package/extensions/google-gemini-cli-auth/node_modules/.bin/symi +0 -21
  26. package/extensions/google-gemini-cli-auth/oauth.test.ts +0 -162
  27. package/extensions/google-gemini-cli-auth/oauth.ts +0 -636
  28. package/extensions/google-gemini-cli-auth/package.json +0 -15
  29. package/extensions/google-gemini-cli-auth/symi.plugin.json +0 -9
  30. package/extensions/learning-loop/index.ts +0 -159
  31. package/extensions/learning-loop/node_modules/.bin/symi +0 -21
  32. package/extensions/learning-loop/package.json +0 -18
  33. package/extensions/learning-loop/src/analytics/gateway-methods.ts +0 -230
  34. package/extensions/learning-loop/src/analytics/metrics-aggregator.ts +0 -153
  35. package/extensions/learning-loop/src/capture/run-tracker.ts +0 -181
  36. package/extensions/learning-loop/src/capture/serializer.ts +0 -74
  37. package/extensions/learning-loop/src/db.ts +0 -583
  38. package/extensions/learning-loop/src/feedback/explicit-feedback.ts +0 -58
  39. package/extensions/learning-loop/src/feedback/implicit-signals.ts +0 -89
  40. package/extensions/learning-loop/src/graph/edge-inference.ts +0 -189
  41. package/extensions/learning-loop/src/graph/graph-retrieval.ts +0 -144
  42. package/extensions/learning-loop/src/graph/graph-store.ts +0 -183
  43. package/extensions/learning-loop/src/hooks.ts +0 -244
  44. package/extensions/learning-loop/src/injection/cache.ts +0 -73
  45. package/extensions/learning-loop/src/injection/context-injector.ts +0 -104
  46. package/extensions/learning-loop/src/injection/prompt-builder.ts +0 -43
  47. package/extensions/learning-loop/src/learning/embedding-bridge.ts +0 -54
  48. package/extensions/learning-loop/src/learning/learning-extractor.ts +0 -217
  49. package/extensions/learning-loop/src/learning/learning-store.ts +0 -158
  50. package/extensions/learning-loop/src/learning/retrieval.ts +0 -87
  51. package/extensions/learning-loop/src/math/confidence-intervals.ts +0 -62
  52. package/extensions/learning-loop/src/math/ewma.ts +0 -51
  53. package/extensions/learning-loop/src/math/weighted-scorer.ts +0 -42
  54. package/extensions/learning-loop/src/schema.ts +0 -176
  55. package/extensions/learning-loop/src/scoring/normalization.ts +0 -32
  56. package/extensions/learning-loop/src/scoring/quality-engine.ts +0 -78
  57. package/extensions/learning-loop/src/scoring/signal-extractors.ts +0 -155
  58. package/extensions/learning-loop/src/test/context-injector.test.ts +0 -142
  59. package/extensions/learning-loop/src/test/fixes.test.ts +0 -1286
  60. package/extensions/learning-loop/src/test/graph.test.ts +0 -711
  61. package/extensions/learning-loop/src/test/integration.test.ts +0 -312
  62. package/extensions/learning-loop/src/test/learning-store.test.ts +0 -191
  63. package/extensions/learning-loop/src/test/math.test.ts +0 -148
  64. package/extensions/learning-loop/src/test/quality-engine.test.ts +0 -231
  65. package/extensions/learning-loop/src/test/run-tracker.test.ts +0 -143
  66. package/extensions/learning-loop/src/types.ts +0 -281
  67. package/extensions/learning-loop/symi.plugin.json +0 -46
  68. package/extensions/llm-task/README.md +0 -97
  69. package/extensions/llm-task/index.ts +0 -6
  70. package/extensions/llm-task/package.json +0 -12
  71. package/extensions/llm-task/src/llm-task-tool.test.ts +0 -138
  72. package/extensions/llm-task/src/llm-task-tool.ts +0 -249
  73. package/extensions/llm-task/symi.plugin.json +0 -21
  74. package/extensions/memory-lancedb/config.ts +0 -161
  75. package/extensions/memory-lancedb/index.test.ts +0 -330
  76. package/extensions/memory-lancedb/index.ts +0 -670
  77. package/extensions/memory-lancedb/node_modules/.bin/arrow2csv +0 -21
  78. package/extensions/memory-lancedb/node_modules/.bin/openai +0 -21
  79. package/extensions/memory-lancedb/node_modules/.bin/symi +0 -21
  80. package/extensions/memory-lancedb/package.json +0 -20
  81. package/extensions/memory-lancedb/symi.plugin.json +0 -71
  82. package/extensions/minimax-portal-auth/README.md +0 -33
  83. package/extensions/minimax-portal-auth/index.ts +0 -161
  84. package/extensions/minimax-portal-auth/node_modules/.bin/symi +0 -21
  85. package/extensions/minimax-portal-auth/oauth.ts +0 -247
  86. package/extensions/minimax-portal-auth/package.json +0 -15
  87. package/extensions/minimax-portal-auth/symi.plugin.json +0 -9
  88. package/extensions/model-equalizer/index.ts +0 -80
  89. package/extensions/model-equalizer/skills/model-equalizer/SKILL.md +0 -58
  90. package/extensions/model-equalizer/src/detection.ts +0 -62
  91. package/extensions/model-equalizer/src/enhancer.ts +0 -63
  92. package/extensions/model-equalizer/src/test/detection.test.ts +0 -218
  93. package/extensions/model-equalizer/src/test/enhancer.test.ts +0 -137
  94. package/extensions/model-equalizer/src/test/integration.test.ts +0 -185
  95. package/extensions/model-equalizer/src/types.ts +0 -24
  96. package/extensions/model-equalizer/symi.plugin.json +0 -12
  97. package/extensions/phone-control/index.ts +0 -421
  98. package/extensions/phone-control/symi.plugin.json +0 -10
  99. package/extensions/pipeline/README.md +0 -75
  100. package/extensions/pipeline/SKILL.md +0 -97
  101. package/extensions/pipeline/index.ts +0 -18
  102. package/extensions/pipeline/package.json +0 -11
  103. package/extensions/pipeline/src/pipeline-tool.test.ts +0 -345
  104. package/extensions/pipeline/src/pipeline-tool.ts +0 -266
  105. package/extensions/pipeline/src/windows-spawn.test.ts +0 -148
  106. package/extensions/pipeline/src/windows-spawn.ts +0 -193
  107. package/extensions/pipeline/symi.plugin.json +0 -10
  108. package/extensions/qwen-portal-auth/README.md +0 -24
  109. package/extensions/qwen-portal-auth/index.ts +0 -134
  110. package/extensions/qwen-portal-auth/oauth.ts +0 -190
  111. package/extensions/qwen-portal-auth/symi.plugin.json +0 -9
  112. package/extensions/talk-voice/index.ts +0 -150
  113. package/extensions/talk-voice/symi.plugin.json +0 -10
  114. package/extensions/thread-ownership/index.test.ts +0 -180
  115. package/extensions/thread-ownership/index.ts +0 -133
  116. 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
- });