@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.
Files changed (117) hide show
  1. package/README.md +146 -394
  2. package/dist/activity-log.d.ts +19 -0
  3. package/dist/activity-log.js +106 -0
  4. package/dist/activity-log.js.map +1 -0
  5. package/dist/builtin-conventions.d.ts +2 -1
  6. package/dist/builtin-conventions.js +7 -2
  7. package/dist/builtin-conventions.js.map +1 -1
  8. package/dist/builtin-instructions.d.ts +1 -1
  9. package/dist/builtin-instructions.js +1 -1
  10. package/dist/builtin-instructions.js.map +1 -1
  11. package/dist/config.d.ts +28 -0
  12. package/dist/config.js +189 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/database-runtime.d.ts +54 -0
  15. package/dist/database-runtime.js +512 -0
  16. package/dist/database-runtime.js.map +1 -0
  17. package/dist/development-warnings.d.ts +93 -0
  18. package/dist/development-warnings.js +589 -0
  19. package/dist/development-warnings.js.map +1 -0
  20. package/dist/file-indexing.d.ts +22 -0
  21. package/dist/file-indexing.js +243 -0
  22. package/dist/file-indexing.js.map +1 -0
  23. package/dist/grep.d.ts +64 -0
  24. package/dist/grep.js +475 -0
  25. package/dist/grep.js.map +1 -0
  26. package/dist/index.js +167 -6884
  27. package/dist/index.js.map +1 -1
  28. package/dist/large-file-split.d.ts +29 -0
  29. package/dist/large-file-split.js +146 -0
  30. package/dist/large-file-split.js.map +1 -0
  31. package/dist/memory-maintenance.d.ts +80 -0
  32. package/dist/memory-maintenance.js +537 -0
  33. package/dist/memory-maintenance.js.map +1 -0
  34. package/dist/memory-mutations.d.ts +23 -0
  35. package/dist/memory-mutations.js +83 -0
  36. package/dist/memory-mutations.js.map +1 -0
  37. package/dist/memory-recall.d.ts +93 -0
  38. package/dist/memory-recall.js +796 -0
  39. package/dist/memory-recall.js.map +1 -0
  40. package/dist/path-filters.d.ts +1 -0
  41. package/dist/path-filters.js +27 -0
  42. package/dist/path-filters.js.map +1 -0
  43. package/dist/path-rules.d.ts +12 -0
  44. package/dist/path-rules.js +208 -0
  45. package/dist/path-rules.js.map +1 -0
  46. package/dist/pending-changes.d.ts +22 -0
  47. package/dist/pending-changes.js +166 -0
  48. package/dist/pending-changes.js.map +1 -0
  49. package/dist/project-files.d.ts +66 -0
  50. package/dist/project-files.js +215 -0
  51. package/dist/project-files.js.map +1 -0
  52. package/dist/root.d.ts +15 -0
  53. package/dist/root.js +191 -0
  54. package/dist/root.js.map +1 -0
  55. package/dist/rtk-tools.d.ts +20 -0
  56. package/dist/rtk-tools.js +318 -0
  57. package/dist/rtk-tools.js.map +1 -0
  58. package/dist/server-instructions.d.ts +1 -0
  59. package/dist/server-instructions.js +88 -0
  60. package/dist/server-instructions.js.map +1 -0
  61. package/dist/symbols.d.ts +2 -0
  62. package/dist/symbols.js +159 -0
  63. package/dist/symbols.js.map +1 -0
  64. package/dist/token-savings.d.ts +40 -0
  65. package/dist/token-savings.js +50 -0
  66. package/dist/token-savings.js.map +1 -0
  67. package/dist/tool-catalog.d.ts +18 -0
  68. package/dist/tool-catalog.js +243 -0
  69. package/dist/tool-catalog.js.map +1 -0
  70. package/dist/tool-handlers/context-recovery.d.ts +6 -0
  71. package/dist/tool-handlers/context-recovery.js +319 -0
  72. package/dist/tool-handlers/context-recovery.js.map +1 -0
  73. package/dist/tool-handlers/context.d.ts +34 -0
  74. package/dist/tool-handlers/context.js +2 -0
  75. package/dist/tool-handlers/context.js.map +1 -0
  76. package/dist/tool-handlers/diagnostics.d.ts +8 -0
  77. package/dist/tool-handlers/diagnostics.js +114 -0
  78. package/dist/tool-handlers/diagnostics.js.map +1 -0
  79. package/dist/tool-handlers/files.d.ts +8 -0
  80. package/dist/tool-handlers/files.js +493 -0
  81. package/dist/tool-handlers/files.js.map +1 -0
  82. package/dist/tool-handlers/large-files.d.ts +4 -0
  83. package/dist/tool-handlers/large-files.js +139 -0
  84. package/dist/tool-handlers/large-files.js.map +1 -0
  85. package/dist/tool-handlers/maintenance.d.ts +4 -0
  86. package/dist/tool-handlers/maintenance.js +160 -0
  87. package/dist/tool-handlers/maintenance.js.map +1 -0
  88. package/dist/tool-handlers/memory-diagnostics.d.ts +5 -0
  89. package/dist/tool-handlers/memory-diagnostics.js +509 -0
  90. package/dist/tool-handlers/memory-diagnostics.js.map +1 -0
  91. package/dist/tool-handlers/memory.d.ts +6 -0
  92. package/dist/tool-handlers/memory.js +334 -0
  93. package/dist/tool-handlers/memory.js.map +1 -0
  94. package/dist/tool-handlers/notes-decisions.d.ts +7 -0
  95. package/dist/tool-handlers/notes-decisions.js +169 -0
  96. package/dist/tool-handlers/notes-decisions.js.map +1 -0
  97. package/dist/tool-handlers/requirements.d.ts +7 -0
  98. package/dist/tool-handlers/requirements.js +357 -0
  99. package/dist/tool-handlers/requirements.js.map +1 -0
  100. package/dist/tool-handlers.d.ts +4 -0
  101. package/dist/tool-handlers.js +105 -0
  102. package/dist/tool-handlers.js.map +1 -0
  103. package/dist/tool-output.d.ts +213 -0
  104. package/dist/tool-output.js +278 -0
  105. package/dist/tool-output.js.map +1 -0
  106. package/dist/tool-schemas.d.ts +434 -0
  107. package/dist/tool-schemas.js +328 -0
  108. package/dist/tool-schemas.js.map +1 -0
  109. package/dist/types.d.ts +61 -0
  110. package/dist/types.js +2 -0
  111. package/dist/types.js.map +1 -0
  112. package/docs/capability-matrix.md +43 -0
  113. package/package.json +11 -10
  114. package/skills/vector-mind-autopilot/SKILL.md +149 -0
  115. package/skills/vector-mind-autopilot/references/claude-desktop-mcp-config.json +9 -0
  116. package/skills/vector-mind-autopilot/references/claude-project-instructions.md +47 -0
  117. 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