recallnest 1.4.1 → 2.2.1

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 (233) hide show
  1. package/.env +4 -0
  2. package/CLAUDE.md +7 -3
  3. package/README.md +202 -130
  4. package/README_CN.md +263 -190
  5. package/bin/recallnest.mjs +62 -0
  6. package/config.json +55 -0
  7. package/daily-recall.sh +23 -0
  8. package/integrations/setup-all.sh +31 -0
  9. package/logs/daily-recall.error.log +0 -0
  10. package/logs/daily-recall.log +30 -0
  11. package/logs/ingest-2026-04-11.log +110 -0
  12. package/logs/ingest-2026-04-12.log +90 -0
  13. package/logs/launchagent-stderr.log +0 -0
  14. package/logs/launchagent-stdout.log +0 -0
  15. package/package.json +10 -3
  16. package/src/access-tracker.ts +3 -2
  17. package/src/activity-counter.ts +109 -0
  18. package/src/adaptive-retrieval.ts +6 -5
  19. package/src/admission-control.ts +163 -0
  20. package/src/anchor-generator.ts +80 -0
  21. package/src/api-server.ts +10 -0
  22. package/src/audit-log.ts +122 -0
  23. package/src/auto-gc.ts +98 -29
  24. package/src/auto-recall.ts +33 -4
  25. package/src/capture-engine.ts +372 -23
  26. package/src/cascade-forget.ts +3 -2
  27. package/src/cli.ts +107 -5
  28. package/src/compat.ts +22 -0
  29. package/src/confidence-tracker.ts +210 -47
  30. package/src/conflict-store.ts +12 -3
  31. package/src/consolidation-engine.ts +296 -8
  32. package/src/context-collapse-renderer.ts +222 -0
  33. package/src/context-composer-stable-selection.ts +5 -1
  34. package/src/context-composer.ts +239 -4
  35. package/src/context-reconstructor.ts +404 -0
  36. package/src/conversation-importer.ts +441 -0
  37. package/src/data-checkup.ts +232 -0
  38. package/src/decay-engine.ts +84 -0
  39. package/src/distill-lock.ts +122 -0
  40. package/src/doctor.ts +8 -10
  41. package/src/dream-pipeline.ts +227 -0
  42. package/src/emotion-detector.ts +90 -0
  43. package/src/eval.ts +3 -2
  44. package/src/forget-engine.ts +269 -0
  45. package/src/frequency-tracker.ts +175 -0
  46. package/src/graph-export.ts +644 -0
  47. package/src/ingest.ts +36 -6
  48. package/src/interference-detector.ts +187 -0
  49. package/src/kg-extractor.ts +15 -24
  50. package/src/kg-store.ts +2 -1
  51. package/src/language-hook.ts +67 -0
  52. package/src/llm-client.ts +289 -6
  53. package/src/mcp-server.ts +707 -80
  54. package/src/memory-assets.ts +22 -5
  55. package/src/memory-evolution.ts +365 -0
  56. package/src/memory-lint.ts +429 -0
  57. package/src/memory-output.ts +69 -0
  58. package/src/memory-schema.ts +74 -2
  59. package/src/narrative-schema.ts +79 -0
  60. package/src/narrative-tagger.ts +164 -0
  61. package/src/pii-detector.ts +98 -0
  62. package/src/post-retrieval-filter.ts +76 -0
  63. package/src/prediction-engine.ts +253 -0
  64. package/src/preference-slots.ts +65 -2
  65. package/src/prospective-memory.ts +215 -0
  66. package/src/query-expander.ts +67 -0
  67. package/src/recall-governor.ts +183 -0
  68. package/src/retention-policy.ts +90 -0
  69. package/src/retriever.ts +828 -55
  70. package/src/rif.ts +34 -8
  71. package/src/runtime-config.ts +23 -8
  72. package/src/scope-inventory.ts +4 -3
  73. package/src/scope-policy.ts +4 -0
  74. package/src/session-distiller.ts +307 -0
  75. package/src/session-output.ts +40 -0
  76. package/src/session-schema.ts +37 -0
  77. package/src/session-store.ts +7 -2
  78. package/src/skill-engine.ts +208 -0
  79. package/src/skill-promotion.ts +293 -0
  80. package/src/skill-schema.ts +39 -0
  81. package/src/store.ts +96 -10
  82. package/src/topic-tag.ts +66 -0
  83. package/src/tracker.ts +3 -1
  84. package/src/ui-server.ts +80 -8
  85. package/src/workflow-observation-scope-review.ts +2 -1
  86. package/src/workflow-observation-store.ts +2 -1
  87. package/src/write-verifier.ts +126 -0
  88. package/.claude-plugin/CLAUDE.md +0 -29
  89. package/.claude-plugin/marketplace.json +0 -18
  90. package/.github/workflows/ci.yml +0 -28
  91. package/.mcp.json +0 -9
  92. package/CHANGELOG.md +0 -51
  93. package/DEMO.md +0 -94
  94. package/EVAL.md +0 -89
  95. package/FAILURES.md +0 -75
  96. package/OPERATIONS.md +0 -59
  97. package/ROADMAP.md +0 -198
  98. package/assets/recallnest-workbench.png +0 -0
  99. package/assets/ui/app.js +0 -807
  100. package/assets/ui/index.html +0 -154
  101. package/assets/ui/styles.css +0 -605
  102. package/assets/wechat-qr.jpg +0 -0
  103. package/daily-reflections/2026-03-05.md +0 -15
  104. package/daily-reflections/2026-03-06.md +0 -15
  105. package/daily-reflections/2026-03-07.md +0 -7
  106. package/daily-reflections/2026-03-08.md +0 -34
  107. package/daily-reflections/2026-03-09.md +0 -35
  108. package/daily-reflections/2026-03-10.md +0 -36
  109. package/daily-reflections/2026-03-11.md +0 -35
  110. package/daily-reflections/2026-03-12.md +0 -36
  111. package/daily-reflections/2026-03-13.md +0 -35
  112. package/daily-reflections/2026-03-14.md +0 -35
  113. package/daily-reflections/2026-03-15.md +0 -35
  114. package/daily-reflections/2026-03-16.md +0 -41
  115. package/daily-reflections/2026-03-17.md +0 -10
  116. package/daily-reflections/2026-03-18.md +0 -10
  117. package/daily-reflections/2026-03-19.md +0 -10
  118. package/daily-reflections/2026-03-20.md +0 -10
  119. package/daily-reflections/2026-03-21.md +0 -10
  120. package/daily-reflections/2026-03-22.md +0 -10
  121. package/daily-reflections/2026-03-23.md +0 -10
  122. package/daily-reflections/2026-03-24.md +0 -10
  123. package/daily-reflections/2026-03-25.md +0 -32
  124. package/daily-reflections/2026-03-26.md +0 -37
  125. package/daily-reflections/2026-03-27.md +0 -35
  126. package/daily-reflections/2026-03-28.md +0 -35
  127. package/daily-reflections/2026-03-29.md +0 -35
  128. package/daily-reflections/2026-03-30.md +0 -36
  129. package/daily-reflections/2026-03-31.md +0 -35
  130. package/daily-reflections/2026-04-01.md +0 -35
  131. package/docs/api-reference.md +0 -1291
  132. package/docs/architecture.md +0 -168
  133. package/docs/memory-boundary-contract.md +0 -226
  134. package/docs/memory-categories.md +0 -119
  135. package/docs/pipeline-audit-ultramemory.md +0 -177
  136. package/docs/plans/2026-03-08-query-expansion.md +0 -270
  137. package/docs/plans/2026-03-16-continuity-layer.md +0 -196
  138. package/docs/plans/mcp-tiering-plan.md +0 -126
  139. package/docs/research/recall-quality-audit-2026-03-22.md +0 -153
  140. package/docs/self-evolution.md +0 -198
  141. package/docs/superpowers/plans/2026-03-19-ingest-pipeline-upgrade.md +0 -782
  142. package/docs/superpowers/specs/2026-03-19-ingest-pipeline-upgrade-design.md +0 -77
  143. package/docs/superpowers/specs/2026-03-23-pull-claude-desktop-design.md +0 -95
  144. package/eval/continuity/results-2026-03-16.md +0 -117
  145. package/eval/continuity/results-2026-03-19.md +0 -453
  146. package/eval/continuity/runbook.md +0 -110
  147. package/eval/continuity/seed-cases.md +0 -54
  148. package/eval/reports/2026-03-06-abstract-query-baseline.md +0 -96
  149. package/eval/reports/2026-03-06-aws-ssh-baseline.md +0 -82
  150. package/eval/reports/2026-03-06-baseline.md +0 -82
  151. package/eval/reports/query-expansion.md +0 -138
  152. package/lm +0 -19
  153. package/plan-v1.2-distributable.md +0 -105
  154. package/scripts/archive-distilled-junk.ts +0 -116
  155. package/scripts/backfill-l0l1l2.ts +0 -98
  156. package/scripts/blind-scan.ts +0 -588
  157. package/scripts/cleanup-step1-exact-dups.ts +0 -117
  158. package/scripts/cleanup-step2-template-dups.ts +0 -113
  159. package/scripts/cleanup-step3-stale-content.ts +0 -149
  160. package/scripts/cleanup-step4-orphan-scopes.ts +0 -131
  161. package/scripts/delete-distilled-junk.ts +0 -88
  162. package/scripts/distill-facts.ts +0 -305
  163. package/scripts/health-check.ts +0 -343
  164. package/scripts/incremental-ingest.sh +0 -71
  165. package/scripts/mlp-health-check.ts +0 -382
  166. package/scripts/pull-claude-desktop.ts +0 -515
  167. package/scripts/re-ingest-cc.sh +0 -66
  168. package/scripts/rebalance-memory-health.ts +0 -181
  169. package/scripts/run-desktop-ingest.sh +0 -11
  170. package/scripts/start-server.sh +0 -12
  171. package/scripts/viz-dashboard.html +0 -423
  172. package/scripts/viz-export.ts +0 -703
  173. package/src/__tests__/access-tracker-novelty.test.ts +0 -228
  174. package/src/__tests__/auto-consolidation.test.ts +0 -168
  175. package/src/__tests__/auto-recall.test.ts +0 -131
  176. package/src/__tests__/batch-dedup.test.ts +0 -65
  177. package/src/__tests__/capture-engine.test.ts +0 -1142
  178. package/src/__tests__/capture-heuristic.test.ts +0 -171
  179. package/src/__tests__/cascade-forget.test.ts +0 -210
  180. package/src/__tests__/category-min-scores.test.ts +0 -33
  181. package/src/__tests__/confidence-tracker.test.ts +0 -196
  182. package/src/__tests__/conflict-advisor.test.ts +0 -206
  183. package/src/__tests__/conflict-escalation.test.ts +0 -149
  184. package/src/__tests__/conflict-lifecycle.test.ts +0 -83
  185. package/src/__tests__/conflict-output.test.ts +0 -85
  186. package/src/__tests__/conflict-store.test.ts +0 -374
  187. package/src/__tests__/consolidation-engine.test.ts +0 -181
  188. package/src/__tests__/context-composer-pins.test.ts +0 -64
  189. package/src/__tests__/context-composer-task-selection.test.ts +0 -24
  190. package/src/__tests__/context-renderer.test.ts +0 -91
  191. package/src/__tests__/continuity-eval.test.ts +0 -275
  192. package/src/__tests__/continuity-schema.test.ts +0 -97
  193. package/src/__tests__/core-recall-eval.test.ts +0 -235
  194. package/src/__tests__/core-summary.test.ts +0 -109
  195. package/src/__tests__/dedup-actions.test.ts +0 -58
  196. package/src/__tests__/deterministic-id.test.ts +0 -39
  197. package/src/__tests__/doctor.test.ts +0 -303
  198. package/src/__tests__/drill-down.test.ts +0 -108
  199. package/src/__tests__/embedder.test.ts +0 -122
  200. package/src/__tests__/entity-resolver.test.ts +0 -58
  201. package/src/__tests__/event-segmenter.test.ts +0 -153
  202. package/src/__tests__/hotness-blend.test.ts +0 -76
  203. package/src/__tests__/ingest.test.ts +0 -360
  204. package/src/__tests__/kg-extractor.test.ts +0 -184
  205. package/src/__tests__/kg-store.test.ts +0 -198
  206. package/src/__tests__/llm-consolidation.test.ts +0 -226
  207. package/src/__tests__/memory-boundaries.test.ts +0 -237
  208. package/src/__tests__/memory-health-rebalance.test.ts +0 -144
  209. package/src/__tests__/memory-output.test.ts +0 -215
  210. package/src/__tests__/multi-vector.test.ts +0 -242
  211. package/src/__tests__/ppr-traversal.test.ts +0 -256
  212. package/src/__tests__/preference-guard-smoke.test.ts +0 -211
  213. package/src/__tests__/preference-matcher.test.ts +0 -230
  214. package/src/__tests__/preference-slots.test.ts +0 -106
  215. package/src/__tests__/prospective-memory.test.ts +0 -283
  216. package/src/__tests__/query-entity-detector.test.ts +0 -171
  217. package/src/__tests__/query-expander.test.ts +0 -34
  218. package/src/__tests__/recall-mode.test.ts +0 -64
  219. package/src/__tests__/result-synthesizer.test.ts +0 -198
  220. package/src/__tests__/resume-context.test.ts +0 -4743
  221. package/src/__tests__/retrieval-trace.test.ts +0 -213
  222. package/src/__tests__/retriever-boundary-weight.test.ts +0 -136
  223. package/src/__tests__/retroactive-boost.test.ts +0 -223
  224. package/src/__tests__/scope-inventory.test.ts +0 -200
  225. package/src/__tests__/scope-policy.test.ts +0 -69
  226. package/src/__tests__/session-checkpoint.test.ts +0 -259
  227. package/src/__tests__/session-gc.test.ts +0 -215
  228. package/src/__tests__/store-scope-audit.test.ts +0 -81
  229. package/src/__tests__/temporal-parser.test.ts +0 -241
  230. package/src/__tests__/term-registry.test.ts +0 -121
  231. package/src/__tests__/tool-output-compressor.test.ts +0 -133
  232. package/src/__tests__/version-manager.test.ts +0 -213
  233. package/src/__tests__/workflow-observation.test.ts +0 -400
@@ -1,213 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { TraceCollector } from "../retrieval-trace.js";
3
- import { createRetriever } from "../retriever.js";
4
-
5
- describe("TraceCollector", () => {
6
- it("accumulates stages correctly", () => {
7
- const trace = new TraceCollector();
8
- trace.startStage("vector_search", 0);
9
- trace.endStage(20, [0.3, 0.9]);
10
-
11
- trace.startStage("min_score_filter", 20);
12
- trace.endStage(15, [0.35, 0.9]);
13
-
14
- const result = trace.finalize("test query", "hybrid");
15
- expect(result.query).toBe("test query");
16
- expect(result.mode).toBe("hybrid");
17
- expect(result.stages).toHaveLength(2);
18
- expect(result.finalCount).toBe(15);
19
- expect(result.totalMs).toBeGreaterThanOrEqual(0);
20
- });
21
-
22
- it("computes droppedCount as input - output", () => {
23
- const trace = new TraceCollector();
24
- trace.startStage("hard_min_score", 20);
25
- trace.endStage(12);
26
-
27
- const result = trace.finalize("q", "vector");
28
- expect(result.stages[0].droppedCount).toBe(8);
29
- });
30
-
31
- it("computes scoreRange from surviving scores", () => {
32
- const trace = new TraceCollector();
33
- trace.startStage("rerank", 10);
34
- trace.endStage(8, [0.2, 0.5, 0.8, 0.3, 0.6, 0.7, 0.4, 0.9]);
35
-
36
- const stage = trace.finalize("q", "hybrid").stages[0];
37
- expect(stage.scoreRange).not.toBeNull();
38
- expect(stage.scoreRange![0]).toBeCloseTo(0.2);
39
- expect(stage.scoreRange![1]).toBeCloseTo(0.9);
40
- });
41
-
42
- it("returns null scoreRange when no scores provided", () => {
43
- const trace = new TraceCollector();
44
- trace.startStage("noise_filter", 5);
45
- trace.endStage(3);
46
-
47
- expect(trace.finalize("q", "hybrid").stages[0].scoreRange).toBeNull();
48
- });
49
-
50
- it("summarize() produces readable multi-line output", () => {
51
- const trace = new TraceCollector();
52
- trace.startStage("vector_search", 0);
53
- trace.endStage(20, [0.31, 0.89]);
54
- trace.startStage("min_score_filter", 20);
55
- trace.endStage(18, [0.35, 0.89]);
56
-
57
- const summary = trace.summarize("auth middleware", "hybrid");
58
- expect(summary).toContain('[retrieve] query="auth middleware" mode=hybrid');
59
- expect(summary).toContain("vector_search: 0 → 20");
60
- expect(summary).toContain("min_score_filter: 20 → 18 (2 dropped)");
61
- expect(summary).toContain("final: 18 results");
62
- });
63
- });
64
-
65
- describe("retriever with trace", () => {
66
- it("populates trace stages without changing retrieval behavior", async () => {
67
- const trace = new TraceCollector();
68
-
69
- const retriever = createRetriever(
70
- {
71
- hasFtsSupport: false,
72
- async vectorSearch() {
73
- return [
74
- {
75
- entry: {
76
- id: "m1",
77
- text: "test memory",
78
- vector: [1, 0, 0],
79
- category: "entities",
80
- scope: "project:test",
81
- importance: 0.8,
82
- timestamp: Date.now(),
83
- metadata: "{}",
84
- },
85
- score: 0.8,
86
- },
87
- ];
88
- },
89
- } as any,
90
- {
91
- async embedQuery() { return [1, 0, 0]; },
92
- async embedPassage() { return [1, 0, 0]; },
93
- } as any,
94
- {
95
- mode: "vector",
96
- rerank: "none",
97
- filterNoise: false,
98
- hardMinScore: 0,
99
- minScore: 0,
100
- recencyWeight: 0,
101
- timeDecayHalfLifeDays: 0,
102
- lengthNormAnchor: 0,
103
- },
104
- );
105
-
106
- const results = await retriever.retrieve({
107
- query: "authentication middleware config",
108
- limit: 5,
109
- trace,
110
- });
111
-
112
- // Results unchanged
113
- expect(results).toHaveLength(1);
114
- expect(results[0].entry.id).toBe("m1");
115
-
116
- // Trace populated
117
- const traceResult = trace.finalize("test", "vector");
118
- expect(traceResult.stages.length).toBeGreaterThan(0);
119
- expect(traceResult.stages[0].name).toBe("vector_search");
120
- expect(traceResult.finalCount).toBe(1);
121
- });
122
-
123
- it("works identically without trace (no overhead)", async () => {
124
- const retriever = createRetriever(
125
- {
126
- hasFtsSupport: false,
127
- async vectorSearch() {
128
- return [
129
- {
130
- entry: {
131
- id: "m1",
132
- text: "test memory",
133
- vector: [1, 0, 0],
134
- category: "entities",
135
- scope: "project:test",
136
- importance: 0.8,
137
- timestamp: Date.now(),
138
- metadata: "{}",
139
- },
140
- score: 0.8,
141
- },
142
- ];
143
- },
144
- } as any,
145
- {
146
- async embedQuery() { return [1, 0, 0]; },
147
- async embedPassage() { return [1, 0, 0]; },
148
- } as any,
149
- {
150
- mode: "vector",
151
- rerank: "none",
152
- filterNoise: false,
153
- hardMinScore: 0,
154
- minScore: 0,
155
- recencyWeight: 0,
156
- timeDecayHalfLifeDays: 0,
157
- lengthNormAnchor: 0,
158
- },
159
- );
160
-
161
- // No trace passed — should work exactly as before
162
- const results = await retriever.retrieve({
163
- query: "authentication middleware config",
164
- limit: 5,
165
- });
166
-
167
- expect(results).toHaveLength(1);
168
- expect(results[0].entry.id).toBe("m1");
169
- });
170
-
171
- it("trace captures hard_min_score drops", async () => {
172
- const trace = new TraceCollector();
173
-
174
- const retriever = createRetriever(
175
- {
176
- hasFtsSupport: false,
177
- async vectorSearch() {
178
- return [
179
- {
180
- entry: { id: "high", text: "good", vector: [1, 0, 0], category: "entities", scope: "p:t", importance: 0.8, timestamp: Date.now(), metadata: "{}" },
181
- score: 0.9,
182
- },
183
- {
184
- entry: { id: "low", text: "weak", vector: [0, 1, 0], category: "entities", scope: "p:t", importance: 0.5, timestamp: Date.now(), metadata: "{}" },
185
- score: 0.2,
186
- },
187
- ];
188
- },
189
- } as any,
190
- {
191
- async embedQuery() { return [1, 0, 0]; },
192
- async embedPassage() { return [1, 0, 0]; },
193
- } as any,
194
- {
195
- mode: "vector",
196
- rerank: "none",
197
- filterNoise: false,
198
- hardMinScore: 0.35,
199
- minScore: 0,
200
- recencyWeight: 0,
201
- timeDecayHalfLifeDays: 0,
202
- lengthNormAnchor: 0,
203
- },
204
- );
205
-
206
- await retriever.retrieve({ query: "authentication middleware config", limit: 5, trace });
207
-
208
- const result = trace.finalize("test", "vector");
209
- const hardStage = result.stages.find(s => s.name === "hard_min_score");
210
- expect(hardStage).toBeDefined();
211
- expect(hardStage!.droppedCount).toBeGreaterThan(0);
212
- });
213
- });
@@ -1,136 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
-
3
- import { createRetriever } from "../retriever.js";
4
-
5
- function buildEntry(id: string, scoreText: string, scope: string, metadata: Record<string, unknown>) {
6
- return {
7
- entry: {
8
- id,
9
- text: scoreText,
10
- vector: [1, 0, 0],
11
- category: "preferences",
12
- scope,
13
- importance: 0.8,
14
- timestamp: Date.parse("2026-03-16T00:00:00.000Z"),
15
- metadata: JSON.stringify(metadata),
16
- },
17
- score: 0.8,
18
- };
19
- }
20
-
21
- describe("retriever provenance weighting", () => {
22
- it("prefers durable structured memories over evidence when scores are close", async () => {
23
- const retriever = createRetriever({
24
- hasFtsSupport: false,
25
- async vectorSearch() {
26
- return [
27
- {
28
- ...buildEntry("evidence-1", "Transcript hint", "cc:session1", {
29
- source: "cc",
30
- boundary: {
31
- layer: "evidence",
32
- authority: "transcript-ingest",
33
- conflictPolicy: "append-only",
34
- originalCategory: "preferences",
35
- },
36
- }),
37
- score: 0.8,
38
- },
39
- {
40
- ...buildEntry("durable-1", "Curated durable preference", "memory:agent", {
41
- source: "agent",
42
- boundary: {
43
- layer: "durable",
44
- authority: "structured-memory",
45
- conflictPolicy: "latest-wins",
46
- originalCategory: "preferences",
47
- },
48
- }),
49
- score: 0.79,
50
- },
51
- ];
52
- },
53
- } as any, {
54
- async embedQuery() {
55
- return [1, 0, 0];
56
- },
57
- async embedPassage() {
58
- return [1, 0, 0];
59
- },
60
- } as any, {
61
- mode: "vector",
62
- rerank: "none",
63
- filterNoise: false,
64
- hardMinScore: 0,
65
- minScore: 0,
66
- recencyWeight: 0,
67
- timeDecayHalfLifeDays: 0,
68
- });
69
-
70
- const results = await retriever.retrieve({
71
- query: "user reply style preference",
72
- limit: 5,
73
- category: "preferences",
74
- });
75
-
76
- expect(results[0]?.entry.id).toBe("durable-1");
77
- expect(results[1]?.entry.id).toBe("evidence-1");
78
- });
79
-
80
- it("does not override clearly stronger relevance with provenance weighting", async () => {
81
- const retriever = createRetriever({
82
- hasFtsSupport: false,
83
- async vectorSearch() {
84
- return [
85
- {
86
- ...buildEntry("evidence-strong", "Very strong transcript evidence", "cc:session1", {
87
- source: "cc",
88
- boundary: {
89
- layer: "evidence",
90
- authority: "transcript-ingest",
91
- conflictPolicy: "append-only",
92
- originalCategory: "preferences",
93
- },
94
- }),
95
- score: 0.95,
96
- },
97
- {
98
- ...buildEntry("durable-weaker", "Weaker durable preference", "memory:agent", {
99
- source: "agent",
100
- boundary: {
101
- layer: "durable",
102
- authority: "structured-memory",
103
- conflictPolicy: "latest-wins",
104
- originalCategory: "preferences",
105
- },
106
- }),
107
- score: 0.7,
108
- },
109
- ];
110
- },
111
- } as any, {
112
- async embedQuery() {
113
- return [1, 0, 0];
114
- },
115
- async embedPassage() {
116
- return [1, 0, 0];
117
- },
118
- } as any, {
119
- mode: "vector",
120
- rerank: "none",
121
- filterNoise: false,
122
- hardMinScore: 0,
123
- minScore: 0,
124
- recencyWeight: 0,
125
- timeDecayHalfLifeDays: 0,
126
- });
127
-
128
- const results = await retriever.retrieve({
129
- query: "user reply style preference",
130
- limit: 5,
131
- category: "preferences",
132
- });
133
-
134
- expect(results[0]?.entry.id).toBe("evidence-strong");
135
- });
136
- });
@@ -1,223 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
-
3
- import {
4
- retroactiveBoost,
5
- DEFAULT_RETROACTIVE_BOOST_CONFIG,
6
- type RetroactiveBoostConfig,
7
- } from "../retroactive-boost.js";
8
- import type { MemoryEntry, MemorySearchResult } from "../store.js";
9
-
10
- // ---------------------------------------------------------------------------
11
- // Helpers
12
- // ---------------------------------------------------------------------------
13
-
14
- function makeEntry(id: string, text: string, overrides: Partial<MemoryEntry> = {}): MemoryEntry {
15
- return {
16
- id,
17
- text,
18
- vector: [1, 0, 0],
19
- category: "events",
20
- scope: "project:test",
21
- importance: 0.5,
22
- timestamp: Date.now() - 7 * 86_400_000, // 7 days old by default
23
- metadata: "{}",
24
- ...overrides,
25
- };
26
- }
27
-
28
- function createMockStore(
29
- entries: MemoryEntry[],
30
- searchResults: MemorySearchResult[] = [],
31
- ) {
32
- const data = new Map(entries.map(e => [e.id, { ...e }]));
33
- const updates: Array<{ id: string; importance?: number; metadata?: string }> = [];
34
-
35
- return {
36
- updates,
37
- data,
38
- async vectorSearch(_vector: number[], limit = 5, minScore = 0.3, _scopeFilter?: string[]) {
39
- return searchResults.filter(r => r.score >= minScore).slice(0, limit);
40
- },
41
- async update(id: string, upd: any, _scopeFilter?: string[]) {
42
- const entry = data.get(id);
43
- if (!entry) return null;
44
- if (upd.importance !== undefined) entry.importance = upd.importance;
45
- if (upd.metadata !== undefined) entry.metadata = upd.metadata;
46
- updates.push({ id, ...upd });
47
- return entry;
48
- },
49
- } as any;
50
- }
51
-
52
- // ---------------------------------------------------------------------------
53
- // Tests
54
- // ---------------------------------------------------------------------------
55
-
56
- describe("retroactive-boost (STC)", () => {
57
- it("skips when new entry has low importance", async () => {
58
- const store = createMockStore([]);
59
- const newEntry = makeEntry("new-1", "low importance note", { importance: 0.5 });
60
-
61
- const result = await retroactiveBoost(store, newEntry);
62
- expect(result.triggered).toBe(false);
63
- expect(result.reason).toBe("below_importance_threshold");
64
- });
65
-
66
- it("skips when new entry has no vector", async () => {
67
- const store = createMockStore([]);
68
- const newEntry = makeEntry("new-1", "important note", {
69
- importance: 0.9,
70
- vector: [],
71
- });
72
-
73
- const result = await retroactiveBoost(store, newEntry);
74
- expect(result.triggered).toBe(false);
75
- expect(result.reason).toBe("no_vector");
76
- });
77
-
78
- it("boosts related low-importance old entries", async () => {
79
- const oldEntry = makeEntry("old-1", "Singapore weather info", {
80
- importance: 0.3,
81
- timestamp: Date.now() - 30 * 86_400_000, // 30 days old
82
- });
83
- const searchResults: MemorySearchResult[] = [
84
- { entry: oldEntry, score: 0.85 },
85
- ];
86
- const store = createMockStore([oldEntry], searchResults);
87
-
88
- const newEntry = makeEntry("new-1", "I'm moving to Singapore", {
89
- importance: 0.9,
90
- vector: [1, 0, 0],
91
- });
92
-
93
- const result = await retroactiveBoost(store, newEntry);
94
- expect(result.triggered).toBe(true);
95
- expect(result.boostedCount).toBe(1);
96
- expect(result.boostedIds).toContain("old-1");
97
-
98
- // Verify the update was applied
99
- expect(store.updates.length).toBe(1);
100
- expect(store.updates[0].importance).toBe(0.3 + 0.15); // 0.45
101
- });
102
-
103
- it("does not boost entries that are already high importance", async () => {
104
- const highEntry = makeEntry("high-1", "already important", {
105
- importance: 0.7,
106
- timestamp: Date.now() - 30 * 86_400_000,
107
- });
108
- const searchResults: MemorySearchResult[] = [
109
- { entry: highEntry, score: 0.85 },
110
- ];
111
- const store = createMockStore([highEntry], searchResults);
112
-
113
- const newEntry = makeEntry("new-1", "trigger", { importance: 0.9, vector: [1, 0, 0] });
114
-
115
- const result = await retroactiveBoost(store, newEntry);
116
- expect(result.triggered).toBe(true);
117
- expect(result.boostedCount).toBe(0); // high-1 was already above threshold
118
- });
119
-
120
- it("does not boost very recent entries", async () => {
121
- const recentEntry = makeEntry("recent-1", "just stored", {
122
- importance: 0.3,
123
- timestamp: Date.now() - 3_600_000, // 1 hour old
124
- });
125
- const searchResults: MemorySearchResult[] = [
126
- { entry: recentEntry, score: 0.85 },
127
- ];
128
- const store = createMockStore([recentEntry], searchResults);
129
-
130
- const newEntry = makeEntry("new-1", "trigger", { importance: 0.9, vector: [1, 0, 0] });
131
-
132
- const result = await retroactiveBoost(store, newEntry);
133
- expect(result.triggered).toBe(true);
134
- expect(result.boostedCount).toBe(0); // too recent
135
- });
136
-
137
- it("caps boosted importance at maxBoostedImportance", async () => {
138
- const oldEntry = makeEntry("old-1", "related note", {
139
- importance: 0.55,
140
- timestamp: Date.now() - 30 * 86_400_000,
141
- });
142
- const searchResults: MemorySearchResult[] = [
143
- { entry: oldEntry, score: 0.85 },
144
- ];
145
- const store = createMockStore([oldEntry], searchResults);
146
-
147
- const newEntry = makeEntry("new-1", "trigger", { importance: 0.9, vector: [1, 0, 0] });
148
-
149
- const config: RetroactiveBoostConfig = {
150
- ...DEFAULT_RETROACTIVE_BOOST_CONFIG,
151
- boostAmount: 0.3, // would push to 0.85
152
- maxBoostedImportance: 0.75, // but capped at 0.75
153
- };
154
-
155
- const result = await retroactiveBoost(store, newEntry, config);
156
- expect(result.boostedCount).toBe(1);
157
- expect(store.updates[0].importance).toBe(0.75); // capped
158
- });
159
-
160
- it("promotes tier from peripheral to working when boosted above 0.5", async () => {
161
- const oldEntry = makeEntry("old-1", "peripheral memory", {
162
- importance: 0.3,
163
- timestamp: Date.now() - 30 * 86_400_000,
164
- metadata: JSON.stringify({ tier: "peripheral" }),
165
- });
166
- const searchResults: MemorySearchResult[] = [
167
- { entry: oldEntry, score: 0.85 },
168
- ];
169
- const store = createMockStore([oldEntry], searchResults);
170
-
171
- const newEntry = makeEntry("new-1", "important trigger", { importance: 0.9, vector: [1, 0, 0] });
172
-
173
- const config: RetroactiveBoostConfig = {
174
- ...DEFAULT_RETROACTIVE_BOOST_CONFIG,
175
- boostAmount: 0.25, // 0.3 + 0.25 = 0.55 > 0.5
176
- };
177
-
178
- const result = await retroactiveBoost(store, newEntry, config);
179
- expect(result.boostedCount).toBe(1);
180
-
181
- // Check metadata for tier promotion
182
- const updatedMeta = JSON.parse(store.updates[0].metadata);
183
- expect(updatedMeta.tier).toBe("working");
184
- expect(updatedMeta.stc_boosts).toHaveLength(1);
185
- expect(updatedMeta.stc_boosts[0].triggeredBy).toBe("new-1".slice(0, 8));
186
- });
187
-
188
- it("respects maxBoostPerTrigger limit", async () => {
189
- const entries: MemoryEntry[] = [];
190
- const searchResults: MemorySearchResult[] = [];
191
-
192
- for (let i = 0; i < 10; i++) {
193
- const e = makeEntry(`old-${i}`, `entry ${i}`, {
194
- importance: 0.3,
195
- timestamp: Date.now() - 30 * 86_400_000,
196
- });
197
- entries.push(e);
198
- searchResults.push({ entry: e, score: 0.85 - i * 0.01 });
199
- }
200
-
201
- const store = createMockStore(entries, searchResults);
202
- const newEntry = makeEntry("new-1", "trigger", { importance: 0.9, vector: [1, 0, 0] });
203
-
204
- const result = await retroactiveBoost(store, newEntry);
205
- expect(result.boostedCount).toBe(5); // default maxBoostPerTrigger
206
- });
207
-
208
- it("skips self in search results", async () => {
209
- const newEntry = makeEntry("new-1", "I'm important", {
210
- importance: 0.9,
211
- vector: [1, 0, 0],
212
- });
213
- // Search returns the new entry itself
214
- const searchResults: MemorySearchResult[] = [
215
- { entry: newEntry, score: 1.0 },
216
- ];
217
- const store = createMockStore([newEntry], searchResults);
218
-
219
- const result = await retroactiveBoost(store, newEntry);
220
- expect(result.triggered).toBe(true);
221
- expect(result.boostedCount).toBe(0);
222
- });
223
- });