@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.
- package/README.md +308 -37
- package/dist/agent-memory-scope.d.ts.map +1 -1
- package/dist/agent-memory-scope.js +10 -2
- package/dist/agent-memory-scope.js.map +1 -1
- package/dist/agentdb-adapter-key-isolation.test.d.ts +2 -0
- package/dist/agentdb-adapter-key-isolation.test.d.ts.map +1 -0
- package/dist/agentdb-adapter-key-isolation.test.js +175 -0
- package/dist/agentdb-adapter-key-isolation.test.js.map +1 -0
- package/dist/agentdb-adapter-upsert.test.d.ts +2 -0
- package/dist/agentdb-adapter-upsert.test.d.ts.map +1 -0
- package/dist/agentdb-adapter-upsert.test.js +216 -0
- package/dist/agentdb-adapter-upsert.test.js.map +1 -0
- package/dist/agentdb-adapter.d.ts +70 -1
- package/dist/agentdb-adapter.d.ts.map +1 -1
- package/dist/agentdb-adapter.js +365 -32
- package/dist/agentdb-adapter.js.map +1 -1
- package/dist/agentdb-backend.d.ts +25 -0
- package/dist/agentdb-backend.d.ts.map +1 -1
- package/dist/agentdb-backend.js +113 -28
- package/dist/agentdb-backend.js.map +1 -1
- package/dist/agentdb-retrieval-guard.d.ts +76 -0
- package/dist/agentdb-retrieval-guard.d.ts.map +1 -0
- package/dist/agentdb-retrieval-guard.js +77 -0
- package/dist/agentdb-retrieval-guard.js.map +1 -0
- package/dist/agentdb-retrieval-guard.test.d.ts +12 -0
- package/dist/agentdb-retrieval-guard.test.d.ts.map +1 -0
- package/dist/agentdb-retrieval-guard.test.js +129 -0
- package/dist/agentdb-retrieval-guard.test.js.map +1 -0
- package/dist/auto-memory-bridge.d.ts +12 -1
- package/dist/auto-memory-bridge.d.ts.map +1 -1
- package/dist/auto-memory-bridge.js +101 -7
- package/dist/auto-memory-bridge.js.map +1 -1
- package/dist/auto-memory-bridge.test.js +121 -15
- package/dist/auto-memory-bridge.test.js.map +1 -1
- package/dist/auto-memory-index-3224.test.d.ts +2 -0
- package/dist/auto-memory-index-3224.test.d.ts.map +1 -0
- package/dist/auto-memory-index-3224.test.js +77 -0
- package/dist/auto-memory-index-3224.test.js.map +1 -0
- package/dist/consolidator-embedding-benchmark.test.d.ts +39 -0
- package/dist/consolidator-embedding-benchmark.test.d.ts.map +1 -0
- package/dist/consolidator-embedding-benchmark.test.js +366 -0
- package/dist/consolidator-embedding-benchmark.test.js.map +1 -0
- package/dist/consolidator.d.ts +131 -0
- package/dist/consolidator.d.ts.map +1 -0
- package/dist/consolidator.js +309 -0
- package/dist/consolidator.js.map +1 -0
- package/dist/consolidator.test.d.ts +17 -0
- package/dist/consolidator.test.d.ts.map +1 -0
- package/dist/consolidator.test.js +376 -0
- package/dist/consolidator.test.js.map +1 -0
- package/dist/controller-registry.d.ts +290 -0
- package/dist/controller-registry.d.ts.map +1 -0
- package/dist/controller-registry.js +1095 -0
- package/dist/controller-registry.js.map +1 -0
- package/dist/controller-registry.test.d.ts +14 -0
- package/dist/controller-registry.test.d.ts.map +1 -0
- package/dist/controller-registry.test.js +818 -0
- package/dist/controller-registry.test.js.map +1 -0
- package/dist/database-provider.d.ts +14 -2
- package/dist/database-provider.d.ts.map +1 -1
- package/dist/database-provider.js +63 -3
- package/dist/database-provider.js.map +1 -1
- package/dist/database-provider.test.js +56 -3
- package/dist/database-provider.test.js.map +1 -1
- package/dist/entity-tagger.d.ts +31 -0
- package/dist/entity-tagger.d.ts.map +1 -0
- package/dist/entity-tagger.js +80 -0
- package/dist/entity-tagger.js.map +1 -0
- package/dist/entity-tagger.test.d.ts +12 -0
- package/dist/entity-tagger.test.d.ts.map +1 -0
- package/dist/entity-tagger.test.js +70 -0
- package/dist/entity-tagger.test.js.map +1 -0
- package/dist/fts5.test.d.ts +13 -0
- package/dist/fts5.test.d.ts.map +1 -0
- package/dist/fts5.test.js +144 -0
- package/dist/fts5.test.js.map +1 -0
- package/dist/graceful-retrieval.test.d.ts +14 -0
- package/dist/graceful-retrieval.test.d.ts.map +1 -0
- package/dist/graceful-retrieval.test.js +251 -0
- package/dist/graceful-retrieval.test.js.map +1 -0
- package/dist/hnsw-index.d.ts +62 -0
- package/dist/hnsw-index.d.ts.map +1 -1
- package/dist/hnsw-index.js +461 -16
- package/dist/hnsw-index.js.map +1 -1
- package/dist/hnsw-persistence.test.d.ts +13 -0
- package/dist/hnsw-persistence.test.d.ts.map +1 -0
- package/dist/hnsw-persistence.test.js +208 -0
- package/dist/hnsw-persistence.test.js.map +1 -0
- package/dist/hnsw-quantization.test.d.ts +17 -0
- package/dist/hnsw-quantization.test.d.ts.map +1 -0
- package/dist/hnsw-quantization.test.js +140 -0
- package/dist/hnsw-quantization.test.js.map +1 -0
- package/dist/hybrid-backend.d.ts +46 -2
- package/dist/hybrid-backend.d.ts.map +1 -1
- package/dist/hybrid-backend.js +120 -21
- package/dist/hybrid-backend.js.map +1 -1
- package/dist/hybrid-backend.test.js +139 -5
- package/dist/hybrid-backend.test.js.map +1 -1
- package/dist/index.d.ts +164 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +272 -30
- package/dist/index.js.map +1 -1
- package/dist/index.test.d.ts +10 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +71 -0
- package/dist/index.test.js.map +1 -0
- 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/learning-bridge.d.ts +29 -0
- package/dist/learning-bridge.d.ts.map +1 -1
- package/dist/learning-bridge.js +71 -6
- package/dist/learning-bridge.js.map +1 -1
- package/dist/learning-bridge.test.js +181 -1
- package/dist/learning-bridge.test.js.map +1 -1
- package/dist/memory-key.d.ts +3 -0
- package/dist/memory-key.d.ts.map +1 -0
- package/dist/memory-key.js +5 -0
- package/dist/memory-key.js.map +1 -0
- package/dist/migration.d.ts +19 -4
- package/dist/migration.d.ts.map +1 -1
- package/dist/migration.js +99 -24
- package/dist/migration.js.map +1 -1
- package/dist/migration.test.d.ts +11 -0
- package/dist/migration.test.d.ts.map +1 -0
- package/dist/migration.test.js +141 -0
- package/dist/migration.test.js.map +1 -0
- package/dist/mmr-benchmark.test.d.ts +2 -0
- package/dist/mmr-benchmark.test.d.ts.map +1 -0
- package/dist/mmr-benchmark.test.js +286 -0
- package/dist/mmr-benchmark.test.js.map +1 -0
- package/dist/mmr-rerank-perf.test.d.ts +2 -0
- package/dist/mmr-rerank-perf.test.d.ts.map +1 -0
- package/dist/mmr-rerank-perf.test.js +191 -0
- package/dist/mmr-rerank-perf.test.js.map +1 -0
- package/dist/persistent-sona.d.ts +144 -0
- package/dist/persistent-sona.d.ts.map +1 -0
- package/dist/persistent-sona.js +332 -0
- package/dist/persistent-sona.js.map +1 -0
- package/dist/rvf-backend.d.ts +53 -0
- package/dist/rvf-backend.d.ts.map +1 -0
- package/dist/rvf-backend.js +643 -0
- package/dist/rvf-backend.js.map +1 -0
- package/dist/rvf-learning-store.d.ts +139 -0
- package/dist/rvf-learning-store.d.ts.map +1 -0
- package/dist/rvf-learning-store.js +295 -0
- package/dist/rvf-learning-store.js.map +1 -0
- package/dist/rvf-migration.d.ts +45 -0
- package/dist/rvf-migration.d.ts.map +1 -0
- package/dist/rvf-migration.js +254 -0
- package/dist/rvf-migration.js.map +1 -0
- package/dist/smart-retrieval-scores.test.d.ts +2 -0
- package/dist/smart-retrieval-scores.test.d.ts.map +1 -0
- package/dist/smart-retrieval-scores.test.js +75 -0
- package/dist/smart-retrieval-scores.test.js.map +1 -0
- package/dist/smart-retrieval.d.ts +141 -0
- package/dist/smart-retrieval.d.ts.map +1 -0
- package/dist/smart-retrieval.js +389 -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 +418 -0
- package/dist/smart-retrieval.test.js.map +1 -0
- package/dist/sqlite-backend.d.ts +10 -0
- package/dist/sqlite-backend.d.ts.map +1 -1
- package/dist/sqlite-backend.js +101 -5
- package/dist/sqlite-backend.js.map +1 -1
- package/dist/sqljs-backend.d.ts +9 -0
- package/dist/sqljs-backend.d.ts.map +1 -1
- package/dist/sqljs-backend.js +91 -3
- package/dist/sqljs-backend.js.map +1 -1
- package/dist/tiered-memory.d.ts +171 -0
- package/dist/tiered-memory.d.ts.map +1 -0
- package/dist/tiered-memory.js +350 -0
- package/dist/tiered-memory.js.map +1 -0
- package/dist/tiered-memory.test.d.ts +9 -0
- package/dist/tiered-memory.test.d.ts.map +1 -0
- package/dist/tiered-memory.test.js +220 -0
- package/dist/tiered-memory.test.js.map +1 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- 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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"}
|
package/dist/sqlite-backend.d.ts
CHANGED
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"sqlite-backend.d.ts","sourceRoot":"","sources":["../src/sqlite-backend.ts"],"names":[],"mappings":"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"}
|
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
|
*/
|
|
@@ -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
|
|
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:
|
|
530
|
-
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:
|
|
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
|