@coreyuan/vector-mind 1.0.48 → 1.0.52
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 +146 -394
- package/dist/activity-log.d.ts +19 -0
- package/dist/activity-log.js +106 -0
- package/dist/activity-log.js.map +1 -0
- package/dist/builtin-conventions.d.ts +2 -1
- package/dist/builtin-conventions.js +7 -2
- package/dist/builtin-conventions.js.map +1 -1
- package/dist/builtin-instructions.d.ts +1 -1
- package/dist/builtin-instructions.js +1 -1
- package/dist/builtin-instructions.js.map +1 -1
- package/dist/config.d.ts +28 -0
- package/dist/config.js +189 -0
- package/dist/config.js.map +1 -0
- package/dist/database-runtime.d.ts +54 -0
- package/dist/database-runtime.js +512 -0
- package/dist/database-runtime.js.map +1 -0
- package/dist/development-warnings.d.ts +93 -0
- package/dist/development-warnings.js +589 -0
- package/dist/development-warnings.js.map +1 -0
- package/dist/file-indexing.d.ts +22 -0
- package/dist/file-indexing.js +243 -0
- package/dist/file-indexing.js.map +1 -0
- package/dist/grep.d.ts +64 -0
- package/dist/grep.js +475 -0
- package/dist/grep.js.map +1 -0
- package/dist/index.js +167 -6884
- package/dist/index.js.map +1 -1
- package/dist/large-file-split.d.ts +29 -0
- package/dist/large-file-split.js +146 -0
- package/dist/large-file-split.js.map +1 -0
- package/dist/memory-maintenance.d.ts +80 -0
- package/dist/memory-maintenance.js +537 -0
- package/dist/memory-maintenance.js.map +1 -0
- package/dist/memory-mutations.d.ts +23 -0
- package/dist/memory-mutations.js +83 -0
- package/dist/memory-mutations.js.map +1 -0
- package/dist/memory-recall.d.ts +93 -0
- package/dist/memory-recall.js +796 -0
- package/dist/memory-recall.js.map +1 -0
- package/dist/path-filters.d.ts +1 -0
- package/dist/path-filters.js +27 -0
- package/dist/path-filters.js.map +1 -0
- package/dist/path-rules.d.ts +12 -0
- package/dist/path-rules.js +208 -0
- package/dist/path-rules.js.map +1 -0
- package/dist/pending-changes.d.ts +22 -0
- package/dist/pending-changes.js +166 -0
- package/dist/pending-changes.js.map +1 -0
- package/dist/project-files.d.ts +66 -0
- package/dist/project-files.js +215 -0
- package/dist/project-files.js.map +1 -0
- package/dist/root.d.ts +15 -0
- package/dist/root.js +191 -0
- package/dist/root.js.map +1 -0
- package/dist/rtk-tools.d.ts +20 -0
- package/dist/rtk-tools.js +318 -0
- package/dist/rtk-tools.js.map +1 -0
- package/dist/server-instructions.d.ts +1 -0
- package/dist/server-instructions.js +88 -0
- package/dist/server-instructions.js.map +1 -0
- package/dist/symbols.d.ts +2 -0
- package/dist/symbols.js +159 -0
- package/dist/symbols.js.map +1 -0
- package/dist/token-savings.d.ts +40 -0
- package/dist/token-savings.js +50 -0
- package/dist/token-savings.js.map +1 -0
- package/dist/tool-catalog.d.ts +18 -0
- package/dist/tool-catalog.js +243 -0
- package/dist/tool-catalog.js.map +1 -0
- package/dist/tool-handlers/context-recovery.d.ts +6 -0
- package/dist/tool-handlers/context-recovery.js +319 -0
- package/dist/tool-handlers/context-recovery.js.map +1 -0
- package/dist/tool-handlers/context.d.ts +34 -0
- package/dist/tool-handlers/context.js +2 -0
- package/dist/tool-handlers/context.js.map +1 -0
- package/dist/tool-handlers/diagnostics.d.ts +8 -0
- package/dist/tool-handlers/diagnostics.js +114 -0
- package/dist/tool-handlers/diagnostics.js.map +1 -0
- package/dist/tool-handlers/files.d.ts +8 -0
- package/dist/tool-handlers/files.js +493 -0
- package/dist/tool-handlers/files.js.map +1 -0
- package/dist/tool-handlers/large-files.d.ts +4 -0
- package/dist/tool-handlers/large-files.js +139 -0
- package/dist/tool-handlers/large-files.js.map +1 -0
- package/dist/tool-handlers/maintenance.d.ts +4 -0
- package/dist/tool-handlers/maintenance.js +160 -0
- package/dist/tool-handlers/maintenance.js.map +1 -0
- package/dist/tool-handlers/memory-diagnostics.d.ts +5 -0
- package/dist/tool-handlers/memory-diagnostics.js +509 -0
- package/dist/tool-handlers/memory-diagnostics.js.map +1 -0
- package/dist/tool-handlers/memory.d.ts +6 -0
- package/dist/tool-handlers/memory.js +334 -0
- package/dist/tool-handlers/memory.js.map +1 -0
- package/dist/tool-handlers/notes-decisions.d.ts +7 -0
- package/dist/tool-handlers/notes-decisions.js +169 -0
- package/dist/tool-handlers/notes-decisions.js.map +1 -0
- package/dist/tool-handlers/requirements.d.ts +7 -0
- package/dist/tool-handlers/requirements.js +357 -0
- package/dist/tool-handlers/requirements.js.map +1 -0
- package/dist/tool-handlers.d.ts +4 -0
- package/dist/tool-handlers.js +105 -0
- package/dist/tool-handlers.js.map +1 -0
- package/dist/tool-output.d.ts +213 -0
- package/dist/tool-output.js +278 -0
- package/dist/tool-output.js.map +1 -0
- package/dist/tool-schemas.d.ts +434 -0
- package/dist/tool-schemas.js +328 -0
- package/dist/tool-schemas.js.map +1 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/docs/capability-matrix.md +43 -0
- package/package.json +11 -10
- package/skills/vector-mind-autopilot/SKILL.md +149 -0
- package/skills/vector-mind-autopilot/references/claude-desktop-mcp-config.json +9 -0
- package/skills/vector-mind-autopilot/references/claude-project-instructions.md +47 -0
- package/skills/vector-mind-autopilot/references/universal-system-prompt.md +78 -0
|
@@ -0,0 +1,796 @@
|
|
|
1
|
+
import { BUILTIN_CONVENTIONS } from "./builtin-conventions.js";
|
|
2
|
+
import { MAX_DECISIONS_LIMIT } from "./tool-schemas.js";
|
|
3
|
+
import { shouldIgnoreDbFilePath } from "./path-rules.js";
|
|
4
|
+
import { safeJson, sliceTextForOutput } from "./tool-output.js";
|
|
5
|
+
export const MEMORY_ITEMS_FTS_TABLE = "memory_items_fts";
|
|
6
|
+
let memoryRecallContext = null;
|
|
7
|
+
export function configureMemoryRecall(context) {
|
|
8
|
+
memoryRecallContext = context;
|
|
9
|
+
}
|
|
10
|
+
function requireMemoryRecallContext() {
|
|
11
|
+
if (!memoryRecallContext)
|
|
12
|
+
throw new Error("[VectorMind] memory recall context is not configured");
|
|
13
|
+
return memoryRecallContext;
|
|
14
|
+
}
|
|
15
|
+
function getDb() {
|
|
16
|
+
const db = requireMemoryRecallContext().getDb();
|
|
17
|
+
if (!db)
|
|
18
|
+
throw new Error("[VectorMind] database is not initialized");
|
|
19
|
+
return db;
|
|
20
|
+
}
|
|
21
|
+
function getListConventionsStatement() {
|
|
22
|
+
return requireMemoryRecallContext().getListConventionsStatement();
|
|
23
|
+
}
|
|
24
|
+
function getListCurrentDecisionsStatement() {
|
|
25
|
+
return requireMemoryRecallContext().getListCurrentDecisionsStatement();
|
|
26
|
+
}
|
|
27
|
+
function getListActiveRequirementsStatement() {
|
|
28
|
+
return requireMemoryRecallContext().getListActiveRequirementsStatement();
|
|
29
|
+
}
|
|
30
|
+
function getRequirementMemoryItemIdStatement() {
|
|
31
|
+
return requireMemoryRecallContext().getRequirementMemoryItemIdStatement();
|
|
32
|
+
}
|
|
33
|
+
function getMemoryItemByIdStatement() {
|
|
34
|
+
return requireMemoryRecallContext().getMemoryItemByIdStatement();
|
|
35
|
+
}
|
|
36
|
+
function isFtsAvailable() {
|
|
37
|
+
return requireMemoryRecallContext().isFtsAvailable();
|
|
38
|
+
}
|
|
39
|
+
function sha256Hex(input) {
|
|
40
|
+
return requireMemoryRecallContext().sha256Hex(input);
|
|
41
|
+
}
|
|
42
|
+
function escapeLike(pattern) {
|
|
43
|
+
return pattern.replace(/[\\%_]/g, (m) => `\\${m}`);
|
|
44
|
+
}
|
|
45
|
+
const SEMANTIC_TOKEN_STOPWORDS = new Set([
|
|
46
|
+
"a",
|
|
47
|
+
"an",
|
|
48
|
+
"and",
|
|
49
|
+
"are",
|
|
50
|
+
"as",
|
|
51
|
+
"at",
|
|
52
|
+
"be",
|
|
53
|
+
"by",
|
|
54
|
+
"for",
|
|
55
|
+
"from",
|
|
56
|
+
"has",
|
|
57
|
+
"have",
|
|
58
|
+
"if",
|
|
59
|
+
"in",
|
|
60
|
+
"into",
|
|
61
|
+
"is",
|
|
62
|
+
"it",
|
|
63
|
+
"its",
|
|
64
|
+
"of",
|
|
65
|
+
"on",
|
|
66
|
+
"or",
|
|
67
|
+
"that",
|
|
68
|
+
"the",
|
|
69
|
+
"this",
|
|
70
|
+
"to",
|
|
71
|
+
"with",
|
|
72
|
+
]);
|
|
73
|
+
export const BOOTSTRAP_DEFAULT_CONTEXT_KINDS = [
|
|
74
|
+
"decision",
|
|
75
|
+
"convention",
|
|
76
|
+
"project_summary",
|
|
77
|
+
"memory_compaction",
|
|
78
|
+
"note",
|
|
79
|
+
"requirement",
|
|
80
|
+
"change_intent",
|
|
81
|
+
];
|
|
82
|
+
const TOKEN_SEARCH_DEFAULT_KINDS = [
|
|
83
|
+
"decision",
|
|
84
|
+
"convention",
|
|
85
|
+
"project_summary",
|
|
86
|
+
"memory_compaction",
|
|
87
|
+
"note",
|
|
88
|
+
"requirement",
|
|
89
|
+
"change_intent",
|
|
90
|
+
"code_chunk",
|
|
91
|
+
"doc_chunk",
|
|
92
|
+
];
|
|
93
|
+
const DECISION_CANDIDATE_KEYWORDS = [
|
|
94
|
+
"用户确认",
|
|
95
|
+
"用户要求",
|
|
96
|
+
"明确",
|
|
97
|
+
"架构决策",
|
|
98
|
+
"最终",
|
|
99
|
+
"默认",
|
|
100
|
+
"只保留",
|
|
101
|
+
"统一",
|
|
102
|
+
"不需要",
|
|
103
|
+
"无需",
|
|
104
|
+
"不再",
|
|
105
|
+
"改成",
|
|
106
|
+
"改为",
|
|
107
|
+
"替换为",
|
|
108
|
+
"废弃",
|
|
109
|
+
"过时",
|
|
110
|
+
"移除旧",
|
|
111
|
+
"decision",
|
|
112
|
+
"decided",
|
|
113
|
+
"confirmed",
|
|
114
|
+
"must",
|
|
115
|
+
"default",
|
|
116
|
+
"only",
|
|
117
|
+
"single",
|
|
118
|
+
"no longer",
|
|
119
|
+
"instead",
|
|
120
|
+
];
|
|
121
|
+
export function parseMetadataJson(metadata) {
|
|
122
|
+
if (!metadata)
|
|
123
|
+
return {};
|
|
124
|
+
try {
|
|
125
|
+
const parsed = JSON.parse(metadata);
|
|
126
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
127
|
+
? parsed
|
|
128
|
+
: {};
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return {};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
export function metadataStatus(row) {
|
|
135
|
+
const meta = parseMetadataJson(row.metadata_json);
|
|
136
|
+
return typeof meta.status === "string" ? meta.status : "";
|
|
137
|
+
}
|
|
138
|
+
function isSupersededMemory(row) {
|
|
139
|
+
const meta = parseMetadataJson(row.metadata_json);
|
|
140
|
+
return meta.superseded === true || meta.status === "superseded";
|
|
141
|
+
}
|
|
142
|
+
function isCompactedMemory(row) {
|
|
143
|
+
const meta = parseMetadataJson(row.metadata_json);
|
|
144
|
+
return meta.compacted === true || meta.status === "compacted";
|
|
145
|
+
}
|
|
146
|
+
export function isHiddenFromDefaultRecall(row) {
|
|
147
|
+
return isSupersededMemory(row) || isCompactedMemory(row);
|
|
148
|
+
}
|
|
149
|
+
function semanticRecencyWeight(updatedAt) {
|
|
150
|
+
if (!updatedAt)
|
|
151
|
+
return 0;
|
|
152
|
+
const t = Date.parse(updatedAt.endsWith("Z") ? updatedAt : `${updatedAt}Z`);
|
|
153
|
+
if (!Number.isFinite(t))
|
|
154
|
+
return 0;
|
|
155
|
+
const ageDays = Math.max(0, (Date.now() - t) / 86_400_000);
|
|
156
|
+
if (ageDays <= 1)
|
|
157
|
+
return 0.8;
|
|
158
|
+
if (ageDays <= 7)
|
|
159
|
+
return 0.45;
|
|
160
|
+
if (ageDays <= 30)
|
|
161
|
+
return 0.2;
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
function semanticKindWeight(kind) {
|
|
165
|
+
switch (kind) {
|
|
166
|
+
case "decision":
|
|
167
|
+
return 16;
|
|
168
|
+
case "convention":
|
|
169
|
+
return 2.6;
|
|
170
|
+
case "project_summary":
|
|
171
|
+
return 2.2;
|
|
172
|
+
case "note":
|
|
173
|
+
return 1.1;
|
|
174
|
+
case "requirement":
|
|
175
|
+
return 0.4;
|
|
176
|
+
case "change_intent":
|
|
177
|
+
return 0.2;
|
|
178
|
+
case "memory_compaction":
|
|
179
|
+
return 0.7;
|
|
180
|
+
default:
|
|
181
|
+
return 0;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function adjustSemanticScore(row, rawScore) {
|
|
185
|
+
if (isHiddenFromDefaultRecall(row))
|
|
186
|
+
return rawScore - 1000;
|
|
187
|
+
let score = rawScore + semanticKindWeight(row.kind) + semanticRecencyWeight(row.updated_at);
|
|
188
|
+
const status = metadataStatus(row);
|
|
189
|
+
if (status === "current")
|
|
190
|
+
score += row.kind === "decision" ? 24 : 1.2;
|
|
191
|
+
if (status === "active")
|
|
192
|
+
score += 1.2;
|
|
193
|
+
if (row.kind === "change_intent" && row.file_path && shouldIgnoreDbFilePath(row.file_path)) {
|
|
194
|
+
// Human-synced intent for generated/build/runtime files is often the only durable
|
|
195
|
+
// "why" for that change. Do not let built-in path ignores hide the decision trail.
|
|
196
|
+
score += 0.4;
|
|
197
|
+
}
|
|
198
|
+
return score;
|
|
199
|
+
}
|
|
200
|
+
function normalizeSearchText(input) {
|
|
201
|
+
return (input ?? "").normalize("NFKC").toLowerCase();
|
|
202
|
+
}
|
|
203
|
+
function extractSearchTokens(raw) {
|
|
204
|
+
const text = normalizeSearchText(raw);
|
|
205
|
+
const tokens = new Set();
|
|
206
|
+
for (const token of text.match(/[a-z0-9_./:@#-]{2,}/g) ?? []) {
|
|
207
|
+
if (!SEMANTIC_TOKEN_STOPWORDS.has(token))
|
|
208
|
+
tokens.add(token);
|
|
209
|
+
for (const part of token.split(/[^a-z0-9]+/).filter((p) => p.length >= 2)) {
|
|
210
|
+
if (!SEMANTIC_TOKEN_STOPWORDS.has(part))
|
|
211
|
+
tokens.add(part);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
for (const seq of text.match(/\p{Script=Han}+/gu) ?? []) {
|
|
215
|
+
if (seq.length >= 2 && seq.length <= 18)
|
|
216
|
+
tokens.add(seq);
|
|
217
|
+
for (const n of [2, 3, 4]) {
|
|
218
|
+
if (seq.length < n)
|
|
219
|
+
continue;
|
|
220
|
+
for (let i = 0; i <= seq.length - n; i++) {
|
|
221
|
+
tokens.add(seq.slice(i, i + n));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return Array.from(tokens)
|
|
226
|
+
.filter((token) => token.length >= 2 && !SEMANTIC_TOKEN_STOPWORDS.has(token))
|
|
227
|
+
.sort((a, b) => b.length - a.length)
|
|
228
|
+
.slice(0, 48);
|
|
229
|
+
}
|
|
230
|
+
function countNeedleOccurrences(haystack, needle) {
|
|
231
|
+
if (!haystack || !needle)
|
|
232
|
+
return 0;
|
|
233
|
+
let count = 0;
|
|
234
|
+
let idx = 0;
|
|
235
|
+
while ((idx = haystack.indexOf(needle, idx)) >= 0) {
|
|
236
|
+
count++;
|
|
237
|
+
idx += Math.max(1, needle.length);
|
|
238
|
+
if (count >= 8)
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
return count;
|
|
242
|
+
}
|
|
243
|
+
function tokenLexicalScore(row, query, tokens) {
|
|
244
|
+
if (!tokens.length)
|
|
245
|
+
return 0;
|
|
246
|
+
const title = normalizeSearchText(row.title);
|
|
247
|
+
const content = normalizeSearchText(row.content);
|
|
248
|
+
const filePath = normalizeSearchText(row.file_path);
|
|
249
|
+
const metadata = normalizeSearchText(row.metadata_json);
|
|
250
|
+
const exact = normalizeSearchText(query).trim();
|
|
251
|
+
let score = 0;
|
|
252
|
+
if (exact.length >= 4) {
|
|
253
|
+
if (title.includes(exact))
|
|
254
|
+
score += 8;
|
|
255
|
+
if (content.includes(exact))
|
|
256
|
+
score += 6;
|
|
257
|
+
if (filePath.includes(exact))
|
|
258
|
+
score += 4;
|
|
259
|
+
}
|
|
260
|
+
let matched = 0;
|
|
261
|
+
for (const token of tokens) {
|
|
262
|
+
let tokenScore = 0;
|
|
263
|
+
if (title.includes(token))
|
|
264
|
+
tokenScore += 3.2;
|
|
265
|
+
if (filePath.includes(token))
|
|
266
|
+
tokenScore += 2.4;
|
|
267
|
+
const contentHits = countNeedleOccurrences(content, token);
|
|
268
|
+
if (contentHits)
|
|
269
|
+
tokenScore += Math.min(3.2, 0.75 + contentHits * 0.45);
|
|
270
|
+
if (metadata.includes(token))
|
|
271
|
+
tokenScore += 0.8;
|
|
272
|
+
if (tokenScore > 0) {
|
|
273
|
+
matched++;
|
|
274
|
+
score += tokenScore * Math.min(2.4, Math.max(1, token.length / 4));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (matched >= Math.min(3, tokens.length))
|
|
278
|
+
score += 2;
|
|
279
|
+
score += matched / Math.max(1, tokens.length);
|
|
280
|
+
return score;
|
|
281
|
+
}
|
|
282
|
+
function looksLikeDecisionContent(content) {
|
|
283
|
+
const text = normalizeSearchText(content);
|
|
284
|
+
return DECISION_CANDIDATE_KEYWORDS.some((kw) => text.includes(normalizeSearchText(kw)));
|
|
285
|
+
}
|
|
286
|
+
function mergeSemanticMatches(sets, opts) {
|
|
287
|
+
const best = new Map();
|
|
288
|
+
for (const matches of sets) {
|
|
289
|
+
for (const match of matches) {
|
|
290
|
+
const prev = best.get(match.item.id);
|
|
291
|
+
if (!prev || match.score > prev.score)
|
|
292
|
+
best.set(match.item.id, match);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return Array.from(best.values())
|
|
296
|
+
.sort((a, b) => b.score - a.score || b.item.id - a.item.id)
|
|
297
|
+
.slice(0, opts.topK);
|
|
298
|
+
}
|
|
299
|
+
function filterAndRankSemanticRows(rows, scoreOf, opts) {
|
|
300
|
+
return rows
|
|
301
|
+
.map((r) => ({ row: r, score: adjustSemanticScore(r, scoreOf(r)) }))
|
|
302
|
+
.filter(({ row }) => {
|
|
303
|
+
if (isHiddenFromDefaultRecall(row))
|
|
304
|
+
return false;
|
|
305
|
+
if (shouldIgnoreDbFilePath(row.file_path) && row.kind !== "change_intent")
|
|
306
|
+
return false;
|
|
307
|
+
return true;
|
|
308
|
+
})
|
|
309
|
+
.sort((a, b) => b.score - a.score)
|
|
310
|
+
.slice(0, opts.topK)
|
|
311
|
+
.map(({ row, score }) => toSemanticMatch(row, score, opts.includeContent, opts.previewChars, opts.contentMaxChars));
|
|
312
|
+
}
|
|
313
|
+
export function makePreviewText(content, max) {
|
|
314
|
+
if (max <= 0)
|
|
315
|
+
return "";
|
|
316
|
+
if (content.length <= max)
|
|
317
|
+
return content;
|
|
318
|
+
return `${content.slice(0, max)}...`;
|
|
319
|
+
}
|
|
320
|
+
function toSemanticMatch(row, score, includeContent, previewChars, contentMaxChars) {
|
|
321
|
+
const preview = makePreviewText(row.content, previewChars);
|
|
322
|
+
const contentSlice = includeContent ? sliceTextForOutput(row.content, contentMaxChars) : null;
|
|
323
|
+
return {
|
|
324
|
+
score,
|
|
325
|
+
item: {
|
|
326
|
+
id: row.id,
|
|
327
|
+
kind: row.kind,
|
|
328
|
+
title: row.title,
|
|
329
|
+
file_path: row.file_path,
|
|
330
|
+
start_line: row.start_line,
|
|
331
|
+
end_line: row.end_line,
|
|
332
|
+
req_id: row.req_id,
|
|
333
|
+
preview,
|
|
334
|
+
content: contentSlice ? contentSlice.text : undefined,
|
|
335
|
+
content_truncated: contentSlice ? contentSlice.truncated : undefined,
|
|
336
|
+
metadata_json: row.metadata_json,
|
|
337
|
+
updated_at: row.updated_at,
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
export function toMemoryItemPreview(row, includeContent, previewChars, contentMaxChars) {
|
|
342
|
+
const preview = makePreviewText(row.content, previewChars);
|
|
343
|
+
const contentSlice = includeContent ? sliceTextForOutput(row.content, contentMaxChars) : null;
|
|
344
|
+
return {
|
|
345
|
+
id: row.id,
|
|
346
|
+
kind: row.kind,
|
|
347
|
+
title: row.title,
|
|
348
|
+
file_path: row.file_path,
|
|
349
|
+
start_line: row.start_line,
|
|
350
|
+
end_line: row.end_line,
|
|
351
|
+
req_id: row.req_id,
|
|
352
|
+
preview,
|
|
353
|
+
content: contentSlice ? contentSlice.text : undefined,
|
|
354
|
+
content_truncated: contentSlice ? contentSlice.truncated : undefined,
|
|
355
|
+
metadata_json: row.metadata_json,
|
|
356
|
+
updated_at: row.updated_at,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
function getBuiltinConventionRows() {
|
|
360
|
+
return BUILTIN_CONVENTIONS.map((spec, idx) => ({
|
|
361
|
+
id: -1000 - idx,
|
|
362
|
+
kind: "convention",
|
|
363
|
+
title: spec.key,
|
|
364
|
+
content: spec.content,
|
|
365
|
+
file_path: null,
|
|
366
|
+
start_line: null,
|
|
367
|
+
end_line: null,
|
|
368
|
+
req_id: null,
|
|
369
|
+
metadata_json: safeJson({ source: "builtin", key: spec.key, tags: spec.tags ?? [] }),
|
|
370
|
+
content_hash: sha256Hex(spec.content),
|
|
371
|
+
created_at: "builtin",
|
|
372
|
+
updated_at: "builtin",
|
|
373
|
+
}));
|
|
374
|
+
}
|
|
375
|
+
export function getConventionPreviews(conventionsLimit, previewChars, contentMaxChars) {
|
|
376
|
+
if (conventionsLimit <= 0)
|
|
377
|
+
return [];
|
|
378
|
+
const builtin = getBuiltinConventionRows()
|
|
379
|
+
.map((row) => toMemoryItemPreview(row, false, previewChars, contentMaxChars))
|
|
380
|
+
.slice(0, conventionsLimit);
|
|
381
|
+
if (builtin.length >= conventionsLimit)
|
|
382
|
+
return builtin;
|
|
383
|
+
const remaining = conventionsLimit - builtin.length;
|
|
384
|
+
const stored = getListConventionsStatement().all(remaining).map((c) => toMemoryItemPreview(c, false, previewChars, contentMaxChars));
|
|
385
|
+
return [...builtin, ...stored];
|
|
386
|
+
}
|
|
387
|
+
export function getDecisionPreviews(decisionsLimit, previewChars, contentMaxChars) {
|
|
388
|
+
if (decisionsLimit <= 0)
|
|
389
|
+
return [];
|
|
390
|
+
const rows = getListCurrentDecisionsStatement().all(Math.min(MAX_DECISIONS_LIMIT * 4, Math.max(decisionsLimit, decisionsLimit * 4)));
|
|
391
|
+
return rows
|
|
392
|
+
.filter((d) => !isHiddenFromDefaultRecall(d))
|
|
393
|
+
.slice(0, decisionsLimit)
|
|
394
|
+
.map((d) => toMemoryItemPreview(d, false, previewChars, contentMaxChars));
|
|
395
|
+
}
|
|
396
|
+
export function getCurrentContextPreviews(currentContextLimit, previewChars, contentMaxChars) {
|
|
397
|
+
if (currentContextLimit <= 0)
|
|
398
|
+
return [];
|
|
399
|
+
const picked = new Map();
|
|
400
|
+
const addRow = (row) => {
|
|
401
|
+
if (!row)
|
|
402
|
+
return;
|
|
403
|
+
if (isHiddenFromDefaultRecall(row))
|
|
404
|
+
return;
|
|
405
|
+
if (shouldIgnoreDbFilePath(row.file_path) && row.kind !== "change_intent")
|
|
406
|
+
return;
|
|
407
|
+
if (!picked.has(row.id)) {
|
|
408
|
+
picked.set(row.id, toMemoryItemPreview(row, false, previewChars, contentMaxChars));
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
const activeReqs = getListActiveRequirementsStatement().all(Math.max(currentContextLimit, 10));
|
|
412
|
+
for (const req of activeReqs) {
|
|
413
|
+
const memId = getRequirementMemoryItemIdStatement().get(req.id)?.id;
|
|
414
|
+
if (memId != null)
|
|
415
|
+
addRow(getMemoryItemByIdStatement().get(memId));
|
|
416
|
+
}
|
|
417
|
+
const recentContextPageStmt = getDb().prepare(`SELECT id, kind, title, content, file_path, start_line, end_line, req_id, metadata_json, content_hash, created_at, updated_at
|
|
418
|
+
FROM memory_items
|
|
419
|
+
WHERE kind IN ('note', 'requirement', 'change_intent')
|
|
420
|
+
ORDER BY updated_at DESC, id DESC
|
|
421
|
+
LIMIT ? OFFSET ?`);
|
|
422
|
+
const pageSize = 200;
|
|
423
|
+
const scanCap = 10_000;
|
|
424
|
+
let offset = 0;
|
|
425
|
+
while (picked.size < currentContextLimit && offset < scanCap) {
|
|
426
|
+
const recentRows = recentContextPageStmt.all(pageSize, offset);
|
|
427
|
+
if (!recentRows.length)
|
|
428
|
+
break;
|
|
429
|
+
for (const row of recentRows) {
|
|
430
|
+
if (picked.size >= currentContextLimit)
|
|
431
|
+
break;
|
|
432
|
+
if (row.kind === "requirement" || row.kind === "change_intent") {
|
|
433
|
+
if (!looksLikeDecisionContent(`${row.title ?? ""}\n${row.content}`))
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
addRow(row);
|
|
437
|
+
}
|
|
438
|
+
offset += recentRows.length;
|
|
439
|
+
if (recentRows.length < pageSize)
|
|
440
|
+
break;
|
|
441
|
+
}
|
|
442
|
+
return Array.from(picked.values()).slice(0, currentContextLimit);
|
|
443
|
+
}
|
|
444
|
+
export function toRequirementPreview(req, includeContent, previewChars, contentMaxChars) {
|
|
445
|
+
const context = req.context_data ?? null;
|
|
446
|
+
const contextPreview = context ? makePreviewText(context, previewChars) : null;
|
|
447
|
+
const contextSlice = includeContent && context ? sliceTextForOutput(context, contentMaxChars) : null;
|
|
448
|
+
const memRow = getRequirementMemoryItemIdStatement().get(req.id) ?? undefined;
|
|
449
|
+
return {
|
|
450
|
+
id: req.id,
|
|
451
|
+
title: req.title,
|
|
452
|
+
status: req.status,
|
|
453
|
+
created_at: req.created_at,
|
|
454
|
+
memory_item_id: memRow?.id ?? null,
|
|
455
|
+
context_preview: contextPreview,
|
|
456
|
+
context_data: contextSlice ? contextSlice.text : undefined,
|
|
457
|
+
context_truncated: contextSlice ? contextSlice.truncated : undefined,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
export function toChangeLogPreview(change, includeContent, previewChars, contentMaxChars) {
|
|
461
|
+
const preview = makePreviewText(change.intent_summary, previewChars);
|
|
462
|
+
const intentSlice = includeContent ? sliceTextForOutput(change.intent_summary, contentMaxChars) : null;
|
|
463
|
+
return {
|
|
464
|
+
id: change.id,
|
|
465
|
+
file_path: change.file_path,
|
|
466
|
+
timestamp: change.timestamp,
|
|
467
|
+
intent_preview: preview,
|
|
468
|
+
intent_summary: intentSlice ? intentSlice.text : undefined,
|
|
469
|
+
intent_truncated: intentSlice ? intentSlice.truncated : undefined,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
export function buildFtsMatchQuery(raw) {
|
|
473
|
+
const terms = raw
|
|
474
|
+
.trim()
|
|
475
|
+
.split(/\s+/)
|
|
476
|
+
.map((t) => t.trim())
|
|
477
|
+
.filter(Boolean)
|
|
478
|
+
.slice(0, 20);
|
|
479
|
+
if (!terms.length)
|
|
480
|
+
return '""';
|
|
481
|
+
return terms.map((t) => `"${t.replaceAll('"', '""')}"`).join(" AND ");
|
|
482
|
+
}
|
|
483
|
+
function ftsSearchInternal(opts) {
|
|
484
|
+
if (!isFtsAvailable()) {
|
|
485
|
+
throw new Error("FTS is unavailable");
|
|
486
|
+
}
|
|
487
|
+
const q = opts.query.trim();
|
|
488
|
+
if (!q)
|
|
489
|
+
return { query: "", top_k: opts.topK, mode: "fts", matches: [] };
|
|
490
|
+
const matchQuery = buildFtsMatchQuery(q);
|
|
491
|
+
const rawLimit = Math.min(500, Math.max(opts.topK, opts.topK * 8));
|
|
492
|
+
const rows = (() => {
|
|
493
|
+
if (opts.kinds?.length) {
|
|
494
|
+
const placeholders = opts.kinds.map(() => "?").join(", ");
|
|
495
|
+
const stmt = getDb().prepare(`
|
|
496
|
+
SELECT
|
|
497
|
+
m.id as id,
|
|
498
|
+
m.kind as kind,
|
|
499
|
+
m.title as title,
|
|
500
|
+
m.content as content,
|
|
501
|
+
m.file_path as file_path,
|
|
502
|
+
m.start_line as start_line,
|
|
503
|
+
m.end_line as end_line,
|
|
504
|
+
m.req_id as req_id,
|
|
505
|
+
m.metadata_json as metadata_json,
|
|
506
|
+
m.updated_at as updated_at,
|
|
507
|
+
bm25(${MEMORY_ITEMS_FTS_TABLE}) as rank
|
|
508
|
+
FROM ${MEMORY_ITEMS_FTS_TABLE}
|
|
509
|
+
JOIN memory_items m ON m.id = ${MEMORY_ITEMS_FTS_TABLE}.rowid
|
|
510
|
+
WHERE ${MEMORY_ITEMS_FTS_TABLE} MATCH ?
|
|
511
|
+
AND m.kind IN (${placeholders})
|
|
512
|
+
ORDER BY rank ASC
|
|
513
|
+
LIMIT ?
|
|
514
|
+
`);
|
|
515
|
+
return stmt.all(matchQuery, ...opts.kinds, rawLimit);
|
|
516
|
+
}
|
|
517
|
+
const stmt = getDb().prepare(`
|
|
518
|
+
SELECT
|
|
519
|
+
m.id as id,
|
|
520
|
+
m.kind as kind,
|
|
521
|
+
m.title as title,
|
|
522
|
+
m.content as content,
|
|
523
|
+
m.file_path as file_path,
|
|
524
|
+
m.start_line as start_line,
|
|
525
|
+
m.end_line as end_line,
|
|
526
|
+
m.req_id as req_id,
|
|
527
|
+
m.metadata_json as metadata_json,
|
|
528
|
+
m.updated_at as updated_at,
|
|
529
|
+
bm25(${MEMORY_ITEMS_FTS_TABLE}) as rank
|
|
530
|
+
FROM ${MEMORY_ITEMS_FTS_TABLE}
|
|
531
|
+
JOIN memory_items m ON m.id = ${MEMORY_ITEMS_FTS_TABLE}.rowid
|
|
532
|
+
WHERE ${MEMORY_ITEMS_FTS_TABLE} MATCH ?
|
|
533
|
+
ORDER BY rank ASC
|
|
534
|
+
LIMIT ?
|
|
535
|
+
`);
|
|
536
|
+
return stmt.all(matchQuery, rawLimit);
|
|
537
|
+
})();
|
|
538
|
+
const matches = filterAndRankSemanticRows(rows, (r) => -Number(r.rank), opts);
|
|
539
|
+
return { query: q, top_k: opts.topK, mode: "fts", matches };
|
|
540
|
+
}
|
|
541
|
+
function likeSearchInternal(opts) {
|
|
542
|
+
const q = opts.query.trim();
|
|
543
|
+
if (!q)
|
|
544
|
+
return { query: "", top_k: opts.topK, mode: "like", matches: [] };
|
|
545
|
+
const escaped = escapeLike(q);
|
|
546
|
+
const like = `%${escaped}%`;
|
|
547
|
+
const rawLimit = Math.min(500, Math.max(opts.topK, opts.topK * 8));
|
|
548
|
+
const rows = (() => {
|
|
549
|
+
if (opts.kinds?.length) {
|
|
550
|
+
const placeholders = opts.kinds.map(() => "?").join(", ");
|
|
551
|
+
const stmt = getDb().prepare(`
|
|
552
|
+
SELECT
|
|
553
|
+
id,
|
|
554
|
+
kind,
|
|
555
|
+
title,
|
|
556
|
+
content,
|
|
557
|
+
file_path,
|
|
558
|
+
start_line,
|
|
559
|
+
end_line,
|
|
560
|
+
req_id,
|
|
561
|
+
metadata_json,
|
|
562
|
+
updated_at,
|
|
563
|
+
CASE
|
|
564
|
+
WHEN title LIKE ? ESCAPE '\\' THEN 3
|
|
565
|
+
WHEN file_path LIKE ? ESCAPE '\\' THEN 2
|
|
566
|
+
ELSE 1
|
|
567
|
+
END AS score
|
|
568
|
+
FROM memory_items
|
|
569
|
+
WHERE (content LIKE ? ESCAPE '\\'
|
|
570
|
+
OR title LIKE ? ESCAPE '\\'
|
|
571
|
+
OR file_path LIKE ? ESCAPE '\\')
|
|
572
|
+
AND kind IN (${placeholders})
|
|
573
|
+
ORDER BY score DESC, updated_at DESC, id DESC
|
|
574
|
+
LIMIT ?
|
|
575
|
+
`);
|
|
576
|
+
return stmt.all(like, like, like, like, like, ...opts.kinds, rawLimit);
|
|
577
|
+
}
|
|
578
|
+
const stmt = getDb().prepare(`
|
|
579
|
+
SELECT
|
|
580
|
+
id,
|
|
581
|
+
kind,
|
|
582
|
+
title,
|
|
583
|
+
content,
|
|
584
|
+
file_path,
|
|
585
|
+
start_line,
|
|
586
|
+
end_line,
|
|
587
|
+
req_id,
|
|
588
|
+
metadata_json,
|
|
589
|
+
updated_at,
|
|
590
|
+
CASE
|
|
591
|
+
WHEN title LIKE ? ESCAPE '\\' THEN 3
|
|
592
|
+
WHEN file_path LIKE ? ESCAPE '\\' THEN 2
|
|
593
|
+
ELSE 1
|
|
594
|
+
END AS score
|
|
595
|
+
FROM memory_items
|
|
596
|
+
WHERE (content LIKE ? ESCAPE '\\'
|
|
597
|
+
OR title LIKE ? ESCAPE '\\'
|
|
598
|
+
OR file_path LIKE ? ESCAPE '\\')
|
|
599
|
+
ORDER BY score DESC, updated_at DESC, id DESC
|
|
600
|
+
LIMIT ?
|
|
601
|
+
`);
|
|
602
|
+
return stmt.all(like, like, like, like, like, rawLimit);
|
|
603
|
+
})();
|
|
604
|
+
const matches = filterAndRankSemanticRows(rows, (r) => Number(r.score), opts);
|
|
605
|
+
return { query: q, top_k: opts.topK, mode: "like", matches };
|
|
606
|
+
}
|
|
607
|
+
function tokenSearchInternal(opts) {
|
|
608
|
+
const q = opts.query.trim();
|
|
609
|
+
if (!q)
|
|
610
|
+
return { query: "", top_k: opts.topK, mode: "token", matches: [] };
|
|
611
|
+
const tokens = extractSearchTokens(q);
|
|
612
|
+
if (!tokens.length)
|
|
613
|
+
return { query: q, top_k: opts.topK, mode: "token", matches: [] };
|
|
614
|
+
const rawLimit = Math.min(160, Math.max(opts.topK * 12, 80));
|
|
615
|
+
const searchTokens = tokens.slice(0, 8);
|
|
616
|
+
const effectiveKinds = opts.kinds?.length ? opts.kinds : TOKEN_SEARCH_DEFAULT_KINDS;
|
|
617
|
+
const kindClause = effectiveKinds.length
|
|
618
|
+
? `AND kind IN (${effectiveKinds.map(() => "?").join(", ")})`
|
|
619
|
+
: "";
|
|
620
|
+
const recencyBoost = `
|
|
621
|
+
+ CASE
|
|
622
|
+
WHEN updated_at >= datetime('now', '-2 days') THEN 4
|
|
623
|
+
WHEN updated_at >= datetime('now', '-14 days') THEN 2
|
|
624
|
+
WHEN updated_at >= datetime('now', '-60 days') THEN 1
|
|
625
|
+
ELSE 0
|
|
626
|
+
END`;
|
|
627
|
+
const candidateScore = `
|
|
628
|
+
(
|
|
629
|
+
CASE kind
|
|
630
|
+
WHEN 'decision' THEN 9
|
|
631
|
+
WHEN 'convention' THEN 7
|
|
632
|
+
WHEN 'project_summary' THEN 6
|
|
633
|
+
WHEN 'note' THEN 5
|
|
634
|
+
WHEN 'requirement' THEN 4
|
|
635
|
+
WHEN 'change_intent' THEN 3
|
|
636
|
+
ELSE 0
|
|
637
|
+
END
|
|
638
|
+
${recencyBoost}
|
|
639
|
+
)`;
|
|
640
|
+
const includesIndexedChunks = effectiveKinds.some((k) => k === "code_chunk" || k === "doc_chunk");
|
|
641
|
+
if (!includesIndexedChunks) {
|
|
642
|
+
const candidateLimit = Math.min(1600, Math.max(rawLimit * 5, 800));
|
|
643
|
+
const stmt = getDb().prepare(`
|
|
644
|
+
SELECT
|
|
645
|
+
id,
|
|
646
|
+
kind,
|
|
647
|
+
title,
|
|
648
|
+
content,
|
|
649
|
+
file_path,
|
|
650
|
+
start_line,
|
|
651
|
+
end_line,
|
|
652
|
+
req_id,
|
|
653
|
+
metadata_json,
|
|
654
|
+
updated_at
|
|
655
|
+
FROM memory_items
|
|
656
|
+
WHERE 1=1
|
|
657
|
+
${kindClause}
|
|
658
|
+
ORDER BY
|
|
659
|
+
${candidateScore} DESC,
|
|
660
|
+
updated_at DESC,
|
|
661
|
+
id DESC
|
|
662
|
+
LIMIT ?
|
|
663
|
+
`);
|
|
664
|
+
const candidates = stmt.all(...effectiveKinds, candidateLimit);
|
|
665
|
+
const scoreMap = new Map();
|
|
666
|
+
const rows = candidates.filter((row) => {
|
|
667
|
+
const score = tokenLexicalScore(row, q, tokens);
|
|
668
|
+
if (score <= 0)
|
|
669
|
+
return false;
|
|
670
|
+
scoreMap.set(row.id, score);
|
|
671
|
+
return true;
|
|
672
|
+
});
|
|
673
|
+
const matches = filterAndRankSemanticRows(rows, (r) => scoreMap.get(r.id) ?? 0, opts);
|
|
674
|
+
return { query: q, top_k: opts.topK, mode: "token", matches };
|
|
675
|
+
}
|
|
676
|
+
const memoryFirstKinds = effectiveKinds.filter((k) => k !== "code_chunk" && k !== "doc_chunk");
|
|
677
|
+
const memoryFirstLimit = Math.min(1200, Math.max(rawLimit * 4, 300));
|
|
678
|
+
let memoryFirstRows = [];
|
|
679
|
+
const memoryFirstScores = new Map();
|
|
680
|
+
if (memoryFirstKinds.length) {
|
|
681
|
+
const memoryKindClause = `AND kind IN (${memoryFirstKinds.map(() => "?").join(", ")})`;
|
|
682
|
+
const memoryStmt = getDb().prepare(`
|
|
683
|
+
SELECT
|
|
684
|
+
id,
|
|
685
|
+
kind,
|
|
686
|
+
title,
|
|
687
|
+
content,
|
|
688
|
+
file_path,
|
|
689
|
+
start_line,
|
|
690
|
+
end_line,
|
|
691
|
+
req_id,
|
|
692
|
+
metadata_json,
|
|
693
|
+
updated_at
|
|
694
|
+
FROM memory_items
|
|
695
|
+
WHERE 1=1
|
|
696
|
+
${memoryKindClause}
|
|
697
|
+
ORDER BY
|
|
698
|
+
${candidateScore} DESC,
|
|
699
|
+
updated_at DESC,
|
|
700
|
+
id DESC
|
|
701
|
+
LIMIT ?
|
|
702
|
+
`);
|
|
703
|
+
const candidates = memoryStmt.all(...memoryFirstKinds, memoryFirstLimit);
|
|
704
|
+
memoryFirstRows = candidates.filter((row) => {
|
|
705
|
+
const score = tokenLexicalScore(row, q, tokens);
|
|
706
|
+
if (score <= 0)
|
|
707
|
+
return false;
|
|
708
|
+
memoryFirstScores.set(row.id, score);
|
|
709
|
+
return true;
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
const conditions = [];
|
|
713
|
+
const values = [];
|
|
714
|
+
for (const token of searchTokens) {
|
|
715
|
+
const like = `%${escapeLike(token)}%`;
|
|
716
|
+
conditions.push(`content LIKE ? ESCAPE '\\'`);
|
|
717
|
+
values.push(like);
|
|
718
|
+
conditions.push(`title LIKE ? ESCAPE '\\'`);
|
|
719
|
+
values.push(like);
|
|
720
|
+
conditions.push(`file_path LIKE ? ESCAPE '\\'`);
|
|
721
|
+
values.push(like);
|
|
722
|
+
}
|
|
723
|
+
if (!conditions.length)
|
|
724
|
+
return { query: q, top_k: opts.topK, mode: "token", matches: [] };
|
|
725
|
+
const stmt = getDb().prepare(`
|
|
726
|
+
SELECT
|
|
727
|
+
id,
|
|
728
|
+
kind,
|
|
729
|
+
title,
|
|
730
|
+
content,
|
|
731
|
+
file_path,
|
|
732
|
+
start_line,
|
|
733
|
+
end_line,
|
|
734
|
+
req_id,
|
|
735
|
+
metadata_json,
|
|
736
|
+
updated_at
|
|
737
|
+
FROM memory_items
|
|
738
|
+
WHERE (${conditions.join(" OR ")})
|
|
739
|
+
${kindClause}
|
|
740
|
+
ORDER BY
|
|
741
|
+
${candidateScore} DESC,
|
|
742
|
+
updated_at DESC,
|
|
743
|
+
id DESC
|
|
744
|
+
LIMIT ?
|
|
745
|
+
`);
|
|
746
|
+
const rows = stmt.all(...values, ...effectiveKinds, rawLimit);
|
|
747
|
+
const scoreMap = new Map(memoryFirstScores);
|
|
748
|
+
for (const row of rows) {
|
|
749
|
+
if (!scoreMap.has(row.id))
|
|
750
|
+
scoreMap.set(row.id, tokenLexicalScore(row, q, tokens));
|
|
751
|
+
}
|
|
752
|
+
const rowMap = new Map();
|
|
753
|
+
for (const row of memoryFirstRows)
|
|
754
|
+
rowMap.set(row.id, row);
|
|
755
|
+
for (const row of rows)
|
|
756
|
+
rowMap.set(row.id, row);
|
|
757
|
+
const matches = filterAndRankSemanticRows(Array.from(rowMap.values()), (r) => scoreMap.get(r.id) ?? 0, opts);
|
|
758
|
+
return { query: q, top_k: opts.topK, mode: "token", matches };
|
|
759
|
+
}
|
|
760
|
+
function chooseLexicalResult(opts) {
|
|
761
|
+
const tokenResult = tokenSearchInternal(opts);
|
|
762
|
+
const tokenTopScore = tokenResult.matches[0]?.score ?? 0;
|
|
763
|
+
const tokenEnough = tokenResult.matches.length >= Math.min(opts.topK, 3) && tokenTopScore >= 8;
|
|
764
|
+
if (tokenEnough || tokenResult.matches.length >= opts.topK) {
|
|
765
|
+
return { result: tokenResult, mode: "token" };
|
|
766
|
+
}
|
|
767
|
+
let textResult = null;
|
|
768
|
+
if (isFtsAvailable()) {
|
|
769
|
+
try {
|
|
770
|
+
textResult = ftsSearchInternal(opts);
|
|
771
|
+
}
|
|
772
|
+
catch (err) {
|
|
773
|
+
console.error("[vectormind] fts semantic_search failed; falling back:", err);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
if (!textResult) {
|
|
777
|
+
textResult = likeSearchInternal(opts);
|
|
778
|
+
}
|
|
779
|
+
const merged = mergeSemanticMatches([textResult.matches, tokenResult.matches], opts);
|
|
780
|
+
const tokenIds = new Set(tokenResult.matches.map((m) => m.item.id));
|
|
781
|
+
const ftsKept = textResult.matches.some((m) => !tokenIds.has(m.item.id));
|
|
782
|
+
if (tokenResult.matches.length && ftsKept) {
|
|
783
|
+
return {
|
|
784
|
+
result: { query: opts.query.trim(), top_k: opts.topK, mode: "hybrid", matches: merged },
|
|
785
|
+
mode: "hybrid",
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
if (tokenResult.matches.length) {
|
|
789
|
+
return { result: { query: opts.query.trim(), top_k: opts.topK, mode: "token", matches: merged }, mode: "token" };
|
|
790
|
+
}
|
|
791
|
+
return { result: textResult, mode: textResult.mode === "fts" ? "fts" : "like" };
|
|
792
|
+
}
|
|
793
|
+
export async function semanticSearchHybridInternal(opts) {
|
|
794
|
+
return chooseLexicalResult(opts).result;
|
|
795
|
+
}
|
|
796
|
+
//# sourceMappingURL=memory-recall.js.map
|