@claude-flow/memory 3.0.0-alpha.13 → 3.0.0-alpha.15

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 (45) hide show
  1. package/dist/agentdb-backend.d.ts +6 -0
  2. package/dist/agentdb-backend.d.ts.map +1 -1
  3. package/dist/agentdb-backend.js +71 -24
  4. package/dist/agentdb-backend.js.map +1 -1
  5. package/dist/auto-memory-bridge.d.ts.map +1 -1
  6. package/dist/auto-memory-bridge.js +10 -1
  7. package/dist/auto-memory-bridge.js.map +1 -1
  8. package/dist/auto-memory-bridge.test.js +45 -9
  9. package/dist/auto-memory-bridge.test.js.map +1 -1
  10. package/dist/controller-registry.test.js +1 -1
  11. package/dist/controller-registry.test.js.map +1 -1
  12. package/dist/database-provider.test.js +15 -3
  13. package/dist/database-provider.test.js.map +1 -1
  14. package/dist/hnsw-index.d.ts.map +1 -1
  15. package/dist/hnsw-index.js +179 -12
  16. package/dist/hnsw-index.js.map +1 -1
  17. package/dist/hybrid-backend.test.js +6 -5
  18. package/dist/hybrid-backend.test.js.map +1 -1
  19. package/dist/index.d.ts +2 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +2 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/json-security.d.ts +12 -0
  24. package/dist/json-security.d.ts.map +1 -0
  25. package/dist/json-security.js +20 -0
  26. package/dist/json-security.js.map +1 -0
  27. package/dist/rvf-backend.d.ts +2 -0
  28. package/dist/rvf-backend.d.ts.map +1 -1
  29. package/dist/rvf-backend.js +9 -1
  30. package/dist/rvf-backend.js.map +1 -1
  31. package/dist/smart-retrieval.d.ts +94 -0
  32. package/dist/smart-retrieval.d.ts.map +1 -0
  33. package/dist/smart-retrieval.js +303 -0
  34. package/dist/smart-retrieval.js.map +1 -0
  35. package/dist/smart-retrieval.test.d.ts +2 -0
  36. package/dist/smart-retrieval.test.d.ts.map +1 -0
  37. package/dist/smart-retrieval.test.js +238 -0
  38. package/dist/smart-retrieval.test.js.map +1 -0
  39. package/dist/sqlite-backend.d.ts.map +1 -1
  40. package/dist/sqlite-backend.js +14 -5
  41. package/dist/sqlite-backend.js.map +1 -1
  42. package/dist/sqljs-backend.d.ts.map +1 -1
  43. package/dist/sqljs-backend.js +4 -3
  44. package/dist/sqljs-backend.js.map +1 -1
  45. package/package.json +5 -3
@@ -0,0 +1,303 @@
1
+ /**
2
+ * SmartRetrieval — LongMemEval-derived retrieval pipeline (ADR-090)
3
+ *
4
+ * Wraps a raw HNSW `SearchFn` with the optimizations identified by the
5
+ * ADR-088 LongMemEval benchmark:
6
+ *
7
+ * 1. Query expansion (template-based, no LLM)
8
+ * 2. Multi-query fan-out + Reciprocal Rank Fusion
9
+ * 3. Recency boost from metadata timestamps
10
+ * 4. MMR diversity re-ranking (token-Jaccard proxy)
11
+ * 5. Session round-robin for multi-session coverage
12
+ *
13
+ * The module is pluggable: callers provide a `SearchFn` that hits whatever
14
+ * raw store they use (AgentDB HNSW, sql.js, a test fake, etc.). That keeps
15
+ * `@claude-flow/memory` free of a hard dependency on the CLI's memory-initializer
16
+ * and makes the pipeline easy to benchmark in isolation.
17
+ */
18
+ // ── Query Expansion ────────────────────────────────────────────
19
+ const STOPWORDS = new Set([
20
+ 'a', 'an', 'and', 'are', 'as', 'at', 'be', 'but', 'by', 'did', 'do', 'does',
21
+ 'for', 'from', 'has', 'have', 'how', 'i', 'if', 'in', 'is', 'it', 'its', 'me',
22
+ 'my', 'of', 'on', 'or', 'that', 'the', 'this', 'to', 'was', 'were', 'what',
23
+ 'when', 'where', 'which', 'who', 'why', 'will', 'with', 'you', 'your',
24
+ ]);
25
+ /**
26
+ * Default query expansion set. Keeps variants cheap (≤3) so we only pay
27
+ * ~3× the HNSW cost on the hot path.
28
+ */
29
+ export function defaultQueryExpansions(query) {
30
+ const trimmed = query.trim();
31
+ if (!trimmed)
32
+ return [];
33
+ const variants = new Set();
34
+ variants.add(trimmed);
35
+ const keywords = keywordExtract(trimmed);
36
+ if (keywords && keywords !== trimmed.toLowerCase()) {
37
+ variants.add(keywords);
38
+ }
39
+ // Context-priming variant — helps when the query is short or imperative.
40
+ const words = trimmed.toLowerCase().replace(/[?.!]+$/, '');
41
+ if (words && !words.startsWith('tell me')) {
42
+ variants.add(`tell me about ${words}`);
43
+ }
44
+ return [...variants];
45
+ }
46
+ function keywordExtract(query) {
47
+ return query
48
+ .toLowerCase()
49
+ .replace(/[^\w\s]/g, ' ')
50
+ .split(/\s+/)
51
+ .filter((w) => w.length > 2 && !STOPWORDS.has(w))
52
+ .join(' ');
53
+ }
54
+ function reciprocalRankFusion(rankedLists, k) {
55
+ const fused = new Map();
56
+ for (const list of rankedLists) {
57
+ for (let rank = 0; rank < list.length; rank++) {
58
+ const cand = list[rank];
59
+ const rrfContribution = 1 / (k + rank + 1);
60
+ const key = candidateKey(cand);
61
+ const existing = fused.get(key);
62
+ if (existing) {
63
+ existing.score += rrfContribution;
64
+ // Keep the highest raw score we've seen for display purposes.
65
+ if (cand.score > existing.candidate.score) {
66
+ existing.candidate = cand;
67
+ }
68
+ }
69
+ else {
70
+ fused.set(key, { candidate: cand, score: rrfContribution });
71
+ }
72
+ }
73
+ }
74
+ return [...fused.values()].sort((a, b) => b.score - a.score);
75
+ }
76
+ function candidateKey(cand) {
77
+ // Fall back through id → key → content hash so deduplication is robust
78
+ // even when the raw store omits ids.
79
+ return cand.id || cand.key || cand.content.slice(0, 128);
80
+ }
81
+ // ── Recency Boost ──────────────────────────────────────────────
82
+ function applyRecencyBoost(scored, opts) {
83
+ const halfLifeMs = opts.recencyHalfLifeDays * 24 * 60 * 60 * 1000;
84
+ if (halfLifeMs <= 0)
85
+ return scored;
86
+ return scored
87
+ .map(({ candidate, score }) => {
88
+ const ts = pickTimestamp(candidate);
89
+ if (!ts || !Number.isFinite(ts)) {
90
+ return { candidate, score };
91
+ }
92
+ const ageMs = Math.max(0, opts.now - ts);
93
+ // Exponential decay, normalized to [0,1].
94
+ const recency = Math.pow(0.5, ageMs / halfLifeMs);
95
+ const boosted = score * (1 + opts.recencyWeight * recency);
96
+ return { candidate, score: boosted };
97
+ })
98
+ .sort((a, b) => b.score - a.score);
99
+ }
100
+ function pickTimestamp(cand) {
101
+ if (cand.updatedAt && Number.isFinite(cand.updatedAt))
102
+ return cand.updatedAt;
103
+ if (cand.createdAt && Number.isFinite(cand.createdAt))
104
+ return cand.createdAt;
105
+ const meta = cand.metadata;
106
+ if (meta) {
107
+ const candidates = ['timestamp', 'updatedAt', 'createdAt', 'time', 'ts'];
108
+ for (const k of candidates) {
109
+ const v = meta[k];
110
+ if (typeof v === 'number' && Number.isFinite(v))
111
+ return v;
112
+ if (typeof v === 'string') {
113
+ const parsed = Date.parse(v);
114
+ if (Number.isFinite(parsed))
115
+ return parsed;
116
+ }
117
+ }
118
+ }
119
+ return undefined;
120
+ }
121
+ // ── MMR Diversity (token-Jaccard proxy) ────────────────────────
122
+ function mmrRerank(scored, lambda, limit) {
123
+ if (scored.length <= 1)
124
+ return scored.slice(0, limit);
125
+ const selected = [];
126
+ const remaining = [...scored];
127
+ const selectedTokens = [];
128
+ // Seed with the top-scored candidate.
129
+ const first = remaining.shift();
130
+ selected.push(first);
131
+ selectedTokens.push(tokenize(first.candidate.content));
132
+ while (selected.length < limit && remaining.length > 0) {
133
+ let bestIdx = -1;
134
+ let bestMmr = -Infinity;
135
+ for (let i = 0; i < remaining.length; i++) {
136
+ const cand = remaining[i];
137
+ const candTokens = tokenize(cand.candidate.content);
138
+ let maxOverlap = 0;
139
+ for (const selTokens of selectedTokens) {
140
+ const sim = jaccard(candTokens, selTokens);
141
+ if (sim > maxOverlap)
142
+ maxOverlap = sim;
143
+ }
144
+ const mmr = lambda * cand.score - (1 - lambda) * maxOverlap;
145
+ if (mmr > bestMmr) {
146
+ bestMmr = mmr;
147
+ bestIdx = i;
148
+ }
149
+ }
150
+ if (bestIdx < 0)
151
+ break;
152
+ const [chosen] = remaining.splice(bestIdx, 1);
153
+ selected.push(chosen);
154
+ selectedTokens.push(tokenize(chosen.candidate.content));
155
+ }
156
+ return selected;
157
+ }
158
+ function tokenize(text) {
159
+ return new Set(text
160
+ .toLowerCase()
161
+ .replace(/[^\w\s]/g, ' ')
162
+ .split(/\s+/)
163
+ .filter((t) => t.length > 2 && !STOPWORDS.has(t)));
164
+ }
165
+ function jaccard(a, b) {
166
+ if (a.size === 0 && b.size === 0)
167
+ return 0;
168
+ let intersect = 0;
169
+ for (const t of a)
170
+ if (b.has(t))
171
+ intersect++;
172
+ const union = a.size + b.size - intersect;
173
+ return union === 0 ? 0 : intersect / union;
174
+ }
175
+ // ── Session Round-Robin ────────────────────────────────────────
176
+ function sessionRoundRobin(scored, sessionKey, limit) {
177
+ if (scored.length === 0)
178
+ return scored;
179
+ const bySession = new Map();
180
+ for (const item of scored) {
181
+ const sid = getSessionId(item.candidate, sessionKey) ?? '__no_session__';
182
+ const bucket = bySession.get(sid);
183
+ if (bucket)
184
+ bucket.push(item);
185
+ else
186
+ bySession.set(sid, [item]);
187
+ }
188
+ // If every candidate falls in the same bucket we can't diversify — pass through.
189
+ if (bySession.size <= 1)
190
+ return scored.slice(0, limit);
191
+ // Round-robin across session buckets, preferring each bucket's highest score.
192
+ const buckets = [...bySession.values()].map((b) => [...b].sort((a, b) => b.score - a.score));
193
+ const interleaved = [];
194
+ const seen = new Set();
195
+ while (interleaved.length < limit) {
196
+ let progressed = false;
197
+ for (const bucket of buckets) {
198
+ while (bucket.length > 0) {
199
+ const next = bucket.shift();
200
+ const key = candidateKey(next.candidate);
201
+ if (seen.has(key))
202
+ continue;
203
+ seen.add(key);
204
+ interleaved.push(next);
205
+ progressed = true;
206
+ break;
207
+ }
208
+ if (interleaved.length >= limit)
209
+ break;
210
+ }
211
+ if (!progressed)
212
+ break;
213
+ }
214
+ return interleaved;
215
+ }
216
+ function getSessionId(cand, key) {
217
+ const meta = cand.metadata;
218
+ if (!meta)
219
+ return undefined;
220
+ const v = meta[key];
221
+ return typeof v === 'string' ? v : undefined;
222
+ }
223
+ // ── Public API ─────────────────────────────────────────────────
224
+ export async function smartSearch(search, opts) {
225
+ const start = Date.now();
226
+ const limit = opts.limit ?? 10;
227
+ const threshold = opts.threshold ?? 0.3;
228
+ const fanOutK = opts.fanOutK ?? Math.max(limit * 3, 20);
229
+ const rrfK = opts.rrfK ?? 60;
230
+ const recencyHalfLifeDays = opts.recencyHalfLifeDays ?? 30;
231
+ const recencyWeight = opts.recencyWeight ?? 0.2;
232
+ const mmrLambda = opts.mmrLambda ?? 0.7;
233
+ const sessionKey = opts.sessionKey ?? 'session_id';
234
+ const now = opts.now ?? Date.now();
235
+ const multiQuery = opts.multiQuery !== false;
236
+ const recencyBoost = opts.recencyBoost !== false;
237
+ const diversityMMR = opts.diversityMMR !== false;
238
+ const sessionDiversity = opts.sessionDiversity !== false;
239
+ const expander = opts.queryExpansions ?? defaultQueryExpansions;
240
+ // ── Phase 1: query expansion + fan-out ──
241
+ const variants = multiQuery ? expander(opts.query) : [opts.query];
242
+ if (variants.length === 0)
243
+ variants.push(opts.query);
244
+ const ranked = [];
245
+ let totalRaw = 0;
246
+ for (const v of variants) {
247
+ const resp = await search({
248
+ query: v,
249
+ namespace: opts.namespace,
250
+ limit: fanOutK,
251
+ threshold,
252
+ });
253
+ ranked.push(resp.results);
254
+ totalRaw += resp.results.length;
255
+ }
256
+ // ── Phase 2: RRF fusion ──
257
+ let scored = ranked.length === 1
258
+ ? ranked[0].map((c) => ({ candidate: c, score: c.score }))
259
+ : reciprocalRankFusion(ranked, rrfK);
260
+ const afterRrfCount = scored.length;
261
+ // ── Phase 3: recency boost ──
262
+ if (recencyBoost) {
263
+ scored = applyRecencyBoost(scored, { recencyHalfLifeDays, recencyWeight, now });
264
+ }
265
+ const afterRecencyCount = scored.length;
266
+ // Truncate before MMR so we don't re-rank thousands of items.
267
+ if (scored.length > fanOutK)
268
+ scored = scored.slice(0, fanOutK);
269
+ // ── Phase 4: MMR diversity ──
270
+ if (diversityMMR) {
271
+ scored = mmrRerank(scored, mmrLambda, Math.min(limit * 2, scored.length));
272
+ }
273
+ else {
274
+ scored = scored.slice(0, Math.min(limit * 2, scored.length));
275
+ }
276
+ const afterMmrCount = scored.length;
277
+ // ── Phase 5: session round-robin ──
278
+ let final = scored;
279
+ if (sessionDiversity) {
280
+ final = sessionRoundRobin(scored, sessionKey, limit);
281
+ }
282
+ else {
283
+ final = scored.slice(0, limit);
284
+ }
285
+ const afterSessionCount = final.length;
286
+ return {
287
+ results: final.slice(0, limit).map(({ candidate, score }) => ({
288
+ ...candidate,
289
+ score,
290
+ })),
291
+ stats: {
292
+ variantCount: variants.length,
293
+ variants,
294
+ rawCandidateCount: totalRaw,
295
+ afterRrfCount,
296
+ afterRecencyCount,
297
+ afterMmrCount,
298
+ afterSessionCount,
299
+ durationMs: Date.now() - start,
300
+ },
301
+ };
302
+ }
303
+ //# sourceMappingURL=smart-retrieval.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smart-retrieval.js","sourceRoot":"","sources":["../src/smart-retrieval.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=smart-retrieval.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smart-retrieval.test.d.ts","sourceRoot":"","sources":["../src/smart-retrieval.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,238 @@
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
+ const search = vi.fn(async () => ({
63
+ results: [makeCandidate({ id: 'x', content: 'only one' })],
64
+ }));
65
+ await smartSearch(search, {
66
+ query: 'whatever',
67
+ multiQuery: false,
68
+ diversityMMR: false,
69
+ sessionDiversity: false,
70
+ recencyBoost: false,
71
+ });
72
+ expect(search).toHaveBeenCalledTimes(1);
73
+ });
74
+ });
75
+ describe('smartSearch — recency boost', () => {
76
+ it('reorders stale candidates below fresh ones at equal similarity', async () => {
77
+ const now = Date.parse('2026-04-11T00:00:00Z');
78
+ const oneDay = 24 * 60 * 60 * 1000;
79
+ const search = async () => ({
80
+ results: [
81
+ makeCandidate({
82
+ id: 'stale',
83
+ content: 'stale content',
84
+ score: 0.8,
85
+ updatedAt: now - 365 * oneDay, // a year old
86
+ }),
87
+ makeCandidate({
88
+ id: 'fresh',
89
+ content: 'fresh content',
90
+ score: 0.78,
91
+ updatedAt: now - oneDay, // yesterday
92
+ }),
93
+ ],
94
+ });
95
+ const { results } = await smartSearch(search, {
96
+ query: 'something',
97
+ limit: 2,
98
+ multiQuery: false,
99
+ diversityMMR: false,
100
+ sessionDiversity: false,
101
+ recencyHalfLifeDays: 30,
102
+ recencyWeight: 0.5,
103
+ now,
104
+ });
105
+ expect(results[0].id).toBe('fresh');
106
+ expect(results[1].id).toBe('stale');
107
+ });
108
+ it('is a no-op when candidates have no timestamps', async () => {
109
+ const search = async () => ({
110
+ results: [
111
+ makeCandidate({ id: 'a', content: 'alpha', score: 0.9 }),
112
+ makeCandidate({ id: 'b', content: 'beta', score: 0.8 }),
113
+ ],
114
+ });
115
+ const { results } = await smartSearch(search, {
116
+ query: 'x',
117
+ multiQuery: false,
118
+ diversityMMR: false,
119
+ sessionDiversity: false,
120
+ recencyBoost: true,
121
+ });
122
+ expect(results.map((r) => r.id)).toEqual(['a', 'b']);
123
+ });
124
+ });
125
+ describe('smartSearch — MMR diversity', () => {
126
+ it('pushes near-duplicate content down the list', async () => {
127
+ const search = async () => ({
128
+ results: [
129
+ makeCandidate({ id: '1', content: 'the cat sat on the mat quietly', score: 0.95 }),
130
+ makeCandidate({ id: '2', content: 'the cat sat on the mat quietly today', score: 0.94 }),
131
+ makeCandidate({ id: '3', content: 'quantum entanglement paradox resolved', score: 0.6 }),
132
+ ],
133
+ });
134
+ const { results } = await smartSearch(search, {
135
+ query: 'x',
136
+ limit: 3,
137
+ multiQuery: false,
138
+ recencyBoost: false,
139
+ sessionDiversity: false,
140
+ diversityMMR: true,
141
+ mmrLambda: 0.3, // lean heavily on diversity
142
+ });
143
+ // Seed is #1 (top). Between #2 (near-duplicate) and #3 (different topic),
144
+ // MMR with λ=0.3 should pick #3 second.
145
+ expect(results[0].id).toBe('1');
146
+ expect(results[1].id).toBe('3');
147
+ });
148
+ });
149
+ describe('smartSearch — session round-robin', () => {
150
+ it('interleaves results across distinct sessions', async () => {
151
+ const search = async () => ({
152
+ results: [
153
+ makeCandidate({
154
+ id: 'a1',
155
+ content: 'alpha one',
156
+ score: 0.95,
157
+ metadata: { session_id: 'A' },
158
+ }),
159
+ makeCandidate({
160
+ id: 'a2',
161
+ content: 'alpha two',
162
+ score: 0.9,
163
+ metadata: { session_id: 'A' },
164
+ }),
165
+ makeCandidate({
166
+ id: 'b1',
167
+ content: 'beta one',
168
+ score: 0.85,
169
+ metadata: { session_id: 'B' },
170
+ }),
171
+ makeCandidate({
172
+ id: 'c1',
173
+ content: 'gamma one',
174
+ score: 0.8,
175
+ metadata: { session_id: 'C' },
176
+ }),
177
+ ],
178
+ });
179
+ const { results } = await smartSearch(search, {
180
+ query: 'x',
181
+ limit: 3,
182
+ multiQuery: false,
183
+ recencyBoost: false,
184
+ diversityMMR: false,
185
+ sessionDiversity: true,
186
+ });
187
+ const sessions = results.map((r) => r.metadata?.session_id);
188
+ // Top-3 should cover 3 distinct sessions, not just session A twice.
189
+ expect(new Set(sessions).size).toBe(3);
190
+ expect(sessions[0]).toBe('A'); // Highest-scored bucket leader first
191
+ });
192
+ it('passes through when all candidates share one session', async () => {
193
+ const search = async () => ({
194
+ results: [
195
+ makeCandidate({
196
+ id: 'a1',
197
+ content: 'one',
198
+ score: 0.9,
199
+ metadata: { session_id: 'S' },
200
+ }),
201
+ makeCandidate({
202
+ id: 'a2',
203
+ content: 'two',
204
+ score: 0.8,
205
+ metadata: { session_id: 'S' },
206
+ }),
207
+ ],
208
+ });
209
+ const { results } = await smartSearch(search, {
210
+ query: 'x',
211
+ limit: 2,
212
+ multiQuery: false,
213
+ recencyBoost: false,
214
+ diversityMMR: false,
215
+ sessionDiversity: true,
216
+ });
217
+ expect(results.map((r) => r.id)).toEqual(['a1', 'a2']);
218
+ });
219
+ });
220
+ describe('smartSearch — stats reporting', () => {
221
+ it('reports variant count, raw candidate count, and phase counts', async () => {
222
+ const search = async () => ({
223
+ results: [
224
+ makeCandidate({ id: 'a', content: 'alpha' }),
225
+ makeCandidate({ id: 'b', content: 'beta' }),
226
+ ],
227
+ });
228
+ const { stats } = await smartSearch(search, {
229
+ query: 'hello world',
230
+ limit: 2,
231
+ });
232
+ expect(stats.variantCount).toBeGreaterThanOrEqual(1);
233
+ expect(stats.rawCandidateCount).toBeGreaterThan(0);
234
+ expect(stats.variants.length).toBe(stats.variantCount);
235
+ expect(stats.durationMs).toBeGreaterThanOrEqual(0);
236
+ });
237
+ });
238
+ //# 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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"}
@@ -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":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAK3C,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;IAsCjC;;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"}
@@ -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
  */
@@ -51,8 +51,17 @@ export class SQLiteBackend extends EventEmitter {
51
51
  async initialize() {
52
52
  if (this.initialized)
53
53
  return;
54
+ let DatabaseCtor;
55
+ try {
56
+ DatabaseCtor = (await import('better-sqlite3')).default;
57
+ }
58
+ catch (err) {
59
+ throw new Error("@claude-flow/memory: SQLiteBackend requires the optional 'better-sqlite3' package. " +
60
+ "Install it with `npm i better-sqlite3` or use a different DatabaseProvider " +
61
+ "(sql.js / rvf / json). Original error: " + (err instanceof Error ? err.message : String(err)));
62
+ }
54
63
  // Open database connection
55
- this.db = new Database(this.config.databasePath, {
64
+ this.db = new DatabaseCtor(this.config.databasePath, {
56
65
  verbose: this.config.verbose ? console.log : undefined,
57
66
  });
58
67
  // Enable WAL mode for better concurrency
@@ -526,15 +535,15 @@ export class SQLiteBackend extends EventEmitter {
526
535
  embedding,
527
536
  type: row.type,
528
537
  namespace: row.namespace,
529
- tags: JSON.parse(row.tags),
530
- metadata: JSON.parse(row.metadata),
538
+ tags: safeJsonParse(row.tags || '[]'),
539
+ metadata: safeJsonParse(row.metadata || '{}'),
531
540
  ownerId: row.owner_id,
532
541
  accessLevel: row.access_level,
533
542
  createdAt: row.created_at,
534
543
  updatedAt: row.updated_at,
535
544
  expiresAt: row.expires_at,
536
545
  version: row.version,
537
- references: JSON.parse(row.references),
546
+ references: safeJsonParse(row.references || '[]'),
538
547
  accessCount: row.access_count,
539
548
  lastAccessedAt: row.last_accessed_at,
540
549
  };