recallnest 3.0.0 → 4.0.0

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 (246) hide show
  1. package/CHANGELOG.md +304 -0
  2. package/README.md +251 -258
  3. package/README_CN.md +226 -247
  4. package/assets/ui/app.js +6 -1
  5. package/bin/recallnest.mjs +20 -37
  6. package/docs/api-reference.md +8 -0
  7. package/docs/architecture.md +18 -13
  8. package/eval/cases-canary.json +107 -9
  9. package/integrations/examples/claude-agent-sdk/memory-agent.ts +2 -0
  10. package/integrations/examples/langchain/memory-chain.py +4 -0
  11. package/integrations/examples/openai-agents-sdk/memory-agent.py +4 -0
  12. package/package.json +12 -3
  13. package/src/activity-counter.ts +1 -0
  14. package/src/adaptive-retrieval.ts +23 -0
  15. package/src/api-server.ts +13 -11
  16. package/src/asset-sync.ts +26 -2
  17. package/src/audit-log.ts +2 -0
  18. package/src/auto-gc.ts +4 -3
  19. package/src/belief-history.ts +137 -31
  20. package/src/capture-engine.ts +459 -71
  21. package/src/cascade-forget.ts +7 -3
  22. package/src/checkpoint-mirror.ts +417 -0
  23. package/src/cli.ts +574 -54
  24. package/src/conflict-engine.ts +43 -15
  25. package/src/consolidation-engine.ts +24 -3
  26. package/src/context-collapse-renderer.ts +8 -1
  27. package/src/context-composer-stable.ts +5 -1
  28. package/src/context-composer.ts +11 -2
  29. package/src/distill-lock.ts +246 -74
  30. package/src/dream-pipeline.ts +3 -1
  31. package/src/embedder.ts +24 -0
  32. package/src/env-config.ts +136 -0
  33. package/src/flock.ts +97 -0
  34. package/src/forget-engine.ts +342 -58
  35. package/src/gateway-server.ts +4 -0
  36. package/src/ingest-plan.ts +56 -0
  37. package/src/ingest.ts +294 -121
  38. package/src/kg-contradiction.ts +753 -0
  39. package/src/kg-extractor.ts +17 -0
  40. package/src/kg-store.ts +1 -1
  41. package/src/known-item-eval.ts +455 -0
  42. package/src/llm-client.ts +13 -26
  43. package/src/llm-consolidation.ts +2 -2
  44. package/src/mcp-tool-deps.ts +6 -0
  45. package/src/mcp-tools-advanced.ts +80 -45
  46. package/src/mcp-tools-core.ts +23 -17
  47. package/src/mcp-tools-governance.ts +13 -7
  48. package/src/memory-assets.ts +144 -8
  49. package/src/memory-boundaries.ts +19 -0
  50. package/src/memory-evolution.ts +9 -0
  51. package/src/memory-lint.ts +1 -1
  52. package/src/memory-output.ts +311 -14
  53. package/src/memory-reconcile.ts +1437 -0
  54. package/src/memory-schema.ts +11 -0
  55. package/src/memory-store-port.ts +1 -0
  56. package/src/minis-archive.ts +151 -0
  57. package/src/noise-filter.ts +15 -2
  58. package/src/pii-detector.ts +31 -1
  59. package/src/pivot-apply.ts +55 -5
  60. package/src/pivot-evidence.ts +128 -0
  61. package/src/ppr-traversal.ts +25 -1
  62. package/src/preference-matcher.ts +40 -28
  63. package/src/prospective-memory.ts +7 -4
  64. package/src/retriever.ts +297 -57
  65. package/src/retroactive-boost.ts +3 -2
  66. package/src/runtime-config.ts +15 -1
  67. package/src/scope-policy.ts +1 -0
  68. package/src/session-distiller.ts +6 -2
  69. package/src/session-engine.ts +11 -4
  70. package/src/session-output.ts +20 -1
  71. package/src/session-schema.ts +5 -2
  72. package/src/session-store.ts +59 -12
  73. package/src/skill-promotion.ts +67 -1
  74. package/src/store-receipts.ts +109 -0
  75. package/src/store-write-budget.ts +37 -0
  76. package/src/store.ts +298 -57
  77. package/src/text-fingerprint.ts +25 -0
  78. package/src/trigger-store.ts +443 -0
  79. package/src/ui-server.ts +22 -21
  80. package/src/version-manager.ts +19 -5
  81. package/src/write-verifier.ts +16 -11
  82. package/src/__tests__/access-tracker-novelty.test.ts +0 -324
  83. package/src/__tests__/activity-counter.test.ts +0 -125
  84. package/src/__tests__/adaptive-fidelity.test.ts +0 -102
  85. package/src/__tests__/admission-control.test.ts +0 -206
  86. package/src/__tests__/age-label.test.ts +0 -33
  87. package/src/__tests__/anchor-generator.test.ts +0 -67
  88. package/src/__tests__/api-server-security.test.ts +0 -102
  89. package/src/__tests__/auto-consolidation.test.ts +0 -168
  90. package/src/__tests__/auto-recall.test.ts +0 -131
  91. package/src/__tests__/babel-integration.test.ts +0 -144
  92. package/src/__tests__/batch-dedup.test.ts +0 -65
  93. package/src/__tests__/belief-history.test.ts +0 -220
  94. package/src/__tests__/canary-eval.test.ts +0 -114
  95. package/src/__tests__/capture-engine.test.ts +0 -1328
  96. package/src/__tests__/capture-heuristic.test.ts +0 -171
  97. package/src/__tests__/cascade-forget.test.ts +0 -210
  98. package/src/__tests__/category-min-scores.test.ts +0 -116
  99. package/src/__tests__/cc8-cc1-essential-context.test.ts +0 -298
  100. package/src/__tests__/circuit-breaker.test.ts +0 -99
  101. package/src/__tests__/cluster-consolidation.test.ts +0 -1236
  102. package/src/__tests__/confidence-metadata.test.ts +0 -125
  103. package/src/__tests__/confidence-tracker.test.ts +0 -261
  104. package/src/__tests__/conflict-advisor.test.ts +0 -206
  105. package/src/__tests__/conflict-escalation.test.ts +0 -149
  106. package/src/__tests__/conflict-lifecycle.test.ts +0 -83
  107. package/src/__tests__/conflict-output.test.ts +0 -85
  108. package/src/__tests__/conflict-store.test.ts +0 -418
  109. package/src/__tests__/connector-v1.test.ts +0 -181
  110. package/src/__tests__/consolidation-engine.test.ts +0 -520
  111. package/src/__tests__/constructive-retrieval.test.ts +0 -509
  112. package/src/__tests__/context-collapse-renderer.test.ts +0 -214
  113. package/src/__tests__/context-composer-pins.test.ts +0 -64
  114. package/src/__tests__/context-composer-task-selection.test.ts +0 -24
  115. package/src/__tests__/context-reconstructor.test.ts +0 -65
  116. package/src/__tests__/context-renderer.test.ts +0 -91
  117. package/src/__tests__/continuity-eval.test.ts +0 -275
  118. package/src/__tests__/continuity-schema.test.ts +0 -97
  119. package/src/__tests__/contracts.test.ts +0 -262
  120. package/src/__tests__/conversation-importer.test.ts +0 -692
  121. package/src/__tests__/core-recall-eval.test.ts +0 -235
  122. package/src/__tests__/core-summary.test.ts +0 -109
  123. package/src/__tests__/data-checkup.test.ts +0 -304
  124. package/src/__tests__/decay-exemption.test.ts +0 -129
  125. package/src/__tests__/dedup-actions.test.ts +0 -58
  126. package/src/__tests__/dedup-guard.test.ts +0 -39
  127. package/src/__tests__/deterministic-id.test.ts +0 -39
  128. package/src/__tests__/distill-lock.test.ts +0 -305
  129. package/src/__tests__/distill-session-argshape.test.ts +0 -79
  130. package/src/__tests__/doctor.test.ts +0 -319
  131. package/src/__tests__/dream-pipeline.test.ts +0 -820
  132. package/src/__tests__/dream-scope-isolation.test.ts +0 -386
  133. package/src/__tests__/dream-write-batching.test.ts +0 -211
  134. package/src/__tests__/drill-down.test.ts +0 -108
  135. package/src/__tests__/embedder.test.ts +0 -122
  136. package/src/__tests__/emotion-decay.test.ts +0 -46
  137. package/src/__tests__/emotion-detector.test.ts +0 -56
  138. package/src/__tests__/emotion-phase1-integration.test.ts +0 -153
  139. package/src/__tests__/emotion-retrieval.test.ts +0 -56
  140. package/src/__tests__/entity-resolver.test.ts +0 -58
  141. package/src/__tests__/env-config-invariant.test.ts +0 -80
  142. package/src/__tests__/env-config.test.ts +0 -211
  143. package/src/__tests__/error-signature-boost.test.ts +0 -87
  144. package/src/__tests__/error-signature.test.ts +0 -95
  145. package/src/__tests__/error-taxonomy.test.ts +0 -129
  146. package/src/__tests__/ethics-layer.test.ts +0 -476
  147. package/src/__tests__/event-segmenter.test.ts +0 -153
  148. package/src/__tests__/feature-flags-integration.test.ts +0 -54
  149. package/src/__tests__/frequency-tracker.test.ts +0 -113
  150. package/src/__tests__/freshness.test.ts +0 -186
  151. package/src/__tests__/graph-export.test.ts +0 -489
  152. package/src/__tests__/hotness-blend.test.ts +0 -283
  153. package/src/__tests__/ingest-chunking.test.ts +0 -44
  154. package/src/__tests__/ingest-scope-prefix.test.ts +0 -69
  155. package/src/__tests__/ingest.test.ts +0 -360
  156. package/src/__tests__/interference-detector.test.ts +0 -203
  157. package/src/__tests__/kg-extractor.test.ts +0 -192
  158. package/src/__tests__/kg-store.test.ts +0 -300
  159. package/src/__tests__/llm-client-pivot.test.ts +0 -628
  160. package/src/__tests__/llm-consolidation.test.ts +0 -226
  161. package/src/__tests__/maintenance-locks.test.ts +0 -197
  162. package/src/__tests__/mcp-efficiency.test.ts +0 -288
  163. package/src/__tests__/mcp-related-scopes.test.ts +0 -151
  164. package/src/__tests__/memory-boundaries.test.ts +0 -282
  165. package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +0 -362
  166. package/src/__tests__/memory-evolution.test.ts +0 -271
  167. package/src/__tests__/memory-health-rebalance.test.ts +0 -144
  168. package/src/__tests__/memory-lint.test.ts +0 -421
  169. package/src/__tests__/memory-output.test.ts +0 -224
  170. package/src/__tests__/memory-promotion.test.ts +0 -315
  171. package/src/__tests__/memory-utility.test.ts +0 -256
  172. package/src/__tests__/migrate-dedup-ids.test.ts +0 -65
  173. package/src/__tests__/multi-vector.test.ts +0 -242
  174. package/src/__tests__/narrative-architecture.test.ts +0 -456
  175. package/src/__tests__/noise-filter.test.ts +0 -205
  176. package/src/__tests__/noise-prototype-bank.test.ts +0 -280
  177. package/src/__tests__/obsidian-connector.test.ts +0 -363
  178. package/src/__tests__/omitted-reporting.test.ts +0 -98
  179. package/src/__tests__/openai-contract.test.ts +0 -513
  180. package/src/__tests__/pending-review.test.ts +0 -77
  181. package/src/__tests__/pii-redact.test.ts +0 -333
  182. package/src/__tests__/pivot-apply.test.ts +0 -373
  183. package/src/__tests__/pivot-distill-supervisor.test.ts +0 -457
  184. package/src/__tests__/pivot-distill.test.ts +0 -1178
  185. package/src/__tests__/post-retrieval-filter.test.ts +0 -178
  186. package/src/__tests__/ppr-traversal.test.ts +0 -256
  187. package/src/__tests__/prediction-engine.test.ts +0 -349
  188. package/src/__tests__/predictive-prospective-memory.test.ts +0 -416
  189. package/src/__tests__/preference-guard-smoke.test.ts +0 -211
  190. package/src/__tests__/preference-matcher.test.ts +0 -282
  191. package/src/__tests__/preference-slots.test.ts +0 -205
  192. package/src/__tests__/promotion-distinct-sources.test.ts +0 -162
  193. package/src/__tests__/prospective-memory.test.ts +0 -283
  194. package/src/__tests__/pull-from-macbook-sync-only.test.ts +0 -679
  195. package/src/__tests__/query-entity-detector.test.ts +0 -171
  196. package/src/__tests__/query-expander-alias.test.ts +0 -146
  197. package/src/__tests__/query-expander.test.ts +0 -34
  198. package/src/__tests__/recall-gate.test.ts +0 -266
  199. package/src/__tests__/recall-governor.test.ts +0 -227
  200. package/src/__tests__/recall-mode.test.ts +0 -64
  201. package/src/__tests__/resolve-db-path.test.ts +0 -130
  202. package/src/__tests__/resolve-tier.test.ts +0 -64
  203. package/src/__tests__/result-synthesizer.test.ts +0 -198
  204. package/src/__tests__/resume-context.test.ts +0 -4898
  205. package/src/__tests__/retrieval-eval.test.ts +0 -163
  206. package/src/__tests__/retrieval-trace.test.ts +0 -213
  207. package/src/__tests__/retrieve-audit-revision.test.ts +0 -255
  208. package/src/__tests__/retriever-boundary-weight.test.ts +0 -136
  209. package/src/__tests__/retriever-lazy-init-dispatch.test.ts +0 -123
  210. package/src/__tests__/retroactive-boost.test.ts +0 -223
  211. package/src/__tests__/salience-scoring.test.ts +0 -106
  212. package/src/__tests__/scope-inventory.test.ts +0 -200
  213. package/src/__tests__/scope-policy.test.ts +0 -69
  214. package/src/__tests__/scope-suggester.test.ts +0 -80
  215. package/src/__tests__/session-checkpoint.test.ts +0 -291
  216. package/src/__tests__/session-distiller.test.ts +0 -640
  217. package/src/__tests__/session-gc.test.ts +0 -215
  218. package/src/__tests__/skill-engine.test.ts +0 -552
  219. package/src/__tests__/skill-promotion.test.ts +0 -654
  220. package/src/__tests__/skill-verifier.test.ts +0 -122
  221. package/src/__tests__/source-heartbeat.test.ts +0 -254
  222. package/src/__tests__/stable-context-length-contract.test.ts +0 -62
  223. package/src/__tests__/store-patch-metadata.test.ts +0 -235
  224. package/src/__tests__/store-read-consistency.test.ts +0 -122
  225. package/src/__tests__/store-schema-migration.test.ts +0 -87
  226. package/src/__tests__/store-scope-audit.test.ts +0 -81
  227. package/src/__tests__/store-upsert.test.ts +0 -201
  228. package/src/__tests__/supersede-chain.test.ts +0 -154
  229. package/src/__tests__/synthesis-contract.test.ts +0 -252
  230. package/src/__tests__/synthesis-promotion.test.ts +0 -369
  231. package/src/__tests__/temporal-parser.test.ts +0 -241
  232. package/src/__tests__/temporal-validity.test.ts +0 -160
  233. package/src/__tests__/term-registry-terminal-roster.test.ts +0 -127
  234. package/src/__tests__/term-registry.test.ts +0 -121
  235. package/src/__tests__/tool-error-surfacing.test.ts +0 -98
  236. package/src/__tests__/tool-output-compressor.test.ts +0 -160
  237. package/src/__tests__/topic-tag.test.ts +0 -113
  238. package/src/__tests__/trusted-memory.test.ts +0 -867
  239. package/src/__tests__/ui-server-security.test.ts +0 -93
  240. package/src/__tests__/usage-tracker.test.ts +0 -287
  241. package/src/__tests__/utility-weight.test.ts +0 -57
  242. package/src/__tests__/verbatim-self-recall.test.ts +0 -279
  243. package/src/__tests__/version-manager.test.ts +0 -248
  244. package/src/__tests__/workflow-observation-skill-outcome.test.ts +0 -352
  245. package/src/__tests__/workflow-observation.test.ts +0 -433
  246. package/src/__tests__/write-verifier.test.ts +0 -122
@@ -1,178 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
-
3
- import { filterByRelevance, type FilterConfig } from "../post-retrieval-filter.js";
4
- import type { LLMClient } from "../llm-client.js";
5
- import type { RetrievalResult } from "../retriever.js";
6
-
7
- // ---------------------------------------------------------------------------
8
- // Helpers
9
- // ---------------------------------------------------------------------------
10
-
11
- function makeResult(id: string, text: string, score: number): RetrievalResult {
12
- return {
13
- entry: {
14
- id,
15
- text,
16
- vector: [],
17
- category: "events",
18
- scope: "test",
19
- importance: 0.5,
20
- timestamp: Date.now(),
21
- },
22
- score,
23
- sources: {},
24
- };
25
- }
26
-
27
- function makeMockLLM(response: boolean[] | null, shouldThrow = false): LLMClient {
28
- return {
29
- chatJson: async () => {
30
- if (shouldThrow) throw new Error("LLM failure");
31
- return response;
32
- },
33
- } as unknown as LLMClient;
34
- }
35
-
36
- // ---------------------------------------------------------------------------
37
- // Tests
38
- // ---------------------------------------------------------------------------
39
-
40
- describe("filterByRelevance", () => {
41
- it("returns empty array for empty results", async () => {
42
- const llm = makeMockLLM([]);
43
- const out = await filterByRelevance([], "some query", llm);
44
- expect(out).toEqual([]);
45
- });
46
-
47
- it("returns original results when query is empty", async () => {
48
- const results = [makeResult("a", "hello", 0.8)];
49
- const llm = makeMockLLM([true]);
50
- const out = await filterByRelevance(results, "", llm);
51
- expect(out).toEqual(results);
52
- });
53
-
54
- it("returns all when LLM marks all as relevant", async () => {
55
- const results = [
56
- makeResult("a", "TypeScript project setup", 0.9),
57
- makeResult("b", "Docker deployment notes", 0.85),
58
- ];
59
- const llm = makeMockLLM([true, true]);
60
- const out = await filterByRelevance(results, "project setup", llm);
61
- expect(out).toHaveLength(2);
62
- expect(out[0].entry.id).toBe("a");
63
- expect(out[1].entry.id).toBe("b");
64
- });
65
-
66
- it("filters out items LLM marks as irrelevant", async () => {
67
- const results = [
68
- makeResult("a", "TypeScript project setup", 0.9),
69
- makeResult("b", "Favorite pizza recipe", 0.85),
70
- makeResult("c", "Docker deployment notes", 0.8),
71
- ];
72
- const llm = makeMockLLM([true, false, true]);
73
- const out = await filterByRelevance(results, "project deployment", llm);
74
- expect(out).toHaveLength(2);
75
- expect(out[0].entry.id).toBe("a");
76
- expect(out[1].entry.id).toBe("c");
77
- });
78
-
79
- it("drops items below minScoreForFilter without LLM call", async () => {
80
- const results = [
81
- makeResult("a", "High score item", 0.9),
82
- makeResult("b", "Low score item", 0.3),
83
- makeResult("c", "Very low score", 0.1),
84
- ];
85
- // LLM only sees 1 item (the one above threshold)
86
- const llm = makeMockLLM([true]);
87
- const out = await filterByRelevance(results, "test query", llm);
88
- expect(out).toHaveLength(1);
89
- expect(out[0].entry.id).toBe("a");
90
- });
91
-
92
- it("returns all candidates when all are below minScoreForFilter", async () => {
93
- const results = [
94
- makeResult("a", "Low score A", 0.3),
95
- makeResult("b", "Low score B", 0.2),
96
- ];
97
- const llm = makeMockLLM([]);
98
- const out = await filterByRelevance(results, "test query", llm);
99
- expect(out).toHaveLength(0);
100
- });
101
-
102
- it("truncates to maxItems", async () => {
103
- const results = Array.from({ length: 15 }, (_, i) =>
104
- makeResult(`item-${i}`, `Memory item ${i}`, 0.9 - i * 0.01),
105
- );
106
- // LLM receives only 5 items, marks all as relevant
107
- const cfg: Partial<FilterConfig> = { maxItems: 5 };
108
- const llm = makeMockLLM([true, true, true, true, true]);
109
- const out = await filterByRelevance(results, "test query", llm, cfg);
110
- expect(out).toHaveLength(5);
111
- expect(out[0].entry.id).toBe("item-0");
112
- expect(out[4].entry.id).toBe("item-4");
113
- });
114
-
115
- it("returns candidates on LLM failure (graceful fallback)", async () => {
116
- const results = [
117
- makeResult("a", "Important memory", 0.9),
118
- makeResult("b", "Another memory", 0.85),
119
- ];
120
- const llm = makeMockLLM(null, true);
121
- const out = await filterByRelevance(results, "test query", llm);
122
- // Should return all candidates, not lose data
123
- expect(out).toHaveLength(2);
124
- expect(out[0].entry.id).toBe("a");
125
- });
126
-
127
- it("returns candidates when LLM returns non-array", async () => {
128
- const results = [
129
- makeResult("a", "Important memory", 0.9),
130
- ];
131
- const llm = makeMockLLM(null);
132
- const out = await filterByRelevance(results, "test query", llm);
133
- expect(out).toHaveLength(1);
134
- });
135
-
136
- it("uses l0_abstract from metadata when available", async () => {
137
- const result = makeResult("a", "Very long raw text that should not be used", 0.9);
138
- result.entry.metadata = JSON.stringify({ l0_abstract: "Short summary" });
139
- const calls: string[] = [];
140
- const llm = {
141
- chatJson: async (_sys: string, user: string) => {
142
- calls.push(user);
143
- return [true];
144
- },
145
- } as unknown as LLMClient;
146
-
147
- await filterByRelevance([result], "test", llm);
148
- expect(calls[0]).toContain("Short summary");
149
- expect(calls[0]).not.toContain("Very long raw text");
150
- });
151
-
152
- it("handles custom minScoreForFilter", async () => {
153
- const results = [
154
- makeResult("a", "High", 0.9),
155
- makeResult("b", "Medium", 0.6),
156
- makeResult("c", "Low", 0.4),
157
- ];
158
- const cfg: Partial<FilterConfig> = { minScoreForFilter: 0.65 };
159
- const llm = makeMockLLM([true]);
160
- const out = await filterByRelevance(results, "test", llm, cfg);
161
- expect(out).toHaveLength(1);
162
- expect(out[0].entry.id).toBe("a");
163
- });
164
-
165
- it("handles shorter LLM verdict array than candidates", async () => {
166
- const results = [
167
- makeResult("a", "Memory A", 0.9),
168
- makeResult("b", "Memory B", 0.85),
169
- makeResult("c", "Memory C", 0.8),
170
- ];
171
- // LLM returns fewer verdicts than items
172
- const llm = makeMockLLM([true, false]);
173
- const out = await filterByRelevance(results, "test", llm);
174
- // Only first item passes (index 0 = true, index 1 = false, index 2 = out of bounds)
175
- expect(out).toHaveLength(1);
176
- expect(out[0].entry.id).toBe("a");
177
- });
178
- });
@@ -1,256 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import {
3
- buildGraph,
4
- pprTraverse,
5
- pprFromTriples,
6
- edgeWeight,
7
- DEFAULT_PPR_CONFIG,
8
- } from "../ppr-traversal.js";
9
- import type { KGTriple, NeighborhoodResult } from "../kg-store.js";
10
-
11
- // ============================================================================
12
- // Helpers
13
- // ============================================================================
14
-
15
- function makeTriple(
16
- subject: string,
17
- predicate: string,
18
- object: string,
19
- confidence = 0.9,
20
- id?: string,
21
- ): KGTriple {
22
- return {
23
- id: id ?? `${subject}-${predicate}-${object}`,
24
- scope: "test",
25
- subject,
26
- predicate,
27
- object,
28
- confidence,
29
- source_memory_id: `mem-${subject}-${object}`,
30
- source_text: `${subject} ${predicate} ${object}`,
31
- timestamp: Date.now(),
32
- };
33
- }
34
-
35
- function makeNeighborhood(triples: KGTriple[]): NeighborhoodResult[] {
36
- const entities = new Set<string>();
37
- for (const t of triples) {
38
- entities.add(t.subject);
39
- entities.add(t.object);
40
- }
41
- return [...entities].map(entity => ({
42
- entity,
43
- triples: triples.filter(t => t.subject === entity || t.object === entity),
44
- hops: 0,
45
- }));
46
- }
47
-
48
- // ============================================================================
49
- // edgeWeight
50
- // ============================================================================
51
-
52
- describe("edgeWeight", () => {
53
- it("returns known weights for standard predicates", () => {
54
- expect(edgeWeight("created_by")).toBe(0.95);
55
- expect(edgeWeight("uses")).toBe(0.90);
56
- expect(edgeWeight("related_to")).toBe(0.50);
57
- });
58
-
59
- it("returns default weight for unknown predicates", () => {
60
- expect(edgeWeight("unknown_predicate")).toBe(0.60);
61
- });
62
- });
63
-
64
- // ============================================================================
65
- // buildGraph
66
- // ============================================================================
67
-
68
- describe("buildGraph", () => {
69
- it("builds undirected graph from neighborhood", () => {
70
- const triples = [
71
- makeTriple("Alice", "uses", "Python"),
72
- makeTriple("Alice", "works_with", "Bob"),
73
- ];
74
- const neighborhood = makeNeighborhood(triples);
75
- const graph = buildGraph(neighborhood);
76
-
77
- expect(graph.nodes.size).toBe(3); // Alice, Python, Bob
78
- expect(graph.adj.get("Alice")?.length).toBe(2); // -> Python, -> Bob
79
- expect(graph.adj.get("Python")?.length).toBe(1); // -> Alice
80
- expect(graph.adj.get("Bob")?.length).toBe(1); // -> Alice
81
- });
82
-
83
- it("deduplicates triples by id", () => {
84
- const t = makeTriple("Alice", "uses", "Python", 0.9, "same-id");
85
- const neighborhood: NeighborhoodResult[] = [
86
- { entity: "Alice", triples: [t], hops: 0 },
87
- { entity: "Python", triples: [t], hops: 1 },
88
- ];
89
- const graph = buildGraph(neighborhood);
90
-
91
- // Should only have 1 edge per direction despite appearing in 2 neighborhood entries
92
- expect(graph.adj.get("Alice")?.length).toBe(1);
93
- expect(graph.adj.get("Python")?.length).toBe(1);
94
- });
95
-
96
- it("handles empty neighborhood", () => {
97
- const graph = buildGraph([]);
98
- expect(graph.nodes.size).toBe(0);
99
- expect(graph.adj.size).toBe(0);
100
- });
101
- });
102
-
103
- // ============================================================================
104
- // pprTraverse — convergence
105
- // ============================================================================
106
-
107
- describe("pprTraverse", () => {
108
- it("converges on a simple chain A -> B -> C", () => {
109
- const triples = [
110
- makeTriple("A", "uses", "B"),
111
- makeTriple("B", "uses", "C"),
112
- ];
113
- const graph = buildGraph(makeNeighborhood(triples));
114
- const results = pprTraverse(graph, ["A"]);
115
-
116
- // A should have highest score (it's the seed)
117
- expect(results.length).toBeGreaterThan(0);
118
- expect(results[0].entity).toBe("A");
119
-
120
- // B should score higher than C (closer to seed)
121
- const bResult = results.find(r => r.entity === "B");
122
- const cResult = results.find(r => r.entity === "C");
123
- expect(bResult).toBeDefined();
124
- expect(cResult).toBeDefined();
125
- expect(bResult!.score).toBeGreaterThan(cResult!.score);
126
- });
127
-
128
- it("respects hop limits", () => {
129
- const triples = [
130
- makeTriple("A", "uses", "B"),
131
- makeTriple("B", "uses", "C"),
132
- makeTriple("C", "uses", "D"),
133
- ];
134
- const graph = buildGraph(makeNeighborhood(triples));
135
- const results = pprTraverse(graph, ["A"], { hopLimit: 1 });
136
-
137
- // With hopLimit=1, only A and B should be reachable
138
- const entities = results.map(r => r.entity);
139
- expect(entities).toContain("A");
140
- expect(entities).toContain("B");
141
- expect(entities).not.toContain("C");
142
- expect(entities).not.toContain("D");
143
- });
144
-
145
- it("handles multiple seed entities", () => {
146
- const triples = [
147
- makeTriple("A", "uses", "C"),
148
- makeTriple("B", "uses", "C"),
149
- ];
150
- const graph = buildGraph(makeNeighborhood(triples));
151
- const results = pprTraverse(graph, ["A", "B"]);
152
-
153
- // C should be reachable from both seeds
154
- const cResult = results.find(r => r.entity === "C");
155
- expect(cResult).toBeDefined();
156
- expect(cResult!.hops).toBe(1);
157
- });
158
-
159
- it("returns empty for empty graph", () => {
160
- const graph = buildGraph([]);
161
- const results = pprTraverse(graph, ["A"]);
162
- expect(results).toEqual([]);
163
- });
164
-
165
- it("returns empty when seeds not in graph", () => {
166
- const triples = [makeTriple("X", "uses", "Y")];
167
- const graph = buildGraph(makeNeighborhood(triples));
168
- const results = pprTraverse(graph, ["NotInGraph"]);
169
- expect(results).toEqual([]);
170
- });
171
-
172
- it("respects topK limit", () => {
173
- const triples = [
174
- makeTriple("A", "uses", "B"),
175
- makeTriple("A", "uses", "C"),
176
- makeTriple("A", "uses", "D"),
177
- makeTriple("A", "uses", "E"),
178
- ];
179
- const graph = buildGraph(makeNeighborhood(triples));
180
- const results = pprTraverse(graph, ["A"], { topK: 2 });
181
- expect(results.length).toBe(2);
182
- });
183
-
184
- it("computes shortest paths correctly", () => {
185
- const triples = [
186
- makeTriple("A", "uses", "B"),
187
- makeTriple("B", "uses", "C"),
188
- ];
189
- const graph = buildGraph(makeNeighborhood(triples));
190
- const results = pprTraverse(graph, ["A"]);
191
-
192
- const aResult = results.find(r => r.entity === "A");
193
- const bResult = results.find(r => r.entity === "B");
194
- const cResult = results.find(r => r.entity === "C");
195
-
196
- expect(aResult?.hops).toBe(0);
197
- expect(aResult?.path).toEqual(["A"]);
198
- expect(bResult?.hops).toBe(1);
199
- expect(bResult?.path).toEqual(["A", "B"]);
200
- expect(cResult?.hops).toBe(2);
201
- expect(cResult?.path).toEqual(["A", "B", "C"]);
202
- });
203
-
204
- it("edge weights affect scores — stronger predicates yield higher scores", () => {
205
- // Two separate subgraphs from same seed
206
- const triples = [
207
- makeTriple("A", "created_by", "B", 1.0), // weight 0.95
208
- makeTriple("A", "related_to", "C", 1.0), // weight 0.50
209
- ];
210
- const graph = buildGraph(makeNeighborhood(triples));
211
- const results = pprTraverse(graph, ["A"]);
212
-
213
- const bResult = results.find(r => r.entity === "B");
214
- const cResult = results.find(r => r.entity === "C");
215
- expect(bResult).toBeDefined();
216
- expect(cResult).toBeDefined();
217
- // B should score higher due to stronger edge weight
218
- expect(bResult!.score).toBeGreaterThan(cResult!.score);
219
- });
220
-
221
- it("all scores sum to approximately 1.0 (probability distribution)", () => {
222
- const triples = [
223
- makeTriple("A", "uses", "B"),
224
- makeTriple("B", "uses", "C"),
225
- makeTriple("C", "uses", "A"), // cycle
226
- ];
227
- const graph = buildGraph(makeNeighborhood(triples));
228
- const results = pprTraverse(graph, ["A"], { maxIterations: 50 });
229
-
230
- const totalScore = results.reduce((sum, r) => sum + r.score, 0);
231
- // PPR scores approximate a probability distribution
232
- expect(totalScore).toBeGreaterThan(0.8);
233
- expect(totalScore).toBeLessThan(1.2);
234
- });
235
- });
236
-
237
- // ============================================================================
238
- // pprFromTriples (convenience)
239
- // ============================================================================
240
-
241
- describe("pprFromTriples", () => {
242
- it("works with raw triples", () => {
243
- const triples = [
244
- makeTriple("Alice", "uses", "Python"),
245
- makeTriple("Alice", "works_at", "Google"),
246
- ];
247
- const results = pprFromTriples(triples, ["Alice"]);
248
-
249
- expect(results.length).toBeGreaterThan(0);
250
- expect(results[0].entity).toBe("Alice");
251
-
252
- const entities = results.map(r => r.entity);
253
- expect(entities).toContain("Python");
254
- expect(entities).toContain("Google");
255
- });
256
- });