@coreyuan/vector-mind 1.0.17 → 1.0.20
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 +15 -2
- package/dist/index.js +575 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
12
12
|
import { toJsonSchemaCompat } from "@modelcontextprotocol/sdk/server/zod-json-schema-compat.js";
|
|
13
13
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
14
14
|
const SERVER_NAME = "vector-mind";
|
|
15
|
-
const SERVER_VERSION = "1.0.
|
|
15
|
+
const SERVER_VERSION = "1.0.19";
|
|
16
16
|
const rootFromEnv = process.env.VECTORMIND_ROOT?.trim() ?? "";
|
|
17
17
|
const prettyJsonOutput = ["1", "true", "on", "yes"].includes((process.env.VECTORMIND_PRETTY_JSON ?? "").trim().toLowerCase());
|
|
18
18
|
const debugLogEnabled = ["1", "true", "on", "yes"].includes((process.env.VECTORMIND_DEBUG_LOG ?? "").trim().toLowerCase());
|
|
@@ -61,6 +61,36 @@ const PENDING_PRUNE_EVERY = (() => {
|
|
|
61
61
|
return 500;
|
|
62
62
|
return n;
|
|
63
63
|
})();
|
|
64
|
+
const INDEX_MAX_CODE_BYTES = (() => {
|
|
65
|
+
const raw = process.env.VECTORMIND_INDEX_MAX_CODE_BYTES?.trim();
|
|
66
|
+
if (!raw)
|
|
67
|
+
return 400_000;
|
|
68
|
+
const n = Number.parseInt(raw, 10);
|
|
69
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
70
|
+
return 400_000;
|
|
71
|
+
return n;
|
|
72
|
+
})();
|
|
73
|
+
const INDEX_MAX_DOC_BYTES = (() => {
|
|
74
|
+
const raw = process.env.VECTORMIND_INDEX_MAX_DOC_BYTES?.trim();
|
|
75
|
+
if (!raw)
|
|
76
|
+
return 600_000;
|
|
77
|
+
const n = Number.parseInt(raw, 10);
|
|
78
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
79
|
+
return 600_000;
|
|
80
|
+
return n;
|
|
81
|
+
})();
|
|
82
|
+
const INDEX_SKIP_MINIFIED = (() => {
|
|
83
|
+
const raw = (process.env.VECTORMIND_INDEX_SKIP_MINIFIED ?? "").trim().toLowerCase();
|
|
84
|
+
if (!raw)
|
|
85
|
+
return true;
|
|
86
|
+
return ["1", "true", "on", "yes"].includes(raw);
|
|
87
|
+
})();
|
|
88
|
+
const INDEX_AUTO_PRUNE_IGNORED = (() => {
|
|
89
|
+
const raw = (process.env.VECTORMIND_INDEX_AUTO_PRUNE_IGNORED ?? "").trim().toLowerCase();
|
|
90
|
+
if (!raw)
|
|
91
|
+
return true;
|
|
92
|
+
return ["1", "true", "on", "yes"].includes(raw);
|
|
93
|
+
})();
|
|
64
94
|
const ROOTS_LIST_TIMEOUT_MS = (() => {
|
|
65
95
|
const raw = process.env.VECTORMIND_ROOTS_TIMEOUT_MS?.trim();
|
|
66
96
|
if (!raw)
|
|
@@ -99,6 +129,10 @@ let insertChangeLogStmt;
|
|
|
99
129
|
let insertMemoryItemStmt;
|
|
100
130
|
let getMemoryItemByIdStmt;
|
|
101
131
|
let getRequirementMemoryItemIdStmt;
|
|
132
|
+
let getConventionByKeyStmt;
|
|
133
|
+
let insertConventionStmt;
|
|
134
|
+
let updateConventionByIdStmt;
|
|
135
|
+
let listConventionsStmt;
|
|
102
136
|
let upsertProjectSummaryStmt;
|
|
103
137
|
let getProjectSummaryStmt;
|
|
104
138
|
let listRecentNotesStmt;
|
|
@@ -119,6 +153,37 @@ let searchSymbolsStmt;
|
|
|
119
153
|
let indexFileSymbolsTx = null;
|
|
120
154
|
let activitySeq = 0;
|
|
121
155
|
const activityLog = [];
|
|
156
|
+
function sanitizeForLog(value, depth = 0) {
|
|
157
|
+
if (depth > 4)
|
|
158
|
+
return "[max-depth]";
|
|
159
|
+
if (value === null || value === undefined)
|
|
160
|
+
return value;
|
|
161
|
+
if (typeof value === "string") {
|
|
162
|
+
return value.length > 500 ? `${value.slice(0, 500)}...` : value;
|
|
163
|
+
}
|
|
164
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
165
|
+
return value;
|
|
166
|
+
if (Array.isArray(value)) {
|
|
167
|
+
const sliced = value.slice(0, 20).map((v) => sanitizeForLog(v, depth + 1));
|
|
168
|
+
return value.length > 20 ? [...sliced, `[+${value.length - 20} more]`] : sliced;
|
|
169
|
+
}
|
|
170
|
+
if (typeof value === "object") {
|
|
171
|
+
const obj = value;
|
|
172
|
+
const keys = Object.keys(obj).slice(0, 40);
|
|
173
|
+
const out = {};
|
|
174
|
+
for (const k of keys)
|
|
175
|
+
out[k] = sanitizeForLog(obj[k], depth + 1);
|
|
176
|
+
if (Object.keys(obj).length > 40)
|
|
177
|
+
out["__more_keys__"] = Object.keys(obj).length - 40;
|
|
178
|
+
return out;
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
return String(value);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return "[unserializable]";
|
|
185
|
+
}
|
|
186
|
+
}
|
|
122
187
|
function logActivity(type, data) {
|
|
123
188
|
if (!debugLogEnabled)
|
|
124
189
|
return;
|
|
@@ -127,7 +192,7 @@ function logActivity(type, data) {
|
|
|
127
192
|
ts: new Date().toISOString(),
|
|
128
193
|
type,
|
|
129
194
|
project_root: projectRoot || "",
|
|
130
|
-
data,
|
|
195
|
+
data: sanitizeForLog(data),
|
|
131
196
|
});
|
|
132
197
|
while (activityLog.length > debugLogMaxEntries)
|
|
133
198
|
activityLog.shift();
|
|
@@ -143,6 +208,37 @@ function clearActivityLog() {
|
|
|
143
208
|
activityLog.length = 0;
|
|
144
209
|
activitySeq = 0;
|
|
145
210
|
}
|
|
211
|
+
function summarizeActivityEvent(e) {
|
|
212
|
+
const d = e.data ?? {};
|
|
213
|
+
switch (e.type) {
|
|
214
|
+
case "index_file":
|
|
215
|
+
return `index ${String(d.file_path ?? "")} reason=${String(d.reason ?? "")} symbols=${String(d.symbols ?? "")} chunks=${String(d.chunks ?? "")}`;
|
|
216
|
+
case "remove_file":
|
|
217
|
+
return `remove ${String(d.file_path ?? "")}`;
|
|
218
|
+
case "pending_flush":
|
|
219
|
+
return `pending_flush entries=${String(d.entries ?? "")}`;
|
|
220
|
+
case "pending_prune":
|
|
221
|
+
return `pending_prune ${String(d.before ?? "")}->${String(d.after ?? "")}`;
|
|
222
|
+
case "bootstrap_context":
|
|
223
|
+
return `bootstrap q=${String(d.query ?? "")} pending=${String(d.pending_returned ?? "")}/${String(d.pending_total ?? "")} reqs=${String(d.requirements_returned ?? "")} semantic=${String(d.semantic_mode ?? "")}+${String(d.semantic_matches ?? "")}`;
|
|
224
|
+
case "get_brain_dump":
|
|
225
|
+
return `brain_dump pending=${String(d.pending_returned ?? "")}/${String(d.pending_total ?? "")} reqs=${String(d.requirements_returned ?? "")} notes=${String(d.notes_returned ?? "")}`;
|
|
226
|
+
case "get_pending_changes":
|
|
227
|
+
return `pending_list returned=${String(d.returned ?? "")} total=${String(d.total ?? "")}`;
|
|
228
|
+
case "semantic_search":
|
|
229
|
+
return `semantic_search mode=${String(d.mode ?? "")} q=${String(d.query ?? "")} matches=${String(d.matches ?? "")}`;
|
|
230
|
+
case "query_codebase":
|
|
231
|
+
return `query_codebase q=${String(d.query ?? "")} matches=${String(d.matches ?? "")}`;
|
|
232
|
+
case "start_requirement":
|
|
233
|
+
return `start_requirement #${String(d.req_id ?? "")} ${String(d.title ?? "")}`;
|
|
234
|
+
case "sync_change_intent":
|
|
235
|
+
return `sync_change_intent #${String(d.req_id ?? "")} files=${String(d.files_total ?? "")}`;
|
|
236
|
+
case "complete_requirement":
|
|
237
|
+
return `complete_requirement ${String(d.all_active ? "all_active" : d.req_id ?? "")}`;
|
|
238
|
+
default:
|
|
239
|
+
return e.type;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
146
242
|
const FTS_TABLE_NAME = "memory_items_fts";
|
|
147
243
|
let ftsAvailable = false;
|
|
148
244
|
function isProbablyVscodeInstallDir(dir) {
|
|
@@ -340,6 +436,10 @@ const IGNORED_PATH_SEGMENTS = new Set([
|
|
|
340
436
|
".next",
|
|
341
437
|
".nuxt",
|
|
342
438
|
".svelte-kit",
|
|
439
|
+
".turbo",
|
|
440
|
+
".nx",
|
|
441
|
+
".cache",
|
|
442
|
+
".parcel-cache",
|
|
343
443
|
// .NET / VS build artifacts
|
|
344
444
|
"bin",
|
|
345
445
|
"obj",
|
|
@@ -348,9 +448,11 @@ const IGNORED_PATH_SEGMENTS = new Set([
|
|
|
348
448
|
// General build outputs
|
|
349
449
|
"dist",
|
|
350
450
|
"build",
|
|
451
|
+
"buildfiles",
|
|
351
452
|
"out",
|
|
352
453
|
"target",
|
|
353
454
|
"coverage",
|
|
455
|
+
"artifacts",
|
|
354
456
|
// Python caches/venvs
|
|
355
457
|
"__pycache__",
|
|
356
458
|
".pytest_cache",
|
|
@@ -399,7 +501,9 @@ function pruneIgnoredPendingChanges() {
|
|
|
399
501
|
try {
|
|
400
502
|
if (!IGNORED_LIKE_PATTERNS.length)
|
|
401
503
|
return;
|
|
402
|
-
const where = IGNORED_LIKE_PATTERNS
|
|
504
|
+
const where = IGNORED_LIKE_PATTERNS
|
|
505
|
+
.map(() => "LOWER(REPLACE(file_path, '\\\\', '/')) LIKE ?")
|
|
506
|
+
.join(" OR ");
|
|
403
507
|
db.prepare(`DELETE FROM pending_changes WHERE ${where}`).run(...IGNORED_LIKE_PATTERNS);
|
|
404
508
|
}
|
|
405
509
|
catch (err) {
|
|
@@ -432,6 +536,102 @@ function prunePendingChanges() {
|
|
|
432
536
|
console.error("[vectormind] prune pending_changes failed:", err);
|
|
433
537
|
}
|
|
434
538
|
}
|
|
539
|
+
function pruneIgnoredIndexesByPathPatterns() {
|
|
540
|
+
if (!db)
|
|
541
|
+
return { chunks_deleted: 0, symbols_deleted: 0 };
|
|
542
|
+
try {
|
|
543
|
+
if (!IGNORED_LIKE_PATTERNS.length)
|
|
544
|
+
return { chunks_deleted: 0, symbols_deleted: 0 };
|
|
545
|
+
const where = IGNORED_LIKE_PATTERNS
|
|
546
|
+
.map(() => "LOWER(REPLACE(file_path, '\\\\', '/')) LIKE ?")
|
|
547
|
+
.join(" OR ");
|
|
548
|
+
const chunksDeleted = db
|
|
549
|
+
.prepare(`DELETE FROM memory_items
|
|
550
|
+
WHERE file_path IS NOT NULL
|
|
551
|
+
AND (kind = 'code_chunk' OR kind = 'doc_chunk')
|
|
552
|
+
AND (${where})`)
|
|
553
|
+
.run(...IGNORED_LIKE_PATTERNS).changes;
|
|
554
|
+
const symbolsDeleted = db
|
|
555
|
+
.prepare(`DELETE FROM symbols
|
|
556
|
+
WHERE file_path IS NOT NULL
|
|
557
|
+
AND (${where})`)
|
|
558
|
+
.run(...IGNORED_LIKE_PATTERNS).changes;
|
|
559
|
+
if (chunksDeleted || symbolsDeleted) {
|
|
560
|
+
logActivity("index_prune", {
|
|
561
|
+
reason: "ignored_paths",
|
|
562
|
+
chunks_deleted: chunksDeleted,
|
|
563
|
+
symbols_deleted: symbolsDeleted,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
return { chunks_deleted: chunksDeleted, symbols_deleted: symbolsDeleted };
|
|
567
|
+
}
|
|
568
|
+
catch (err) {
|
|
569
|
+
console.error("[vectormind] prune indexes failed:", err);
|
|
570
|
+
return { chunks_deleted: 0, symbols_deleted: 0 };
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
function pruneFilenameNoiseIndexes() {
|
|
574
|
+
if (!db)
|
|
575
|
+
return { chunks_deleted: 0, symbols_deleted: 0 };
|
|
576
|
+
const suffixes = [
|
|
577
|
+
".min.js",
|
|
578
|
+
".min.css",
|
|
579
|
+
".bundle.js",
|
|
580
|
+
".bundle.css",
|
|
581
|
+
".chunk.js",
|
|
582
|
+
".chunk.css",
|
|
583
|
+
];
|
|
584
|
+
const baseNames = [
|
|
585
|
+
"package-lock.json",
|
|
586
|
+
"pnpm-lock.yaml",
|
|
587
|
+
"yarn.lock",
|
|
588
|
+
"bun.lockb",
|
|
589
|
+
"cargo.lock",
|
|
590
|
+
"composer.lock",
|
|
591
|
+
];
|
|
592
|
+
try {
|
|
593
|
+
const suffixWhere = suffixes.map(() => "LOWER(file_path) LIKE ?").join(" OR ");
|
|
594
|
+
const baseWhere = baseNames.map(() => "LOWER(file_path) LIKE ?").join(" OR ");
|
|
595
|
+
const suffixArgs = suffixes.map((s) => `%${s}`);
|
|
596
|
+
const baseArgs = baseNames.map((n) => `%/${n}`);
|
|
597
|
+
const whereParts = [];
|
|
598
|
+
const args = [];
|
|
599
|
+
if (suffixWhere) {
|
|
600
|
+
whereParts.push(`(${suffixWhere})`);
|
|
601
|
+
args.push(...suffixArgs);
|
|
602
|
+
}
|
|
603
|
+
if (baseWhere) {
|
|
604
|
+
whereParts.push(`(${baseWhere})`);
|
|
605
|
+
args.push(...baseArgs);
|
|
606
|
+
}
|
|
607
|
+
if (!whereParts.length)
|
|
608
|
+
return { chunks_deleted: 0, symbols_deleted: 0 };
|
|
609
|
+
const where = whereParts.join(" OR ");
|
|
610
|
+
const chunksDeleted = db
|
|
611
|
+
.prepare(`DELETE FROM memory_items
|
|
612
|
+
WHERE file_path IS NOT NULL
|
|
613
|
+
AND (kind = 'code_chunk' OR kind = 'doc_chunk')
|
|
614
|
+
AND (${where})`)
|
|
615
|
+
.run(...args).changes;
|
|
616
|
+
const symbolsDeleted = db
|
|
617
|
+
.prepare(`DELETE FROM symbols
|
|
618
|
+
WHERE file_path IS NOT NULL
|
|
619
|
+
AND (${where})`)
|
|
620
|
+
.run(...args).changes;
|
|
621
|
+
if (chunksDeleted || symbolsDeleted) {
|
|
622
|
+
logActivity("index_prune", {
|
|
623
|
+
reason: "filename_noise",
|
|
624
|
+
chunks_deleted: chunksDeleted,
|
|
625
|
+
symbols_deleted: symbolsDeleted,
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
return { chunks_deleted: chunksDeleted, symbols_deleted: symbolsDeleted };
|
|
629
|
+
}
|
|
630
|
+
catch (err) {
|
|
631
|
+
console.error("[vectormind] prune filename noise failed:", err);
|
|
632
|
+
return { chunks_deleted: 0, symbols_deleted: 0 };
|
|
633
|
+
}
|
|
634
|
+
}
|
|
435
635
|
function shouldIgnorePath(inputPath) {
|
|
436
636
|
const normalizedAbs = path.resolve(inputPath);
|
|
437
637
|
const rel = path.relative(projectRoot, normalizedAbs);
|
|
@@ -449,6 +649,8 @@ function shouldIgnorePath(inputPath) {
|
|
|
449
649
|
return false;
|
|
450
650
|
}
|
|
451
651
|
function isSymbolIndexableFile(filePath) {
|
|
652
|
+
if (shouldIgnoreContentFile(filePath))
|
|
653
|
+
return false;
|
|
452
654
|
const ext = path.extname(filePath).toLowerCase();
|
|
453
655
|
const allowed = new Set([
|
|
454
656
|
".ts",
|
|
@@ -485,6 +687,56 @@ function shouldIgnoreContentFile(filePath) {
|
|
|
485
687
|
return true;
|
|
486
688
|
if (base.endsWith(".min.js") || base.endsWith(".min.css"))
|
|
487
689
|
return true;
|
|
690
|
+
if (base.endsWith(".bundle.js") || base.endsWith(".bundle.css"))
|
|
691
|
+
return true;
|
|
692
|
+
if (base.endsWith(".chunk.js") || base.endsWith(".chunk.css"))
|
|
693
|
+
return true;
|
|
694
|
+
return false;
|
|
695
|
+
}
|
|
696
|
+
function looksLikeGeneratedFile(content) {
|
|
697
|
+
const head = content.slice(0, 4000).toLowerCase();
|
|
698
|
+
if (head.includes("@generated"))
|
|
699
|
+
return true;
|
|
700
|
+
if (head.includes("do not edit") && (head.includes("generated") || head.includes("auto-generated"))) {
|
|
701
|
+
return true;
|
|
702
|
+
}
|
|
703
|
+
if (head.includes("this file was generated") && head.includes("do not edit"))
|
|
704
|
+
return true;
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
function looksLikeMinifiedBundle(content) {
|
|
708
|
+
if (content.length < 30_000)
|
|
709
|
+
return false;
|
|
710
|
+
let lines = 1;
|
|
711
|
+
let currentLen = 0;
|
|
712
|
+
let maxLineLen = 0;
|
|
713
|
+
let longLines = 0;
|
|
714
|
+
for (let i = 0; i < content.length; i++) {
|
|
715
|
+
const code = content.charCodeAt(i);
|
|
716
|
+
if (code === 10 /* \\n */) {
|
|
717
|
+
if (currentLen > maxLineLen)
|
|
718
|
+
maxLineLen = currentLen;
|
|
719
|
+
if (currentLen >= 800)
|
|
720
|
+
longLines += 1;
|
|
721
|
+
currentLen = 0;
|
|
722
|
+
lines += 1;
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
currentLen += 1;
|
|
726
|
+
}
|
|
727
|
+
if (currentLen > maxLineLen)
|
|
728
|
+
maxLineLen = currentLen;
|
|
729
|
+
if (currentLen >= 800)
|
|
730
|
+
longLines += 1;
|
|
731
|
+
const avgLineLen = content.length / Math.max(1, lines);
|
|
732
|
+
if (lines <= 2 && maxLineLen >= 2000)
|
|
733
|
+
return true;
|
|
734
|
+
if (maxLineLen >= 6000)
|
|
735
|
+
return true;
|
|
736
|
+
if (avgLineLen >= 900)
|
|
737
|
+
return true;
|
|
738
|
+
if (lines <= 10 && longLines >= Math.ceil(lines * 0.6))
|
|
739
|
+
return true;
|
|
488
740
|
return false;
|
|
489
741
|
}
|
|
490
742
|
function getContentChunkKind(filePath) {
|
|
@@ -787,6 +1039,9 @@ function indexFile(absPath, reason) {
|
|
|
787
1039
|
const indexContent = isContentIndexableFile(absPath);
|
|
788
1040
|
if (!indexSymbols && !indexContent)
|
|
789
1041
|
return;
|
|
1042
|
+
const kind = getContentChunkKind(absPath);
|
|
1043
|
+
if (!kind)
|
|
1044
|
+
return;
|
|
790
1045
|
let stat;
|
|
791
1046
|
try {
|
|
792
1047
|
stat = fs.statSync(absPath);
|
|
@@ -796,7 +1051,8 @@ function indexFile(absPath, reason) {
|
|
|
796
1051
|
}
|
|
797
1052
|
if (!stat.isFile())
|
|
798
1053
|
return;
|
|
799
|
-
|
|
1054
|
+
const maxBytes = kind === "code_chunk" ? INDEX_MAX_CODE_BYTES : INDEX_MAX_DOC_BYTES;
|
|
1055
|
+
if (maxBytes > 0 && stat.size > maxBytes)
|
|
800
1056
|
return;
|
|
801
1057
|
let content;
|
|
802
1058
|
try {
|
|
@@ -807,7 +1063,19 @@ function indexFile(absPath, reason) {
|
|
|
807
1063
|
}
|
|
808
1064
|
if (content.includes("\u0000"))
|
|
809
1065
|
return;
|
|
1066
|
+
const ext = path.extname(absPath).toLowerCase();
|
|
810
1067
|
const filePath = normalizeToDbPath(absPath);
|
|
1068
|
+
if (INDEX_SKIP_MINIFIED &&
|
|
1069
|
+
kind === "code_chunk" &&
|
|
1070
|
+
(ext === ".js" || ext === ".mjs" || ext === ".cjs" || ext === ".css") &&
|
|
1071
|
+
looksLikeMinifiedBundle(content)) {
|
|
1072
|
+
logActivity("index_skip", { file_path: filePath, reason: "minified_bundle", bytes: stat.size });
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
if (kind === "code_chunk" && stat.size >= 20_000 && looksLikeGeneratedFile(content)) {
|
|
1076
|
+
logActivity("index_skip", { file_path: filePath, reason: "generated_file", bytes: stat.size });
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
811
1079
|
let symbolCount = 0;
|
|
812
1080
|
let chunkCount = 0;
|
|
813
1081
|
if (indexSymbols) {
|
|
@@ -873,6 +1141,18 @@ const AddNoteArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
|
873
1141
|
content: z.string().min(1),
|
|
874
1142
|
tags: z.array(z.string().min(1)).optional(),
|
|
875
1143
|
}));
|
|
1144
|
+
const PruneIndexArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
1145
|
+
dry_run: z.boolean().optional().default(true),
|
|
1146
|
+
prune_ignored_paths: z.boolean().optional().default(true),
|
|
1147
|
+
prune_minified_bundles: z.boolean().optional().default(false),
|
|
1148
|
+
max_files: z.number().int().min(1).max(50_000).optional().default(2000),
|
|
1149
|
+
vacuum: z.boolean().optional().default(false),
|
|
1150
|
+
}));
|
|
1151
|
+
const UpsertConventionArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
1152
|
+
key: z.string().min(1),
|
|
1153
|
+
content: z.string().min(1),
|
|
1154
|
+
tags: z.array(z.string().min(1)).optional(),
|
|
1155
|
+
}));
|
|
876
1156
|
const DEFAULT_PENDING_LIMIT = 50;
|
|
877
1157
|
const MAX_PENDING_LIMIT = 2000;
|
|
878
1158
|
const PendingPagingSchema = z.object({
|
|
@@ -890,10 +1170,12 @@ const ContentMaxSchema = z.object({
|
|
|
890
1170
|
const DEFAULT_RECENT_REQUIREMENTS = 3;
|
|
891
1171
|
const DEFAULT_RECENT_CHANGES_PER_REQ = 5;
|
|
892
1172
|
const DEFAULT_RECENT_NOTES = 5;
|
|
1173
|
+
const DEFAULT_CONVENTIONS_LIMIT = 20;
|
|
893
1174
|
const BrainDumpLimitsSchema = z.object({
|
|
894
1175
|
requirements_limit: z.number().int().min(1).max(20).optional().default(DEFAULT_RECENT_REQUIREMENTS),
|
|
895
1176
|
changes_limit: z.number().int().min(1).max(100).optional().default(DEFAULT_RECENT_CHANGES_PER_REQ),
|
|
896
1177
|
notes_limit: z.number().int().min(0).max(50).optional().default(DEFAULT_RECENT_NOTES),
|
|
1178
|
+
conventions_limit: z.number().int().min(0).max(200).optional().default(DEFAULT_CONVENTIONS_LIMIT),
|
|
897
1179
|
});
|
|
898
1180
|
const GetPendingChangesArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
899
1181
|
offset: z.number().int().min(0).optional().default(0),
|
|
@@ -905,7 +1187,12 @@ const CompleteRequirementArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
|
905
1187
|
}));
|
|
906
1188
|
const GetActivityLogArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
907
1189
|
since_id: z.number().int().min(0).optional().default(0),
|
|
908
|
-
limit: z.number().int().min(1).max(500).optional().default(
|
|
1190
|
+
limit: z.number().int().min(1).max(500).optional().default(30),
|
|
1191
|
+
verbose: z.boolean().optional().default(false),
|
|
1192
|
+
}));
|
|
1193
|
+
const GetActivitySummaryArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
1194
|
+
since_id: z.number().int().min(0).optional().default(0),
|
|
1195
|
+
max_files: z.number().int().min(0).max(200).optional().default(20),
|
|
909
1196
|
}));
|
|
910
1197
|
const ClearActivityLogArgsSchema = ProjectRootArgSchema;
|
|
911
1198
|
const GetBrainDumpArgsSchema = ProjectRootArgSchema.merge(PendingPagingSchema)
|
|
@@ -1415,6 +1702,7 @@ const server = new Server({ name: SERVER_NAME, version: SERVER_VERSION }, {
|
|
|
1415
1702
|
"- BEFORE editing code: call start_requirement(title, background) to set the active requirement.",
|
|
1416
1703
|
"- AFTER editing + saving: call get_pending_changes() to see unsynced files, then call sync_change_intent(intent, files). (You can omit files to auto-link all pending changes.)",
|
|
1417
1704
|
"- After major milestones/decisions: call upsert_project_summary(summary) and/or add_note(...) to persist durable context locally.",
|
|
1705
|
+
"- If the user states a durable project convention (build commands, frameworks, naming rules, output paths): call upsert_convention(key, content, tags) so it is applied in future sessions.",
|
|
1418
1706
|
"- When you need full text for a specific note/summary/match: call read_memory_item(id, offset, limit) and page through it.",
|
|
1419
1707
|
"- When asked to locate code (class/function/type): call query_codebase(query) instead of guessing.",
|
|
1420
1708
|
"- When you need to recall relevant context from history/code/docs: call semantic_search(query, ...) instead of guessing.",
|
|
@@ -1629,6 +1917,9 @@ function initDatabase() {
|
|
|
1629
1917
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_memory_items_project_summary
|
|
1630
1918
|
ON memory_items(kind) WHERE kind = 'project_summary';
|
|
1631
1919
|
|
|
1920
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_memory_items_convention_key
|
|
1921
|
+
ON memory_items(kind, title) WHERE kind = 'convention';
|
|
1922
|
+
|
|
1632
1923
|
CREATE INDEX IF NOT EXISTS idx_memory_items_kind_updated_at
|
|
1633
1924
|
ON memory_items(kind, updated_at DESC);
|
|
1634
1925
|
|
|
@@ -1688,12 +1979,27 @@ function initDatabase() {
|
|
|
1688
1979
|
LIMIT ?`);
|
|
1689
1980
|
insertChangeLogStmt = db.prepare(`INSERT INTO change_logs (req_id, file_path, intent_summary) VALUES (?, ?, ?)`);
|
|
1690
1981
|
insertMemoryItemStmt = db.prepare(`INSERT INTO memory_items
|
|
1691
|
-
|
|
1982
|
+
(kind, title, content, file_path, start_line, end_line, req_id, metadata_json, content_hash)
|
|
1692
1983
|
VALUES
|
|
1693
|
-
|
|
1984
|
+
(?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
1694
1985
|
getMemoryItemByIdStmt = db.prepare(`SELECT id, kind, title, content, file_path, start_line, end_line, req_id, metadata_json, content_hash, created_at, updated_at
|
|
1695
1986
|
FROM memory_items
|
|
1696
1987
|
WHERE id = ?`);
|
|
1988
|
+
getConventionByKeyStmt = db.prepare(`SELECT id, kind, title, content, file_path, start_line, end_line, req_id, metadata_json, content_hash, created_at, updated_at
|
|
1989
|
+
FROM memory_items
|
|
1990
|
+
WHERE kind = 'convention' AND title = ?
|
|
1991
|
+
ORDER BY updated_at DESC, id DESC
|
|
1992
|
+
LIMIT 1`);
|
|
1993
|
+
insertConventionStmt = db.prepare(`INSERT INTO memory_items (kind, title, content, metadata_json, content_hash)
|
|
1994
|
+
VALUES ('convention', ?, ?, ?, ?)`);
|
|
1995
|
+
updateConventionByIdStmt = db.prepare(`UPDATE memory_items
|
|
1996
|
+
SET content = ?, metadata_json = ?, content_hash = ?, updated_at = CURRENT_TIMESTAMP
|
|
1997
|
+
WHERE id = ?`);
|
|
1998
|
+
listConventionsStmt = db.prepare(`SELECT id, kind, title, content, file_path, start_line, end_line, req_id, metadata_json, content_hash, created_at, updated_at
|
|
1999
|
+
FROM memory_items
|
|
2000
|
+
WHERE kind = 'convention'
|
|
2001
|
+
ORDER BY updated_at DESC, id DESC
|
|
2002
|
+
LIMIT ?`);
|
|
1697
2003
|
getRequirementMemoryItemIdStmt = db.prepare(`SELECT id
|
|
1698
2004
|
FROM memory_items
|
|
1699
2005
|
WHERE kind = 'requirement' AND req_id = ?
|
|
@@ -1773,6 +2079,13 @@ function initDatabase() {
|
|
|
1773
2079
|
});
|
|
1774
2080
|
// Clean up noisy pending changes recorded by older versions (build artifacts, node_modules, etc).
|
|
1775
2081
|
prunePendingChanges();
|
|
2082
|
+
// Clean up noisy indexes recorded by older versions (build artifacts, etc).
|
|
2083
|
+
if (INDEX_AUTO_PRUNE_IGNORED) {
|
|
2084
|
+
pruneIgnoredIndexesByPathPatterns();
|
|
2085
|
+
}
|
|
2086
|
+
// Clean up common "file name noise" recorded by older versions.
|
|
2087
|
+
// (These files are ignored by current index rules; keep the DB consistent automatically.)
|
|
2088
|
+
pruneFilenameNoiseIndexes();
|
|
1776
2089
|
}
|
|
1777
2090
|
function initWatcher() {
|
|
1778
2091
|
watcherReady = false;
|
|
@@ -1942,6 +2255,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1942
2255
|
description: "Get recent debug activity (indexing/search/pending) for troubleshooting. Enable logging with VECTORMIND_DEBUG_LOG=1. Use since_id/limit to page.",
|
|
1943
2256
|
inputSchema: toJsonSchemaCompat(GetActivityLogArgsSchema),
|
|
1944
2257
|
},
|
|
2258
|
+
{
|
|
2259
|
+
name: "get_activity_summary",
|
|
2260
|
+
description: "Get a compact summary of recent debug activity (counts + small samples). Enable logging with VECTORMIND_DEBUG_LOG=1. Use since_id to get incremental summaries.",
|
|
2261
|
+
inputSchema: toJsonSchemaCompat(GetActivitySummaryArgsSchema),
|
|
2262
|
+
},
|
|
1945
2263
|
{
|
|
1946
2264
|
name: "clear_activity_log",
|
|
1947
2265
|
description: "Clear the in-memory debug activity log. Enable logging with VECTORMIND_DEBUG_LOG=1.",
|
|
@@ -1962,11 +2280,21 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1962
2280
|
description: "Save a durable project note (decision, constraint, TODO, architecture detail). Use this to persist important context locally instead of relying on chat memory.",
|
|
1963
2281
|
inputSchema: toJsonSchemaCompat(AddNoteArgsSchema),
|
|
1964
2282
|
},
|
|
2283
|
+
{
|
|
2284
|
+
name: "upsert_convention",
|
|
2285
|
+
description: "Save/update a project convention (framework choice, build command, naming rules, etc). Conventions are durable and should be applied automatically in future sessions.",
|
|
2286
|
+
inputSchema: toJsonSchemaCompat(UpsertConventionArgsSchema),
|
|
2287
|
+
},
|
|
1965
2288
|
{
|
|
1966
2289
|
name: "semantic_search",
|
|
1967
2290
|
description: "Semantic search across the local memory store (requirements, change intents, notes, project summary, and indexed code/doc chunks). Use this to retrieve relevant context instead of guessing.",
|
|
1968
2291
|
inputSchema: toJsonSchemaCompat(SemanticSearchArgsSchema),
|
|
1969
2292
|
},
|
|
2293
|
+
{
|
|
2294
|
+
name: "prune_index",
|
|
2295
|
+
description: "Prune noisy auto-indexed items (code_chunk/doc_chunk + symbols). Useful after tightening ignore rules to shrink the index and improve search relevance.",
|
|
2296
|
+
inputSchema: toJsonSchemaCompat(PruneIndexArgsSchema),
|
|
2297
|
+
},
|
|
1970
2298
|
],
|
|
1971
2299
|
};
|
|
1972
2300
|
});
|
|
@@ -2013,6 +2341,142 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2013
2341
|
],
|
|
2014
2342
|
};
|
|
2015
2343
|
}
|
|
2344
|
+
if (toolName === "prune_index") {
|
|
2345
|
+
const args = PruneIndexArgsSchema.parse(rawArgs);
|
|
2346
|
+
flushPendingChangeBuffer();
|
|
2347
|
+
const result = {
|
|
2348
|
+
ok: true,
|
|
2349
|
+
dry_run: args.dry_run,
|
|
2350
|
+
config: {
|
|
2351
|
+
index_max_code_bytes: INDEX_MAX_CODE_BYTES,
|
|
2352
|
+
index_max_doc_bytes: INDEX_MAX_DOC_BYTES,
|
|
2353
|
+
index_skip_minified: INDEX_SKIP_MINIFIED,
|
|
2354
|
+
index_auto_prune_ignored: INDEX_AUTO_PRUNE_IGNORED,
|
|
2355
|
+
},
|
|
2356
|
+
pruned: {
|
|
2357
|
+
ignored_paths: { chunks_deleted: 0, symbols_deleted: 0 },
|
|
2358
|
+
minified_bundles: { files_matched: 0, chunks_deleted: 0, symbols_deleted: 0 },
|
|
2359
|
+
},
|
|
2360
|
+
};
|
|
2361
|
+
if (args.prune_ignored_paths) {
|
|
2362
|
+
if (!IGNORED_LIKE_PATTERNS.length) {
|
|
2363
|
+
result.pruned.ignored_paths = { chunks_deleted: 0, symbols_deleted: 0 };
|
|
2364
|
+
}
|
|
2365
|
+
else if (args.dry_run) {
|
|
2366
|
+
const where = IGNORED_LIKE_PATTERNS
|
|
2367
|
+
.map(() => "LOWER(REPLACE(file_path, '\\\\', '/')) LIKE ?")
|
|
2368
|
+
.join(" OR ");
|
|
2369
|
+
const chunksWould = Number(db
|
|
2370
|
+
.prepare(`SELECT COUNT(1) AS c
|
|
2371
|
+
FROM memory_items
|
|
2372
|
+
WHERE file_path IS NOT NULL
|
|
2373
|
+
AND (kind = 'code_chunk' OR kind = 'doc_chunk')
|
|
2374
|
+
AND (${where})`)
|
|
2375
|
+
.get(...IGNORED_LIKE_PATTERNS)?.c ?? 0);
|
|
2376
|
+
const symbolsWould = Number(db
|
|
2377
|
+
.prepare(`SELECT COUNT(1) AS c
|
|
2378
|
+
FROM symbols
|
|
2379
|
+
WHERE file_path IS NOT NULL
|
|
2380
|
+
AND (${where})`)
|
|
2381
|
+
.get(...IGNORED_LIKE_PATTERNS)?.c ?? 0);
|
|
2382
|
+
result.pruned.ignored_paths = { chunks_deleted: chunksWould, symbols_deleted: symbolsWould };
|
|
2383
|
+
}
|
|
2384
|
+
else {
|
|
2385
|
+
result.pruned.ignored_paths = pruneIgnoredIndexesByPathPatterns();
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
if (args.prune_minified_bundles) {
|
|
2389
|
+
const maxFiles = args.max_files;
|
|
2390
|
+
const candidates = db
|
|
2391
|
+
.prepare(`SELECT file_path, content
|
|
2392
|
+
FROM memory_items
|
|
2393
|
+
WHERE kind = 'code_chunk'
|
|
2394
|
+
AND file_path IS NOT NULL
|
|
2395
|
+
AND (
|
|
2396
|
+
LOWER(file_path) LIKE '%.js'
|
|
2397
|
+
OR LOWER(file_path) LIKE '%.mjs'
|
|
2398
|
+
OR LOWER(file_path) LIKE '%.cjs'
|
|
2399
|
+
OR LOWER(file_path) LIKE '%.css'
|
|
2400
|
+
)
|
|
2401
|
+
ORDER BY updated_at DESC, id DESC
|
|
2402
|
+
LIMIT ?`)
|
|
2403
|
+
.all(Math.min(50_000, maxFiles * 5));
|
|
2404
|
+
const matched = new Set();
|
|
2405
|
+
for (const row of candidates) {
|
|
2406
|
+
if (matched.size >= maxFiles)
|
|
2407
|
+
break;
|
|
2408
|
+
const fp = row.file_path;
|
|
2409
|
+
if (!fp || matched.has(fp))
|
|
2410
|
+
continue;
|
|
2411
|
+
if (looksLikeMinifiedBundle(row.content))
|
|
2412
|
+
matched.add(fp);
|
|
2413
|
+
}
|
|
2414
|
+
if (args.dry_run) {
|
|
2415
|
+
let chunksWould = 0;
|
|
2416
|
+
let symbolsWould = 0;
|
|
2417
|
+
const countChunksStmt = db.prepare(`SELECT COUNT(1) AS c
|
|
2418
|
+
FROM memory_items
|
|
2419
|
+
WHERE file_path = ?
|
|
2420
|
+
AND (kind = 'code_chunk' OR kind = 'doc_chunk')`);
|
|
2421
|
+
const countSymbolsStmt = db.prepare(`SELECT COUNT(1) AS c FROM symbols WHERE file_path = ?`);
|
|
2422
|
+
for (const fp of matched) {
|
|
2423
|
+
chunksWould += Number(countChunksStmt.get(fp)?.c ?? 0);
|
|
2424
|
+
symbolsWould += Number(countSymbolsStmt.get(fp)?.c ?? 0);
|
|
2425
|
+
}
|
|
2426
|
+
result.pruned.minified_bundles = {
|
|
2427
|
+
files_matched: matched.size,
|
|
2428
|
+
chunks_deleted: chunksWould,
|
|
2429
|
+
symbols_deleted: symbolsWould,
|
|
2430
|
+
};
|
|
2431
|
+
}
|
|
2432
|
+
else {
|
|
2433
|
+
let chunksDeleted = 0;
|
|
2434
|
+
let symbolsDeleted = 0;
|
|
2435
|
+
const tx = db.transaction(() => {
|
|
2436
|
+
for (const fp of matched) {
|
|
2437
|
+
chunksDeleted += deleteFileChunkItemsStmt.run(fp).changes;
|
|
2438
|
+
symbolsDeleted += deleteSymbolsForFileStmt.run(fp).changes;
|
|
2439
|
+
}
|
|
2440
|
+
});
|
|
2441
|
+
try {
|
|
2442
|
+
tx();
|
|
2443
|
+
}
|
|
2444
|
+
catch (err) {
|
|
2445
|
+
console.error("[vectormind] prune minified bundles failed:", err);
|
|
2446
|
+
}
|
|
2447
|
+
if (matched.size) {
|
|
2448
|
+
logActivity("index_prune", {
|
|
2449
|
+
reason: "minified_bundles",
|
|
2450
|
+
files_matched: matched.size,
|
|
2451
|
+
chunks_deleted: chunksDeleted,
|
|
2452
|
+
symbols_deleted: symbolsDeleted,
|
|
2453
|
+
});
|
|
2454
|
+
}
|
|
2455
|
+
result.pruned.minified_bundles = {
|
|
2456
|
+
files_matched: matched.size,
|
|
2457
|
+
chunks_deleted: chunksDeleted,
|
|
2458
|
+
symbols_deleted: symbolsDeleted,
|
|
2459
|
+
};
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
if (!args.dry_run && args.vacuum) {
|
|
2463
|
+
try {
|
|
2464
|
+
db.exec("VACUUM");
|
|
2465
|
+
logActivity("index_prune", { reason: "vacuum" });
|
|
2466
|
+
}
|
|
2467
|
+
catch (err) {
|
|
2468
|
+
console.error("[vectormind] vacuum failed:", err);
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
return {
|
|
2472
|
+
content: [
|
|
2473
|
+
{
|
|
2474
|
+
type: "text",
|
|
2475
|
+
text: toolJson(result),
|
|
2476
|
+
},
|
|
2477
|
+
],
|
|
2478
|
+
};
|
|
2479
|
+
}
|
|
2016
2480
|
if (toolName === "sync_change_intent") {
|
|
2017
2481
|
const args = SyncChangeIntentArgsSchema.parse(rawArgs);
|
|
2018
2482
|
flushPendingChangeBuffer();
|
|
@@ -2132,6 +2596,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2132
2596
|
const requirementsLimit = args.requirements_limit;
|
|
2133
2597
|
const changesLimit = args.changes_limit;
|
|
2134
2598
|
const notesLimit = args.notes_limit;
|
|
2599
|
+
const conventionsLimit = args.conventions_limit;
|
|
2135
2600
|
const recent = listRecentRequirementsStmt.all(requirementsLimit);
|
|
2136
2601
|
const items = recent.map((req) => {
|
|
2137
2602
|
const changes = listChangeLogsForRequirementStmt.all(req.id, changesLimit);
|
|
@@ -2145,6 +2610,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2145
2610
|
? toMemoryItemPreview(projectSummaryRow, includeContent, previewChars, contentMaxChars)
|
|
2146
2611
|
: null;
|
|
2147
2612
|
const recent_notes = listRecentNotesStmt.all(notesLimit).map((n) => toMemoryItemPreview(n, includeContent, previewChars, contentMaxChars));
|
|
2613
|
+
const conventions = listConventionsStmt.all(conventionsLimit).map((c) => toMemoryItemPreview(c, false, previewChars, contentMaxChars));
|
|
2148
2614
|
const pending_total = Number(countPendingChangesStmt.get()?.total ?? 0);
|
|
2149
2615
|
const pending_offset = args.pending_offset;
|
|
2150
2616
|
const pending_limit = args.pending_limit;
|
|
@@ -2172,6 +2638,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2172
2638
|
pending_total,
|
|
2173
2639
|
pending_returned: pending_changes.length,
|
|
2174
2640
|
requirements_returned: items.length,
|
|
2641
|
+
conventions_returned: conventions.length,
|
|
2175
2642
|
semantic_mode: semantic?.mode ?? null,
|
|
2176
2643
|
semantic_matches: semantic?.matches?.length ?? 0,
|
|
2177
2644
|
});
|
|
@@ -2199,8 +2666,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2199
2666
|
requirements_limit: requirementsLimit,
|
|
2200
2667
|
changes_limit: changesLimit,
|
|
2201
2668
|
notes_limit: notesLimit,
|
|
2669
|
+
conventions_limit: conventionsLimit,
|
|
2202
2670
|
},
|
|
2203
2671
|
project_summary,
|
|
2672
|
+
conventions,
|
|
2204
2673
|
recent_notes,
|
|
2205
2674
|
pending_total,
|
|
2206
2675
|
pending_offset,
|
|
@@ -2223,6 +2692,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2223
2692
|
const requirementsLimit = args.requirements_limit;
|
|
2224
2693
|
const changesLimit = args.changes_limit;
|
|
2225
2694
|
const notesLimit = args.notes_limit;
|
|
2695
|
+
const conventionsLimit = args.conventions_limit;
|
|
2226
2696
|
const recent = listRecentRequirementsStmt.all(requirementsLimit);
|
|
2227
2697
|
const items = recent.map((req) => {
|
|
2228
2698
|
const changes = listChangeLogsForRequirementStmt.all(req.id, changesLimit);
|
|
@@ -2236,6 +2706,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2236
2706
|
? toMemoryItemPreview(projectSummaryRow, includeContent, previewChars, contentMaxChars)
|
|
2237
2707
|
: null;
|
|
2238
2708
|
const recent_notes = listRecentNotesStmt.all(notesLimit).map((n) => toMemoryItemPreview(n, includeContent, previewChars, contentMaxChars));
|
|
2709
|
+
const conventions = listConventionsStmt.all(conventionsLimit).map((c) => toMemoryItemPreview(c, false, previewChars, contentMaxChars));
|
|
2239
2710
|
const pending_total = Number(countPendingChangesStmt.get()?.total ?? 0);
|
|
2240
2711
|
const pending_offset = args.pending_offset;
|
|
2241
2712
|
const pending_limit = args.pending_limit;
|
|
@@ -2246,6 +2717,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2246
2717
|
pending_returned: pending_changes.length,
|
|
2247
2718
|
requirements_returned: items.length,
|
|
2248
2719
|
notes_returned: recent_notes.length,
|
|
2720
|
+
conventions_returned: conventions.length,
|
|
2249
2721
|
});
|
|
2250
2722
|
return {
|
|
2251
2723
|
content: [
|
|
@@ -2271,8 +2743,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2271
2743
|
requirements_limit: requirementsLimit,
|
|
2272
2744
|
changes_limit: changesLimit,
|
|
2273
2745
|
notes_limit: notesLimit,
|
|
2746
|
+
conventions_limit: conventionsLimit,
|
|
2274
2747
|
},
|
|
2275
2748
|
project_summary,
|
|
2749
|
+
conventions,
|
|
2276
2750
|
recent_notes,
|
|
2277
2751
|
pending_total,
|
|
2278
2752
|
pending_offset,
|
|
@@ -2384,6 +2858,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2384
2858
|
const args = GetActivityLogArgsSchema.parse(rawArgs);
|
|
2385
2859
|
flushPendingChangeBuffer();
|
|
2386
2860
|
const { events, last_id } = snapshotActivityLog({ sinceId: args.since_id, limit: args.limit });
|
|
2861
|
+
const outEvents = args.verbose
|
|
2862
|
+
? events
|
|
2863
|
+
: events.map((e) => ({ id: e.id, ts: e.ts, type: e.type, summary: summarizeActivityEvent(e) }));
|
|
2387
2864
|
return {
|
|
2388
2865
|
content: [
|
|
2389
2866
|
{
|
|
@@ -2393,7 +2870,61 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2393
2870
|
enabled: debugLogEnabled,
|
|
2394
2871
|
max_entries: debugLogMaxEntries,
|
|
2395
2872
|
last_id,
|
|
2396
|
-
events,
|
|
2873
|
+
events: outEvents,
|
|
2874
|
+
}),
|
|
2875
|
+
},
|
|
2876
|
+
],
|
|
2877
|
+
};
|
|
2878
|
+
}
|
|
2879
|
+
if (toolName === "get_activity_summary") {
|
|
2880
|
+
const args = GetActivitySummaryArgsSchema.parse(rawArgs);
|
|
2881
|
+
flushPendingChangeBuffer();
|
|
2882
|
+
const { events, last_id } = snapshotActivityLog({ sinceId: args.since_id, limit: 500 });
|
|
2883
|
+
const counts = {};
|
|
2884
|
+
const indexedFiles = new Set();
|
|
2885
|
+
let semanticCount = 0;
|
|
2886
|
+
let queryCodebaseCount = 0;
|
|
2887
|
+
let pendingFlushes = 0;
|
|
2888
|
+
let pendingPrunes = 0;
|
|
2889
|
+
let lastSemantic = null;
|
|
2890
|
+
let lastQueryCodebase = null;
|
|
2891
|
+
for (const e of events) {
|
|
2892
|
+
counts[e.type] = (counts[e.type] ?? 0) + 1;
|
|
2893
|
+
if (e.type === "index_file") {
|
|
2894
|
+
const fp = String(e.data.file_path ?? "");
|
|
2895
|
+
if (fp)
|
|
2896
|
+
indexedFiles.add(fp);
|
|
2897
|
+
}
|
|
2898
|
+
if (e.type === "semantic_search") {
|
|
2899
|
+
semanticCount += 1;
|
|
2900
|
+
lastSemantic = e.data;
|
|
2901
|
+
}
|
|
2902
|
+
if (e.type === "query_codebase") {
|
|
2903
|
+
queryCodebaseCount += 1;
|
|
2904
|
+
lastQueryCodebase = e.data;
|
|
2905
|
+
}
|
|
2906
|
+
if (e.type === "pending_flush")
|
|
2907
|
+
pendingFlushes += 1;
|
|
2908
|
+
if (e.type === "pending_prune")
|
|
2909
|
+
pendingPrunes += 1;
|
|
2910
|
+
}
|
|
2911
|
+
const sampleFiles = Array.from(indexedFiles).slice(0, args.max_files);
|
|
2912
|
+
return {
|
|
2913
|
+
content: [
|
|
2914
|
+
{
|
|
2915
|
+
type: "text",
|
|
2916
|
+
text: toolJson({
|
|
2917
|
+
ok: true,
|
|
2918
|
+
enabled: debugLogEnabled,
|
|
2919
|
+
last_id,
|
|
2920
|
+
since_id: args.since_id,
|
|
2921
|
+
counts,
|
|
2922
|
+
indexed_files: { unique: indexedFiles.size, sample: sampleFiles },
|
|
2923
|
+
searches: {
|
|
2924
|
+
semantic_search: { count: semanticCount, last: lastSemantic },
|
|
2925
|
+
query_codebase: { count: queryCodebaseCount, last: lastQueryCodebase },
|
|
2926
|
+
},
|
|
2927
|
+
pending: { flushes: pendingFlushes, prunes: pendingPrunes },
|
|
2397
2928
|
}),
|
|
2398
2929
|
},
|
|
2399
2930
|
],
|
|
@@ -2461,6 +2992,42 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2461
2992
|
],
|
|
2462
2993
|
};
|
|
2463
2994
|
}
|
|
2995
|
+
if (toolName === "upsert_convention") {
|
|
2996
|
+
const args = UpsertConventionArgsSchema.parse(rawArgs);
|
|
2997
|
+
const key = args.key.trim();
|
|
2998
|
+
const content = args.content.trim();
|
|
2999
|
+
const contentHash = sha256Hex(content);
|
|
3000
|
+
const meta = safeJson({ tags: args.tags ?? [] });
|
|
3001
|
+
const existing = getConventionByKeyStmt.get(key);
|
|
3002
|
+
if (existing) {
|
|
3003
|
+
updateConventionByIdStmt.run(content, meta, contentHash, existing.id);
|
|
3004
|
+
}
|
|
3005
|
+
else {
|
|
3006
|
+
insertConventionStmt.run(key, content, meta, contentHash);
|
|
3007
|
+
}
|
|
3008
|
+
const row = getConventionByKeyStmt.get(key);
|
|
3009
|
+
if (row)
|
|
3010
|
+
enqueueEmbedding(row.id);
|
|
3011
|
+
logActivity("upsert_convention", { key, content_preview: makePreviewText(content, 200) });
|
|
3012
|
+
return {
|
|
3013
|
+
content: [
|
|
3014
|
+
{
|
|
3015
|
+
type: "text",
|
|
3016
|
+
text: toolJson({
|
|
3017
|
+
ok: true,
|
|
3018
|
+
convention: row
|
|
3019
|
+
? {
|
|
3020
|
+
id: row.id,
|
|
3021
|
+
key: row.title,
|
|
3022
|
+
updated_at: row.updated_at,
|
|
3023
|
+
preview: makePreviewText(row.content, DEFAULT_PREVIEW_CHARS),
|
|
3024
|
+
}
|
|
3025
|
+
: null,
|
|
3026
|
+
}),
|
|
3027
|
+
},
|
|
3028
|
+
],
|
|
3029
|
+
};
|
|
3030
|
+
}
|
|
2464
3031
|
if (toolName === "semantic_search") {
|
|
2465
3032
|
const args = SemanticSearchArgsSchema.parse(rawArgs);
|
|
2466
3033
|
const result = await semanticSearchHybridInternal({
|