@coreyuan/vector-mind 1.0.49 → 1.0.53

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