@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.
- package/dist/agentdb-backend.d.ts +6 -0
- package/dist/agentdb-backend.d.ts.map +1 -1
- package/dist/agentdb-backend.js +71 -24
- package/dist/agentdb-backend.js.map +1 -1
- package/dist/auto-memory-bridge.d.ts.map +1 -1
- package/dist/auto-memory-bridge.js +10 -1
- package/dist/auto-memory-bridge.js.map +1 -1
- package/dist/auto-memory-bridge.test.js +45 -9
- package/dist/auto-memory-bridge.test.js.map +1 -1
- package/dist/controller-registry.test.js +1 -1
- package/dist/controller-registry.test.js.map +1 -1
- package/dist/database-provider.test.js +15 -3
- package/dist/database-provider.test.js.map +1 -1
- package/dist/hnsw-index.d.ts.map +1 -1
- package/dist/hnsw-index.js +179 -12
- package/dist/hnsw-index.js.map +1 -1
- package/dist/hybrid-backend.test.js +6 -5
- package/dist/hybrid-backend.test.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/json-security.d.ts +12 -0
- package/dist/json-security.d.ts.map +1 -0
- package/dist/json-security.js +20 -0
- package/dist/json-security.js.map +1 -0
- package/dist/rvf-backend.d.ts +2 -0
- package/dist/rvf-backend.d.ts.map +1 -1
- package/dist/rvf-backend.js +9 -1
- package/dist/rvf-backend.js.map +1 -1
- package/dist/smart-retrieval.d.ts +94 -0
- package/dist/smart-retrieval.d.ts.map +1 -0
- package/dist/smart-retrieval.js +303 -0
- package/dist/smart-retrieval.js.map +1 -0
- package/dist/smart-retrieval.test.d.ts +2 -0
- package/dist/smart-retrieval.test.d.ts.map +1 -0
- package/dist/smart-retrieval.test.js +238 -0
- package/dist/smart-retrieval.test.js.map +1 -0
- package/dist/sqlite-backend.d.ts.map +1 -1
- package/dist/sqlite-backend.js +14 -5
- package/dist/sqlite-backend.js.map +1 -1
- package/dist/sqljs-backend.d.ts.map +1 -1
- package/dist/sqljs-backend.js +4 -3
- package/dist/sqljs-backend.js.map +1 -1
- 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 @@
|
|
|
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;
|
|
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"}
|
package/dist/sqlite-backend.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @module v3/memory/sqlite-backend
|
|
9
9
|
*/
|
|
10
10
|
import { EventEmitter } from 'node:events';
|
|
11
|
-
import
|
|
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
|
|
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:
|
|
530
|
-
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:
|
|
546
|
+
references: safeJsonParse(row.references || '[]'),
|
|
538
547
|
accessCount: row.access_count,
|
|
539
548
|
lastAccessedAt: row.last_accessed_at,
|
|
540
549
|
};
|