@vortex-os/memory-extended 0.5.1 → 0.5.3
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 +17 -17
- package/dist/mcp/recall-tool.d.ts +13 -1
- package/dist/mcp/recall-tool.d.ts.map +1 -1
- package/dist/mcp/recall-tool.js +37 -8
- package/dist/mcp/recall-tool.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +4 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/recall/engine.d.ts +20 -18
- package/dist/recall/engine.d.ts.map +1 -1
- package/dist/recall/engine.js +248 -56
- package/dist/recall/engine.js.map +1 -1
- package/dist/recall/ftsQuery.d.ts +29 -0
- package/dist/recall/ftsQuery.d.ts.map +1 -0
- package/dist/recall/ftsQuery.js +36 -0
- package/dist/recall/ftsQuery.js.map +1 -0
- package/dist/recall/fusion.d.ts +58 -0
- package/dist/recall/fusion.d.ts.map +1 -0
- package/dist/recall/fusion.js +115 -0
- package/dist/recall/fusion.js.map +1 -0
- package/dist/recall/index.d.ts +3 -1
- package/dist/recall/index.d.ts.map +1 -1
- package/dist/recall/index.js +1 -0
- package/dist/recall/index.js.map +1 -1
- package/dist/recall/types.d.ts +24 -2
- package/dist/recall/types.d.ts.map +1 -1
- package/dist/sessionArchive/adapters/claude-code.d.ts.map +1 -1
- package/dist/sessionArchive/adapters/claude-code.js +38 -4
- package/dist/sessionArchive/adapters/claude-code.js.map +1 -1
- package/dist/sessionArchive/index.d.ts +1 -1
- package/dist/sessionArchive/index.d.ts.map +1 -1
- package/dist/sessionArchive/index.js.map +1 -1
- package/dist/sessionArchive/store.d.ts +22 -1
- package/dist/sessionArchive/store.d.ts.map +1 -1
- package/dist/sessionArchive/store.js +143 -12
- package/dist/sessionArchive/store.js.map +1 -1
- package/dist/sqlite/fts.d.ts +38 -0
- package/dist/sqlite/fts.d.ts.map +1 -0
- package/dist/sqlite/fts.js +102 -0
- package/dist/sqlite/fts.js.map +1 -0
- package/dist/sqlite/index.d.ts +2 -0
- package/dist/sqlite/index.d.ts.map +1 -1
- package/dist/sqlite/index.js +1 -0
- package/dist/sqlite/index.js.map +1 -1
- package/dist/sqlite/store.d.ts +8 -1
- package/dist/sqlite/store.d.ts.map +1 -1
- package/dist/sqlite/store.js +29 -7
- package/dist/sqlite/store.js.map +1 -1
- package/dist/vector/embedder.d.ts +11 -0
- package/dist/vector/embedder.d.ts.map +1 -1
- package/dist/vector/embedder.js +4 -1
- package/dist/vector/embedder.js.map +1 -1
- package/dist/vector/segment.d.ts +1 -1
- package/dist/vector/store.d.ts +12 -2
- package/dist/vector/store.d.ts.map +1 -1
- package/dist/vector/store.js +17 -2
- package/dist/vector/store.js.map +1 -1
- package/dist/vector/types.d.ts +1 -1
- package/dist/vector/types.js +1 -1
- package/package.json +3 -3
package/dist/recall/engine.js
CHANGED
|
@@ -1,72 +1,203 @@
|
|
|
1
1
|
import { parseIntent } from "./intent.js";
|
|
2
|
+
import { fuse, fetchK, fusionKey, TOOL_RESULT_MULTIPLIER, } from "./fusion.js";
|
|
2
3
|
const DEFAULT_K = 5;
|
|
3
4
|
const EXCERPT_CHARS = 280;
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Hybrid recall (P3). Branches on `mode`:
|
|
7
|
+
* - `semantic` — the original cosine pipeline (memory hard-filter → vector
|
|
8
|
+
* rerank), unchanged in behavior.
|
|
9
|
+
* - `keyword` — FTS5 over memories + session events.
|
|
10
|
+
* - `hybrid` (default) — both lanes, fused by Reciprocal Rank Fusion.
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* filter never costs you a relevant-but-unfiltered memory.
|
|
18
|
-
* 3. **Semantic rerank** — embed the (filter-stripped) query text and ask
|
|
19
|
-
* the vector store for the closest candidates.
|
|
20
|
-
* 4. **Hydrate** — join vector hits back to their memory rows for name,
|
|
21
|
-
* description, tags, and a body excerpt.
|
|
12
|
+
* Returns structured {@link RecallResult} data — never a pre-rendered report.
|
|
13
|
+
* The memory hard-filter (parsed type/tag/date) constrains the MEMORY lanes
|
|
14
|
+
* only; session lanes filter by their own semantics (§12 R8). Session hits fuse
|
|
15
|
+
* at the SESSION level — the semantic lane is de-duped to one best chunk per
|
|
16
|
+
* session before ranking (§13). The tool_result keyword downweight applies in
|
|
17
|
+
* `hybrid` only (§12 R6). `score` stays cosine for any semantically-matched
|
|
18
|
+
* hit; a keyword-only hit gets `1/(1+rank)` (§12 R7).
|
|
22
19
|
*/
|
|
23
20
|
export async function recall(params, deps) {
|
|
24
21
|
const { sqlite, vector, embed } = deps;
|
|
25
22
|
const k = params.k ?? DEFAULT_K;
|
|
23
|
+
const mode = params.mode ?? "hybrid";
|
|
26
24
|
const intent = parseIntent(params.query);
|
|
27
25
|
const corpusSize = vector.count(params.source);
|
|
26
|
+
const fk = fetchK(k);
|
|
27
|
+
// ---- memory hard-filter — MEMORY lanes only (never session lanes, §12 R8) ----
|
|
28
28
|
let appliedFilters = {};
|
|
29
|
-
let
|
|
29
|
+
let memIds;
|
|
30
30
|
let hardFilterCandidates = corpusSize;
|
|
31
31
|
let hardFilterDropped = false;
|
|
32
|
-
|
|
33
|
-
if (hasFilters && !params.noHardFilter) {
|
|
32
|
+
if (hasAnyFilter(intent.filters) && !params.noHardFilter) {
|
|
34
33
|
const filtered = sqlite.query(intent.filters);
|
|
35
34
|
if (filtered.length < k) {
|
|
36
|
-
// Too narrow — drop it
|
|
37
|
-
//
|
|
35
|
+
// Too narrow — drop it; searching the full corpus is safer than excluding
|
|
36
|
+
// a relevant memory the guessed filter happened to miss.
|
|
38
37
|
hardFilterDropped = true;
|
|
39
|
-
hardFilterCandidates = corpusSize;
|
|
40
38
|
}
|
|
41
39
|
else {
|
|
42
40
|
appliedFilters = intent.filters;
|
|
43
|
-
|
|
41
|
+
memIds = new Set(filtered.map((r) => r.id));
|
|
44
42
|
hardFilterCandidates = filtered.length;
|
|
45
43
|
}
|
|
46
44
|
}
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
const runKeyword = mode !== "semantic";
|
|
46
|
+
const runSemantic = mode !== "keyword";
|
|
47
|
+
const wantMemory = params.source !== "session-archive";
|
|
48
|
+
const wantSession = params.source !== "memory";
|
|
49
|
+
const hydration = new Map();
|
|
50
|
+
const semanticItems = [];
|
|
51
|
+
const keywordItems = [];
|
|
52
|
+
// ---- SEMANTIC lane (cosine) ----
|
|
53
|
+
if (runSemantic) {
|
|
54
|
+
if (wantMemory) {
|
|
55
|
+
const hits = await vector.search(intent.semanticText, embed, {
|
|
56
|
+
source: "memory",
|
|
57
|
+
ids: memIds,
|
|
58
|
+
k: fk,
|
|
59
|
+
});
|
|
60
|
+
for (const vh of hits) {
|
|
61
|
+
const row = sqlite.getById(vh.id);
|
|
62
|
+
if (!row)
|
|
63
|
+
continue; // drift: vector present, memory deleted
|
|
64
|
+
const key = fusionKey("memory", vh.id);
|
|
65
|
+
if (!hydration.has(key))
|
|
66
|
+
hydration.set(key, { kind: "memory", row });
|
|
67
|
+
semanticItems.push({ key, weight: 1, score: vh.score, recency: row.updated });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (wantSession && deps.sessionChunks) {
|
|
71
|
+
const sessCount = vector.count("session-archive");
|
|
72
|
+
const vhits = await vector.search(intent.semanticText, embed, {
|
|
73
|
+
source: "session-archive",
|
|
74
|
+
k: sessCount > 0 ? sessCount : fk,
|
|
75
|
+
});
|
|
76
|
+
if (mode === "semantic") {
|
|
77
|
+
// Legacy behavior: PER-CHUNK session hits (id = chunk id), no collapse —
|
|
78
|
+
// mode:"semantic" reproduces the original cosine pipeline exactly (§12 R1).
|
|
79
|
+
for (const vh of vhits.slice(0, fk)) {
|
|
80
|
+
const chunk = deps.sessionChunks.getById(vh.id);
|
|
81
|
+
if (!chunk)
|
|
82
|
+
continue;
|
|
83
|
+
const key = fusionKey("session-archive", chunk.id);
|
|
84
|
+
hydration.set(key, { kind: "session", chunk, perChunk: true });
|
|
85
|
+
semanticItems.push({ key, weight: 1, score: vh.score, recency: chunk.startedAt });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
// Hybrid: collapse to the single best chunk per session so the keyword and
|
|
90
|
+
// semantic lanes fuse on a shared SESSION-level key (§13). Over-fetch in
|
|
91
|
+
// DISTINCT sessions (§14) — search broadly, collapse, then take fk sessions.
|
|
92
|
+
const best = new Map();
|
|
93
|
+
for (const vh of vhits) {
|
|
94
|
+
const chunk = deps.sessionChunks.getById(vh.id);
|
|
95
|
+
if (!chunk)
|
|
96
|
+
continue;
|
|
97
|
+
const sk = `${chunk.host}/${chunk.sessionId}`;
|
|
98
|
+
const prev = best.get(sk);
|
|
99
|
+
if (!prev ||
|
|
100
|
+
vh.score > prev.score ||
|
|
101
|
+
(vh.score === prev.score && chunk.chunkIdx < prev.chunk.chunkIdx)) {
|
|
102
|
+
best.set(sk, { chunk, score: vh.score });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const sessions = [...best.entries()]
|
|
106
|
+
.map(([sk, v]) => ({ sk, chunk: v.chunk, score: v.score }))
|
|
107
|
+
.sort(compareSessionSemantic)
|
|
108
|
+
.slice(0, fk);
|
|
109
|
+
for (const { sk, chunk, score } of sessions) {
|
|
110
|
+
const key = fusionKey("session-archive", sk);
|
|
111
|
+
const ref = hydration.get(key);
|
|
112
|
+
if (ref)
|
|
113
|
+
ref.chunk = chunk;
|
|
114
|
+
else
|
|
115
|
+
hydration.set(key, { kind: "session", chunk });
|
|
116
|
+
semanticItems.push({ key, weight: 1, score, recency: chunk.startedAt });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// ---- KEYWORD lane (FTS5) ----
|
|
122
|
+
if (runKeyword) {
|
|
123
|
+
if (wantMemory) {
|
|
124
|
+
const mids = sqlite.keywordSearch(intent.semanticText, { limit: fk, ids: memIds });
|
|
125
|
+
mids.forEach((mid, i) => {
|
|
126
|
+
const key = fusionKey("memory", mid);
|
|
127
|
+
let ref = hydration.get(key);
|
|
128
|
+
if (!ref) {
|
|
129
|
+
const row = sqlite.getById(mid);
|
|
130
|
+
if (!row)
|
|
131
|
+
return; // drift
|
|
132
|
+
ref = { kind: "memory", row };
|
|
133
|
+
hydration.set(key, ref);
|
|
134
|
+
}
|
|
135
|
+
keywordItems.push({ key, weight: 1, score: rankConfidence(i), recency: ref.row.updated });
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (wantSession && deps.sessionArchive) {
|
|
139
|
+
// Fetch broadly so the per-session collapse below still yields fk DISTINCT
|
|
140
|
+
// sessions even when one chatty session owns many of the top events (Codex F3).
|
|
141
|
+
// O(window) at current archive scale; a SQL GROUP-BY per session is the scale follow-up.
|
|
142
|
+
const evHits = deps.sessionArchive.searchEvents(intent.semanticText, {
|
|
143
|
+
limit: Math.max(fk * 50, 500),
|
|
144
|
+
});
|
|
145
|
+
// Collapse to the best (first = best-ranked) event per session.
|
|
146
|
+
const bestEv = new Map();
|
|
147
|
+
for (const ev of evHits) {
|
|
148
|
+
const sk = `${ev.host}/${ev.sessionId}`;
|
|
149
|
+
if (!bestEv.has(sk))
|
|
150
|
+
bestEv.set(sk, ev);
|
|
151
|
+
}
|
|
152
|
+
let r = 0;
|
|
153
|
+
for (const [sk, ev] of bestEv) {
|
|
154
|
+
if (r >= fk)
|
|
155
|
+
break; // distinct-session over-fetch budget
|
|
156
|
+
const key = fusionKey("session-archive", sk);
|
|
157
|
+
// tool_result downweight is HYBRID-only — keyword-only forensic recall
|
|
158
|
+
// must not be penalized (§12 R6).
|
|
159
|
+
const weight = mode === "hybrid" && ev.kind === "tool_result" ? TOOL_RESULT_MULTIPLIER : 1;
|
|
160
|
+
keywordItems.push({ key, weight, score: rankConfidence(r), recency: ev.at });
|
|
161
|
+
const ref = hydration.get(key);
|
|
162
|
+
if (ref)
|
|
163
|
+
ref.keywordEvent = ev; // §12 R2: prefer the matched event's snippet
|
|
164
|
+
else
|
|
165
|
+
hydration.set(key, { kind: "session", keywordEvent: ev });
|
|
166
|
+
r++;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// ---- total per-lane ordering before RRF rank assignment (§13 R3) ----
|
|
171
|
+
semanticItems.sort(compareLaneItem);
|
|
172
|
+
keywordItems.sort(compareLaneItem);
|
|
173
|
+
const lanes = [];
|
|
174
|
+
if (runSemantic)
|
|
175
|
+
lanes.push({ name: "semantic", items: semanticItems });
|
|
176
|
+
if (runKeyword)
|
|
177
|
+
lanes.push({ name: "keyword", items: keywordItems });
|
|
178
|
+
const fused = fuse(lanes, k);
|
|
52
179
|
const hits = [];
|
|
53
|
-
for (const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (!chunk)
|
|
57
|
-
continue; // no session-chunk metadata (not hydratable) — skip
|
|
58
|
-
hits.push(toSessionHit(chunk, vh.score));
|
|
180
|
+
for (const entry of fused) {
|
|
181
|
+
const ref = hydration.get(entry.key);
|
|
182
|
+
if (!ref)
|
|
59
183
|
continue;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
hits.push(toHit(row, vh.source, vh.score));
|
|
184
|
+
const score = entry.scoreByLane.semantic ?? entry.scoreByLane.keyword ?? 0;
|
|
185
|
+
// mode:"semantic" keeps the original hit shape — no fusion metadata (§12 R1).
|
|
186
|
+
const meta = mode === "semantic" ? {} : { lanes: entry.lanes, fusedScore: entry.fusedScore };
|
|
187
|
+
hits.push(ref.kind === "memory" ? hydrateMemory(ref, score, meta) : hydrateSession(ref, score, meta));
|
|
65
188
|
}
|
|
66
189
|
return {
|
|
67
190
|
query: params.query,
|
|
68
191
|
intent,
|
|
69
|
-
stage: {
|
|
192
|
+
stage: {
|
|
193
|
+
appliedFilters,
|
|
194
|
+
hardFilterCandidates,
|
|
195
|
+
hardFilterDropped,
|
|
196
|
+
corpusSize,
|
|
197
|
+
mode,
|
|
198
|
+
lanesRun: lanes.map((l) => l.name),
|
|
199
|
+
laneCandidates: { semantic: semanticItems.length, keyword: keywordItems.length },
|
|
200
|
+
},
|
|
70
201
|
hits,
|
|
71
202
|
};
|
|
72
203
|
}
|
|
@@ -76,10 +207,11 @@ function hasAnyFilter(q) {
|
|
|
76
207
|
(q.privacy && q.privacy.length) ||
|
|
77
208
|
q.updatedSinceMs !== undefined);
|
|
78
209
|
}
|
|
79
|
-
function
|
|
210
|
+
function hydrateMemory(ref, score, meta) {
|
|
211
|
+
const row = ref.row;
|
|
80
212
|
return {
|
|
81
213
|
id: row.id,
|
|
82
|
-
source,
|
|
214
|
+
source: "memory",
|
|
83
215
|
score,
|
|
84
216
|
name: row.name,
|
|
85
217
|
description: row.description,
|
|
@@ -87,27 +219,87 @@ function toHit(row, source, score) {
|
|
|
87
219
|
updated: row.updated,
|
|
88
220
|
tags: row.tags,
|
|
89
221
|
excerpt: excerpt(row.body),
|
|
222
|
+
...meta,
|
|
90
223
|
};
|
|
91
224
|
}
|
|
92
|
-
function
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
225
|
+
function hydrateSession(ref, score, meta) {
|
|
226
|
+
const chunk = ref.chunk;
|
|
227
|
+
const ev = ref.keywordEvent;
|
|
228
|
+
if (ref.perChunk && chunk) {
|
|
229
|
+
// Legacy per-CHUNK session hit (mode:"semantic") — original id/name shape (§12 R1).
|
|
230
|
+
const cday = chunk.startedAt ? chunk.startedAt.slice(0, 10) : null;
|
|
231
|
+
return {
|
|
232
|
+
id: chunk.id,
|
|
233
|
+
source: "session-archive",
|
|
234
|
+
score,
|
|
235
|
+
name: `${chunk.host} session${cday ? ` (${cday})` : ""} · part ${chunk.chunkIdx + 1}`,
|
|
236
|
+
description: "",
|
|
237
|
+
type: "session",
|
|
238
|
+
updated: chunk.startedAt,
|
|
239
|
+
tags: [chunk.host],
|
|
240
|
+
excerpt: chunk.excerpt,
|
|
241
|
+
...meta,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const host = chunk?.host ?? ev?.host ?? "session";
|
|
245
|
+
const sessionId = chunk?.sessionId ?? ev?.sessionId ?? "";
|
|
246
|
+
const when = chunk?.startedAt ?? ev?.at ?? null;
|
|
247
|
+
const day = when ? when.slice(0, 10) : null;
|
|
248
|
+
// §12 R2: when the keyword lane matched, prefer the matched event's text as
|
|
249
|
+
// the excerpt (the actual evidence — e.g. the error line) over an unrelated
|
|
250
|
+
// semantic chunk; otherwise use the chunk excerpt.
|
|
251
|
+
const excerptText = ev?.text ? excerpt(ev.text) : chunk ? chunk.excerpt : "";
|
|
101
252
|
return {
|
|
102
|
-
id:
|
|
253
|
+
id: `${host}/${sessionId}`,
|
|
103
254
|
source: "session-archive",
|
|
104
255
|
score,
|
|
105
|
-
name: `${
|
|
256
|
+
name: `${host} session${day ? ` (${day})` : ""}`,
|
|
106
257
|
description: "",
|
|
107
258
|
type: "session",
|
|
108
|
-
updated:
|
|
109
|
-
tags: [
|
|
110
|
-
excerpt:
|
|
259
|
+
updated: when,
|
|
260
|
+
tags: [host],
|
|
261
|
+
excerpt: excerptText,
|
|
262
|
+
...meta,
|
|
111
263
|
};
|
|
112
264
|
}
|
|
265
|
+
function excerpt(body) {
|
|
266
|
+
const collapsed = body.replace(/\s+/g, " ").trim();
|
|
267
|
+
return collapsed.length <= EXCERPT_CHARS
|
|
268
|
+
? collapsed
|
|
269
|
+
: `${collapsed.slice(0, EXCERPT_CHARS).trimEnd()}…`;
|
|
270
|
+
}
|
|
271
|
+
/** Display/tie-break confidence for a keyword hit at 0-based position `i` → `1/(1+rank)` (§12 R7). */
|
|
272
|
+
function rankConfidence(i) {
|
|
273
|
+
return 1 / (2 + i);
|
|
274
|
+
}
|
|
275
|
+
/** Total order for a lane's items before RRF (§13 R3): score desc, recency desc (null last), key asc. */
|
|
276
|
+
function compareLaneItem(a, b) {
|
|
277
|
+
if (b.score !== a.score)
|
|
278
|
+
return b.score - a.score;
|
|
279
|
+
const r = compareRecencyDesc(a.recency, b.recency);
|
|
280
|
+
if (r !== 0)
|
|
281
|
+
return r;
|
|
282
|
+
return a.key < b.key ? -1 : a.key > b.key ? 1 : 0;
|
|
283
|
+
}
|
|
284
|
+
/** Total order for best-chunk-per-session: score desc, startedAt desc (null last), chunkIdx asc, key asc. */
|
|
285
|
+
function compareSessionSemantic(a, b) {
|
|
286
|
+
if (b.score !== a.score)
|
|
287
|
+
return b.score - a.score;
|
|
288
|
+
const r = compareRecencyDesc(a.chunk.startedAt, b.chunk.startedAt);
|
|
289
|
+
if (r !== 0)
|
|
290
|
+
return r;
|
|
291
|
+
if (a.chunk.chunkIdx !== b.chunk.chunkIdx)
|
|
292
|
+
return a.chunk.chunkIdx - b.chunk.chunkIdx;
|
|
293
|
+
return a.sk < b.sk ? -1 : a.sk > b.sk ? 1 : 0;
|
|
294
|
+
}
|
|
295
|
+
/** Order newer-first; null sorts last (ISO strings compare chronologically). */
|
|
296
|
+
function compareRecencyDesc(a, b) {
|
|
297
|
+
if (a === b)
|
|
298
|
+
return 0;
|
|
299
|
+
if (a === null)
|
|
300
|
+
return 1;
|
|
301
|
+
if (b === null)
|
|
302
|
+
return -1;
|
|
303
|
+
return a < b ? 1 : -1;
|
|
304
|
+
}
|
|
113
305
|
//# sourceMappingURL=engine.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/recall/engine.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/recall/engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Result of parsing a recall query into FTS5-safe tokens (P3 hybrid search). */
|
|
2
|
+
export interface FtsQuery {
|
|
3
|
+
/**
|
|
4
|
+
* FTS5 MATCH string: each >=3-codepoint token as a double-quoted phrase,
|
|
5
|
+
* space-joined (implicit AND). Empty string when no long token is present.
|
|
6
|
+
*/
|
|
7
|
+
readonly match: string;
|
|
8
|
+
/**
|
|
9
|
+
* Sub-3-codepoint terms the `trigram` tokenizer cannot match (a trigram
|
|
10
|
+
* needs >=3 codepoints — verified on SQLite 3.53.1). These need a LIKE
|
|
11
|
+
* fallback over the source text instead.
|
|
12
|
+
*/
|
|
13
|
+
readonly shortTerms: readonly string[];
|
|
14
|
+
/** Whether the query yielded any usable token at all (long or short). */
|
|
15
|
+
readonly hasTokens: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Parse a natural-language query into an FTS5-safe MATCH string plus the short
|
|
19
|
+
* terms that need a LIKE fallback. Pure + deterministic — the one shared query
|
|
20
|
+
* builder for BOTH the memory and session keyword lanes (design §2-B / §11).
|
|
21
|
+
*
|
|
22
|
+
* Each whitespace-delimited token of >=3 codepoints becomes a double-quoted
|
|
23
|
+
* phrase (internal `"` doubled) so paths, `:`, `-`, and error codes are treated
|
|
24
|
+
* as literal phrases, never FTS5 operators — an unquoted `:`/path throws
|
|
25
|
+
* (`no such column` — verified), so the quoting is load-bearing. Shorter tokens
|
|
26
|
+
* go to `shortTerms` for the LIKE branch.
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildFtsQuery(query: string): FtsQuery;
|
|
29
|
+
//# sourceMappingURL=ftsQuery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ftsQuery.d.ts","sourceRoot":"","sources":["../../src/recall/ftsQuery.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,MAAM,WAAW,QAAQ;IACvB;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,yEAAyE;IACzE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAKD;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAsBrD"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** The `trigram` tokenizer cannot form a trigram from fewer than 3 codepoints. */
|
|
2
|
+
const TRIGRAM_MIN = 3;
|
|
3
|
+
/**
|
|
4
|
+
* Parse a natural-language query into an FTS5-safe MATCH string plus the short
|
|
5
|
+
* terms that need a LIKE fallback. Pure + deterministic — the one shared query
|
|
6
|
+
* builder for BOTH the memory and session keyword lanes (design §2-B / §11).
|
|
7
|
+
*
|
|
8
|
+
* Each whitespace-delimited token of >=3 codepoints becomes a double-quoted
|
|
9
|
+
* phrase (internal `"` doubled) so paths, `:`, `-`, and error codes are treated
|
|
10
|
+
* as literal phrases, never FTS5 operators — an unquoted `:`/path throws
|
|
11
|
+
* (`no such column` — verified), so the quoting is load-bearing. Shorter tokens
|
|
12
|
+
* go to `shortTerms` for the LIKE branch.
|
|
13
|
+
*/
|
|
14
|
+
export function buildFtsQuery(query) {
|
|
15
|
+
const tokens = query
|
|
16
|
+
.split(/\s+/)
|
|
17
|
+
.map((t) => t.trim())
|
|
18
|
+
.filter((t) => t.length > 0);
|
|
19
|
+
const phrases = [];
|
|
20
|
+
const shortTerms = [];
|
|
21
|
+
for (const tok of tokens) {
|
|
22
|
+
const codepoints = [...tok].length; // surrogate-safe codepoint count
|
|
23
|
+
if (codepoints >= TRIGRAM_MIN) {
|
|
24
|
+
phrases.push(`"${tok.replace(/"/g, '""')}"`);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
shortTerms.push(tok);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
match: phrases.join(" "),
|
|
32
|
+
shortTerms: [...new Set(shortTerms)],
|
|
33
|
+
hasTokens: phrases.length > 0 || shortTerms.length > 0,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=ftsQuery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ftsQuery.js","sourceRoot":"","sources":["../../src/recall/ftsQuery.ts"],"names":[],"mappings":"AAiBA,kFAAkF;AAClF,MAAM,WAAW,GAAG,CAAC,CAAC;AAEtB;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,MAAM,MAAM,GAAG,KAAK;SACjB,KAAK,CAAC,KAAK,CAAC;SACZ,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAE/B,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACzB,MAAM,UAAU,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,iCAAiC;QACrE,IAAI,UAAU,IAAI,WAAW,EAAE,CAAC;YAC9B,OAAO,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/C,CAAC;aAAM,CAAC;YACN,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAED,OAAO;QACL,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;QACxB,UAAU,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;QACpC,SAAS,EAAE,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;KACvD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reciprocal Rank Fusion (RRF) for P3 hybrid search.
|
|
3
|
+
*
|
|
4
|
+
* Merges the keyword and semantic lanes by RANK, so their incomparable scores
|
|
5
|
+
* (bm25 is negative/unbounded; e5 cosine sits in a high, narrow band) never need
|
|
6
|
+
* per-query normalization. Pure + deterministic. See
|
|
7
|
+
* `docs/P3-hybrid-search-design.md` §2-A and the §11-§14 revisions.
|
|
8
|
+
*/
|
|
9
|
+
export type LaneName = "keyword" | "semantic";
|
|
10
|
+
/** RRF rank-bias constant — the established default. */
|
|
11
|
+
export declare const RRF_K = 60;
|
|
12
|
+
/** Per-item keyword-contribution multiplier for a tool_result hit (hybrid only; §11 C / §12 R6). */
|
|
13
|
+
export declare const TOOL_RESULT_MULTIPLIER = 0.5;
|
|
14
|
+
/** Over-fetch budget per lane so a hit ranked low in one lane still surfaces after fusion. */
|
|
15
|
+
export declare function fetchK(k: number): number;
|
|
16
|
+
export interface LaneItem {
|
|
17
|
+
/** Fusion identity — the SAME key across lanes denotes the SAME result. */
|
|
18
|
+
readonly key: string;
|
|
19
|
+
/** Per-item contribution multiplier (1, or {@link TOOL_RESULT_MULTIPLIER}). */
|
|
20
|
+
readonly weight: number;
|
|
21
|
+
/** Raw lane score: cosine for semantic, a rank-confidence proxy for keyword. Tie-break + display. */
|
|
22
|
+
readonly score: number;
|
|
23
|
+
/** Recency (ISO) for the deterministic tie-break; null sorts last. */
|
|
24
|
+
readonly recency: string | null;
|
|
25
|
+
}
|
|
26
|
+
export interface Lane {
|
|
27
|
+
readonly name: LaneName;
|
|
28
|
+
/** Items best-first, ALREADY totally ordered by the caller (§13 R3). */
|
|
29
|
+
readonly items: readonly LaneItem[];
|
|
30
|
+
}
|
|
31
|
+
export interface FusedEntry {
|
|
32
|
+
readonly key: string;
|
|
33
|
+
readonly fusedScore: number;
|
|
34
|
+
/** Which lanes matched this key (provenance). */
|
|
35
|
+
readonly lanes: readonly LaneName[];
|
|
36
|
+
/** 1-based rank of this key within each lane that matched it. */
|
|
37
|
+
readonly rankByLane: Partial<Record<LaneName, number>>;
|
|
38
|
+
/** Raw score of this key within each lane that matched it. */
|
|
39
|
+
readonly scoreByLane: Partial<Record<LaneName, number>>;
|
|
40
|
+
/** Max raw score across matching lanes (tie-break + a sane display value). */
|
|
41
|
+
readonly bestScore: number;
|
|
42
|
+
readonly recency: string | null;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Fuse the given lanes and return the top-`k` entries, deterministically
|
|
46
|
+
* ordered. Each item contributes `weight / (RRF_K + rank)` to its key, **at most
|
|
47
|
+
* once per lane** — a key repeated within one lane keeps its best (first) rank,
|
|
48
|
+
* the "one contribution per lane per item" invariant (§13 HIGH). Tie-break is
|
|
49
|
+
* total: fusedScore desc, then #lanes desc, then bestScore desc, then recency
|
|
50
|
+
* desc (nulls last), then key asc — never depends on input/DB row order.
|
|
51
|
+
*/
|
|
52
|
+
export declare function fuse(lanes: readonly Lane[], k: number): FusedEntry[];
|
|
53
|
+
/**
|
|
54
|
+
* Canonical fusion key — a value-equal string usable directly as a Map key
|
|
55
|
+
* (§13 LOW: raw arrays compare by object identity, not value).
|
|
56
|
+
*/
|
|
57
|
+
export declare function fusionKey(source: string, id: string): string;
|
|
58
|
+
//# sourceMappingURL=fusion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fusion.d.ts","sourceRoot":"","sources":["../../src/recall/fusion.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;AAE9C,wDAAwD;AACxD,eAAO,MAAM,KAAK,KAAK,CAAC;AAExB,oGAAoG;AACpG,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C,8FAA8F;AAC9F,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAExC;AAED,MAAM,WAAW,QAAQ;IACvB,2EAA2E;IAC3E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,qGAAqG;IACrG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,sEAAsE;IACtE,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAC;IACpC,iEAAiE;IACjE,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;IACvD,8DAA8D;IAC9D,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;IACxD,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED;;;;;;;GAOG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,UAAU,EAAE,CAmDpE;AAkCD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAE5D"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reciprocal Rank Fusion (RRF) for P3 hybrid search.
|
|
3
|
+
*
|
|
4
|
+
* Merges the keyword and semantic lanes by RANK, so their incomparable scores
|
|
5
|
+
* (bm25 is negative/unbounded; e5 cosine sits in a high, narrow band) never need
|
|
6
|
+
* per-query normalization. Pure + deterministic. See
|
|
7
|
+
* `docs/P3-hybrid-search-design.md` §2-A and the §11-§14 revisions.
|
|
8
|
+
*/
|
|
9
|
+
/** RRF rank-bias constant — the established default. */
|
|
10
|
+
export const RRF_K = 60;
|
|
11
|
+
/** Per-item keyword-contribution multiplier for a tool_result hit (hybrid only; §11 C / §12 R6). */
|
|
12
|
+
export const TOOL_RESULT_MULTIPLIER = 0.5;
|
|
13
|
+
/** Over-fetch budget per lane so a hit ranked low in one lane still surfaces after fusion. */
|
|
14
|
+
export function fetchK(k) {
|
|
15
|
+
return Math.max(k * 4, 20);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Fuse the given lanes and return the top-`k` entries, deterministically
|
|
19
|
+
* ordered. Each item contributes `weight / (RRF_K + rank)` to its key, **at most
|
|
20
|
+
* once per lane** — a key repeated within one lane keeps its best (first) rank,
|
|
21
|
+
* the "one contribution per lane per item" invariant (§13 HIGH). Tie-break is
|
|
22
|
+
* total: fusedScore desc, then #lanes desc, then bestScore desc, then recency
|
|
23
|
+
* desc (nulls last), then key asc — never depends on input/DB row order.
|
|
24
|
+
*/
|
|
25
|
+
export function fuse(lanes, k) {
|
|
26
|
+
const acc = new Map();
|
|
27
|
+
for (const lane of lanes) {
|
|
28
|
+
const seenInLane = new Set();
|
|
29
|
+
lane.items.forEach((item, i) => {
|
|
30
|
+
if (seenInLane.has(item.key))
|
|
31
|
+
return; // one contribution per lane per item
|
|
32
|
+
seenInLane.add(item.key);
|
|
33
|
+
const rank = i + 1;
|
|
34
|
+
let e = acc.get(item.key);
|
|
35
|
+
if (!e) {
|
|
36
|
+
e = {
|
|
37
|
+
fusedScore: 0,
|
|
38
|
+
lanes: new Set(),
|
|
39
|
+
rankByLane: {},
|
|
40
|
+
scoreByLane: {},
|
|
41
|
+
bestScore: -Infinity,
|
|
42
|
+
recency: null,
|
|
43
|
+
};
|
|
44
|
+
acc.set(item.key, e);
|
|
45
|
+
}
|
|
46
|
+
e.fusedScore += item.weight / (RRF_K + rank);
|
|
47
|
+
e.lanes.add(lane.name);
|
|
48
|
+
e.rankByLane[lane.name] = rank;
|
|
49
|
+
e.scoreByLane[lane.name] = item.score;
|
|
50
|
+
if (item.score > e.bestScore)
|
|
51
|
+
e.bestScore = item.score;
|
|
52
|
+
if (isNewer(item.recency, e.recency))
|
|
53
|
+
e.recency = item.recency;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const entries = [];
|
|
57
|
+
for (const [key, e] of acc) {
|
|
58
|
+
entries.push({
|
|
59
|
+
key,
|
|
60
|
+
fusedScore: e.fusedScore,
|
|
61
|
+
lanes: [...e.lanes],
|
|
62
|
+
rankByLane: e.rankByLane,
|
|
63
|
+
scoreByLane: e.scoreByLane,
|
|
64
|
+
bestScore: e.bestScore,
|
|
65
|
+
recency: e.recency,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
entries.sort(compareFused);
|
|
69
|
+
return entries.slice(0, k);
|
|
70
|
+
}
|
|
71
|
+
function compareFused(a, b) {
|
|
72
|
+
if (b.fusedScore !== a.fusedScore)
|
|
73
|
+
return b.fusedScore - a.fusedScore;
|
|
74
|
+
if (b.lanes.length !== a.lanes.length)
|
|
75
|
+
return b.lanes.length - a.lanes.length;
|
|
76
|
+
if (b.bestScore !== a.bestScore)
|
|
77
|
+
return b.bestScore - a.bestScore;
|
|
78
|
+
const r = compareRecencyDesc(a.recency, b.recency);
|
|
79
|
+
if (r !== 0)
|
|
80
|
+
return r;
|
|
81
|
+
return a.key < b.key ? -1 : a.key > b.key ? 1 : 0;
|
|
82
|
+
}
|
|
83
|
+
/** True iff `cand` is strictly more recent than `cur` (null = oldest). */
|
|
84
|
+
function isNewer(cand, cur) {
|
|
85
|
+
if (cand === null)
|
|
86
|
+
return false;
|
|
87
|
+
if (cur === null)
|
|
88
|
+
return true;
|
|
89
|
+
return cand > cur;
|
|
90
|
+
}
|
|
91
|
+
/** Order newer-first; null sorts last (ISO strings compare chronologically). */
|
|
92
|
+
function compareRecencyDesc(a, b) {
|
|
93
|
+
if (a === b)
|
|
94
|
+
return 0;
|
|
95
|
+
if (a === null)
|
|
96
|
+
return 1;
|
|
97
|
+
if (b === null)
|
|
98
|
+
return -1;
|
|
99
|
+
return a < b ? 1 : -1;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Fusion-key delimiter: a NUL (U+0000), built with `String.fromCharCode` so the
|
|
103
|
+
* SOURCE holds no literal control byte (see feedback_no_literal_control_bytes).
|
|
104
|
+
* NUL cannot occur in `source` (a fixed enum) or `id` (a memory slug, a
|
|
105
|
+
* `host/sessionId`, or a chunk id), so the joined key is collision-free.
|
|
106
|
+
*/
|
|
107
|
+
const KEY_SEP = String.fromCharCode(0);
|
|
108
|
+
/**
|
|
109
|
+
* Canonical fusion key — a value-equal string usable directly as a Map key
|
|
110
|
+
* (§13 LOW: raw arrays compare by object identity, not value).
|
|
111
|
+
*/
|
|
112
|
+
export function fusionKey(source, id) {
|
|
113
|
+
return source + KEY_SEP + id;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=fusion.js.map
|