@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,89 +0,0 @@
1
- /**
2
- * Implicit feedback detection from user behavior.
3
- *
4
- * Analyzes the message_received hook to detect:
5
- * - Re-ask: next message within 60s with Jaccard similarity > 0.5 => negative signal
6
- * - Topic change: Jaccard similarity < 0.2 => positive signal (user moved on)
7
- * - Follow-up continuation: similarity 0.2-0.5 => neutral
8
- */
9
-
10
- import crypto from "node:crypto";
11
- import { jaccardSimilarity, tokenize } from "../../../../src/memory/mmr.js";
12
- import type { DatabaseManager } from "../db.js";
13
- import type { LearningLoopConfig, FeedbackRecord } from "../types.js";
14
-
15
- export type ImplicitSignals = ReturnType<typeof createImplicitSignals>;
16
-
17
- type RecentPrompt = {
18
- runId: string;
19
- content: string;
20
- tokens: Set<string>;
21
- timestamp: number;
22
- };
23
-
24
- const RE_ASK_WINDOW_MS = 60_000;
25
- const RE_ASK_SIMILARITY_THRESHOLD = 0.5;
26
- const TOPIC_CHANGE_THRESHOLD = 0.2;
27
-
28
- export function createImplicitSignals(params: { db: DatabaseManager; config: LearningLoopConfig }) {
29
- const { db } = params;
30
- const recentPrompts: RecentPrompt[] = [];
31
- const MAX_RECENT = 20;
32
-
33
- /**
34
- * Record a prompt that was just sent to the LLM, for later comparison.
35
- */
36
- function recordPrompt(runId: string, content: string): void {
37
- recentPrompts.push({
38
- runId,
39
- content,
40
- tokens: tokenize(content),
41
- timestamp: Date.now(),
42
- });
43
-
44
- // Keep bounded
45
- while (recentPrompts.length > MAX_RECENT) {
46
- recentPrompts.shift();
47
- }
48
- }
49
-
50
- /**
51
- * Analyze an incoming user message against recent prompts.
52
- * Returns a feedback record if an implicit signal is detected, null otherwise.
53
- */
54
- function analyzeMessage(content: string, timestamp: number): FeedbackRecord | null {
55
- if (recentPrompts.length === 0) return null;
56
-
57
- const messageTokens = tokenize(content);
58
- const lastPrompt = recentPrompts[recentPrompts.length - 1]!;
59
- const timeDelta = timestamp - lastPrompt.timestamp;
60
-
61
- const similarity = jaccardSimilarity(messageTokens, lastPrompt.tokens);
62
-
63
- let score: number | null = null;
64
-
65
- if (timeDelta <= RE_ASK_WINDOW_MS && similarity > RE_ASK_SIMILARITY_THRESHOLD) {
66
- // Re-ask: user is repeating themselves => bad signal
67
- score = 0.0;
68
- } else if (similarity < TOPIC_CHANGE_THRESHOLD) {
69
- // Topic change: user moved on => good signal
70
- score = 1.0;
71
- } else {
72
- // Continuation: neutral, no signal
73
- return null;
74
- }
75
-
76
- const feedback: FeedbackRecord = {
77
- id: `fb_${Date.now()}_${crypto.randomBytes(4).toString("hex")}`,
78
- runId: lastPrompt.runId,
79
- source: "implicit",
80
- score,
81
- createdAt: timestamp,
82
- };
83
-
84
- db.insertFeedback(feedback);
85
- return feedback;
86
- }
87
-
88
- return { recordPrompt, analyzeMessage };
89
- }
@@ -1,189 +0,0 @@
1
- /**
2
- * Automatic edge creation from learning-loop events.
3
- * No manual edge management -- edges are inferred from:
4
- * - Temporal co-occurrence (same run)
5
- * - Semantic similarity (embedding cosine)
6
- * - Supersession (same category, higher confidence)
7
- * - Contradiction (opposing categories with similar content)
8
- * - Reinforcement (co-applied learnings)
9
- * - Causation (injected learnings -> extracted learnings)
10
- */
11
-
12
- import { cosineSimilarity } from "../../../../src/memory/internal.js";
13
- import type { LearningStore } from "../learning/learning-store.js";
14
- import type { LearningRecord } from "../types.js";
15
- import type { GraphStore } from "./graph-store.js";
16
-
17
- export type EdgeInference = ReturnType<typeof createEdgeInference>;
18
-
19
- const NEGATION_TOKENS = new Set([
20
- "not",
21
- "avoid",
22
- "never",
23
- "don't",
24
- "dont",
25
- "fails",
26
- "instead",
27
- "shouldn't",
28
- "shouldnt",
29
- "won't",
30
- "wont",
31
- "cannot",
32
- "can't",
33
- "cant",
34
- ]);
35
-
36
- function tokenize(text: string): string[] {
37
- return text
38
- .toLowerCase()
39
- .replace(/[^\w\s]/g, " ")
40
- .split(/\s+/)
41
- .filter(Boolean);
42
- }
43
-
44
- function jaccardSimilarity(a: Set<string>, b: Set<string>): number {
45
- let intersection = 0;
46
- for (const token of a) {
47
- if (b.has(token)) intersection++;
48
- }
49
- const union = a.size + b.size - intersection;
50
- return union === 0 ? 0 : intersection / union;
51
- }
52
-
53
- export function createEdgeInference(params: {
54
- graphStore: GraphStore;
55
- learningStore: LearningStore;
56
- }) {
57
- const { graphStore, learningStore } = params;
58
-
59
- /**
60
- * Called after a new learning is created. Infers T, S, U, X edges.
61
- */
62
- function onLearningCreated(newLearning: LearningRecord, runId: string): void {
63
- const recentLearnings = learningStore.listLearnings({ limit: 100 });
64
-
65
- // 1. Temporal edges (T): other learnings from same run
66
- for (const existing of recentLearnings) {
67
- if (existing.id === newLearning.id) continue;
68
- if (existing.runId === runId) {
69
- graphStore.addEdge(existing.id, newLearning.id, "T", 0.5);
70
- }
71
- }
72
-
73
- // 2-4 require comparing against recent learnings
74
- for (const existing of recentLearnings) {
75
- if (existing.id === newLearning.id) continue;
76
-
77
- // 2. Semantic edges (S): cosine similarity between embeddings
78
- if (newLearning.embedding && existing.embedding) {
79
- const similarity = cosineSimilarity(newLearning.embedding, existing.embedding);
80
-
81
- if (similarity >= 0.6 && similarity < 0.92) {
82
- // Canonical ordering for bidirectional edges
83
- const [a, b] =
84
- existing.id < newLearning.id
85
- ? [existing.id, newLearning.id]
86
- : [newLearning.id, existing.id];
87
- graphStore.addEdge(a, b, "S", similarity);
88
- }
89
-
90
- // 3. Supersession edges (U): same category + high similarity + higher confidence
91
- if (
92
- similarity >= 0.8 &&
93
- existing.category === newLearning.category &&
94
- newLearning.confidence > existing.confidence
95
- ) {
96
- graphStore.addEdge(existing.id, newLearning.id, "U", 1.0);
97
- }
98
- }
99
-
100
- // 4. Contradiction edges (X)
101
- if (detectContradiction(newLearning, existing)) {
102
- const [a, b] =
103
- existing.id < newLearning.id
104
- ? [existing.id, newLearning.id]
105
- : [newLearning.id, existing.id];
106
- const weight =
107
- newLearning.embedding && existing.embedding
108
- ? Math.max(0.5, cosineSimilarity(newLearning.embedding, existing.embedding))
109
- : 0.7;
110
- graphStore.addEdge(a, b, "X", weight);
111
- }
112
- }
113
- }
114
-
115
- /**
116
- * Called after a run is scored. Infers R and C edges.
117
- * @param runId - The run that was scored
118
- * @param appliedIds - Learning IDs that were injected into this run
119
- * @param extractedIds - Learning IDs that were extracted from this run
120
- */
121
- function onRunScored(runId: string, appliedIds: string[], extractedIds: string[]): void {
122
- // 1. Reinforcement edges (R): between co-applied learnings
123
- for (let i = 0; i < appliedIds.length; i++) {
124
- for (let j = i + 1; j < appliedIds.length; j++) {
125
- const a = appliedIds[i]!;
126
- const b = appliedIds[j]!;
127
- const [source, target] = a < b ? [a, b] : [b, a];
128
-
129
- // Check existing edge weight to increment
130
- const existing = graphStore
131
- .getEdges(source, { typeFilter: ["R"] })
132
- .find((e) => e.sourceId === source && e.targetId === target);
133
-
134
- if (existing) {
135
- const newWeight = Math.min(0.8, existing.weight + 0.1);
136
- graphStore.upsertEdge(source, target, "R", newWeight);
137
- } else {
138
- graphStore.addEdge(source, target, "R", 0.3);
139
- }
140
- }
141
- }
142
-
143
- // 2. Causal edges (C): from injected learnings to extracted learnings
144
- for (const injectedId of appliedIds) {
145
- for (const extractedId of extractedIds) {
146
- graphStore.addEdge(injectedId, extractedId, "C", 0.6);
147
- }
148
- }
149
- }
150
-
151
- /**
152
- * Detect contradiction between two learnings via two heuristics:
153
- * 1. Category opposition (tool_pattern vs anti_pattern) with semantic similarity
154
- * 2. Negation density with high non-negation token overlap
155
- */
156
- function detectContradiction(a: LearningRecord, b: LearningRecord): boolean {
157
- // Heuristic 1: Category opposition
158
- const opposingCategories =
159
- (a.category === "tool_pattern" && b.category === "anti_pattern") ||
160
- (a.category === "anti_pattern" && b.category === "tool_pattern");
161
-
162
- if (opposingCategories && a.embedding && b.embedding) {
163
- const sim = cosineSimilarity(a.embedding, b.embedding);
164
- if (sim >= 0.7) return true;
165
- }
166
-
167
- // Heuristic 2: Negation density
168
- const tokensA = tokenize(a.content);
169
- const tokensB = tokenize(b.content);
170
-
171
- const negationsA = tokensA.filter((t) => NEGATION_TOKENS.has(t));
172
- const negationsB = tokensB.filter((t) => NEGATION_TOKENS.has(t));
173
-
174
- // One has negations, the other doesn't (or significantly fewer)
175
- const hasNegationAsymmetry =
176
- (negationsA.length > 0 && negationsB.length === 0) ||
177
- (negationsB.length > 0 && negationsA.length === 0);
178
-
179
- if (hasNegationAsymmetry) {
180
- const nonNegA = new Set(tokensA.filter((t) => !NEGATION_TOKENS.has(t)));
181
- const nonNegB = new Set(tokensB.filter((t) => !NEGATION_TOKENS.has(t)));
182
- if (jaccardSimilarity(nonNegA, nonNegB) >= 0.5) return true;
183
- }
184
-
185
- return false;
186
- }
187
-
188
- return { onLearningCreated, onRunScored };
189
- }
@@ -1,144 +0,0 @@
1
- /**
2
- * Hop expansion integrated into the retrieval pipeline.
3
- *
4
- * Takes seed results from vector/FTS retrieval, expands via graph neighbors,
5
- * computes expanded scores with hop decay and type multipliers, then merges
6
- * and re-applies MMR for diversity.
7
- */
8
-
9
- import { mmrRerank, type MMRItem } from "../../../../src/memory/mmr.js";
10
- import type { LearningStore } from "../learning/learning-store.js";
11
- import type { RetrievalResult } from "../learning/retrieval.js";
12
- import type { EdgeType, LearningLoopConfig } from "../types.js";
13
- import type { GraphStore } from "./graph-store.js";
14
-
15
- export type GraphRetrieval = ReturnType<typeof createGraphRetrieval>;
16
-
17
- const HOP_DECAY = 0.6;
18
- const CENTRALITY_HIGH_THRESHOLD = 2.0;
19
-
20
- const TYPE_MULTIPLIERS: Record<EdgeType, number> = {
21
- C: 1.0,
22
- S: 0.8,
23
- R: 0.7,
24
- T: 0.5,
25
- U: 0.3,
26
- X: 0.0, // Contradictions excluded from expansion
27
- };
28
-
29
- export function createGraphRetrieval(params: {
30
- graphStore: GraphStore;
31
- learningStore: LearningStore;
32
- config: LearningLoopConfig;
33
- }) {
34
- const { graphStore, learningStore } = params;
35
-
36
- /**
37
- * Expand seed retrieval results with graph neighbors.
38
- *
39
- * 1. Take top-5 seeds
40
- * 2. 1-hop expansion (2-hop for high-centrality seeds)
41
- * 3. Compute expanded scores with decay, type multiplier, centrality boost
42
- * 4. Merge with seeds, dedup, re-apply MMR
43
- * 5. Annotate contested results (X-edges)
44
- */
45
- function expandWithGraph(seeds: RetrievalResult[], limit: number): RetrievalResult[] {
46
- if (seeds.length === 0) return seeds;
47
-
48
- const topSeeds = seeds.slice(0, 5);
49
- const expanded = new Map<string, RetrievalResult>();
50
-
51
- // Keep all original seeds
52
- for (const seed of seeds) {
53
- expanded.set(seed.learning.id, seed);
54
- }
55
-
56
- // Expand each top seed
57
- for (const seed of topSeeds) {
58
- const seedId = seed.learning.id;
59
- const seedCentrality = graphStore.getCentrality(seedId);
60
- const maxHops = seedCentrality > CENTRALITY_HIGH_THRESHOLD ? 2 : 1;
61
-
62
- // Check for contradictions and annotate seed
63
- const contradictions = graphStore.getContradictions(seedId);
64
- if (contradictions.length > 0) {
65
- const existing = expanded.get(seedId)!;
66
- expanded.set(seedId, { ...existing, contested: true });
67
- }
68
-
69
- // Get edges for expansion
70
- const edges = graphStore.getEdges(seedId);
71
-
72
- for (const edge of edges) {
73
- if (TYPE_MULTIPLIERS[edge.edgeType] === 0) continue; // Skip contradictions
74
-
75
- const neighborId = edge.sourceId === seedId ? edge.targetId : edge.sourceId;
76
- const neighborLearning = learningStore.getLearning(neighborId);
77
- if (!neighborLearning) continue;
78
-
79
- const neighborCentrality = graphStore.getCentrality(neighborId);
80
- const centralityBoost = 1.0 + 0.1 * Math.log2(1 + neighborCentrality);
81
- const expandedScore =
82
- seed.decayedScore * HOP_DECAY * TYPE_MULTIPLIERS[edge.edgeType] * centralityBoost;
83
-
84
- const existing = expanded.get(neighborId);
85
- if (!existing || existing.decayedScore < expandedScore) {
86
- expanded.set(neighborId, {
87
- learning: neighborLearning,
88
- score: expandedScore,
89
- decayedScore: expandedScore,
90
- });
91
- }
92
-
93
- // 2-hop expansion for high-centrality seeds
94
- if (maxHops >= 2) {
95
- const hop2Edges = graphStore.getEdges(neighborId);
96
- for (const hop2Edge of hop2Edges) {
97
- if (TYPE_MULTIPLIERS[hop2Edge.edgeType] === 0) continue;
98
-
99
- const hop2Id = hop2Edge.sourceId === neighborId ? hop2Edge.targetId : hop2Edge.sourceId;
100
- if (hop2Id === seedId) continue;
101
-
102
- const hop2Learning = learningStore.getLearning(hop2Id);
103
- if (!hop2Learning) continue;
104
-
105
- const hop2Centrality = graphStore.getCentrality(hop2Id);
106
- const hop2CentralityBoost = 1.0 + 0.1 * Math.log2(1 + hop2Centrality);
107
- const hop2Score =
108
- seed.decayedScore *
109
- HOP_DECAY *
110
- HOP_DECAY *
111
- TYPE_MULTIPLIERS[hop2Edge.edgeType] *
112
- hop2CentralityBoost;
113
-
114
- const existingHop2 = expanded.get(hop2Id);
115
- if (!existingHop2 || existingHop2.decayedScore < hop2Score) {
116
- expanded.set(hop2Id, {
117
- learning: hop2Learning,
118
- score: hop2Score,
119
- decayedScore: hop2Score,
120
- });
121
- }
122
- }
123
- }
124
- }
125
- }
126
-
127
- // Convert to array and sort by score
128
- const allResults = Array.from(expanded.values());
129
- allResults.sort((a, b) => b.decayedScore - a.decayedScore);
130
-
131
- // Re-apply MMR reranking
132
- const mmrItems: (MMRItem & { original: RetrievalResult })[] = allResults.map((r) => ({
133
- id: r.learning.id,
134
- score: r.decayedScore,
135
- content: r.learning.content,
136
- original: r,
137
- }));
138
-
139
- const reranked = mmrRerank(mmrItems, { enabled: true, lambda: 0.7 });
140
- return reranked.slice(0, limit).map((item) => item.original);
141
- }
142
-
143
- return { expandWithGraph };
144
- }
@@ -1,183 +0,0 @@
1
- /**
2
- * Graph store: edge CRUD + incremental centrality tracking.
3
- *
4
- * Centrality model: log-weighted degree centrality.
5
- * centrality(node) = sum over edges(node): log2(1 + weight)
6
- *
7
- * Updated O(1) per edge add via delta. Log dampening prevents
8
- * high-degree nodes from dominating.
9
- */
10
-
11
- import type { DatabaseManager } from "../db.js";
12
- import type { EdgeType, LearningEdge, EdgeRow } from "../types.js";
13
-
14
- export type GraphStore = ReturnType<typeof createGraphStore>;
15
-
16
- function rowToEdge(row: EdgeRow): LearningEdge {
17
- return {
18
- id: row.id,
19
- sourceId: row.source_id,
20
- targetId: row.target_id,
21
- edgeType: row.edge_type as EdgeType,
22
- weight: row.weight,
23
- createdAt: row.created_at,
24
- };
25
- }
26
-
27
- export function createGraphStore(params: { db: DatabaseManager }) {
28
- const { db } = params;
29
-
30
- /**
31
- * Add an edge and incrementally update centrality for both endpoints.
32
- */
33
- function addEdge(source: string, target: string, type: EdgeType, weight: number): void {
34
- db.insertEdge(source, target, type, weight);
35
- const delta = Math.log2(1 + weight);
36
- updateCentralityDelta(source, delta);
37
- updateCentralityDelta(target, delta);
38
- }
39
-
40
- /**
41
- * Upsert an edge (update weight if exists). Adjusts centrality accordingly.
42
- */
43
- function upsertEdge(source: string, target: string, type: EdgeType, newWeight: number): void {
44
- // Check if edge already exists to compute delta
45
- const existing = db
46
- .getEdgesByNode(source)
47
- .find((e) => e.source_id === source && e.target_id === target && e.edge_type === type);
48
-
49
- if (existing) {
50
- const oldDelta = Math.log2(1 + existing.weight);
51
- const newDelta = Math.log2(1 + newWeight);
52
- const diff = newDelta - oldDelta;
53
- db.upsertEdge(source, target, type, newWeight);
54
- updateCentralityDelta(source, diff);
55
- updateCentralityDelta(target, diff);
56
- } else {
57
- db.upsertEdge(source, target, type, newWeight);
58
- const delta = Math.log2(1 + newWeight);
59
- updateCentralityDelta(source, delta);
60
- updateCentralityDelta(target, delta);
61
- }
62
- }
63
-
64
- /**
65
- * Get all edges for a learning, optionally filtered by type.
66
- */
67
- function getEdges(learningId: string, opts?: { typeFilter?: EdgeType[] }): LearningEdge[] {
68
- const rows = db.getEdgesByNode(learningId);
69
- let edges = rows.map(rowToEdge);
70
- if (opts?.typeFilter && opts.typeFilter.length > 0) {
71
- const allowed = new Set(opts.typeFilter);
72
- edges = edges.filter((e) => allowed.has(e.edgeType));
73
- }
74
- return edges;
75
- }
76
-
77
- /**
78
- * Get N-hop neighbors with accumulated weights.
79
- * Returns Map<learningId, accumulatedWeight>.
80
- */
81
- function getNeighbors(
82
- learningId: string,
83
- hops: number = 1,
84
- typeFilter?: EdgeType[],
85
- ): Map<string, number> {
86
- const result = new Map<string, number>();
87
- const visited = new Set<string>([learningId]);
88
- let frontier = new Map<string, number>([[learningId, 1.0]]);
89
-
90
- for (let hop = 0; hop < hops; hop++) {
91
- const nextFrontier = new Map<string, number>();
92
-
93
- for (const [nodeId, parentWeight] of frontier) {
94
- const edges = getEdges(nodeId, { typeFilter });
95
- for (const edge of edges) {
96
- const neighborId = edge.sourceId === nodeId ? edge.targetId : edge.sourceId;
97
- if (visited.has(neighborId)) continue;
98
-
99
- const weight = parentWeight * edge.weight;
100
- const existing = nextFrontier.get(neighborId) ?? 0;
101
- nextFrontier.set(neighborId, Math.max(existing, weight));
102
- }
103
- }
104
-
105
- for (const [nodeId, weight] of nextFrontier) {
106
- visited.add(nodeId);
107
- const existing = result.get(nodeId) ?? 0;
108
- result.set(nodeId, Math.max(existing, weight));
109
- }
110
-
111
- frontier = nextFrontier;
112
- if (frontier.size === 0) break;
113
- }
114
-
115
- return result;
116
- }
117
-
118
- /**
119
- * Get centrality score for a learning.
120
- */
121
- function getCentrality(learningId: string): number {
122
- return db.getCentralityScore(learningId);
123
- }
124
-
125
- /**
126
- * Remove all edges for a learning and recompute affected neighbors' centrality.
127
- */
128
- function removeEdgesFor(learningId: string): void {
129
- const edges = db.getEdgesByNode(learningId);
130
-
131
- // Collect affected neighbors
132
- const affected = new Set<string>();
133
- for (const edge of edges) {
134
- if (edge.source_id !== learningId) affected.add(edge.source_id);
135
- if (edge.target_id !== learningId) affected.add(edge.target_id);
136
- }
137
-
138
- // Delete all edges
139
- db.deleteEdgesForLearning(learningId);
140
- db.deleteCentralityScore(learningId);
141
-
142
- // Recompute centrality for affected neighbors
143
- for (const neighborId of affected) {
144
- recomputeCentrality(neighborId);
145
- }
146
- }
147
-
148
- /**
149
- * Get all contradiction edges, optionally for a specific learning.
150
- */
151
- function getContradictions(learningId?: string): LearningEdge[] {
152
- if (learningId) {
153
- return getEdges(learningId, { typeFilter: ["X"] });
154
- }
155
- return db.getEdgesByType("X").map(rowToEdge);
156
- }
157
-
158
- // --- Internal helpers ---
159
-
160
- function updateCentralityDelta(id: string, delta: number): void {
161
- const current = db.getCentralityScore(id);
162
- db.upsertCentralityScore(id, current + delta);
163
- }
164
-
165
- function recomputeCentrality(id: string): void {
166
- const edges = db.getEdgesByNode(id);
167
- let score = 0;
168
- for (const edge of edges) {
169
- score += Math.log2(1 + edge.weight);
170
- }
171
- db.upsertCentralityScore(id, score);
172
- }
173
-
174
- return {
175
- addEdge,
176
- upsertEdge,
177
- getEdges,
178
- getNeighbors,
179
- getCentrality,
180
- removeEdgesFor,
181
- getContradictions,
182
- };
183
- }