@claude-flow/memory 3.0.0-alpha.9 → 3.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 (184) hide show
  1. package/README.md +308 -37
  2. package/dist/agent-memory-scope.d.ts.map +1 -1
  3. package/dist/agent-memory-scope.js +10 -2
  4. package/dist/agent-memory-scope.js.map +1 -1
  5. package/dist/agentdb-adapter-key-isolation.test.d.ts +2 -0
  6. package/dist/agentdb-adapter-key-isolation.test.d.ts.map +1 -0
  7. package/dist/agentdb-adapter-key-isolation.test.js +175 -0
  8. package/dist/agentdb-adapter-key-isolation.test.js.map +1 -0
  9. package/dist/agentdb-adapter-upsert.test.d.ts +2 -0
  10. package/dist/agentdb-adapter-upsert.test.d.ts.map +1 -0
  11. package/dist/agentdb-adapter-upsert.test.js +216 -0
  12. package/dist/agentdb-adapter-upsert.test.js.map +1 -0
  13. package/dist/agentdb-adapter.d.ts +70 -1
  14. package/dist/agentdb-adapter.d.ts.map +1 -1
  15. package/dist/agentdb-adapter.js +365 -32
  16. package/dist/agentdb-adapter.js.map +1 -1
  17. package/dist/agentdb-backend.d.ts +25 -0
  18. package/dist/agentdb-backend.d.ts.map +1 -1
  19. package/dist/agentdb-backend.js +113 -28
  20. package/dist/agentdb-backend.js.map +1 -1
  21. package/dist/agentdb-retrieval-guard.d.ts +76 -0
  22. package/dist/agentdb-retrieval-guard.d.ts.map +1 -0
  23. package/dist/agentdb-retrieval-guard.js +77 -0
  24. package/dist/agentdb-retrieval-guard.js.map +1 -0
  25. package/dist/agentdb-retrieval-guard.test.d.ts +12 -0
  26. package/dist/agentdb-retrieval-guard.test.d.ts.map +1 -0
  27. package/dist/agentdb-retrieval-guard.test.js +129 -0
  28. package/dist/agentdb-retrieval-guard.test.js.map +1 -0
  29. package/dist/auto-memory-bridge.d.ts +12 -1
  30. package/dist/auto-memory-bridge.d.ts.map +1 -1
  31. package/dist/auto-memory-bridge.js +101 -7
  32. package/dist/auto-memory-bridge.js.map +1 -1
  33. package/dist/auto-memory-bridge.test.js +121 -15
  34. package/dist/auto-memory-bridge.test.js.map +1 -1
  35. package/dist/auto-memory-index-3224.test.d.ts +2 -0
  36. package/dist/auto-memory-index-3224.test.d.ts.map +1 -0
  37. package/dist/auto-memory-index-3224.test.js +77 -0
  38. package/dist/auto-memory-index-3224.test.js.map +1 -0
  39. package/dist/consolidator-embedding-benchmark.test.d.ts +39 -0
  40. package/dist/consolidator-embedding-benchmark.test.d.ts.map +1 -0
  41. package/dist/consolidator-embedding-benchmark.test.js +366 -0
  42. package/dist/consolidator-embedding-benchmark.test.js.map +1 -0
  43. package/dist/consolidator.d.ts +131 -0
  44. package/dist/consolidator.d.ts.map +1 -0
  45. package/dist/consolidator.js +309 -0
  46. package/dist/consolidator.js.map +1 -0
  47. package/dist/consolidator.test.d.ts +17 -0
  48. package/dist/consolidator.test.d.ts.map +1 -0
  49. package/dist/consolidator.test.js +376 -0
  50. package/dist/consolidator.test.js.map +1 -0
  51. package/dist/controller-registry.d.ts +290 -0
  52. package/dist/controller-registry.d.ts.map +1 -0
  53. package/dist/controller-registry.js +1095 -0
  54. package/dist/controller-registry.js.map +1 -0
  55. package/dist/controller-registry.test.d.ts +14 -0
  56. package/dist/controller-registry.test.d.ts.map +1 -0
  57. package/dist/controller-registry.test.js +818 -0
  58. package/dist/controller-registry.test.js.map +1 -0
  59. package/dist/database-provider.d.ts +14 -2
  60. package/dist/database-provider.d.ts.map +1 -1
  61. package/dist/database-provider.js +63 -3
  62. package/dist/database-provider.js.map +1 -1
  63. package/dist/database-provider.test.js +56 -3
  64. package/dist/database-provider.test.js.map +1 -1
  65. package/dist/entity-tagger.d.ts +31 -0
  66. package/dist/entity-tagger.d.ts.map +1 -0
  67. package/dist/entity-tagger.js +80 -0
  68. package/dist/entity-tagger.js.map +1 -0
  69. package/dist/entity-tagger.test.d.ts +12 -0
  70. package/dist/entity-tagger.test.d.ts.map +1 -0
  71. package/dist/entity-tagger.test.js +70 -0
  72. package/dist/entity-tagger.test.js.map +1 -0
  73. package/dist/fts5.test.d.ts +13 -0
  74. package/dist/fts5.test.d.ts.map +1 -0
  75. package/dist/fts5.test.js +144 -0
  76. package/dist/fts5.test.js.map +1 -0
  77. package/dist/graceful-retrieval.test.d.ts +14 -0
  78. package/dist/graceful-retrieval.test.d.ts.map +1 -0
  79. package/dist/graceful-retrieval.test.js +251 -0
  80. package/dist/graceful-retrieval.test.js.map +1 -0
  81. package/dist/hnsw-index.d.ts +62 -0
  82. package/dist/hnsw-index.d.ts.map +1 -1
  83. package/dist/hnsw-index.js +461 -16
  84. package/dist/hnsw-index.js.map +1 -1
  85. package/dist/hnsw-persistence.test.d.ts +13 -0
  86. package/dist/hnsw-persistence.test.d.ts.map +1 -0
  87. package/dist/hnsw-persistence.test.js +208 -0
  88. package/dist/hnsw-persistence.test.js.map +1 -0
  89. package/dist/hnsw-quantization.test.d.ts +17 -0
  90. package/dist/hnsw-quantization.test.d.ts.map +1 -0
  91. package/dist/hnsw-quantization.test.js +140 -0
  92. package/dist/hnsw-quantization.test.js.map +1 -0
  93. package/dist/hybrid-backend.d.ts +46 -2
  94. package/dist/hybrid-backend.d.ts.map +1 -1
  95. package/dist/hybrid-backend.js +120 -21
  96. package/dist/hybrid-backend.js.map +1 -1
  97. package/dist/hybrid-backend.test.js +139 -5
  98. package/dist/hybrid-backend.test.js.map +1 -1
  99. package/dist/index.d.ts +164 -10
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/index.js +272 -30
  102. package/dist/index.js.map +1 -1
  103. package/dist/index.test.d.ts +10 -0
  104. package/dist/index.test.d.ts.map +1 -0
  105. package/dist/index.test.js +71 -0
  106. package/dist/index.test.js.map +1 -0
  107. package/dist/json-security.d.ts +12 -0
  108. package/dist/json-security.d.ts.map +1 -0
  109. package/dist/json-security.js +20 -0
  110. package/dist/json-security.js.map +1 -0
  111. package/dist/learning-bridge.d.ts +29 -0
  112. package/dist/learning-bridge.d.ts.map +1 -1
  113. package/dist/learning-bridge.js +71 -6
  114. package/dist/learning-bridge.js.map +1 -1
  115. package/dist/learning-bridge.test.js +181 -1
  116. package/dist/learning-bridge.test.js.map +1 -1
  117. package/dist/memory-key.d.ts +3 -0
  118. package/dist/memory-key.d.ts.map +1 -0
  119. package/dist/memory-key.js +5 -0
  120. package/dist/memory-key.js.map +1 -0
  121. package/dist/migration.d.ts +19 -4
  122. package/dist/migration.d.ts.map +1 -1
  123. package/dist/migration.js +99 -24
  124. package/dist/migration.js.map +1 -1
  125. package/dist/migration.test.d.ts +11 -0
  126. package/dist/migration.test.d.ts.map +1 -0
  127. package/dist/migration.test.js +141 -0
  128. package/dist/migration.test.js.map +1 -0
  129. package/dist/mmr-benchmark.test.d.ts +2 -0
  130. package/dist/mmr-benchmark.test.d.ts.map +1 -0
  131. package/dist/mmr-benchmark.test.js +286 -0
  132. package/dist/mmr-benchmark.test.js.map +1 -0
  133. package/dist/mmr-rerank-perf.test.d.ts +2 -0
  134. package/dist/mmr-rerank-perf.test.d.ts.map +1 -0
  135. package/dist/mmr-rerank-perf.test.js +191 -0
  136. package/dist/mmr-rerank-perf.test.js.map +1 -0
  137. package/dist/persistent-sona.d.ts +144 -0
  138. package/dist/persistent-sona.d.ts.map +1 -0
  139. package/dist/persistent-sona.js +332 -0
  140. package/dist/persistent-sona.js.map +1 -0
  141. package/dist/rvf-backend.d.ts +53 -0
  142. package/dist/rvf-backend.d.ts.map +1 -0
  143. package/dist/rvf-backend.js +643 -0
  144. package/dist/rvf-backend.js.map +1 -0
  145. package/dist/rvf-learning-store.d.ts +139 -0
  146. package/dist/rvf-learning-store.d.ts.map +1 -0
  147. package/dist/rvf-learning-store.js +295 -0
  148. package/dist/rvf-learning-store.js.map +1 -0
  149. package/dist/rvf-migration.d.ts +45 -0
  150. package/dist/rvf-migration.d.ts.map +1 -0
  151. package/dist/rvf-migration.js +254 -0
  152. package/dist/rvf-migration.js.map +1 -0
  153. package/dist/smart-retrieval-scores.test.d.ts +2 -0
  154. package/dist/smart-retrieval-scores.test.d.ts.map +1 -0
  155. package/dist/smart-retrieval-scores.test.js +75 -0
  156. package/dist/smart-retrieval-scores.test.js.map +1 -0
  157. package/dist/smart-retrieval.d.ts +141 -0
  158. package/dist/smart-retrieval.d.ts.map +1 -0
  159. package/dist/smart-retrieval.js +389 -0
  160. package/dist/smart-retrieval.js.map +1 -0
  161. package/dist/smart-retrieval.test.d.ts +2 -0
  162. package/dist/smart-retrieval.test.d.ts.map +1 -0
  163. package/dist/smart-retrieval.test.js +418 -0
  164. package/dist/smart-retrieval.test.js.map +1 -0
  165. package/dist/sqlite-backend.d.ts +10 -0
  166. package/dist/sqlite-backend.d.ts.map +1 -1
  167. package/dist/sqlite-backend.js +101 -5
  168. package/dist/sqlite-backend.js.map +1 -1
  169. package/dist/sqljs-backend.d.ts +9 -0
  170. package/dist/sqljs-backend.d.ts.map +1 -1
  171. package/dist/sqljs-backend.js +91 -3
  172. package/dist/sqljs-backend.js.map +1 -1
  173. package/dist/tiered-memory.d.ts +171 -0
  174. package/dist/tiered-memory.d.ts.map +1 -0
  175. package/dist/tiered-memory.js +350 -0
  176. package/dist/tiered-memory.js.map +1 -0
  177. package/dist/tiered-memory.test.d.ts +9 -0
  178. package/dist/tiered-memory.test.d.ts.map +1 -0
  179. package/dist/tiered-memory.test.js +220 -0
  180. package/dist/tiered-memory.test.js.map +1 -0
  181. package/dist/types.d.ts +17 -0
  182. package/dist/types.d.ts.map +1 -1
  183. package/dist/types.js.map +1 -1
  184. package/package.json +10 -8
@@ -0,0 +1,418 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { smartSearch, defaultQueryExpansions, } from './smart-retrieval.js';
3
+ function makeCandidate(partial) {
4
+ return {
5
+ key: partial.id,
6
+ namespace: 'test',
7
+ score: 0.5,
8
+ ...partial,
9
+ };
10
+ }
11
+ describe('defaultQueryExpansions', () => {
12
+ it('returns the original query plus keyword-only and context variants', () => {
13
+ const variants = defaultQueryExpansions('What is the capital of France?');
14
+ expect(variants.length).toBeGreaterThanOrEqual(2);
15
+ expect(variants[0]).toBe('What is the capital of France?');
16
+ // Keyword variant strips stopwords
17
+ expect(variants.some((v) => v.includes('capital') && v.includes('france'))).toBe(true);
18
+ });
19
+ it('drops duplicate variants', () => {
20
+ const variants = defaultQueryExpansions('hello');
21
+ expect(new Set(variants).size).toBe(variants.length);
22
+ });
23
+ it('handles empty input', () => {
24
+ expect(defaultQueryExpansions('')).toEqual([]);
25
+ });
26
+ });
27
+ describe('smartSearch — multi-query fan-out', () => {
28
+ it('fans out one call per variant and fuses with RRF', async () => {
29
+ const calls = [];
30
+ const search = async ({ query }) => {
31
+ calls.push(query);
32
+ // Each variant returns a slightly different list. Item A is top of first,
33
+ // bottom of second; item B is the inverse. RRF should put A and B both
34
+ // near the top because they each appear in two variant lists.
35
+ if (query.includes('tell me about')) {
36
+ return {
37
+ results: [
38
+ makeCandidate({ id: 'b', content: 'beta content', score: 0.9 }),
39
+ makeCandidate({ id: 'a', content: 'alpha content', score: 0.4 }),
40
+ ],
41
+ };
42
+ }
43
+ return {
44
+ results: [
45
+ makeCandidate({ id: 'a', content: 'alpha content', score: 0.9 }),
46
+ makeCandidate({ id: 'b', content: 'beta content', score: 0.4 }),
47
+ ],
48
+ };
49
+ };
50
+ const { results, stats } = await smartSearch(search, {
51
+ query: 'alpha beta',
52
+ limit: 5,
53
+ diversityMMR: false,
54
+ sessionDiversity: false,
55
+ recencyBoost: false,
56
+ });
57
+ expect(stats.variantCount).toBeGreaterThanOrEqual(1);
58
+ expect(calls.length).toBe(stats.variantCount);
59
+ expect(results.map((r) => r.id)).toEqual(expect.arrayContaining(['a', 'b']));
60
+ });
61
+ it('falls back to a single raw call when multiQuery=false', async () => {
62
+ // NOTE: no vi.fn<T> generic — the hoisted vitest (1.x) types Mock as
63
+ // Mock<Args extends any[], Returns>, so the vitest-4 single-generic
64
+ // form fails `tsc`. The inferred signature is identical.
65
+ const search = vi.fn(async () => ({
66
+ results: [makeCandidate({ id: 'x', content: 'only one' })],
67
+ }));
68
+ await smartSearch(search, {
69
+ query: 'whatever',
70
+ multiQuery: false,
71
+ diversityMMR: false,
72
+ sessionDiversity: false,
73
+ recencyBoost: false,
74
+ });
75
+ expect(search).toHaveBeenCalledTimes(1);
76
+ });
77
+ });
78
+ describe('smartSearch — recency boost', () => {
79
+ it('reorders stale candidates below fresh ones at equal similarity', async () => {
80
+ const now = Date.parse('2026-04-11T00:00:00Z');
81
+ const oneDay = 24 * 60 * 60 * 1000;
82
+ const search = async () => ({
83
+ results: [
84
+ makeCandidate({
85
+ id: 'stale',
86
+ content: 'stale content',
87
+ score: 0.8,
88
+ updatedAt: now - 365 * oneDay, // a year old
89
+ }),
90
+ makeCandidate({
91
+ id: 'fresh',
92
+ content: 'fresh content',
93
+ score: 0.78,
94
+ updatedAt: now - oneDay, // yesterday
95
+ }),
96
+ ],
97
+ });
98
+ const { results } = await smartSearch(search, {
99
+ query: 'something',
100
+ limit: 2,
101
+ multiQuery: false,
102
+ diversityMMR: false,
103
+ sessionDiversity: false,
104
+ recencyHalfLifeDays: 30,
105
+ recencyWeight: 0.5,
106
+ now,
107
+ });
108
+ expect(results[0].id).toBe('fresh');
109
+ expect(results[1].id).toBe('stale');
110
+ });
111
+ it('is a no-op when candidates have no timestamps', async () => {
112
+ const search = async () => ({
113
+ results: [
114
+ makeCandidate({ id: 'a', content: 'alpha', score: 0.9 }),
115
+ makeCandidate({ id: 'b', content: 'beta', score: 0.8 }),
116
+ ],
117
+ });
118
+ const { results } = await smartSearch(search, {
119
+ query: 'x',
120
+ multiQuery: false,
121
+ diversityMMR: false,
122
+ sessionDiversity: false,
123
+ recencyBoost: true,
124
+ });
125
+ expect(results.map((r) => r.id)).toEqual(['a', 'b']);
126
+ });
127
+ });
128
+ describe('smartSearch — MMR diversity', () => {
129
+ it('pushes near-duplicate content down the list', async () => {
130
+ const search = async () => ({
131
+ results: [
132
+ makeCandidate({ id: '1', content: 'the cat sat on the mat quietly', score: 0.95 }),
133
+ makeCandidate({ id: '2', content: 'the cat sat on the mat quietly today', score: 0.94 }),
134
+ makeCandidate({ id: '3', content: 'quantum entanglement paradox resolved', score: 0.6 }),
135
+ ],
136
+ });
137
+ const { results } = await smartSearch(search, {
138
+ query: 'x',
139
+ limit: 3,
140
+ multiQuery: false,
141
+ recencyBoost: false,
142
+ sessionDiversity: false,
143
+ diversityMMR: true,
144
+ mmrLambda: 0.3, // lean heavily on diversity
145
+ });
146
+ // Seed is #1 (top). Between #2 (near-duplicate) and #3 (different topic),
147
+ // MMR with λ=0.3 should pick #3 second.
148
+ expect(results[0].id).toBe('1');
149
+ expect(results[1].id).toBe('3');
150
+ });
151
+ });
152
+ describe('smartSearch — MMR diversity uses embedding-cosine when available', () => {
153
+ it('prefers a genuinely different topic over a low-token-overlap paraphrase, when Jaccard alone would not', async () => {
154
+ // A is the seed. B is a paraphrase of A (barely any shared tokens, but
155
+ // near-identical embedding — the "verbose restatement" case token
156
+ // overlap misses). C is a genuinely different topic (no shared tokens,
157
+ // orthogonal embedding). Under the old token-Jaccard-only proxy, B and
158
+ // C look almost equally "diverse" from A (both share ~0 tokens), so B's
159
+ // higher relevance score wins second place. With embedding-cosine, B's
160
+ // near-1.0 similarity to A correctly suppresses it in favor of C.
161
+ const search = async () => ({
162
+ results: [
163
+ makeCandidate({
164
+ id: 'a',
165
+ content: 'the quarterly revenue report shows strong growth',
166
+ score: 0.95,
167
+ embedding: [1, 0, 0],
168
+ }),
169
+ makeCandidate({
170
+ id: 'b-paraphrase',
171
+ content: 'quarterly earnings update reveals robust expansion',
172
+ score: 0.9,
173
+ embedding: [0.99, 0.01, 0], // near-duplicate of A in embedding space
174
+ }),
175
+ makeCandidate({
176
+ id: 'c-different-topic',
177
+ content: 'a stray cat wandered into the office kitchen',
178
+ score: 0.6,
179
+ embedding: [0, 1, 0], // orthogonal to A
180
+ }),
181
+ ],
182
+ });
183
+ const { results } = await smartSearch(search, {
184
+ query: 'x',
185
+ limit: 3,
186
+ multiQuery: false,
187
+ recencyBoost: false,
188
+ sessionDiversity: false,
189
+ diversityMMR: true,
190
+ mmrLambda: 0.3, // lean heavily on diversity
191
+ });
192
+ expect(results[0].id).toBe('a');
193
+ // Discriminating assertion: token-Jaccard between A and both B and C is
194
+ // near-zero either way, so a Jaccard-only proxy picks B second (higher
195
+ // relevance score, 0.9 vs 0.6). Embedding-cosine correctly identifies B
196
+ // as a near-duplicate of A and picks C instead.
197
+ expect(results[1].id).toBe('c-different-topic');
198
+ });
199
+ it('falls back to token-Jaccard when embeddings are absent or mismatched (existing behavior unchanged)', async () => {
200
+ const search = async () => ({
201
+ results: [
202
+ makeCandidate({ id: 'a', content: 'the cat sat on the mat quietly', score: 0.95 }),
203
+ makeCandidate({ id: 'b', content: 'the cat sat on the mat quietly today', score: 0.94, embedding: [1, 0] }), // mismatched: only one side has an embedding
204
+ makeCandidate({ id: 'c', content: 'quantum entanglement paradox resolved', score: 0.6 }),
205
+ ],
206
+ });
207
+ const { results } = await smartSearch(search, {
208
+ query: 'x',
209
+ limit: 3,
210
+ multiQuery: false,
211
+ recencyBoost: false,
212
+ sessionDiversity: false,
213
+ diversityMMR: true,
214
+ mmrLambda: 0.3,
215
+ });
216
+ expect(results[0].id).toBe('a');
217
+ expect(results[1].id).toBe('c');
218
+ });
219
+ it('falls back to Jaccard on a NaN/Infinity-poisoned embedding instead of corrupting the comparison', async () => {
220
+ // A single non-finite component would poison cosineSimilarity's dot
221
+ // product (NaN propagates through every subsequent `mmr > bestMmr`
222
+ // comparison, which is always false for NaN, silently breaking
223
+ // selection for the rest of the rerank). isWellFormedEmbedding() must
224
+ // catch this and fall back to token-Jaccard, same as a missing embedding.
225
+ const search = async () => ({
226
+ results: [
227
+ makeCandidate({ id: 'a', content: 'the cat sat on the mat quietly', score: 0.95, embedding: [1, 0, NaN] }),
228
+ makeCandidate({ id: 'b', content: 'the cat sat on the mat quietly today', score: 0.94, embedding: [1, 0, Infinity] }),
229
+ makeCandidate({ id: 'c', content: 'quantum entanglement paradox resolved', score: 0.6, embedding: [0, 1, 0] }),
230
+ ],
231
+ });
232
+ const { results } = await smartSearch(search, {
233
+ query: 'x',
234
+ limit: 3,
235
+ multiQuery: false,
236
+ recencyBoost: false,
237
+ sessionDiversity: false,
238
+ diversityMMR: true,
239
+ mmrLambda: 0.3,
240
+ });
241
+ // Jaccard fallback: b is a near-duplicate of a's text, c is unrelated —
242
+ // same expected ordering as the plain-fallback test above.
243
+ expect(results[0].id).toBe('a');
244
+ expect(results[1].id).toBe('c');
245
+ });
246
+ it('falls back to Jaccard on an empty-array embedding', async () => {
247
+ const search = async () => ({
248
+ results: [
249
+ makeCandidate({ id: 'a', content: 'the cat sat on the mat quietly', score: 0.95, embedding: [] }),
250
+ makeCandidate({ id: 'b', content: 'the cat sat on the mat quietly today', score: 0.94, embedding: [] }),
251
+ makeCandidate({ id: 'c', content: 'quantum entanglement paradox resolved', score: 0.6, embedding: [] }),
252
+ ],
253
+ });
254
+ const { results } = await smartSearch(search, {
255
+ query: 'x',
256
+ limit: 3,
257
+ multiQuery: false,
258
+ recencyBoost: false,
259
+ sessionDiversity: false,
260
+ diversityMMR: true,
261
+ mmrLambda: 0.3,
262
+ });
263
+ expect(results[0].id).toBe('a');
264
+ expect(results[1].id).toBe('c');
265
+ });
266
+ it('produces byte-identical, deterministic ordering across repeated runs on the same input', async () => {
267
+ const makeSearch = () => async () => ({
268
+ results: [
269
+ makeCandidate({ id: 'a', content: 'the quarterly revenue report shows strong growth', score: 0.95, embedding: [1, 0, 0] }),
270
+ makeCandidate({ id: 'b-paraphrase', content: 'quarterly earnings update reveals robust expansion', score: 0.9, embedding: [0.99, 0.01, 0] }),
271
+ makeCandidate({ id: 'c-different-topic', content: 'a stray cat wandered into the office kitchen', score: 0.6, embedding: [0, 1, 0] }),
272
+ makeCandidate({ id: 'd-tie-embedding', content: 'unrelated filler text about weather patterns', score: 0.5, embedding: [0, 1, 0] }),
273
+ ],
274
+ });
275
+ const opts = {
276
+ query: 'x',
277
+ limit: 4,
278
+ multiQuery: false,
279
+ recencyBoost: false,
280
+ sessionDiversity: false,
281
+ diversityMMR: true,
282
+ mmrLambda: 0.3,
283
+ };
284
+ const runs = await Promise.all(Array.from({ length: 5 }, () => smartSearch(makeSearch(), opts)));
285
+ const orderings = runs.map((r) => r.results.map((c) => c.id).join(','));
286
+ expect(new Set(orderings).size).toBe(1); // every run produced the exact same order
287
+ });
288
+ });
289
+ describe('MMR internal output never carries embedding into a CLI/MCP-shaped whitelist', () => {
290
+ it('mirrors the exact field whitelist used by mcp-tools/memory-tools.ts:559-569 and commands/memory.ts:633-639', async () => {
291
+ // Regression guard for the "no leak" claim: both real CLI/MCP call sites
292
+ // build their final response by explicitly picking fields off
293
+ // `smartResult.results` rather than spreading the candidate object
294
+ // (which — per smart-retrieval.ts's own return statement — does still
295
+ // carry `.embedding` internally). This test exercises smartSearch's
296
+ // real output and re-applies each site's literal field list, so a
297
+ // future change to either whitelist that starts including `embedding`
298
+ // (e.g. an errant `...r` spread) fails here without needing to spin up
299
+ // the full CLI/MCP handler stack.
300
+ const search = async () => ({
301
+ results: [
302
+ makeCandidate({ id: 'a', content: 'alpha content', score: 0.9, embedding: [1, 0, 0], provenanceType: 'agent_inference' }),
303
+ ],
304
+ });
305
+ const { results } = await smartSearch(search, { query: 'x', diversityMMR: false, recencyBoost: false, sessionDiversity: false, multiQuery: false });
306
+ expect(results[0].embedding).toBeDefined(); // sanity: the internal object does carry it
307
+ // mcp-tools/memory-tools.ts:559-569 whitelist
308
+ const mcpToolShape = results.map((r) => ({
309
+ key: r.key,
310
+ namespace: r.namespace,
311
+ value: r.content,
312
+ similarity: r.score,
313
+ provenanceType: r.provenanceType,
314
+ }));
315
+ expect(Object.keys(mcpToolShape[0])).not.toContain('embedding');
316
+ expect('embedding' in mcpToolShape[0]).toBe(false);
317
+ // commands/memory.ts:633-639 whitelist
318
+ const cliCommandShape = results.map((r) => ({
319
+ key: r.key,
320
+ score: r.score,
321
+ namespace: r.namespace,
322
+ preview: r.content,
323
+ provenanceType: r.provenanceType,
324
+ }));
325
+ expect(Object.keys(cliCommandShape[0])).not.toContain('embedding');
326
+ expect('embedding' in cliCommandShape[0]).toBe(false);
327
+ });
328
+ });
329
+ describe('smartSearch — session round-robin', () => {
330
+ it('interleaves results across distinct sessions', async () => {
331
+ const search = async () => ({
332
+ results: [
333
+ makeCandidate({
334
+ id: 'a1',
335
+ content: 'alpha one',
336
+ score: 0.95,
337
+ metadata: { session_id: 'A' },
338
+ }),
339
+ makeCandidate({
340
+ id: 'a2',
341
+ content: 'alpha two',
342
+ score: 0.9,
343
+ metadata: { session_id: 'A' },
344
+ }),
345
+ makeCandidate({
346
+ id: 'b1',
347
+ content: 'beta one',
348
+ score: 0.85,
349
+ metadata: { session_id: 'B' },
350
+ }),
351
+ makeCandidate({
352
+ id: 'c1',
353
+ content: 'gamma one',
354
+ score: 0.8,
355
+ metadata: { session_id: 'C' },
356
+ }),
357
+ ],
358
+ });
359
+ const { results } = await smartSearch(search, {
360
+ query: 'x',
361
+ limit: 3,
362
+ multiQuery: false,
363
+ recencyBoost: false,
364
+ diversityMMR: false,
365
+ sessionDiversity: true,
366
+ });
367
+ const sessions = results.map((r) => r.metadata?.session_id);
368
+ // Top-3 should cover 3 distinct sessions, not just session A twice.
369
+ expect(new Set(sessions).size).toBe(3);
370
+ expect(sessions[0]).toBe('A'); // Highest-scored bucket leader first
371
+ });
372
+ it('passes through when all candidates share one session', async () => {
373
+ const search = async () => ({
374
+ results: [
375
+ makeCandidate({
376
+ id: 'a1',
377
+ content: 'one',
378
+ score: 0.9,
379
+ metadata: { session_id: 'S' },
380
+ }),
381
+ makeCandidate({
382
+ id: 'a2',
383
+ content: 'two',
384
+ score: 0.8,
385
+ metadata: { session_id: 'S' },
386
+ }),
387
+ ],
388
+ });
389
+ const { results } = await smartSearch(search, {
390
+ query: 'x',
391
+ limit: 2,
392
+ multiQuery: false,
393
+ recencyBoost: false,
394
+ diversityMMR: false,
395
+ sessionDiversity: true,
396
+ });
397
+ expect(results.map((r) => r.id)).toEqual(['a1', 'a2']);
398
+ });
399
+ });
400
+ describe('smartSearch — stats reporting', () => {
401
+ it('reports variant count, raw candidate count, and phase counts', async () => {
402
+ const search = async () => ({
403
+ results: [
404
+ makeCandidate({ id: 'a', content: 'alpha' }),
405
+ makeCandidate({ id: 'b', content: 'beta' }),
406
+ ],
407
+ });
408
+ const { stats } = await smartSearch(search, {
409
+ query: 'hello world',
410
+ limit: 2,
411
+ });
412
+ expect(stats.variantCount).toBeGreaterThanOrEqual(1);
413
+ expect(stats.rawCandidateCount).toBeGreaterThan(0);
414
+ expect(stats.variants.length).toBe(stats.variantCount);
415
+ expect(stats.durationMs).toBeGreaterThanOrEqual(0);
416
+ });
417
+ });
418
+ //# sourceMappingURL=smart-retrieval.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smart-retrieval.test.js","sourceRoot":"","sources":["../src/smart-retrieval.test.ts"],"names":[],"mappings":"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"}
@@ -42,6 +42,8 @@ export declare class SQLiteBackend extends EventEmitter implements IMemoryBacken
42
42
  private config;
43
43
  private db;
44
44
  private initialized;
45
+ /** Whether the FTS5 virtual table is available on this build. */
46
+ private ftsAvailable;
45
47
  private stats;
46
48
  constructor(config?: Partial<SQLiteBackendConfig>);
47
49
  /**
@@ -72,6 +74,14 @@ export declare class SQLiteBackend extends EventEmitter implements IMemoryBacken
72
74
  * Delete a memory entry
73
75
  */
74
76
  delete(id: string): Promise<boolean>;
77
+ /**
78
+ * ADR-125 Phase 5 — keyword (FTS5) search.
79
+ *
80
+ * Runs `memory_fts MATCH ?` ranked by FTS5's BM25 implementation, then
81
+ * joins back to `memory_entries` for full records. Falls back to a
82
+ * `LIKE`-based scan when FTS5 is unavailable.
83
+ */
84
+ searchKeyword(query: string, limit?: number): Promise<SearchResult[]>;
75
85
  /**
76
86
  * Query memory entries with filters
77
87
  */
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-backend.d.ts","sourceRoot":"","sources":["../src/sqlite-backend.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EACL,cAAc,EACd,WAAW,EAEX,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,iBAAiB,EAGjB,kBAAkB,EAGnB,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,4DAA4D;IAC5D,YAAY,EAAE,MAAM,CAAC;IAErB,6CAA6C;IAC7C,OAAO,EAAE,OAAO,CAAC;IAEjB,gCAAgC;IAChC,QAAQ,EAAE,OAAO,CAAC;IAElB,wBAAwB;IACxB,gBAAgB,EAAE,MAAM,CAAC;IAEzB,+DAA+D;IAC/D,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IAExC,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IAEnB,6BAA6B;IAC7B,OAAO,EAAE,OAAO,CAAC;CAClB;AAcD;;;;;;;;;GASG;AACH,qBAAa,aAAc,SAAQ,YAAa,YAAW,cAAc;IACvE,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,EAAE,CAAkC;IAC5C,OAAO,CAAC,WAAW,CAAkB;IAGrC,OAAO,CAAC,KAAK,CAKX;gBAEU,MAAM,GAAE,OAAO,CAAC,mBAAmB,CAAM;IAKrD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IA2BjC;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAc/B;;OAEG;IACG,KAAK,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IA+C9C;;OAEG;IACG,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAWlD;;OAEG;IACG,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAc3E;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IA0BhF;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiB1C;;OAEG;IACG,KAAK,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAuGvD;;;OAGG;IACG,MAAM,CACV,SAAS,EAAE,YAAY,EACvB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,YAAY,EAAE,CAAC;IAS1B;;OAEG;IACG,UAAU,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAavD;;OAEG;IACG,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAmBhD;;OAEG;IACG,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAgBhD;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAQzC;;OAEG;IACG,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAexD;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,YAAY,CAAC;IAqDvC;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAwE/C,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,YAAY;IAyCpB,OAAO,CAAC,UAAU;IAgClB;;OAEG;IACH,OAAO,CAAC,SAAS;CAoClB;AAED,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"sqlite-backend.d.ts","sourceRoot":"","sources":["../src/sqlite-backend.ts"],"names":[],"mappings":"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"}
@@ -8,7 +8,7 @@
8
8
  * @module v3/memory/sqlite-backend
9
9
  */
10
10
  import { EventEmitter } from 'node:events';
11
- import Database from 'better-sqlite3';
11
+ import { safeJsonParse } from './json-security.js';
12
12
  /**
13
13
  * Default configuration values
14
14
  */
@@ -34,6 +34,8 @@ export class SQLiteBackend extends EventEmitter {
34
34
  config;
35
35
  db = null;
36
36
  initialized = false;
37
+ /** Whether the FTS5 virtual table is available on this build. */
38
+ ftsAvailable = false;
37
39
  // Performance tracking
38
40
  stats = {
39
41
  queryCount: 0,
@@ -51,8 +53,17 @@ export class SQLiteBackend extends EventEmitter {
51
53
  async initialize() {
52
54
  if (this.initialized)
53
55
  return;
56
+ let DatabaseCtor;
57
+ try {
58
+ DatabaseCtor = (await import('better-sqlite3')).default;
59
+ }
60
+ catch (err) {
61
+ throw new Error("@claude-flow/memory: SQLiteBackend requires the optional 'better-sqlite3' package. " +
62
+ "Install it with `npm i better-sqlite3` or use a different DatabaseProvider " +
63
+ "(sql.js / rvf / json). Original error: " + (err instanceof Error ? err.message : String(err)));
64
+ }
54
65
  // Open database connection
55
- this.db = new Database(this.config.databasePath, {
66
+ this.db = new DatabaseCtor(this.config.databasePath, {
56
67
  verbose: this.config.verbose ? console.log : undefined,
57
68
  });
58
69
  // Enable WAL mode for better concurrency
@@ -107,6 +118,13 @@ export class SQLiteBackend extends EventEmitter {
107
118
  `);
108
119
  embeddingStmt.run(entry.id, Buffer.from(entry.embedding.buffer));
109
120
  }
121
+ // ADR-125 Phase 5 — mirror into FTS5 for keyword search.
122
+ if (this.ftsAvailable) {
123
+ // REPLACE semantics for FTS: delete-then-insert so updates reflect.
124
+ this.db.prepare('DELETE FROM memory_fts WHERE id = ?').run(entry.id);
125
+ this.db.prepare('INSERT INTO memory_fts(id, content) VALUES (?, ?)')
126
+ .run(entry.id, entry.content);
127
+ }
110
128
  const duration = performance.now() - startTime;
111
129
  this.stats.writeCount++;
112
130
  this.stats.totalWriteTime += duration;
@@ -175,12 +193,59 @@ export class SQLiteBackend extends EventEmitter {
175
193
  const deleteEmbedding = this.db.prepare('DELETE FROM memory_embeddings WHERE entry_id = ?');
176
194
  const result = deleteEntry.run(id);
177
195
  deleteEmbedding.run(id);
196
+ if (this.ftsAvailable) {
197
+ this.db.prepare('DELETE FROM memory_fts WHERE id = ?').run(id);
198
+ }
178
199
  if (result.changes > 0) {
179
200
  this.emit('entry:deleted', { id });
180
201
  return true;
181
202
  }
182
203
  return false;
183
204
  }
205
+ /**
206
+ * ADR-125 Phase 5 — keyword (FTS5) search.
207
+ *
208
+ * Runs `memory_fts MATCH ?` ranked by FTS5's BM25 implementation, then
209
+ * joins back to `memory_entries` for full records. Falls back to a
210
+ * `LIKE`-based scan when FTS5 is unavailable.
211
+ */
212
+ async searchKeyword(query, limit = 10) {
213
+ this.ensureInitialized();
214
+ if (!query || !query.trim())
215
+ return [];
216
+ if (this.ftsAvailable) {
217
+ try {
218
+ const stmt = this.db.prepare(`
219
+ SELECT e.*, fts.rank as fts_rank
220
+ FROM memory_fts AS fts
221
+ JOIN memory_entries AS e ON e.id = fts.id
222
+ WHERE memory_fts MATCH ?
223
+ ORDER BY fts.rank
224
+ LIMIT ?
225
+ `);
226
+ const rows = stmt.all(escapeFtsQuery(query), limit);
227
+ return rows.map((row) => {
228
+ const entry = this.rowToEntry(row);
229
+ // FTS5 rank is non-positive — closer to 0 is better. Convert to
230
+ // a [0,1] similarity-ish score so callers see a consistent shape.
231
+ const score = 1 / (1 + Math.abs(row.fts_rank || 0));
232
+ return { entry, score, distance: 1 - score };
233
+ });
234
+ }
235
+ catch {
236
+ // Fall through to LIKE
237
+ }
238
+ }
239
+ // LIKE fallback (does no ranking; first N matches)
240
+ const like = `%${query.replace(/[%_]/g, '')}%`;
241
+ const stmt = this.db.prepare('SELECT * FROM memory_entries WHERE content LIKE ? ORDER BY updated_at DESC LIMIT ?');
242
+ const rows = stmt.all(like, limit);
243
+ return rows.map((row) => ({
244
+ entry: this.rowToEntry(row),
245
+ score: 0.5,
246
+ distance: 0.5,
247
+ }));
248
+ }
184
249
  /**
185
250
  * Query memory entries with filters
186
251
  */
@@ -510,6 +575,20 @@ export class SQLiteBackend extends EventEmitter {
510
575
  FOREIGN KEY (entry_id) REFERENCES memory_entries(id) ON DELETE CASCADE
511
576
  );
512
577
  `);
578
+ // ADR-125 Phase 5 — FTS5 virtual table for keyword search.
579
+ // FTS5 is built into better-sqlite3 since 11.x. Surrounding try/catch so
580
+ // older SQLite builds gracefully skip FTS without taking the whole backend
581
+ // offline; queries against `searchKeyword` will fall back to LIKE.
582
+ try {
583
+ this.db.exec(`
584
+ CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts
585
+ USING fts5(id UNINDEXED, content, tokenize='porter unicode61');
586
+ `);
587
+ this.ftsAvailable = true;
588
+ }
589
+ catch {
590
+ this.ftsAvailable = false;
591
+ }
513
592
  }
514
593
  rowToEntry(row) {
515
594
  // Get embedding if exists
@@ -526,15 +605,15 @@ export class SQLiteBackend extends EventEmitter {
526
605
  embedding,
527
606
  type: row.type,
528
607
  namespace: row.namespace,
529
- tags: JSON.parse(row.tags),
530
- metadata: JSON.parse(row.metadata),
608
+ tags: safeJsonParse(row.tags || '[]'),
609
+ metadata: safeJsonParse(row.metadata || '{}'),
531
610
  ownerId: row.owner_id,
532
611
  accessLevel: row.access_level,
533
612
  createdAt: row.created_at,
534
613
  updatedAt: row.updated_at,
535
614
  expiresAt: row.expires_at,
536
615
  version: row.version,
537
- references: JSON.parse(row.references),
616
+ references: safeJsonParse(row.references || '[]'),
538
617
  accessCount: row.access_count,
539
618
  lastAccessedAt: row.last_accessed_at,
540
619
  };
@@ -560,5 +639,22 @@ export class SQLiteBackend extends EventEmitter {
560
639
  }
561
640
  }
562
641
  }
642
+ /**
643
+ * Escape an FTS5 MATCH query string. FTS5 has its own mini-language
644
+ * (`AND`, `OR`, `NOT`, `*` wildcards, parentheses, quoted phrases). User
645
+ * input that's plain prose can hit syntax errors. We wrap each whitespace-
646
+ * separated token in double quotes (FTS5's phrase delimiter), which is
647
+ * close to literal interpretation and is what the keyword-fallback callers
648
+ * actually want.
649
+ *
650
+ * @internal
651
+ */
652
+ function escapeFtsQuery(q) {
653
+ return q
654
+ .split(/\s+/)
655
+ .filter(Boolean)
656
+ .map((tok) => `"${tok.replace(/"/g, '""')}"`)
657
+ .join(' ');
658
+ }
563
659
  export default SQLiteBackend;
564
660
  //# sourceMappingURL=sqlite-backend.js.map