@coreyuan/vector-mind 1.0.38 → 1.0.41
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 +85 -10
- package/dist/builtin-conventions.js +5 -0
- package/dist/builtin-conventions.js.map +1 -1
- package/dist/builtin-instructions.d.ts +1 -0
- package/dist/builtin-instructions.js +1 -0
- package/dist/builtin-instructions.js.map +1 -1
- package/dist/index.js +1242 -200
- package/dist/index.js.map +1 -1
- package/dist/rtk-shim.d.ts +2 -0
- package/dist/rtk-shim.js +255 -0
- package/dist/rtk-shim.js.map +1 -0
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -14,9 +14,9 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
14
14
|
import { toJsonSchemaCompat } from "@modelcontextprotocol/sdk/server/zod-json-schema-compat.js";
|
|
15
15
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
16
16
|
import { BUILTIN_CONVENTIONS } from "./builtin-conventions.js";
|
|
17
|
-
import { BUILTIN_ARCHITECTURE_AND_CODE_ORGANIZATION_INSTRUCTIONS, BUILTIN_DESTRUCTIVE_OPERATION_GUARD_INSTRUCTIONS, BUILTIN_FRONTEND_OUTPUT_PURITY_INSTRUCTIONS, BUILTIN_LOW_OVERHEAD_WORKFLOW_INSTRUCTIONS, BUILTIN_PAYLOAD_GUARD_INSTRUCTIONS, BUILTIN_PLAN_LITE_INSTRUCTIONS, BUILTIN_THREAD_HANDOFF_SWITCH_INSTRUCTIONS, BUILTIN_WRITE_POLICY_INSTRUCTIONS, } from "./builtin-instructions.js";
|
|
17
|
+
import { BUILTIN_ARCHITECTURE_AND_CODE_ORGANIZATION_INSTRUCTIONS, BUILTIN_DESTRUCTIVE_OPERATION_GUARD_INSTRUCTIONS, BUILTIN_FRONTEND_OUTPUT_PURITY_INSTRUCTIONS, BUILTIN_GIT_COMMIT_SUMMARY_INSTRUCTIONS, BUILTIN_LOW_OVERHEAD_WORKFLOW_INSTRUCTIONS, BUILTIN_PAYLOAD_GUARD_INSTRUCTIONS, BUILTIN_PLAN_LITE_INSTRUCTIONS, BUILTIN_THREAD_HANDOFF_SWITCH_INSTRUCTIONS, BUILTIN_WRITE_POLICY_INSTRUCTIONS, } from "./builtin-instructions.js";
|
|
18
18
|
const SERVER_NAME = "vector-mind";
|
|
19
|
-
const SERVER_VERSION = "1.0.
|
|
19
|
+
const SERVER_VERSION = "1.0.41";
|
|
20
20
|
const rootFromEnv = process.env.VECTORMIND_ROOT?.trim() ?? "";
|
|
21
21
|
const prettyJsonOutput = ["1", "true", "on", "yes"].includes((process.env.VECTORMIND_PRETTY_JSON ?? "").trim().toLowerCase());
|
|
22
22
|
const debugLogEnabled = ["1", "true", "on", "yes"].includes((process.env.VECTORMIND_DEBUG_LOG ?? "").trim().toLowerCase());
|
|
@@ -164,9 +164,13 @@ let getConventionByKeyStmt;
|
|
|
164
164
|
let insertConventionStmt;
|
|
165
165
|
let updateConventionByIdStmt;
|
|
166
166
|
let listConventionsStmt;
|
|
167
|
+
let upsertDecisionStmt;
|
|
168
|
+
let getDecisionByKeyStmt;
|
|
169
|
+
let listCurrentDecisionsStmt;
|
|
167
170
|
let upsertProjectSummaryStmt;
|
|
168
171
|
let getProjectSummaryStmt;
|
|
169
172
|
let listRecentNotesStmt;
|
|
173
|
+
let getLatestChangeIntentForFileStmt;
|
|
170
174
|
let deleteFileChunkItemsStmt;
|
|
171
175
|
let getEmbeddingMetaStmt;
|
|
172
176
|
let upsertEmbeddingStmt;
|
|
@@ -181,6 +185,10 @@ let deleteOldestPendingChangesStmt = null;
|
|
|
181
185
|
let deleteSymbolsForFileStmt;
|
|
182
186
|
let upsertSymbolStmt;
|
|
183
187
|
let searchSymbolsStmt;
|
|
188
|
+
let insertTokenSavingsStmt;
|
|
189
|
+
let summarizeTokenSavingsStmt;
|
|
190
|
+
let summarizeTokenSavingsByToolStmt;
|
|
191
|
+
let listRecentTokenSavingsStmt;
|
|
184
192
|
let indexFileSymbolsTx = null;
|
|
185
193
|
let activitySeq = 0;
|
|
186
194
|
const activityLog = [];
|
|
@@ -552,6 +560,89 @@ function shouldIgnoreDbFilePath(filePath) {
|
|
|
552
560
|
return false;
|
|
553
561
|
return pathHasIgnoredSegments(filePath);
|
|
554
562
|
}
|
|
563
|
+
function isProbablyGitRepository() {
|
|
564
|
+
try {
|
|
565
|
+
return fs.existsSync(path.join(projectRoot, ".git"));
|
|
566
|
+
}
|
|
567
|
+
catch {
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
function normalizeGitStatusPath(raw) {
|
|
572
|
+
const first = raw.split("\0")[0] ?? "";
|
|
573
|
+
return first.trim().replace(/\\/g, "/").replace(/^"(.*)"$/, "$1");
|
|
574
|
+
}
|
|
575
|
+
function collectGitPendingChanges(limit) {
|
|
576
|
+
if (limit <= 0 || !isProbablyGitRepository())
|
|
577
|
+
return [];
|
|
578
|
+
const git = spawnSync("git", ["status", "--porcelain=v1", "-z", "--untracked-files=normal"], {
|
|
579
|
+
cwd: projectRoot,
|
|
580
|
+
encoding: "utf8",
|
|
581
|
+
timeout: 5000,
|
|
582
|
+
windowsHide: true,
|
|
583
|
+
maxBuffer: 2_000_000,
|
|
584
|
+
});
|
|
585
|
+
if (git.error || git.status !== 0 || !git.stdout)
|
|
586
|
+
return [];
|
|
587
|
+
const parts = git.stdout.split("\0").filter(Boolean);
|
|
588
|
+
const rows = [];
|
|
589
|
+
for (let i = 0; i < parts.length && rows.length < limit; i++) {
|
|
590
|
+
const rec = parts[i] ?? "";
|
|
591
|
+
const status = rec.slice(0, 2);
|
|
592
|
+
let rawPath = rec.slice(3);
|
|
593
|
+
if (status.startsWith("R") || status.startsWith("C")) {
|
|
594
|
+
// Porcelain -z rename/copy records include the destination in the next NUL field.
|
|
595
|
+
rawPath = parts[i + 1] ?? rawPath;
|
|
596
|
+
i += 1;
|
|
597
|
+
}
|
|
598
|
+
const filePath = normalizeGitStatusPath(rawPath);
|
|
599
|
+
if (!filePath || filePath === ".vectormind" || filePath.startsWith(".vectormind/"))
|
|
600
|
+
continue;
|
|
601
|
+
rows.push({
|
|
602
|
+
file_path: filePath,
|
|
603
|
+
last_event: status.includes("D") ? "unlink" : status === "??" ? "add" : "change",
|
|
604
|
+
updated_at: new Date().toISOString(),
|
|
605
|
+
source: "git",
|
|
606
|
+
git_status: status.trim() || "modified",
|
|
607
|
+
file_state_hash: getFileStateHash(filePath) ?? undefined,
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
return rows;
|
|
611
|
+
}
|
|
612
|
+
function mergePendingWithGit(pending, opts) {
|
|
613
|
+
const byPath = new Map();
|
|
614
|
+
for (const p of pending) {
|
|
615
|
+
if (shouldIgnoreDbFilePath(p.file_path))
|
|
616
|
+
continue;
|
|
617
|
+
byPath.set(p.file_path, { ...p, source: p.source ?? "watcher" });
|
|
618
|
+
}
|
|
619
|
+
const gitRows = collectGitPendingChanges(Math.max(500, opts.offset + opts.limit * 4));
|
|
620
|
+
for (const g of gitRows) {
|
|
621
|
+
const latestSyncedHash = getLatestSyncedFileHash(g.file_path);
|
|
622
|
+
if (latestSyncedHash && g.file_state_hash && latestSyncedHash === g.file_state_hash)
|
|
623
|
+
continue;
|
|
624
|
+
const existing = byPath.get(g.file_path);
|
|
625
|
+
if (!existing) {
|
|
626
|
+
byPath.set(g.file_path, g);
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
byPath.set(g.file_path, {
|
|
630
|
+
...existing,
|
|
631
|
+
source: existing.source === "watcher" ? "watcher" : g.source,
|
|
632
|
+
git_status: g.git_status,
|
|
633
|
+
file_state_hash: g.file_state_hash,
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
const all = Array.from(byPath.values()).sort((a, b) => {
|
|
637
|
+
const at = Date.parse(a.updated_at) || 0;
|
|
638
|
+
const bt = Date.parse(b.updated_at) || 0;
|
|
639
|
+
if (bt !== at)
|
|
640
|
+
return bt - at;
|
|
641
|
+
return a.file_path.localeCompare(b.file_path);
|
|
642
|
+
});
|
|
643
|
+
const page = all.slice(opts.offset, opts.offset + opts.limit);
|
|
644
|
+
return { total: all.length, page, truncated: all.length > opts.offset + opts.limit };
|
|
645
|
+
}
|
|
555
646
|
function pruneIgnoredPendingChanges() {
|
|
556
647
|
if (!db)
|
|
557
648
|
return;
|
|
@@ -1149,6 +1240,9 @@ function removeFileIndexes(absPath) {
|
|
|
1149
1240
|
const ProjectRootArgSchema = z.object({
|
|
1150
1241
|
project_root: z.string().optional(),
|
|
1151
1242
|
});
|
|
1243
|
+
const OutputFormatSchema = z.object({
|
|
1244
|
+
format: z.enum(["compact", "json"]).optional().default("compact"),
|
|
1245
|
+
});
|
|
1152
1246
|
const StartRequirementArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
1153
1247
|
title: z.string().min(1),
|
|
1154
1248
|
background: z.string().optional().default(""),
|
|
@@ -1159,10 +1253,10 @@ const SyncChangeIntentArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
|
1159
1253
|
files: z.array(z.string().min(1)).optional(),
|
|
1160
1254
|
affected_files: z.array(z.string().min(1)).optional(),
|
|
1161
1255
|
}));
|
|
1162
|
-
const QueryCodebaseArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
1256
|
+
const QueryCodebaseArgsSchema = ProjectRootArgSchema.merge(OutputFormatSchema).merge(z.object({
|
|
1163
1257
|
query: z.string().min(1),
|
|
1164
1258
|
}));
|
|
1165
|
-
const GrepArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
1259
|
+
const GrepArgsSchema = ProjectRootArgSchema.merge(OutputFormatSchema).merge(z.object({
|
|
1166
1260
|
// Pattern to search for. Defaults to regex mode for parity with tools like ripgrep.
|
|
1167
1261
|
query: z.string().min(1),
|
|
1168
1262
|
mode: z.enum(["regex", "literal"]).optional().default("regex"),
|
|
@@ -1179,7 +1273,7 @@ const GrepArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
|
1179
1273
|
// Compatibility knob for the indexed fallback when ripgrep is unavailable.
|
|
1180
1274
|
max_candidates: z.number().int().min(1).max(50_000).optional(),
|
|
1181
1275
|
}));
|
|
1182
|
-
const ReadFileLinesArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
1276
|
+
const ReadFileLinesArgsSchema = ProjectRootArgSchema.merge(OutputFormatSchema).merge(z.object({
|
|
1183
1277
|
// Relative to project_root, or an absolute path under project_root.
|
|
1184
1278
|
path: z.string().min(1),
|
|
1185
1279
|
from_line: z.number().int().min(1).optional().default(1),
|
|
@@ -1190,7 +1284,7 @@ const ReadFileLinesArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
|
1190
1284
|
max_lines: z.number().int().min(1).max(2000).optional().default(400),
|
|
1191
1285
|
max_chars: z.number().int().min(200).max(200_000).optional().default(20_000),
|
|
1192
1286
|
}));
|
|
1193
|
-
const ReadFileTextArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
1287
|
+
const ReadFileTextArgsSchema = ProjectRootArgSchema.merge(OutputFormatSchema).merge(z.object({
|
|
1194
1288
|
// Relative to project_root, or an absolute path under project_root.
|
|
1195
1289
|
path: z.string().min(1),
|
|
1196
1290
|
// Character offset in the decoded UTF-8 text.
|
|
@@ -1200,14 +1294,14 @@ const ReadFileTextArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
|
1200
1294
|
// Safety guard for raw reads; use read_file_lines on larger files.
|
|
1201
1295
|
max_file_bytes: z.number().int().min(1_000).max(5_000_000).optional().default(1_000_000),
|
|
1202
1296
|
}));
|
|
1203
|
-
const ReadCodexTextFileArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
1297
|
+
const ReadCodexTextFileArgsSchema = ProjectRootArgSchema.merge(OutputFormatSchema).merge(z.object({
|
|
1204
1298
|
// Absolute path, file:// URI, or a path under CODEX_HOME / AGENTS_HOME allowed roots.
|
|
1205
1299
|
path: z.string().min(1),
|
|
1206
1300
|
offset: z.number().int().min(0).optional().default(0),
|
|
1207
1301
|
max_chars: z.number().int().min(1).max(200_000).optional().default(20_000),
|
|
1208
1302
|
max_file_bytes: z.number().int().min(1_000).max(5_000_000).optional().default(1_000_000),
|
|
1209
1303
|
}));
|
|
1210
|
-
const ListProjectFilesArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
1304
|
+
const ListProjectFilesArgsSchema = ProjectRootArgSchema.merge(OutputFormatSchema).merge(z.object({
|
|
1211
1305
|
// Relative directory/file path under project_root. "." means the project root.
|
|
1212
1306
|
path: z.string().optional().default("."),
|
|
1213
1307
|
recursive: z.boolean().optional().default(false),
|
|
@@ -1242,29 +1336,48 @@ const UpsertConventionArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
|
1242
1336
|
content: z.string().min(1),
|
|
1243
1337
|
tags: z.array(z.string().min(1)).optional(),
|
|
1244
1338
|
}));
|
|
1245
|
-
const
|
|
1339
|
+
const UpsertDecisionArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
1340
|
+
key: z.string().min(1),
|
|
1341
|
+
title: z.string().optional().default(""),
|
|
1342
|
+
content: z.string().min(1),
|
|
1343
|
+
tags: z.array(z.string().min(1)).optional(),
|
|
1344
|
+
supersedes_req_ids: z.array(z.number().int().positive()).optional(),
|
|
1345
|
+
supersedes_memory_ids: z.array(z.number().int().positive()).optional(),
|
|
1346
|
+
related_files: z.array(z.string().min(1)).optional(),
|
|
1347
|
+
}));
|
|
1348
|
+
const SupersedeMemoryArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
1349
|
+
superseded_req_ids: z.array(z.number().int().positive()).optional(),
|
|
1350
|
+
superseded_memory_ids: z.array(z.number().int().positive()).optional(),
|
|
1351
|
+
replacement_req_id: z.number().int().positive().optional(),
|
|
1352
|
+
replacement_memory_id: z.number().int().positive().optional(),
|
|
1353
|
+
reason: z.string().min(1),
|
|
1354
|
+
}));
|
|
1355
|
+
const DEFAULT_PENDING_LIMIT = 10;
|
|
1246
1356
|
const MAX_PENDING_LIMIT = 2000;
|
|
1247
1357
|
const PendingPagingSchema = z.object({
|
|
1248
1358
|
pending_offset: z.number().int().min(0).optional().default(0),
|
|
1249
1359
|
pending_limit: z.number().int().min(1).max(MAX_PENDING_LIMIT).optional().default(DEFAULT_PENDING_LIMIT),
|
|
1250
1360
|
});
|
|
1251
|
-
const DEFAULT_PREVIEW_CHARS =
|
|
1361
|
+
const DEFAULT_PREVIEW_CHARS = 120;
|
|
1252
1362
|
const PreviewSchema = z.object({
|
|
1253
1363
|
preview_chars: z.number().int().min(50).max(10_000).optional().default(DEFAULT_PREVIEW_CHARS),
|
|
1254
1364
|
});
|
|
1255
|
-
const DEFAULT_CONTENT_MAX_CHARS =
|
|
1365
|
+
const DEFAULT_CONTENT_MAX_CHARS = 1200;
|
|
1256
1366
|
const ContentMaxSchema = z.object({
|
|
1257
1367
|
content_max_chars: z.number().int().min(0).max(200_000).optional().default(DEFAULT_CONTENT_MAX_CHARS),
|
|
1258
1368
|
});
|
|
1259
|
-
const DEFAULT_RECENT_REQUIREMENTS =
|
|
1260
|
-
const DEFAULT_RECENT_CHANGES_PER_REQ =
|
|
1261
|
-
const DEFAULT_RECENT_NOTES =
|
|
1262
|
-
const DEFAULT_CONVENTIONS_LIMIT =
|
|
1369
|
+
const DEFAULT_RECENT_REQUIREMENTS = 2;
|
|
1370
|
+
const DEFAULT_RECENT_CHANGES_PER_REQ = 3;
|
|
1371
|
+
const DEFAULT_RECENT_NOTES = 3;
|
|
1372
|
+
const DEFAULT_CONVENTIONS_LIMIT = 0;
|
|
1373
|
+
const DEFAULT_DECISIONS_LIMIT = 5;
|
|
1374
|
+
const MAX_DECISIONS_LIMIT = 50;
|
|
1263
1375
|
const BrainDumpLimitsSchema = z.object({
|
|
1264
1376
|
requirements_limit: z.number().int().min(1).max(20).optional().default(DEFAULT_RECENT_REQUIREMENTS),
|
|
1265
1377
|
changes_limit: z.number().int().min(1).max(100).optional().default(DEFAULT_RECENT_CHANGES_PER_REQ),
|
|
1266
1378
|
notes_limit: z.number().int().min(0).max(50).optional().default(DEFAULT_RECENT_NOTES),
|
|
1267
1379
|
conventions_limit: z.number().int().min(0).max(200).optional().default(DEFAULT_CONVENTIONS_LIMIT),
|
|
1380
|
+
decisions_limit: z.number().int().min(0).max(MAX_DECISIONS_LIMIT).optional().default(DEFAULT_DECISIONS_LIMIT),
|
|
1268
1381
|
});
|
|
1269
1382
|
const GetPendingChangesArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
1270
1383
|
offset: z.number().int().min(0).optional().default(0),
|
|
@@ -1285,6 +1398,7 @@ const GetActivitySummaryArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
|
1285
1398
|
}));
|
|
1286
1399
|
const ClearActivityLogArgsSchema = ProjectRootArgSchema;
|
|
1287
1400
|
const GetBrainDumpArgsSchema = ProjectRootArgSchema.merge(PendingPagingSchema)
|
|
1401
|
+
.merge(OutputFormatSchema)
|
|
1288
1402
|
.merge(PreviewSchema)
|
|
1289
1403
|
.merge(ContentMaxSchema)
|
|
1290
1404
|
.merge(BrainDumpLimitsSchema)
|
|
@@ -1293,16 +1407,17 @@ const GetBrainDumpArgsSchema = ProjectRootArgSchema.merge(PendingPagingSchema)
|
|
|
1293
1407
|
}));
|
|
1294
1408
|
const BootstrapContextArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
1295
1409
|
query: z.string().optional(),
|
|
1296
|
-
top_k: z.number().int().min(1).max(50).optional().default(
|
|
1410
|
+
top_k: z.number().int().min(1).max(50).optional().default(3),
|
|
1297
1411
|
kinds: z.array(z.string().min(1)).optional(),
|
|
1298
1412
|
include_content: z.boolean().optional().default(false),
|
|
1299
1413
|
pending_offset: z.number().int().min(0).optional().default(0),
|
|
1300
1414
|
pending_limit: z.number().int().min(1).max(MAX_PENDING_LIMIT).optional().default(DEFAULT_PENDING_LIMIT),
|
|
1301
1415
|
})
|
|
1416
|
+
.merge(OutputFormatSchema)
|
|
1302
1417
|
.merge(PreviewSchema)
|
|
1303
1418
|
.merge(ContentMaxSchema)
|
|
1304
1419
|
.merge(BrainDumpLimitsSchema));
|
|
1305
|
-
const SemanticSearchArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
1420
|
+
const SemanticSearchArgsSchema = ProjectRootArgSchema.merge(OutputFormatSchema).merge(z.object({
|
|
1306
1421
|
query: z.string().min(1),
|
|
1307
1422
|
top_k: z.number().int().min(1).max(50).optional().default(8),
|
|
1308
1423
|
kinds: z.array(z.string().min(1)).optional(),
|
|
@@ -1311,6 +1426,21 @@ const SemanticSearchArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
|
1311
1426
|
content_max_chars: z.number().int().min(0).max(200_000).optional().default(DEFAULT_CONTENT_MAX_CHARS),
|
|
1312
1427
|
}));
|
|
1313
1428
|
const ProjectRootOnlyArgsSchema = ProjectRootArgSchema;
|
|
1429
|
+
const GetTokenSavingsArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
1430
|
+
limit: z.number().int().min(1).max(100).optional().default(10),
|
|
1431
|
+
format: z.enum(["compact", "json"]).optional().default("compact"),
|
|
1432
|
+
}));
|
|
1433
|
+
const DetectRtkArgsSchema = ProjectRootArgSchema;
|
|
1434
|
+
const InstallRtkArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
1435
|
+
dry_run: z.boolean().optional().default(true),
|
|
1436
|
+
method: z.enum(["auto", "cargo", "brew", "shell_script"]).optional().default("auto"),
|
|
1437
|
+
init: z
|
|
1438
|
+
.enum(["none", "global_no_patch", "global_auto_patch", "global_hook_only", "local", "codex_global", "codex_local"])
|
|
1439
|
+
.optional()
|
|
1440
|
+
.default("none"),
|
|
1441
|
+
uninstall_wrong_cargo_rtk: z.boolean().optional().default(false),
|
|
1442
|
+
timeout_ms: z.number().int().min(10_000).max(1_800_000).optional().default(600_000),
|
|
1443
|
+
}));
|
|
1314
1444
|
const ReadMemoryItemArgsSchema = ProjectRootArgSchema.merge(z.object({
|
|
1315
1445
|
id: z.number().int().positive(),
|
|
1316
1446
|
offset: z.number().int().min(0).optional().default(0),
|
|
@@ -1322,6 +1452,28 @@ function escapeLike(pattern) {
|
|
|
1322
1452
|
function sha256Hex(input) {
|
|
1323
1453
|
return crypto.createHash("sha256").update(input).digest("hex");
|
|
1324
1454
|
}
|
|
1455
|
+
function getFileStateHash(dbOrAbsPath) {
|
|
1456
|
+
try {
|
|
1457
|
+
const abs = path.isAbsolute(dbOrAbsPath) ? dbOrAbsPath : path.join(projectRoot, dbOrAbsPath);
|
|
1458
|
+
const st = fs.statSync(abs);
|
|
1459
|
+
if (!st.isFile())
|
|
1460
|
+
return sha256Hex(`non-file:${st.mtimeMs}:${st.size}`);
|
|
1461
|
+
if (st.size <= 5_000_000) {
|
|
1462
|
+
return crypto.createHash("sha256").update(fs.readFileSync(abs)).digest("hex");
|
|
1463
|
+
}
|
|
1464
|
+
return sha256Hex(`large:${st.size}:${Math.floor(st.mtimeMs)}`);
|
|
1465
|
+
}
|
|
1466
|
+
catch {
|
|
1467
|
+
return sha256Hex("missing");
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
function getLatestSyncedFileHash(dbFilePath) {
|
|
1471
|
+
const row = getLatestChangeIntentForFileStmt?.get(dbFilePath);
|
|
1472
|
+
if (!row)
|
|
1473
|
+
return null;
|
|
1474
|
+
const meta = parseMetadataJson(row.metadata_json);
|
|
1475
|
+
return typeof meta.file_state_hash === "string" ? meta.file_state_hash : null;
|
|
1476
|
+
}
|
|
1325
1477
|
function safeJson(value) {
|
|
1326
1478
|
if (value === undefined)
|
|
1327
1479
|
return null;
|
|
@@ -1335,6 +1487,511 @@ function safeJson(value) {
|
|
|
1335
1487
|
function toolJson(value) {
|
|
1336
1488
|
return JSON.stringify(value, null, prettyJsonOutput ? 2 : undefined);
|
|
1337
1489
|
}
|
|
1490
|
+
function estimateTokens(text) {
|
|
1491
|
+
if (!text)
|
|
1492
|
+
return 0;
|
|
1493
|
+
return Math.ceil(text.length / 4);
|
|
1494
|
+
}
|
|
1495
|
+
function recordTokenSavings(tool, rawText, outputText) {
|
|
1496
|
+
if (!db || !insertTokenSavingsStmt)
|
|
1497
|
+
return;
|
|
1498
|
+
const rawTokens = estimateTokens(rawText);
|
|
1499
|
+
const outputTokens = estimateTokens(outputText);
|
|
1500
|
+
const savedTokens = Math.max(0, rawTokens - outputTokens);
|
|
1501
|
+
const savingsPct = rawTokens > 0 ? (savedTokens / rawTokens) * 100 : 0;
|
|
1502
|
+
try {
|
|
1503
|
+
insertTokenSavingsStmt.run(tool, rawTokens, outputTokens, savedTokens, savingsPct);
|
|
1504
|
+
}
|
|
1505
|
+
catch (err) {
|
|
1506
|
+
console.error("[vectormind] token savings record failed:", err);
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
function toolText(tool, rawValue, compactText, format = "compact") {
|
|
1510
|
+
const rawText = toolJson(rawValue);
|
|
1511
|
+
if (format === "json")
|
|
1512
|
+
return rawText;
|
|
1513
|
+
recordTokenSavings(tool, rawText, compactText);
|
|
1514
|
+
return compactText;
|
|
1515
|
+
}
|
|
1516
|
+
function toolCompactOrJson(tool, rawValue, compactText, format) {
|
|
1517
|
+
return toolText(tool, rawValue, compactText, format);
|
|
1518
|
+
}
|
|
1519
|
+
function oneLine(input, max = 120) {
|
|
1520
|
+
const text = (input ?? "").replace(/\s+/g, " ").trim();
|
|
1521
|
+
if (text.length <= max)
|
|
1522
|
+
return text;
|
|
1523
|
+
return `${text.slice(0, Math.max(0, max - 3))}...`;
|
|
1524
|
+
}
|
|
1525
|
+
function compactMemoryLabel(item, max = 120) {
|
|
1526
|
+
const title = item.title ? ` ${oneLine(item.title, 48)}` : "";
|
|
1527
|
+
const loc = item.file_path ? ` ${item.file_path}${item.start_line != null ? `:${item.start_line}` : ""}` : "";
|
|
1528
|
+
const body = item.preview ? ` — ${oneLine(item.preview, max)}` : "";
|
|
1529
|
+
return `#${item.id} ${item.kind}${title}${loc}${body}`;
|
|
1530
|
+
}
|
|
1531
|
+
function compactRequirementLabel(req) {
|
|
1532
|
+
const ctx = req.context_preview ? ` — ${oneLine(req.context_preview, 100)}` : "";
|
|
1533
|
+
const mem = req.memory_item_id ? ` mem#${req.memory_item_id}` : "";
|
|
1534
|
+
return `req#${req.id}${mem} [${req.status}] ${oneLine(req.title, 80)}${ctx}`;
|
|
1535
|
+
}
|
|
1536
|
+
function compactChangeLabel(change) {
|
|
1537
|
+
return `change#${change.id} ${change.file_path}: ${oneLine(change.intent_preview, 120)}`;
|
|
1538
|
+
}
|
|
1539
|
+
function compactPendingLabel(p) {
|
|
1540
|
+
const source = "source" in p && p.source === "git" ? " git" : "";
|
|
1541
|
+
const status = "git_status" in p && p.git_status ? ` ${p.git_status}` : "";
|
|
1542
|
+
return `${p.last_event}${source}${status} ${p.file_path}`;
|
|
1543
|
+
}
|
|
1544
|
+
function compactSemanticSearchText(data) {
|
|
1545
|
+
const lines = [
|
|
1546
|
+
`semantic ${data.mode} ${data.matches.length}/${data.top_k} q="${oneLine(data.query, 100)}"`,
|
|
1547
|
+
];
|
|
1548
|
+
for (const m of data.matches.slice(0, data.top_k)) {
|
|
1549
|
+
lines.push(`- score=${m.score.toFixed(3)} ${compactMemoryLabel(m.item, 160)}`);
|
|
1550
|
+
}
|
|
1551
|
+
if (!data.matches.length)
|
|
1552
|
+
lines.push("- no matches");
|
|
1553
|
+
lines.push("hint: use format=json for full metadata; read_memory_item(id) for full content");
|
|
1554
|
+
return lines.join("\n");
|
|
1555
|
+
}
|
|
1556
|
+
function compactGrepText(data) {
|
|
1557
|
+
const total = data.total_matches ?? data.matches.length;
|
|
1558
|
+
const fallback = data.fallback_reason ? ` fallback=${data.fallback_reason}` : "";
|
|
1559
|
+
const candidateText = data.candidates ? ` candidates=${data.candidates.scanned}/${data.candidates.total}` : "";
|
|
1560
|
+
const lines = [
|
|
1561
|
+
`grep ${data.backend}${fallback} mode=${data.mode} matches=${data.matches.length}/${total} truncated=${data.truncated}${candidateText} q="${oneLine(data.query, 100)}"`,
|
|
1562
|
+
];
|
|
1563
|
+
if (data.ripgrep_error)
|
|
1564
|
+
lines.push(`ripgrep_error ${oneLine(data.ripgrep_error, 180)}`);
|
|
1565
|
+
for (const m of data.matches.slice(0, 80)) {
|
|
1566
|
+
lines.push(`${m.file_path}:${m.line}:${m.col}: ${oneLine(m.preview, 220)}`);
|
|
1567
|
+
}
|
|
1568
|
+
if (!data.matches.length)
|
|
1569
|
+
lines.push("- no matches");
|
|
1570
|
+
if (data.truncated)
|
|
1571
|
+
lines.push("hint: refine query/include_paths or raise max_results; use format=json for full match objects");
|
|
1572
|
+
return lines.join("\n");
|
|
1573
|
+
}
|
|
1574
|
+
function compactListProjectFilesText(data) {
|
|
1575
|
+
const lines = [
|
|
1576
|
+
`files path=${data.path} kind=${data.path_kind} returned=${data.returned} scanned=${data.scanned} recursive=${data.recursive} depth=${data.max_depth} truncated=${data.truncated}`,
|
|
1577
|
+
];
|
|
1578
|
+
for (const e of data.entries.slice(0, 200)) {
|
|
1579
|
+
const stat = e.size != null ? ` ${e.size}B` : "";
|
|
1580
|
+
lines.push(`${e.kind === "dir" ? "d" : "f"} ${e.path}${stat}`);
|
|
1581
|
+
}
|
|
1582
|
+
if (!data.entries.length)
|
|
1583
|
+
lines.push("- empty");
|
|
1584
|
+
if (data.truncated)
|
|
1585
|
+
lines.push("hint: narrow path/filters or raise max_results; use format=json for full entry metadata");
|
|
1586
|
+
return lines.join("\n");
|
|
1587
|
+
}
|
|
1588
|
+
function compactReadTextFileText(data) {
|
|
1589
|
+
const offset = data.offset != null ? ` offset=${data.offset}` : "";
|
|
1590
|
+
const header = `file ${data.file_path}${offset} chars=${data.returned_chars}/${data.total_chars} truncated=${data.truncated}`;
|
|
1591
|
+
const hint = data.truncated ? "\nhint: continue with offset or read_file_lines; use format=json for metadata fields" : "";
|
|
1592
|
+
return `${header}\n${data.text}${hint}`;
|
|
1593
|
+
}
|
|
1594
|
+
function compactReadFileLinesText(data) {
|
|
1595
|
+
const header = `lines ${data.file_path}:${data.from_line}-${data.to_line} returned=${data.returned} truncated=${data.truncated}`;
|
|
1596
|
+
const hint = data.truncated ? "\nhint: narrow range or raise max_lines/max_chars; use format=json for metadata fields" : "";
|
|
1597
|
+
return `${header}\n${data.text}${hint}`;
|
|
1598
|
+
}
|
|
1599
|
+
function compactQueryCodebaseText(data) {
|
|
1600
|
+
const lines = [`query_codebase matches=${data.matches.length} q="${oneLine(data.query, 100)}"`];
|
|
1601
|
+
for (const m of data.matches.slice(0, 50)) {
|
|
1602
|
+
lines.push(`${m.file_path}: ${m.type} ${m.name}${m.signature ? ` — ${oneLine(m.signature, 160)}` : ""}`);
|
|
1603
|
+
}
|
|
1604
|
+
if (!data.matches.length)
|
|
1605
|
+
lines.push("- no matches");
|
|
1606
|
+
return lines.join("\n");
|
|
1607
|
+
}
|
|
1608
|
+
function compactBootstrapText(data) {
|
|
1609
|
+
const lines = [];
|
|
1610
|
+
lines.push(`ok ctx ${data.root_source} watcher=${data.watcher_enabled ? (data.watcher_ready ? "ready" : "starting") : "off"} root=${data.project_root}`);
|
|
1611
|
+
if (data.project_summary)
|
|
1612
|
+
lines.push(`summary ${compactMemoryLabel(data.project_summary, 140)}`);
|
|
1613
|
+
if (data.decisions.length) {
|
|
1614
|
+
lines.push("current decisions:");
|
|
1615
|
+
for (const d of data.decisions.slice(0, 5))
|
|
1616
|
+
lines.push(`- ${compactMemoryLabel(d, 160)}`);
|
|
1617
|
+
}
|
|
1618
|
+
if (data.pending_total) {
|
|
1619
|
+
lines.push(`pending ${data.pending_changes.length}/${data.pending_total}${data.pending_truncated ? " truncated" : ""}: ${data.pending_changes
|
|
1620
|
+
.slice(0, 8)
|
|
1621
|
+
.map(compactPendingLabel)
|
|
1622
|
+
.join("; ")}`);
|
|
1623
|
+
}
|
|
1624
|
+
else {
|
|
1625
|
+
lines.push("pending 0");
|
|
1626
|
+
}
|
|
1627
|
+
if (data.items.length) {
|
|
1628
|
+
lines.push("requirements:");
|
|
1629
|
+
for (const item of data.items) {
|
|
1630
|
+
lines.push(`- ${compactRequirementLabel(item.requirement)}`);
|
|
1631
|
+
for (const c of item.recent_changes.slice(0, 3))
|
|
1632
|
+
lines.push(` - ${compactChangeLabel(c)}`);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
else {
|
|
1636
|
+
lines.push("requirements: none");
|
|
1637
|
+
}
|
|
1638
|
+
if (data.recent_notes.length) {
|
|
1639
|
+
lines.push("notes:");
|
|
1640
|
+
for (const n of data.recent_notes.slice(0, 3))
|
|
1641
|
+
lines.push(`- ${compactMemoryLabel(n, 120)}`);
|
|
1642
|
+
}
|
|
1643
|
+
if (data.conventions.length) {
|
|
1644
|
+
lines.push(`conventions ${data.conventions.length}: ${data.conventions
|
|
1645
|
+
.slice(0, 5)
|
|
1646
|
+
.map((c) => c.title ?? `#${c.id}`)
|
|
1647
|
+
.join(", ")}`);
|
|
1648
|
+
}
|
|
1649
|
+
if (data.semantic) {
|
|
1650
|
+
lines.push(`semantic ${data.semantic.mode} ${data.semantic.matches.length}/${data.semantic.top_k} for "${oneLine(data.semantic.query, 80)}":`);
|
|
1651
|
+
for (const m of data.semantic.matches.slice(0, 5)) {
|
|
1652
|
+
lines.push(`- score=${m.score.toFixed(3)} ${compactMemoryLabel(m.item, 120)}`);
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
lines.push("hint: use format=json for full structured output; read_memory_item(id) for full content");
|
|
1656
|
+
return lines.join("\n");
|
|
1657
|
+
}
|
|
1658
|
+
function compactBrainDumpText(data) {
|
|
1659
|
+
return compactBootstrapText(data);
|
|
1660
|
+
}
|
|
1661
|
+
function shellQuoteArg(arg) {
|
|
1662
|
+
if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(arg))
|
|
1663
|
+
return arg;
|
|
1664
|
+
if (process.platform === "win32")
|
|
1665
|
+
return `"${arg.replace(/"/g, '\\"')}"`;
|
|
1666
|
+
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
1667
|
+
}
|
|
1668
|
+
function getPackageRtkShimPath() {
|
|
1669
|
+
try {
|
|
1670
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
1671
|
+
const candidate = path.join(currentDir, "rtk-shim.js");
|
|
1672
|
+
if (fs.existsSync(candidate))
|
|
1673
|
+
return candidate;
|
|
1674
|
+
}
|
|
1675
|
+
catch {
|
|
1676
|
+
// import.meta.url may be unavailable only in unexpected runtimes.
|
|
1677
|
+
}
|
|
1678
|
+
return null;
|
|
1679
|
+
}
|
|
1680
|
+
function runRtkProbe(spec) {
|
|
1681
|
+
const argsPrefix = spec.execArgsPrefix ?? [];
|
|
1682
|
+
const result = spawnSync(spec.execCommand, [...argsPrefix, "--version"], {
|
|
1683
|
+
encoding: "utf8",
|
|
1684
|
+
timeout: 120_000,
|
|
1685
|
+
windowsHide: true,
|
|
1686
|
+
shell: spec.execShell ?? false,
|
|
1687
|
+
});
|
|
1688
|
+
if (result.status === 0) {
|
|
1689
|
+
const gain = spawnSync(spec.execCommand, [...argsPrefix, "gain"], {
|
|
1690
|
+
encoding: "utf8",
|
|
1691
|
+
timeout: 120_000,
|
|
1692
|
+
windowsHide: true,
|
|
1693
|
+
shell: spec.execShell ?? false,
|
|
1694
|
+
});
|
|
1695
|
+
let resolvedPath = spec.path;
|
|
1696
|
+
if (spec.source === "path") {
|
|
1697
|
+
const whereCommand = process.platform === "win32" ? "where.exe" : "which";
|
|
1698
|
+
const whereResult = spawnSync(whereCommand, ["rtk"], {
|
|
1699
|
+
encoding: "utf8",
|
|
1700
|
+
timeout: 2000,
|
|
1701
|
+
windowsHide: true,
|
|
1702
|
+
});
|
|
1703
|
+
resolvedPath = whereResult.status === 0 ? oneLine(whereResult.stdout, 240) : resolvedPath;
|
|
1704
|
+
}
|
|
1705
|
+
const gainText = `${gain.stdout}${gain.stderr}`.trim();
|
|
1706
|
+
return {
|
|
1707
|
+
available: gain.status === 0,
|
|
1708
|
+
command: spec.displayCommand,
|
|
1709
|
+
version: `${result.stdout}${result.stderr}`.trim(),
|
|
1710
|
+
gain_ok: gain.status === 0,
|
|
1711
|
+
gain_preview: oneLine(gainText, 240),
|
|
1712
|
+
path: resolvedPath,
|
|
1713
|
+
source: spec.source,
|
|
1714
|
+
exec_command: spec.execCommand,
|
|
1715
|
+
exec_args_prefix: argsPrefix,
|
|
1716
|
+
exec_shell: spec.execShell ?? false,
|
|
1717
|
+
note: gain.status === 0
|
|
1718
|
+
? spec.source === "package_shim"
|
|
1719
|
+
? `Prefer prefixing shell commands with ${spec.displayCommand} for compact outputs. This is VectorMind's bundled RTK shim; first run auto-installs/caches rtk-ai/rtk if needed.`
|
|
1720
|
+
: "Prefer prefixing shell commands with rtk for compact outputs, e.g. rtk git status / rtk npm run build / rtk rg pattern ."
|
|
1721
|
+
: spec.source === "package_shim"
|
|
1722
|
+
? "VectorMind's bundled RTK shim exists, but `gain` failed. Check network/cache or set VECTORMIND_RTK_REAL to an existing rtk-ai/rtk binary."
|
|
1723
|
+
: "An rtk binary exists, but `rtk gain` failed. This may be the wrong rtk project. Use install_rtk with uninstall_wrong_cargo_rtk=true only after confirming it is safe.",
|
|
1724
|
+
};
|
|
1725
|
+
}
|
|
1726
|
+
return null;
|
|
1727
|
+
}
|
|
1728
|
+
function detectRtk() {
|
|
1729
|
+
const pathProbe = runRtkProbe({
|
|
1730
|
+
source: "path",
|
|
1731
|
+
displayCommand: "rtk",
|
|
1732
|
+
execCommand: "rtk",
|
|
1733
|
+
execShell: process.platform === "win32",
|
|
1734
|
+
});
|
|
1735
|
+
if (pathProbe?.available)
|
|
1736
|
+
return pathProbe;
|
|
1737
|
+
const shimPath = getPackageRtkShimPath();
|
|
1738
|
+
if (shimPath) {
|
|
1739
|
+
const displayCommand = `node ${shellQuoteArg(shimPath)}`;
|
|
1740
|
+
const shimProbe = runRtkProbe({
|
|
1741
|
+
source: "package_shim",
|
|
1742
|
+
displayCommand,
|
|
1743
|
+
execCommand: process.execPath,
|
|
1744
|
+
execArgsPrefix: [shimPath],
|
|
1745
|
+
path: shimPath,
|
|
1746
|
+
});
|
|
1747
|
+
if (shimProbe)
|
|
1748
|
+
return shimProbe;
|
|
1749
|
+
}
|
|
1750
|
+
if (pathProbe)
|
|
1751
|
+
return pathProbe;
|
|
1752
|
+
return {
|
|
1753
|
+
available: false,
|
|
1754
|
+
command: shimPath ? `node ${shellQuoteArg(shimPath)}` : "rtk",
|
|
1755
|
+
path: shimPath ?? undefined,
|
|
1756
|
+
source: shimPath ? "package_shim" : undefined,
|
|
1757
|
+
note: shimPath
|
|
1758
|
+
? "rtk was not found on PATH, and VectorMind's bundled RTK shim could not verify rtk gain. VectorMind compact MCP output still works; check network/cache or set VECTORMIND_RTK_REAL."
|
|
1759
|
+
: "rtk was not found on PATH and the package RTK shim is unavailable. VectorMind compact MCP output still works; install rtk to compact shell command output too.",
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
function commandExists(command) {
|
|
1763
|
+
const probe = process.platform === "win32" ? "where.exe" : "which";
|
|
1764
|
+
const result = spawnSync(probe, [command], { encoding: "utf8", timeout: 2000, windowsHide: true });
|
|
1765
|
+
return result.status === 0;
|
|
1766
|
+
}
|
|
1767
|
+
function runInstallStep(command, args, timeoutMs) {
|
|
1768
|
+
const result = spawnSync(command, args, {
|
|
1769
|
+
encoding: "utf8",
|
|
1770
|
+
timeout: timeoutMs,
|
|
1771
|
+
windowsHide: true,
|
|
1772
|
+
shell: false,
|
|
1773
|
+
});
|
|
1774
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
1775
|
+
return {
|
|
1776
|
+
command: [command, ...args].join(" "),
|
|
1777
|
+
status: result.status,
|
|
1778
|
+
ok: result.status === 0,
|
|
1779
|
+
output: oneLine(output, 1200),
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
function runDetectedRtkStep(detected, args, timeoutMs) {
|
|
1783
|
+
const execCommand = detected.exec_command ?? "rtk";
|
|
1784
|
+
const argsPrefix = detected.exec_args_prefix ?? [];
|
|
1785
|
+
const result = spawnSync(execCommand, [...argsPrefix, ...args], {
|
|
1786
|
+
encoding: "utf8",
|
|
1787
|
+
timeout: timeoutMs,
|
|
1788
|
+
windowsHide: true,
|
|
1789
|
+
shell: detected.exec_shell ?? (execCommand === "rtk" && process.platform === "win32"),
|
|
1790
|
+
});
|
|
1791
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
1792
|
+
return {
|
|
1793
|
+
command: [detected.command, ...args].join(" "),
|
|
1794
|
+
status: result.status,
|
|
1795
|
+
ok: result.status === 0,
|
|
1796
|
+
output: oneLine(output, 1200),
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
function appendRtkInitStep(steps, detected, init, timeoutMs) {
|
|
1800
|
+
if (init === "none")
|
|
1801
|
+
return;
|
|
1802
|
+
if (init === "global_no_patch")
|
|
1803
|
+
steps.push(runDetectedRtkStep(detected, ["init", "-g", "--no-patch"], timeoutMs));
|
|
1804
|
+
if (init === "global_auto_patch")
|
|
1805
|
+
steps.push(runDetectedRtkStep(detected, ["init", "-g", "--auto-patch"], timeoutMs));
|
|
1806
|
+
if (init === "global_hook_only") {
|
|
1807
|
+
steps.push(runDetectedRtkStep(detected, ["init", "-g", "--hook-only", "--no-patch"], timeoutMs));
|
|
1808
|
+
}
|
|
1809
|
+
if (init === "local")
|
|
1810
|
+
steps.push(runDetectedRtkStep(detected, ["init"], timeoutMs));
|
|
1811
|
+
if (init === "codex_global")
|
|
1812
|
+
steps.push(runDetectedRtkStep(detected, ["init", "-g", "--codex"], timeoutMs));
|
|
1813
|
+
if (init === "codex_local")
|
|
1814
|
+
steps.push(runDetectedRtkStep(detected, ["init", "--codex"], timeoutMs));
|
|
1815
|
+
}
|
|
1816
|
+
function chooseRtkInstallMethod(method) {
|
|
1817
|
+
if (method !== "auto")
|
|
1818
|
+
return method;
|
|
1819
|
+
if (process.platform === "darwin" && commandExists("brew"))
|
|
1820
|
+
return "brew";
|
|
1821
|
+
if (commandExists("cargo"))
|
|
1822
|
+
return "cargo";
|
|
1823
|
+
return "shell_script";
|
|
1824
|
+
}
|
|
1825
|
+
function buildRtkInstallPlan(args) {
|
|
1826
|
+
const method = chooseRtkInstallMethod(args.method);
|
|
1827
|
+
const commands = [];
|
|
1828
|
+
const notes = [];
|
|
1829
|
+
if (args.uninstall_wrong_cargo_rtk) {
|
|
1830
|
+
commands.push("cargo uninstall rtk");
|
|
1831
|
+
notes.push("Only use uninstall_wrong_cargo_rtk after verifying the existing rtk is the wrong Cargo package.");
|
|
1832
|
+
}
|
|
1833
|
+
if (method === "brew") {
|
|
1834
|
+
commands.push("brew install rtk");
|
|
1835
|
+
}
|
|
1836
|
+
else if (method === "cargo") {
|
|
1837
|
+
commands.push("cargo install --git https://github.com/rtk-ai/rtk");
|
|
1838
|
+
}
|
|
1839
|
+
else {
|
|
1840
|
+
if (process.platform === "win32") {
|
|
1841
|
+
notes.push("shell_script install is Linux/macOS-oriented; on Windows prefer method=cargo after installing Rust/Cargo.");
|
|
1842
|
+
commands.push("cargo install --git https://github.com/rtk-ai/rtk");
|
|
1843
|
+
}
|
|
1844
|
+
else {
|
|
1845
|
+
commands.push("curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/master/install.sh | sh");
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
commands.push("rtk --version");
|
|
1849
|
+
commands.push("rtk gain");
|
|
1850
|
+
if (args.init === "global_no_patch")
|
|
1851
|
+
commands.push("rtk init -g --no-patch");
|
|
1852
|
+
if (args.init === "global_auto_patch")
|
|
1853
|
+
commands.push("rtk init -g --auto-patch");
|
|
1854
|
+
if (args.init === "global_hook_only")
|
|
1855
|
+
commands.push("rtk init -g --hook-only --no-patch");
|
|
1856
|
+
if (args.init === "local")
|
|
1857
|
+
commands.push("rtk init");
|
|
1858
|
+
if (args.init === "codex_global")
|
|
1859
|
+
commands.push("rtk init -g --codex");
|
|
1860
|
+
if (args.init === "codex_local")
|
|
1861
|
+
commands.push("rtk init --codex");
|
|
1862
|
+
if (args.init !== "none") {
|
|
1863
|
+
notes.push("rtk init may modify Claude/RTK configuration. Use init=none for binary-only installation.");
|
|
1864
|
+
}
|
|
1865
|
+
return { method, commands, notes };
|
|
1866
|
+
}
|
|
1867
|
+
function installRtk(args) {
|
|
1868
|
+
const detectedBefore = detectRtk();
|
|
1869
|
+
const plan = buildRtkInstallPlan(args);
|
|
1870
|
+
const steps = [];
|
|
1871
|
+
const notes = [...plan.notes];
|
|
1872
|
+
if (detectedBefore.available) {
|
|
1873
|
+
notes.push("rtk is already installed and verified with `rtk gain`; installation skipped.");
|
|
1874
|
+
if (!args.dry_run && args.init !== "none") {
|
|
1875
|
+
appendRtkInitStep(steps, detectedBefore, args.init, args.timeout_ms);
|
|
1876
|
+
}
|
|
1877
|
+
return {
|
|
1878
|
+
ok: true,
|
|
1879
|
+
dry_run: args.dry_run,
|
|
1880
|
+
already_available: true,
|
|
1881
|
+
method: plan.method,
|
|
1882
|
+
commands: plan.commands,
|
|
1883
|
+
notes,
|
|
1884
|
+
steps,
|
|
1885
|
+
detected_before: detectedBefore,
|
|
1886
|
+
detected_after: detectedBefore,
|
|
1887
|
+
};
|
|
1888
|
+
}
|
|
1889
|
+
if (args.dry_run) {
|
|
1890
|
+
notes.push("dry_run=true: no command was executed. Call install_rtk with dry_run=false to install.");
|
|
1891
|
+
return {
|
|
1892
|
+
ok: true,
|
|
1893
|
+
dry_run: true,
|
|
1894
|
+
already_available: false,
|
|
1895
|
+
method: plan.method,
|
|
1896
|
+
commands: plan.commands,
|
|
1897
|
+
notes,
|
|
1898
|
+
steps,
|
|
1899
|
+
detected_before: detectedBefore,
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1902
|
+
if (plan.method === "brew") {
|
|
1903
|
+
steps.push(runInstallStep("brew", ["install", "rtk"], args.timeout_ms));
|
|
1904
|
+
}
|
|
1905
|
+
else if (plan.method === "cargo") {
|
|
1906
|
+
if (args.uninstall_wrong_cargo_rtk) {
|
|
1907
|
+
steps.push(runInstallStep("cargo", ["uninstall", "rtk"], args.timeout_ms));
|
|
1908
|
+
}
|
|
1909
|
+
steps.push(runInstallStep("cargo", ["install", "--git", "https://github.com/rtk-ai/rtk"], args.timeout_ms));
|
|
1910
|
+
}
|
|
1911
|
+
else if (process.platform === "win32") {
|
|
1912
|
+
notes.push("Windows fallback uses Cargo because the upstream shell installer targets POSIX shells.");
|
|
1913
|
+
if (args.uninstall_wrong_cargo_rtk) {
|
|
1914
|
+
steps.push(runInstallStep("cargo", ["uninstall", "rtk"], args.timeout_ms));
|
|
1915
|
+
}
|
|
1916
|
+
steps.push(runInstallStep("cargo", ["install", "--git", "https://github.com/rtk-ai/rtk"], args.timeout_ms));
|
|
1917
|
+
}
|
|
1918
|
+
else {
|
|
1919
|
+
const script = "curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/master/install.sh | sh";
|
|
1920
|
+
steps.push(runInstallStep("sh", ["-c", script], args.timeout_ms));
|
|
1921
|
+
}
|
|
1922
|
+
const detectedAfterInstall = detectRtk();
|
|
1923
|
+
if (detectedAfterInstall.available && args.init !== "none") {
|
|
1924
|
+
appendRtkInitStep(steps, detectedAfterInstall, args.init, args.timeout_ms);
|
|
1925
|
+
}
|
|
1926
|
+
const detectedAfter = detectRtk();
|
|
1927
|
+
return {
|
|
1928
|
+
ok: detectedAfter.available,
|
|
1929
|
+
dry_run: false,
|
|
1930
|
+
already_available: false,
|
|
1931
|
+
method: plan.method,
|
|
1932
|
+
commands: plan.commands,
|
|
1933
|
+
notes,
|
|
1934
|
+
steps,
|
|
1935
|
+
detected_before: detectedBefore,
|
|
1936
|
+
detected_after: detectedAfter,
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
function compactInstallRtkText(data) {
|
|
1940
|
+
const lines = [];
|
|
1941
|
+
lines.push(`install_rtk ok=${data.ok} dry_run=${data.dry_run} already_available=${data.already_available} method=${data.method}`);
|
|
1942
|
+
lines.push(`before available=${data.detected_before.available} version=${data.detected_before.version ?? "none"} gain_ok=${data.detected_before.gain_ok ?? false}`);
|
|
1943
|
+
if (data.detected_after) {
|
|
1944
|
+
lines.push(`after available=${data.detected_after.available} version=${data.detected_after.version ?? "none"} gain_ok=${data.detected_after.gain_ok ?? false}`);
|
|
1945
|
+
}
|
|
1946
|
+
if (data.commands.length) {
|
|
1947
|
+
lines.push("commands:");
|
|
1948
|
+
for (const command of data.commands)
|
|
1949
|
+
lines.push(`- ${command}`);
|
|
1950
|
+
}
|
|
1951
|
+
if (data.steps.length) {
|
|
1952
|
+
lines.push("steps:");
|
|
1953
|
+
for (const step of data.steps) {
|
|
1954
|
+
lines.push(`- ${step.ok ? "ok" : "fail"} [${step.status ?? "null"}] ${step.command}: ${oneLine(step.output, 240)}`);
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
if (data.notes.length) {
|
|
1958
|
+
lines.push("notes:");
|
|
1959
|
+
for (const note of data.notes)
|
|
1960
|
+
lines.push(`- ${note}`);
|
|
1961
|
+
}
|
|
1962
|
+
return lines.join("\n");
|
|
1963
|
+
}
|
|
1964
|
+
function tokenSavingsSummary(limit) {
|
|
1965
|
+
const summary = summarizeTokenSavingsStmt.get();
|
|
1966
|
+
const by_tool = summarizeTokenSavingsByToolStmt.all(limit);
|
|
1967
|
+
const recent = listRecentTokenSavingsStmt.all(limit);
|
|
1968
|
+
return {
|
|
1969
|
+
ok: true,
|
|
1970
|
+
summary: summary ?? { calls: 0, raw_tokens: 0, output_tokens: 0, saved_tokens: 0, avg_savings_pct: 0 },
|
|
1971
|
+
by_tool,
|
|
1972
|
+
recent,
|
|
1973
|
+
};
|
|
1974
|
+
}
|
|
1975
|
+
function compactTokenSavingsText(data) {
|
|
1976
|
+
const s = data.summary;
|
|
1977
|
+
const pct = Number(s.raw_tokens) > 0 ? (Number(s.saved_tokens) / Number(s.raw_tokens)) * 100 : 0;
|
|
1978
|
+
const lines = [
|
|
1979
|
+
`token_savings calls=${s.calls} raw=${s.raw_tokens} out=${s.output_tokens} saved=${s.saved_tokens} (${pct.toFixed(1)}%)`,
|
|
1980
|
+
];
|
|
1981
|
+
if (data.by_tool.length) {
|
|
1982
|
+
lines.push("by_tool:");
|
|
1983
|
+
for (const t of data.by_tool.slice(0, 10)) {
|
|
1984
|
+
lines.push(`- ${t.tool}: calls=${t.calls} saved=${t.saved_tokens} raw=${t.raw_tokens} out=${t.output_tokens} avg=${Number(t.avg_savings_pct).toFixed(1)}%`);
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
if (data.recent.length) {
|
|
1988
|
+
lines.push("recent:");
|
|
1989
|
+
for (const r of data.recent.slice(0, 10)) {
|
|
1990
|
+
lines.push(`- #${r.id} ${r.tool}: ${r.raw_tokens}->${r.output_tokens} saved=${r.saved_tokens}`);
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
return lines.join("\n");
|
|
1994
|
+
}
|
|
1338
1995
|
function sliceTextForOutput(input, maxChars) {
|
|
1339
1996
|
const total = input.length;
|
|
1340
1997
|
if (maxChars <= 0)
|
|
@@ -1472,6 +2129,88 @@ function dotProduct(a, b) {
|
|
|
1472
2129
|
s += a[i] * b[i];
|
|
1473
2130
|
return s;
|
|
1474
2131
|
}
|
|
2132
|
+
function parseMetadataJson(metadata) {
|
|
2133
|
+
if (!metadata)
|
|
2134
|
+
return {};
|
|
2135
|
+
try {
|
|
2136
|
+
const parsed = JSON.parse(metadata);
|
|
2137
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
2138
|
+
? parsed
|
|
2139
|
+
: {};
|
|
2140
|
+
}
|
|
2141
|
+
catch {
|
|
2142
|
+
return {};
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
function metadataStatus(row) {
|
|
2146
|
+
const meta = parseMetadataJson(row.metadata_json);
|
|
2147
|
+
return typeof meta.status === "string" ? meta.status : "";
|
|
2148
|
+
}
|
|
2149
|
+
function isSupersededMemory(row) {
|
|
2150
|
+
const meta = parseMetadataJson(row.metadata_json);
|
|
2151
|
+
return meta.superseded === true || meta.status === "superseded";
|
|
2152
|
+
}
|
|
2153
|
+
function semanticRecencyWeight(updatedAt) {
|
|
2154
|
+
if (!updatedAt)
|
|
2155
|
+
return 0;
|
|
2156
|
+
const t = Date.parse(updatedAt.endsWith("Z") ? updatedAt : `${updatedAt}Z`);
|
|
2157
|
+
if (!Number.isFinite(t))
|
|
2158
|
+
return 0;
|
|
2159
|
+
const ageDays = Math.max(0, (Date.now() - t) / 86_400_000);
|
|
2160
|
+
if (ageDays <= 1)
|
|
2161
|
+
return 0.8;
|
|
2162
|
+
if (ageDays <= 7)
|
|
2163
|
+
return 0.45;
|
|
2164
|
+
if (ageDays <= 30)
|
|
2165
|
+
return 0.2;
|
|
2166
|
+
return 0;
|
|
2167
|
+
}
|
|
2168
|
+
function semanticKindWeight(kind) {
|
|
2169
|
+
switch (kind) {
|
|
2170
|
+
case "decision":
|
|
2171
|
+
return 3.5;
|
|
2172
|
+
case "convention":
|
|
2173
|
+
return 2.6;
|
|
2174
|
+
case "project_summary":
|
|
2175
|
+
return 2.2;
|
|
2176
|
+
case "note":
|
|
2177
|
+
return 1.1;
|
|
2178
|
+
case "requirement":
|
|
2179
|
+
return 0.4;
|
|
2180
|
+
case "change_intent":
|
|
2181
|
+
return 0.2;
|
|
2182
|
+
default:
|
|
2183
|
+
return 0;
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
function adjustSemanticScore(row, rawScore) {
|
|
2187
|
+
if (isSupersededMemory(row))
|
|
2188
|
+
return rawScore - 1000;
|
|
2189
|
+
let score = rawScore + semanticKindWeight(row.kind) + semanticRecencyWeight(row.updated_at);
|
|
2190
|
+
const status = metadataStatus(row);
|
|
2191
|
+
if (status === "active" || status === "current")
|
|
2192
|
+
score += 1.2;
|
|
2193
|
+
if (row.kind === "change_intent" && row.file_path && shouldIgnoreDbFilePath(row.file_path)) {
|
|
2194
|
+
// Human-synced intent for generated/build/runtime files is often the only durable
|
|
2195
|
+
// "why" for that change. Do not let built-in path ignores hide the decision trail.
|
|
2196
|
+
score += 0.4;
|
|
2197
|
+
}
|
|
2198
|
+
return score;
|
|
2199
|
+
}
|
|
2200
|
+
function filterAndRankSemanticRows(rows, scoreOf, opts) {
|
|
2201
|
+
return rows
|
|
2202
|
+
.map((r) => ({ row: r, score: adjustSemanticScore(r, scoreOf(r)) }))
|
|
2203
|
+
.filter(({ row }) => {
|
|
2204
|
+
if (isSupersededMemory(row))
|
|
2205
|
+
return false;
|
|
2206
|
+
if (shouldIgnoreDbFilePath(row.file_path) && row.kind !== "change_intent")
|
|
2207
|
+
return false;
|
|
2208
|
+
return true;
|
|
2209
|
+
})
|
|
2210
|
+
.sort((a, b) => b.score - a.score)
|
|
2211
|
+
.slice(0, opts.topK)
|
|
2212
|
+
.map(({ row, score }) => toSemanticMatch(row, score, opts.includeContent, opts.previewChars, opts.contentMaxChars));
|
|
2213
|
+
}
|
|
1475
2214
|
function makePreviewText(content, max) {
|
|
1476
2215
|
if (max <= 0)
|
|
1477
2216
|
return "";
|
|
@@ -1546,6 +2285,15 @@ function getConventionPreviews(conventionsLimit, previewChars, contentMaxChars)
|
|
|
1546
2285
|
const stored = listConventionsStmt.all(remaining).map((c) => toMemoryItemPreview(c, false, previewChars, contentMaxChars));
|
|
1547
2286
|
return [...builtin, ...stored];
|
|
1548
2287
|
}
|
|
2288
|
+
function getDecisionPreviews(decisionsLimit, previewChars, contentMaxChars) {
|
|
2289
|
+
if (decisionsLimit <= 0)
|
|
2290
|
+
return [];
|
|
2291
|
+
const rows = listCurrentDecisionsStmt.all(Math.min(MAX_DECISIONS_LIMIT * 4, Math.max(decisionsLimit, decisionsLimit * 4)));
|
|
2292
|
+
return rows
|
|
2293
|
+
.filter((d) => !isSupersededMemory(d))
|
|
2294
|
+
.slice(0, decisionsLimit)
|
|
2295
|
+
.map((d) => toMemoryItemPreview(d, false, previewChars, contentMaxChars));
|
|
2296
|
+
}
|
|
1549
2297
|
function toRequirementPreview(req, includeContent, previewChars, contentMaxChars) {
|
|
1550
2298
|
const context = req.context_data ?? null;
|
|
1551
2299
|
const contextPreview = context ? makePreviewText(context, previewChars) : null;
|
|
@@ -1590,6 +2338,46 @@ function completeAllActiveRequirementMemoryItems() {
|
|
|
1590
2338
|
console.error("[vectormind] failed to complete all active requirement memory items:", err);
|
|
1591
2339
|
}
|
|
1592
2340
|
}
|
|
2341
|
+
function patchMemoryItemMetadata(id, patch) {
|
|
2342
|
+
const row = getMemoryItemByIdStmt.get(id);
|
|
2343
|
+
if (!row)
|
|
2344
|
+
return;
|
|
2345
|
+
const meta = { ...parseMetadataJson(row.metadata_json), ...patch };
|
|
2346
|
+
db.prepare(`UPDATE memory_items SET metadata_json = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`).run(safeJson(meta), id);
|
|
2347
|
+
}
|
|
2348
|
+
function supersedeMemoryItemIds(ids, replacement) {
|
|
2349
|
+
const updated = [];
|
|
2350
|
+
for (const id of Array.from(new Set(ids)).filter((n) => Number.isFinite(n) && n > 0)) {
|
|
2351
|
+
const row = getMemoryItemByIdStmt.get(id);
|
|
2352
|
+
if (!row)
|
|
2353
|
+
continue;
|
|
2354
|
+
patchMemoryItemMetadata(id, {
|
|
2355
|
+
...parseMetadataJson(row.metadata_json),
|
|
2356
|
+
status: "superseded",
|
|
2357
|
+
superseded: true,
|
|
2358
|
+
superseded_at: new Date().toISOString(),
|
|
2359
|
+
superseded_reason: replacement.reason,
|
|
2360
|
+
superseded_by_req_id: replacement.req_id ?? null,
|
|
2361
|
+
superseded_by_memory_id: replacement.memory_id ?? null,
|
|
2362
|
+
superseded_by_decision_id: replacement.decision_id ?? null,
|
|
2363
|
+
});
|
|
2364
|
+
updated.push(id);
|
|
2365
|
+
}
|
|
2366
|
+
return updated;
|
|
2367
|
+
}
|
|
2368
|
+
function supersedeRequirementIds(reqIds, replacement) {
|
|
2369
|
+
const updatedReqs = [];
|
|
2370
|
+
for (const reqId of Array.from(new Set(reqIds)).filter((n) => Number.isFinite(n) && n > 0)) {
|
|
2371
|
+
const info = db.prepare(`UPDATE requirements SET status = 'superseded' WHERE id = ?`).run(reqId);
|
|
2372
|
+
if (info.changes > 0)
|
|
2373
|
+
updatedReqs.push(reqId);
|
|
2374
|
+
const rows = db
|
|
2375
|
+
.prepare(`SELECT id FROM memory_items WHERE req_id = ? OR (kind = 'requirement' AND req_id = ?)`)
|
|
2376
|
+
.all(reqId, reqId);
|
|
2377
|
+
supersedeMemoryItemIds(rows.map((r) => r.id), replacement);
|
|
2378
|
+
}
|
|
2379
|
+
return updatedReqs;
|
|
2380
|
+
}
|
|
1593
2381
|
async function semanticSearchInternal(opts) {
|
|
1594
2382
|
if (!embeddingsEnabled) {
|
|
1595
2383
|
throw new Error("Embeddings are disabled");
|
|
@@ -1641,7 +2429,31 @@ async function semanticSearchInternal(opts) {
|
|
|
1641
2429
|
return toSemanticMatch(item, t.score, opts.includeContent, opts.previewChars, opts.contentMaxChars);
|
|
1642
2430
|
})
|
|
1643
2431
|
.filter(Boolean);
|
|
1644
|
-
const filtered = matches
|
|
2432
|
+
const filtered = matches
|
|
2433
|
+
.filter((m) => {
|
|
2434
|
+
if (isSupersededMemory({ metadata_json: m.item.metadata_json }))
|
|
2435
|
+
return false;
|
|
2436
|
+
if (shouldIgnoreDbFilePath(m.item.file_path) && m.item.kind !== "change_intent")
|
|
2437
|
+
return false;
|
|
2438
|
+
return true;
|
|
2439
|
+
})
|
|
2440
|
+
.map((m) => ({
|
|
2441
|
+
...m,
|
|
2442
|
+
score: adjustSemanticScore({
|
|
2443
|
+
id: m.item.id,
|
|
2444
|
+
kind: m.item.kind,
|
|
2445
|
+
title: m.item.title,
|
|
2446
|
+
content: m.item.content ?? m.item.preview,
|
|
2447
|
+
file_path: m.item.file_path,
|
|
2448
|
+
start_line: m.item.start_line,
|
|
2449
|
+
end_line: m.item.end_line,
|
|
2450
|
+
req_id: m.item.req_id,
|
|
2451
|
+
metadata_json: m.item.metadata_json,
|
|
2452
|
+
updated_at: m.item.updated_at,
|
|
2453
|
+
}, m.score),
|
|
2454
|
+
}))
|
|
2455
|
+
.sort((a, b) => b.score - a.score)
|
|
2456
|
+
.slice(0, opts.topK);
|
|
1645
2457
|
return { query: q, top_k: opts.topK, mode: "embeddings", matches: filtered };
|
|
1646
2458
|
}
|
|
1647
2459
|
function buildFtsMatchQuery(raw) {
|
|
@@ -1710,10 +2522,7 @@ function ftsSearchInternal(opts) {
|
|
|
1710
2522
|
`);
|
|
1711
2523
|
return stmt.all(matchQuery, rawLimit);
|
|
1712
2524
|
})();
|
|
1713
|
-
const matches = rows
|
|
1714
|
-
.map((r) => toSemanticMatch(r, -Number(r.rank), opts.includeContent, opts.previewChars, opts.contentMaxChars))
|
|
1715
|
-
.filter((m) => !shouldIgnoreDbFilePath(m.item.file_path))
|
|
1716
|
-
.slice(0, opts.topK);
|
|
2525
|
+
const matches = filterAndRankSemanticRows(rows, (r) => -Number(r.rank), opts);
|
|
1717
2526
|
return { query: q, top_k: opts.topK, mode: "fts", matches };
|
|
1718
2527
|
}
|
|
1719
2528
|
function likeSearchInternal(opts) {
|
|
@@ -1779,10 +2588,7 @@ function likeSearchInternal(opts) {
|
|
|
1779
2588
|
`);
|
|
1780
2589
|
return stmt.all(like, like, like, like, like, rawLimit);
|
|
1781
2590
|
})();
|
|
1782
|
-
const matches = rows
|
|
1783
|
-
.map((r) => toSemanticMatch(r, Number(r.score), opts.includeContent, opts.previewChars, opts.contentMaxChars))
|
|
1784
|
-
.filter((m) => !shouldIgnoreDbFilePath(m.item.file_path))
|
|
1785
|
-
.slice(0, opts.topK);
|
|
2591
|
+
const matches = filterAndRankSemanticRows(rows, (r) => Number(r.score), opts);
|
|
1786
2592
|
return { query: q, top_k: opts.topK, mode: "like", matches };
|
|
1787
2593
|
}
|
|
1788
2594
|
async function semanticSearchHybridInternal(opts) {
|
|
@@ -2527,6 +3333,9 @@ function buildServerInstructions() {
|
|
|
2527
3333
|
"Built-in frontend output-purity policy:",
|
|
2528
3334
|
BUILTIN_FRONTEND_OUTPUT_PURITY_INSTRUCTIONS,
|
|
2529
3335
|
"",
|
|
3336
|
+
"Built-in git commit summary policy:",
|
|
3337
|
+
BUILTIN_GIT_COMMIT_SUMMARY_INSTRUCTIONS,
|
|
3338
|
+
"",
|
|
2530
3339
|
"Built-in low-overhead execution and heavy-thread policy:",
|
|
2531
3340
|
BUILTIN_LOW_OVERHEAD_WORKFLOW_INSTRUCTIONS,
|
|
2532
3341
|
"",
|
|
@@ -2537,11 +3346,14 @@ function buildServerInstructions() {
|
|
|
2537
3346
|
BUILTIN_THREAD_HANDOFF_SWITCH_INSTRUCTIONS,
|
|
2538
3347
|
"",
|
|
2539
3348
|
"Required workflow:",
|
|
2540
|
-
"-
|
|
3349
|
+
"- Tool outputs are compact by default. Pass format=json only when you need full structured data.",
|
|
3350
|
+
"- On every new conversation/session for analysis/design/development work: call bootstrap_context({ query: <current goal> }) first (or at least get_brain_dump()) to restore compact context and retrieve relevant matches from the local memory store (vector if enabled; otherwise FTS/LIKE).",
|
|
2541
3351
|
" - Output is compact by default. Use include_content=true only when you truly need full text (it increases tokens).",
|
|
2542
|
-
" - Tune output size with: requirements_limit/changes_limit/notes_limit, preview_chars, pending_limit/pending_offset.",
|
|
3352
|
+
" - Tune output size with: requirements_limit/changes_limit/notes_limit/decisions_limit, preview_chars, pending_limit/pending_offset.",
|
|
2543
3353
|
" - Prefer read_memory_item(id, offset, limit) to fetch full text on demand instead of returning large content in other tool outputs.",
|
|
2544
3354
|
"- For pure execution-first tasks with explicit targets (for example compile/build/run/launch/package/publish/test rerun), you may skip retrieval and go straight to the minimum necessary shell or host tools unless code/context lookup is actually needed to unblock execution.",
|
|
3355
|
+
"- If rtk is installed or VectorMind's bundled RTK shim is verified (detect_rtk with gain_ok=true), prefix shell commands with the command returned by detect_rtk. Usually this is rtk (rtk git status, rtk npm run build, rtk rg ...); in npx/MCP-only installs it may be a package shim command such as node <...>/rtk-shim.js.",
|
|
3356
|
+
"- If rtk is missing and the user asks to install it, use install_rtk first with dry_run=true to show the exact commands; execute with dry_run=false only after the user clearly approves installation/init choices.",
|
|
2545
3357
|
"- To read local Codex skill/prompt/rule files (for example SKILL.md under CODEX_HOME or AGENTS_HOME), prefer read_codex_text_file({ path }) instead of assuming a filesystem MCP resource server exists.",
|
|
2546
3358
|
"- For project file/directory browsing, prefer list_project_files({ path, recursive?, max_depth? }) over shelling out to Get-ChildItem/ls. It respects ignore rules and keeps output bounded.",
|
|
2547
3359
|
"- For small/medium raw file reads, prefer read_file_text({ path, offset?, max_chars? }) over Get-Content -Raw. Use read_file_lines(...) when you need deterministic line ranges or the file may be large.",
|
|
@@ -2555,11 +3367,13 @@ function buildServerInstructions() {
|
|
|
2555
3367
|
"- BEFORE editing code: call start_requirement(title, background) to set the active requirement.",
|
|
2556
3368
|
"- 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.)",
|
|
2557
3369
|
"- After major milestones/decisions: call upsert_project_summary(summary) and/or add_note(...) to persist durable context locally.",
|
|
3370
|
+
"- When a requirement or user decision changes/reverses an older behavior, call upsert_decision(key, title, content, supersedes_req_ids?/supersedes_memory_ids?) and/or supersede_memory(...). Current decisions are shown in bootstrap_context/get_brain_dump and superseded memories are hidden from default semantic recall so stale requirements do not override newer facts.",
|
|
2558
3371
|
"- 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.",
|
|
2559
3372
|
"- When you need full text for a specific note/summary/match: call read_memory_item(id, offset, limit) and page through it.",
|
|
2560
3373
|
"- When asked to locate code (class/function/type): call query_codebase(query) instead of guessing.",
|
|
2561
3374
|
"- When you need to recall relevant context from history/code/docs: call semantic_search(query, ...) instead of guessing.",
|
|
2562
3375
|
"- If the current thread is already heavy or the user reports it has become slow, switch to a lighter workflow: avoid redundant retrieval, keep outputs compact, and if the user refuses thread switching, continue in light mode without repeating the switch reminder in that same session.",
|
|
3376
|
+
"- Use get_token_savings({ format: 'compact' }) when you need to verify how many tokens VectorMind compact outputs saved.",
|
|
2563
3377
|
"",
|
|
2564
3378
|
"If tool output conflicts with assumptions, trust the tool output.",
|
|
2565
3379
|
].join("\n");
|
|
@@ -2778,6 +3592,9 @@ function initDatabase() {
|
|
|
2778
3592
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_memory_items_convention_key
|
|
2779
3593
|
ON memory_items(kind, title) WHERE kind = 'convention';
|
|
2780
3594
|
|
|
3595
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_memory_items_decision_key
|
|
3596
|
+
ON memory_items(kind, title) WHERE kind = 'decision';
|
|
3597
|
+
|
|
2781
3598
|
CREATE INDEX IF NOT EXISTS idx_memory_items_kind_updated_at
|
|
2782
3599
|
ON memory_items(kind, updated_at DESC);
|
|
2783
3600
|
|
|
@@ -2808,6 +3625,22 @@ function initDatabase() {
|
|
|
2808
3625
|
|
|
2809
3626
|
CREATE INDEX IF NOT EXISTS idx_pending_changes_updated_at
|
|
2810
3627
|
ON pending_changes(updated_at DESC);
|
|
3628
|
+
|
|
3629
|
+
CREATE TABLE IF NOT EXISTS token_savings (
|
|
3630
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
3631
|
+
tool TEXT NOT NULL,
|
|
3632
|
+
raw_tokens INTEGER NOT NULL,
|
|
3633
|
+
output_tokens INTEGER NOT NULL,
|
|
3634
|
+
saved_tokens INTEGER NOT NULL,
|
|
3635
|
+
savings_pct REAL NOT NULL,
|
|
3636
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
3637
|
+
);
|
|
3638
|
+
|
|
3639
|
+
CREATE INDEX IF NOT EXISTS idx_token_savings_created_at
|
|
3640
|
+
ON token_savings(created_at DESC);
|
|
3641
|
+
|
|
3642
|
+
CREATE INDEX IF NOT EXISTS idx_token_savings_tool
|
|
3643
|
+
ON token_savings(tool);
|
|
2811
3644
|
`);
|
|
2812
3645
|
initMemoryItemsFts();
|
|
2813
3646
|
insertRequirementStmt = db.prepare(`INSERT INTO requirements (title, context_data, status) VALUES (?, ?, 'active')`);
|
|
@@ -2858,6 +3691,23 @@ function initDatabase() {
|
|
|
2858
3691
|
WHERE kind = 'convention'
|
|
2859
3692
|
ORDER BY updated_at DESC, id DESC
|
|
2860
3693
|
LIMIT ?`);
|
|
3694
|
+
upsertDecisionStmt = db.prepare(`INSERT INTO memory_items (kind, title, content, metadata_json, content_hash)
|
|
3695
|
+
VALUES ('decision', ?, ?, ?, ?)
|
|
3696
|
+
ON CONFLICT DO UPDATE SET
|
|
3697
|
+
content = excluded.content,
|
|
3698
|
+
metadata_json = excluded.metadata_json,
|
|
3699
|
+
content_hash = excluded.content_hash,
|
|
3700
|
+
updated_at = CURRENT_TIMESTAMP`);
|
|
3701
|
+
getDecisionByKeyStmt = db.prepare(`SELECT id, kind, title, content, file_path, start_line, end_line, req_id, metadata_json, content_hash, created_at, updated_at
|
|
3702
|
+
FROM memory_items
|
|
3703
|
+
WHERE kind = 'decision' AND title = ?
|
|
3704
|
+
ORDER BY updated_at DESC, id DESC
|
|
3705
|
+
LIMIT 1`);
|
|
3706
|
+
listCurrentDecisionsStmt = db.prepare(`SELECT id, kind, title, content, file_path, start_line, end_line, req_id, metadata_json, content_hash, created_at, updated_at
|
|
3707
|
+
FROM memory_items
|
|
3708
|
+
WHERE kind = 'decision'
|
|
3709
|
+
ORDER BY updated_at DESC, id DESC
|
|
3710
|
+
LIMIT ?`);
|
|
2861
3711
|
getRequirementMemoryItemIdStmt = db.prepare(`SELECT id
|
|
2862
3712
|
FROM memory_items
|
|
2863
3713
|
WHERE kind = 'requirement' AND req_id = ?
|
|
@@ -2880,6 +3730,11 @@ function initDatabase() {
|
|
|
2880
3730
|
WHERE kind = 'note'
|
|
2881
3731
|
ORDER BY updated_at DESC, id DESC
|
|
2882
3732
|
LIMIT ?`);
|
|
3733
|
+
getLatestChangeIntentForFileStmt = db.prepare(`SELECT id, kind, title, content, file_path, start_line, end_line, req_id, metadata_json, content_hash, created_at, updated_at
|
|
3734
|
+
FROM memory_items
|
|
3735
|
+
WHERE kind = 'change_intent' AND file_path = ?
|
|
3736
|
+
ORDER BY updated_at DESC, id DESC
|
|
3737
|
+
LIMIT 1`);
|
|
2883
3738
|
deleteFileChunkItemsStmt = db.prepare(`DELETE FROM memory_items
|
|
2884
3739
|
WHERE file_path = ?
|
|
2885
3740
|
AND (kind = 'code_chunk' OR kind = 'doc_chunk')`);
|
|
@@ -2929,6 +3784,30 @@ function initDatabase() {
|
|
|
2929
3784
|
END,
|
|
2930
3785
|
name
|
|
2931
3786
|
LIMIT ?`);
|
|
3787
|
+
insertTokenSavingsStmt = db.prepare(`INSERT INTO token_savings (tool, raw_tokens, output_tokens, saved_tokens, savings_pct)
|
|
3788
|
+
VALUES (?, ?, ?, ?, ?)`);
|
|
3789
|
+
summarizeTokenSavingsStmt = db.prepare(`SELECT
|
|
3790
|
+
COUNT(*) as calls,
|
|
3791
|
+
COALESCE(SUM(raw_tokens), 0) as raw_tokens,
|
|
3792
|
+
COALESCE(SUM(output_tokens), 0) as output_tokens,
|
|
3793
|
+
COALESCE(SUM(saved_tokens), 0) as saved_tokens,
|
|
3794
|
+
COALESCE(AVG(savings_pct), 0) as avg_savings_pct
|
|
3795
|
+
FROM token_savings`);
|
|
3796
|
+
summarizeTokenSavingsByToolStmt = db.prepare(`SELECT
|
|
3797
|
+
tool,
|
|
3798
|
+
COUNT(*) as calls,
|
|
3799
|
+
COALESCE(SUM(raw_tokens), 0) as raw_tokens,
|
|
3800
|
+
COALESCE(SUM(output_tokens), 0) as output_tokens,
|
|
3801
|
+
COALESCE(SUM(saved_tokens), 0) as saved_tokens,
|
|
3802
|
+
COALESCE(AVG(savings_pct), 0) as avg_savings_pct
|
|
3803
|
+
FROM token_savings
|
|
3804
|
+
GROUP BY tool
|
|
3805
|
+
ORDER BY saved_tokens DESC, calls DESC
|
|
3806
|
+
LIMIT ?`);
|
|
3807
|
+
listRecentTokenSavingsStmt = db.prepare(`SELECT id, tool, raw_tokens, output_tokens, saved_tokens, savings_pct, created_at
|
|
3808
|
+
FROM token_savings
|
|
3809
|
+
ORDER BY created_at DESC, id DESC
|
|
3810
|
+
LIMIT ?`);
|
|
2932
3811
|
indexFileSymbolsTx = db.transaction((filePath, symbols) => {
|
|
2933
3812
|
deleteSymbolsForFileStmt.run(filePath);
|
|
2934
3813
|
for (const s of symbols) {
|
|
@@ -3123,6 +4002,21 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
3123
4002
|
description: "Clear the in-memory debug activity log. Enable logging with VECTORMIND_DEBUG_LOG=1.",
|
|
3124
4003
|
inputSchema: toJsonSchemaCompat(ClearActivityLogArgsSchema),
|
|
3125
4004
|
},
|
|
4005
|
+
{
|
|
4006
|
+
name: "detect_rtk",
|
|
4007
|
+
description: "Detect whether rtk is available on PATH or via VectorMind's bundled RTK shim. When available, prefer the returned command as a shell prefix to reduce command-output tokens.",
|
|
4008
|
+
inputSchema: toJsonSchemaCompat(DetectRtkArgsSchema),
|
|
4009
|
+
},
|
|
4010
|
+
{
|
|
4011
|
+
name: "install_rtk",
|
|
4012
|
+
description: "Install the rtk-ai/rtk Rust Token Killer binary when it is missing. Defaults to dry_run=true and never patches hooks unless init is explicitly requested.",
|
|
4013
|
+
inputSchema: toJsonSchemaCompat(InstallRtkArgsSchema),
|
|
4014
|
+
},
|
|
4015
|
+
{
|
|
4016
|
+
name: "get_token_savings",
|
|
4017
|
+
description: "Show VectorMind compact-output token savings recorded by MCP tools. Use this to verify raw-vs-compact output reduction.",
|
|
4018
|
+
inputSchema: toJsonSchemaCompat(GetTokenSavingsArgsSchema),
|
|
4019
|
+
},
|
|
3126
4020
|
{
|
|
3127
4021
|
name: "grep",
|
|
3128
4022
|
description: "Repo text search with precise file/line/col matches, powered by ripgrep against real project files plus built-in noise filters. Falls back to indexed search only when ripgrep is unavailable.",
|
|
@@ -3163,6 +4057,16 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
3163
4057
|
description: "Save a durable project note (decision, constraint, TODO, architecture detail). Use this to persist important context locally instead of relying on chat memory.",
|
|
3164
4058
|
inputSchema: toJsonSchemaCompat(AddNoteArgsSchema),
|
|
3165
4059
|
},
|
|
4060
|
+
{
|
|
4061
|
+
name: "upsert_decision",
|
|
4062
|
+
description: "Save/update the current authoritative project decision for a key. Use it when requirements change, reverse, or supersede older behavior so future sessions prefer the latest decision over old history.",
|
|
4063
|
+
inputSchema: toJsonSchemaCompat(UpsertDecisionArgsSchema),
|
|
4064
|
+
},
|
|
4065
|
+
{
|
|
4066
|
+
name: "supersede_memory",
|
|
4067
|
+
description: "Mark old requirements or memory items as superseded by a newer requirement/decision. Superseded items are hidden from default semantic recall to avoid reverting to stale behavior.",
|
|
4068
|
+
inputSchema: toJsonSchemaCompat(SupersedeMemoryArgsSchema),
|
|
4069
|
+
},
|
|
3166
4070
|
{
|
|
3167
4071
|
name: "upsert_convention",
|
|
3168
4072
|
description: "Save/update a project convention (framework choice, build command, naming rules, etc). Conventions are durable and should be applied automatically in future sessions.",
|
|
@@ -3394,24 +4298,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3394
4298
|
}
|
|
3395
4299
|
else {
|
|
3396
4300
|
const pendingAll = listPendingChangesStmt.all();
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
for (const p of pending) {
|
|
3401
|
-
targets.push({
|
|
3402
|
-
rawFile: p.file_path,
|
|
3403
|
-
dbFilePath: p.file_path,
|
|
3404
|
-
event: p.last_event,
|
|
3405
|
-
source: "pending",
|
|
3406
|
-
});
|
|
3407
|
-
}
|
|
3408
|
-
}
|
|
3409
|
-
else {
|
|
4301
|
+
const merged = mergePendingWithGit(pendingAll, { offset: 0, limit: MAX_PENDING_LIMIT });
|
|
4302
|
+
if (merged.page.length) {
|
|
4303
|
+
for (const p of merged.page) {
|
|
3410
4304
|
targets.push({
|
|
3411
|
-
rawFile:
|
|
3412
|
-
dbFilePath:
|
|
3413
|
-
event:
|
|
3414
|
-
source: "
|
|
4305
|
+
rawFile: p.file_path,
|
|
4306
|
+
dbFilePath: p.file_path,
|
|
4307
|
+
event: p.last_event,
|
|
4308
|
+
source: p.source === "git" ? "pending" : "pending",
|
|
3415
4309
|
});
|
|
3416
4310
|
}
|
|
3417
4311
|
deleteAllPendingChangesStmt.run();
|
|
@@ -3429,7 +4323,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3429
4323
|
const isUnspecified = t.dbFilePath === "(unspecified)";
|
|
3430
4324
|
const changeInfo = insertChangeLogStmt.run(active.id, t.dbFilePath, args.intent);
|
|
3431
4325
|
const change_log_id = Number(changeInfo.lastInsertRowid);
|
|
3432
|
-
const memoryInfo = insertMemoryItemStmt.run("change_intent", active.title, args.intent, isUnspecified ? null : t.dbFilePath, null, null, active.id, safeJson({
|
|
4326
|
+
const memoryInfo = insertMemoryItemStmt.run("change_intent", active.title, args.intent, isUnspecified ? null : t.dbFilePath, null, null, active.id, safeJson({
|
|
4327
|
+
change_log_id,
|
|
4328
|
+
event: t.event,
|
|
4329
|
+
source: t.source,
|
|
4330
|
+
file_state_hash: isUnspecified ? null : getFileStateHash(t.rawFile),
|
|
4331
|
+
}), sha256Hex(args.intent));
|
|
3433
4332
|
const memory_item_id = Number(memoryInfo.lastInsertRowid);
|
|
3434
4333
|
enqueueEmbedding(memory_item_id);
|
|
3435
4334
|
synced_files.push({ file_path: t.dbFilePath, event: t.event, source: t.source });
|
|
@@ -3480,6 +4379,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3480
4379
|
const changesLimit = args.changes_limit;
|
|
3481
4380
|
const notesLimit = args.notes_limit;
|
|
3482
4381
|
const conventionsLimit = args.conventions_limit;
|
|
4382
|
+
const decisionsLimit = args.decisions_limit;
|
|
3483
4383
|
const recent = listRecentRequirementsStmt.all(requirementsLimit);
|
|
3484
4384
|
const items = recent.map((req) => {
|
|
3485
4385
|
const changes = listChangeLogsForRequirementStmt.all(req.id, changesLimit);
|
|
@@ -3493,12 +4393,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3493
4393
|
? toMemoryItemPreview(projectSummaryRow, includeContent, previewChars, contentMaxChars)
|
|
3494
4394
|
: null;
|
|
3495
4395
|
const recent_notes = listRecentNotesStmt.all(notesLimit).map((n) => toMemoryItemPreview(n, includeContent, previewChars, contentMaxChars));
|
|
4396
|
+
const decisions = getDecisionPreviews(decisionsLimit, previewChars, contentMaxChars);
|
|
3496
4397
|
const conventions = getConventionPreviews(conventionsLimit, previewChars, contentMaxChars);
|
|
3497
|
-
const pending_total = Number(countPendingChangesStmt.get()?.total ?? 0);
|
|
3498
4398
|
const pending_offset = args.pending_offset;
|
|
3499
4399
|
const pending_limit = args.pending_limit;
|
|
3500
|
-
const
|
|
3501
|
-
const
|
|
4400
|
+
const pendingDbRows = listPendingChangesStmt.all();
|
|
4401
|
+
const mergedPending = mergePendingWithGit(pendingDbRows, { offset: pending_offset, limit: pending_limit });
|
|
4402
|
+
const pending_total = mergedPending.total;
|
|
4403
|
+
const pending_truncated = mergedPending.truncated;
|
|
4404
|
+
const pending_changes = mergedPending.page;
|
|
3502
4405
|
const q = args.query?.trim() ?? "";
|
|
3503
4406
|
const semantic = q
|
|
3504
4407
|
? await Promise.race([
|
|
@@ -3521,47 +4424,52 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3521
4424
|
pending_total,
|
|
3522
4425
|
pending_returned: pending_changes.length,
|
|
3523
4426
|
requirements_returned: items.length,
|
|
4427
|
+
decisions_returned: decisions.length,
|
|
3524
4428
|
conventions_returned: conventions.length,
|
|
3525
4429
|
semantic_mode: semantic?.mode ?? null,
|
|
3526
4430
|
semantic_matches: semantic?.matches?.length ?? 0,
|
|
3527
4431
|
});
|
|
4432
|
+
const outputValue = {
|
|
4433
|
+
ok: true,
|
|
4434
|
+
generated_at: new Date().toISOString(),
|
|
4435
|
+
project_root: projectRoot,
|
|
4436
|
+
root_source: rootSource,
|
|
4437
|
+
db_path: dbPath,
|
|
4438
|
+
watcher_enabled: !!watcher,
|
|
4439
|
+
watcher_ready: watcherReady,
|
|
4440
|
+
embeddings: {
|
|
4441
|
+
enabled: embeddingsEnabled,
|
|
4442
|
+
model: embedModelName,
|
|
4443
|
+
embed_files: embedFilesMode,
|
|
4444
|
+
},
|
|
4445
|
+
output: {
|
|
4446
|
+
format: args.format,
|
|
4447
|
+
include_content: includeContent,
|
|
4448
|
+
preview_chars: previewChars,
|
|
4449
|
+
content_max_chars: contentMaxChars,
|
|
4450
|
+
requirements_limit: requirementsLimit,
|
|
4451
|
+
changes_limit: changesLimit,
|
|
4452
|
+
notes_limit: notesLimit,
|
|
4453
|
+
decisions_limit: decisionsLimit,
|
|
4454
|
+
conventions_limit: conventionsLimit,
|
|
4455
|
+
},
|
|
4456
|
+
project_summary,
|
|
4457
|
+
decisions,
|
|
4458
|
+
conventions,
|
|
4459
|
+
recent_notes,
|
|
4460
|
+
pending_total,
|
|
4461
|
+
pending_offset,
|
|
4462
|
+
pending_limit,
|
|
4463
|
+
pending_truncated,
|
|
4464
|
+
pending_changes,
|
|
4465
|
+
items,
|
|
4466
|
+
semantic,
|
|
4467
|
+
};
|
|
3528
4468
|
return {
|
|
3529
4469
|
content: [
|
|
3530
4470
|
{
|
|
3531
4471
|
type: "text",
|
|
3532
|
-
text:
|
|
3533
|
-
ok: true,
|
|
3534
|
-
generated_at: new Date().toISOString(),
|
|
3535
|
-
project_root: projectRoot,
|
|
3536
|
-
root_source: rootSource,
|
|
3537
|
-
db_path: dbPath,
|
|
3538
|
-
watcher_enabled: !!watcher,
|
|
3539
|
-
watcher_ready: watcherReady,
|
|
3540
|
-
embeddings: {
|
|
3541
|
-
enabled: embeddingsEnabled,
|
|
3542
|
-
model: embedModelName,
|
|
3543
|
-
embed_files: embedFilesMode,
|
|
3544
|
-
},
|
|
3545
|
-
output: {
|
|
3546
|
-
include_content: includeContent,
|
|
3547
|
-
preview_chars: previewChars,
|
|
3548
|
-
content_max_chars: contentMaxChars,
|
|
3549
|
-
requirements_limit: requirementsLimit,
|
|
3550
|
-
changes_limit: changesLimit,
|
|
3551
|
-
notes_limit: notesLimit,
|
|
3552
|
-
conventions_limit: conventionsLimit,
|
|
3553
|
-
},
|
|
3554
|
-
project_summary,
|
|
3555
|
-
conventions,
|
|
3556
|
-
recent_notes,
|
|
3557
|
-
pending_total,
|
|
3558
|
-
pending_offset,
|
|
3559
|
-
pending_limit,
|
|
3560
|
-
pending_truncated,
|
|
3561
|
-
pending_changes,
|
|
3562
|
-
items,
|
|
3563
|
-
semantic,
|
|
3564
|
-
}),
|
|
4472
|
+
text: toolText("bootstrap_context", outputValue, compactBootstrapText(outputValue), args.format),
|
|
3565
4473
|
},
|
|
3566
4474
|
],
|
|
3567
4475
|
};
|
|
@@ -3576,6 +4484,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3576
4484
|
const changesLimit = args.changes_limit;
|
|
3577
4485
|
const notesLimit = args.notes_limit;
|
|
3578
4486
|
const conventionsLimit = args.conventions_limit;
|
|
4487
|
+
const decisionsLimit = args.decisions_limit;
|
|
3579
4488
|
const recent = listRecentRequirementsStmt.all(requirementsLimit);
|
|
3580
4489
|
const items = recent.map((req) => {
|
|
3581
4490
|
const changes = listChangeLogsForRequirementStmt.all(req.id, changesLimit);
|
|
@@ -3589,55 +4498,64 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3589
4498
|
? toMemoryItemPreview(projectSummaryRow, includeContent, previewChars, contentMaxChars)
|
|
3590
4499
|
: null;
|
|
3591
4500
|
const recent_notes = listRecentNotesStmt.all(notesLimit).map((n) => toMemoryItemPreview(n, includeContent, previewChars, contentMaxChars));
|
|
4501
|
+
const decisions = getDecisionPreviews(decisionsLimit, previewChars, contentMaxChars);
|
|
3592
4502
|
const conventions = getConventionPreviews(conventionsLimit, previewChars, contentMaxChars);
|
|
3593
|
-
const pending_total = Number(countPendingChangesStmt.get()?.total ?? 0);
|
|
3594
4503
|
const pending_offset = args.pending_offset;
|
|
3595
4504
|
const pending_limit = args.pending_limit;
|
|
3596
|
-
const
|
|
3597
|
-
const
|
|
4505
|
+
const pendingDbRows = listPendingChangesStmt.all();
|
|
4506
|
+
const mergedPending = mergePendingWithGit(pendingDbRows, { offset: pending_offset, limit: pending_limit });
|
|
4507
|
+
const pending_total = mergedPending.total;
|
|
4508
|
+
const pending_truncated = mergedPending.truncated;
|
|
4509
|
+
const pending_changes = mergedPending.page;
|
|
3598
4510
|
logActivity("get_brain_dump", {
|
|
3599
4511
|
pending_total,
|
|
3600
4512
|
pending_returned: pending_changes.length,
|
|
3601
4513
|
requirements_returned: items.length,
|
|
3602
4514
|
notes_returned: recent_notes.length,
|
|
4515
|
+
decisions_returned: decisions.length,
|
|
3603
4516
|
conventions_returned: conventions.length,
|
|
3604
4517
|
});
|
|
4518
|
+
const outputValue = {
|
|
4519
|
+
ok: true,
|
|
4520
|
+
generated_at: new Date().toISOString(),
|
|
4521
|
+
project_root: projectRoot,
|
|
4522
|
+
root_source: rootSource,
|
|
4523
|
+
db_path: dbPath,
|
|
4524
|
+
watcher_enabled: !!watcher,
|
|
4525
|
+
watcher_ready: watcherReady,
|
|
4526
|
+
embeddings: {
|
|
4527
|
+
enabled: embeddingsEnabled,
|
|
4528
|
+
model: embedModelName,
|
|
4529
|
+
embed_files: embedFilesMode,
|
|
4530
|
+
},
|
|
4531
|
+
output: {
|
|
4532
|
+
format: args.format,
|
|
4533
|
+
include_content: includeContent,
|
|
4534
|
+
preview_chars: previewChars,
|
|
4535
|
+
content_max_chars: contentMaxChars,
|
|
4536
|
+
requirements_limit: requirementsLimit,
|
|
4537
|
+
changes_limit: changesLimit,
|
|
4538
|
+
notes_limit: notesLimit,
|
|
4539
|
+
decisions_limit: decisionsLimit,
|
|
4540
|
+
conventions_limit: conventionsLimit,
|
|
4541
|
+
},
|
|
4542
|
+
project_summary,
|
|
4543
|
+
decisions,
|
|
4544
|
+
conventions,
|
|
4545
|
+
recent_notes,
|
|
4546
|
+
pending_total,
|
|
4547
|
+
pending_offset,
|
|
4548
|
+
pending_limit,
|
|
4549
|
+
pending_truncated,
|
|
4550
|
+
pending_changes,
|
|
4551
|
+
items,
|
|
4552
|
+
semantic: null,
|
|
4553
|
+
};
|
|
3605
4554
|
return {
|
|
3606
4555
|
content: [
|
|
3607
4556
|
{
|
|
3608
4557
|
type: "text",
|
|
3609
|
-
text:
|
|
3610
|
-
ok: true,
|
|
3611
|
-
generated_at: new Date().toISOString(),
|
|
3612
|
-
project_root: projectRoot,
|
|
3613
|
-
root_source: rootSource,
|
|
3614
|
-
db_path: dbPath,
|
|
3615
|
-
watcher_enabled: !!watcher,
|
|
3616
|
-
watcher_ready: watcherReady,
|
|
3617
|
-
embeddings: {
|
|
3618
|
-
enabled: embeddingsEnabled,
|
|
3619
|
-
model: embedModelName,
|
|
3620
|
-
embed_files: embedFilesMode,
|
|
3621
|
-
},
|
|
3622
|
-
output: {
|
|
3623
|
-
include_content: includeContent,
|
|
3624
|
-
preview_chars: previewChars,
|
|
3625
|
-
content_max_chars: contentMaxChars,
|
|
3626
|
-
requirements_limit: requirementsLimit,
|
|
3627
|
-
changes_limit: changesLimit,
|
|
3628
|
-
notes_limit: notesLimit,
|
|
3629
|
-
conventions_limit: conventionsLimit,
|
|
3630
|
-
},
|
|
3631
|
-
project_summary,
|
|
3632
|
-
conventions,
|
|
3633
|
-
recent_notes,
|
|
3634
|
-
pending_total,
|
|
3635
|
-
pending_offset,
|
|
3636
|
-
pending_limit,
|
|
3637
|
-
pending_truncated,
|
|
3638
|
-
pending_changes,
|
|
3639
|
-
items,
|
|
3640
|
-
}),
|
|
4558
|
+
text: toolText("get_brain_dump", outputValue, compactBrainDumpText(outputValue), args.format),
|
|
3641
4559
|
},
|
|
3642
4560
|
],
|
|
3643
4561
|
};
|
|
@@ -3645,11 +4563,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3645
4563
|
if (toolName === "get_pending_changes") {
|
|
3646
4564
|
const args = GetPendingChangesArgsSchema.parse(rawArgs);
|
|
3647
4565
|
flushPendingChangeBuffer();
|
|
3648
|
-
const total = Number(countPendingChangesStmt.get()?.total ?? 0);
|
|
3649
4566
|
const offset = args.offset;
|
|
3650
4567
|
const limit = args.limit;
|
|
3651
|
-
const
|
|
3652
|
-
const
|
|
4568
|
+
const pendingDbRows = listPendingChangesStmt.all();
|
|
4569
|
+
const mergedPending = mergePendingWithGit(pendingDbRows, { offset, limit });
|
|
4570
|
+
const total = mergedPending.total;
|
|
4571
|
+
const truncated = mergedPending.truncated;
|
|
4572
|
+
const pending = mergedPending.page;
|
|
3653
4573
|
logActivity("get_pending_changes", {
|
|
3654
4574
|
total,
|
|
3655
4575
|
offset,
|
|
@@ -3818,6 +4738,31 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3818
4738
|
clearActivityLog();
|
|
3819
4739
|
return { content: [{ type: "text", text: toolJson({ ok: true }) }] };
|
|
3820
4740
|
}
|
|
4741
|
+
if (toolName === "detect_rtk") {
|
|
4742
|
+
DetectRtkArgsSchema.parse(rawArgs);
|
|
4743
|
+
const result = detectRtk();
|
|
4744
|
+
const text = result.available
|
|
4745
|
+
? `rtk available: ${result.version ?? result.command}\ncommand=${result.command} source=${result.source ?? "unknown"} gain_ok=${result.gain_ok ?? false}${result.path ? ` path=${result.path}` : ""}\n${result.note}`
|
|
4746
|
+
: `rtk unavailable: ${result.command}\nsource=${result.source ?? "none"} gain_ok=${result.gain_ok ?? false}${result.version ? ` version=${result.version}` : ""}${result.path ? ` path=${result.path}` : ""}\n${result.note}`;
|
|
4747
|
+
return { content: [{ type: "text", text }] };
|
|
4748
|
+
}
|
|
4749
|
+
if (toolName === "install_rtk") {
|
|
4750
|
+
const args = InstallRtkArgsSchema.parse(rawArgs);
|
|
4751
|
+
const result = installRtk(args);
|
|
4752
|
+
return { content: [{ type: "text", text: compactInstallRtkText(result) }] };
|
|
4753
|
+
}
|
|
4754
|
+
if (toolName === "get_token_savings") {
|
|
4755
|
+
const args = GetTokenSavingsArgsSchema.parse(rawArgs);
|
|
4756
|
+
const result = tokenSavingsSummary(args.limit);
|
|
4757
|
+
return {
|
|
4758
|
+
content: [
|
|
4759
|
+
{
|
|
4760
|
+
type: "text",
|
|
4761
|
+
text: args.format === "json" ? toolJson(result) : compactTokenSavingsText(result),
|
|
4762
|
+
},
|
|
4763
|
+
],
|
|
4764
|
+
};
|
|
4765
|
+
}
|
|
3821
4766
|
if (toolName === "grep") {
|
|
3822
4767
|
const args = GrepArgsSchema.parse(rawArgs);
|
|
3823
4768
|
const q = args.query;
|
|
@@ -3851,24 +4796,25 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3851
4796
|
total_matches: ripgrepResult.total_matches,
|
|
3852
4797
|
truncated: ripgrepResult.truncated,
|
|
3853
4798
|
});
|
|
4799
|
+
const outputValue = {
|
|
4800
|
+
ok: true,
|
|
4801
|
+
backend: ripgrepResult.backend,
|
|
4802
|
+
rg_command: ripgrepResult.rg_command,
|
|
4803
|
+
query: q,
|
|
4804
|
+
mode,
|
|
4805
|
+
case_sensitive: caseSensitive,
|
|
4806
|
+
smart_case: smartCase,
|
|
4807
|
+
include_paths: includePaths ?? [],
|
|
4808
|
+
exclude_paths: excludePaths ?? [],
|
|
4809
|
+
matches: ripgrepResult.matches,
|
|
4810
|
+
total_matches: ripgrepResult.total_matches,
|
|
4811
|
+
truncated: ripgrepResult.truncated,
|
|
4812
|
+
};
|
|
3854
4813
|
return {
|
|
3855
4814
|
content: [
|
|
3856
4815
|
{
|
|
3857
4816
|
type: "text",
|
|
3858
|
-
text:
|
|
3859
|
-
ok: true,
|
|
3860
|
-
backend: ripgrepResult.backend,
|
|
3861
|
-
rg_command: ripgrepResult.rg_command,
|
|
3862
|
-
query: q,
|
|
3863
|
-
mode,
|
|
3864
|
-
case_sensitive: caseSensitive,
|
|
3865
|
-
smart_case: smartCase,
|
|
3866
|
-
include_paths: includePaths ?? [],
|
|
3867
|
-
exclude_paths: excludePaths ?? [],
|
|
3868
|
-
matches: ripgrepResult.matches,
|
|
3869
|
-
total_matches: ripgrepResult.total_matches,
|
|
3870
|
-
truncated: ripgrepResult.truncated,
|
|
3871
|
-
}),
|
|
4817
|
+
text: toolCompactOrJson("grep", outputValue, compactGrepText(outputValue), args.format),
|
|
3872
4818
|
},
|
|
3873
4819
|
],
|
|
3874
4820
|
};
|
|
@@ -3945,28 +4891,29 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3945
4891
|
matches: indexedResult.matches.length,
|
|
3946
4892
|
truncated: indexedResult.truncated,
|
|
3947
4893
|
});
|
|
4894
|
+
const outputValue = {
|
|
4895
|
+
ok: true,
|
|
4896
|
+
backend: indexedResult.backend,
|
|
4897
|
+
fallback_reason: "ripgrep_unavailable",
|
|
4898
|
+
ripgrep_error: ripgrepResult.error,
|
|
4899
|
+
ripgrep_attempts: ripgrepResult.attempts,
|
|
4900
|
+
query: q,
|
|
4901
|
+
mode,
|
|
4902
|
+
case_sensitive: caseSensitive,
|
|
4903
|
+
smart_case: smartCase,
|
|
4904
|
+
hint: indexedResult.hint,
|
|
4905
|
+
kinds,
|
|
4906
|
+
include_paths: includePaths ?? [],
|
|
4907
|
+
exclude_paths: excludePaths ?? [],
|
|
4908
|
+
candidates: indexedResult.candidates,
|
|
4909
|
+
matches: indexedResult.matches,
|
|
4910
|
+
truncated: indexedResult.truncated,
|
|
4911
|
+
};
|
|
3948
4912
|
return {
|
|
3949
4913
|
content: [
|
|
3950
4914
|
{
|
|
3951
4915
|
type: "text",
|
|
3952
|
-
text:
|
|
3953
|
-
ok: true,
|
|
3954
|
-
backend: indexedResult.backend,
|
|
3955
|
-
fallback_reason: "ripgrep_unavailable",
|
|
3956
|
-
ripgrep_error: ripgrepResult.error,
|
|
3957
|
-
ripgrep_attempts: ripgrepResult.attempts,
|
|
3958
|
-
query: q,
|
|
3959
|
-
mode,
|
|
3960
|
-
case_sensitive: caseSensitive,
|
|
3961
|
-
smart_case: smartCase,
|
|
3962
|
-
hint: indexedResult.hint,
|
|
3963
|
-
kinds,
|
|
3964
|
-
include_paths: includePaths ?? [],
|
|
3965
|
-
exclude_paths: excludePaths ?? [],
|
|
3966
|
-
candidates: indexedResult.candidates,
|
|
3967
|
-
matches: indexedResult.matches,
|
|
3968
|
-
truncated: indexedResult.truncated,
|
|
3969
|
-
}),
|
|
4916
|
+
text: toolCompactOrJson("grep", outputValue, compactGrepText(outputValue), args.format),
|
|
3970
4917
|
},
|
|
3971
4918
|
],
|
|
3972
4919
|
};
|
|
@@ -4018,28 +4965,29 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4018
4965
|
truncated: result.truncated,
|
|
4019
4966
|
path_kind: st.isFile() ? "file" : st.isDirectory() ? "dir" : "other",
|
|
4020
4967
|
});
|
|
4968
|
+
const outputValue = {
|
|
4969
|
+
ok: true,
|
|
4970
|
+
path: resolved.dbFilePath,
|
|
4971
|
+
path_kind: st.isFile() ? "file" : st.isDirectory() ? "dir" : "other",
|
|
4972
|
+
recursive: args.recursive,
|
|
4973
|
+
max_depth: args.recursive ? args.max_depth : 1,
|
|
4974
|
+
include_files: args.include_files,
|
|
4975
|
+
include_dirs: args.include_dirs,
|
|
4976
|
+
include_hidden: args.include_hidden,
|
|
4977
|
+
respect_ignore: args.respect_ignore,
|
|
4978
|
+
include_paths: includePaths ?? [],
|
|
4979
|
+
exclude_paths: excludePaths ?? [],
|
|
4980
|
+
extensions: extensions ?? [],
|
|
4981
|
+
returned: result.returned,
|
|
4982
|
+
scanned: result.scanned,
|
|
4983
|
+
truncated: result.truncated,
|
|
4984
|
+
entries: result.entries,
|
|
4985
|
+
};
|
|
4021
4986
|
return {
|
|
4022
4987
|
content: [
|
|
4023
4988
|
{
|
|
4024
4989
|
type: "text",
|
|
4025
|
-
text:
|
|
4026
|
-
ok: true,
|
|
4027
|
-
path: resolved.dbFilePath,
|
|
4028
|
-
path_kind: st.isFile() ? "file" : st.isDirectory() ? "dir" : "other",
|
|
4029
|
-
recursive: args.recursive,
|
|
4030
|
-
max_depth: args.recursive ? args.max_depth : 1,
|
|
4031
|
-
include_files: args.include_files,
|
|
4032
|
-
include_dirs: args.include_dirs,
|
|
4033
|
-
include_hidden: args.include_hidden,
|
|
4034
|
-
respect_ignore: args.respect_ignore,
|
|
4035
|
-
include_paths: includePaths ?? [],
|
|
4036
|
-
exclude_paths: excludePaths ?? [],
|
|
4037
|
-
extensions: extensions ?? [],
|
|
4038
|
-
returned: result.returned,
|
|
4039
|
-
scanned: result.scanned,
|
|
4040
|
-
truncated: result.truncated,
|
|
4041
|
-
entries: result.entries,
|
|
4042
|
-
}),
|
|
4990
|
+
text: toolCompactOrJson("list_project_files", outputValue, compactListProjectFilesText(outputValue), args.format),
|
|
4043
4991
|
},
|
|
4044
4992
|
],
|
|
4045
4993
|
};
|
|
@@ -4079,19 +5027,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4079
5027
|
total_chars: result.totalChars,
|
|
4080
5028
|
truncated: result.truncated,
|
|
4081
5029
|
});
|
|
5030
|
+
const outputValue = {
|
|
5031
|
+
ok: true,
|
|
5032
|
+
file_path: resolved.dbFilePath,
|
|
5033
|
+
offset: args.offset,
|
|
5034
|
+
returned_chars: result.returnedChars,
|
|
5035
|
+
total_chars: result.totalChars,
|
|
5036
|
+
truncated: result.truncated,
|
|
5037
|
+
text: result.text,
|
|
5038
|
+
};
|
|
4082
5039
|
return {
|
|
4083
5040
|
content: [
|
|
4084
5041
|
{
|
|
4085
5042
|
type: "text",
|
|
4086
|
-
text:
|
|
4087
|
-
ok: true,
|
|
4088
|
-
file_path: resolved.dbFilePath,
|
|
4089
|
-
offset: args.offset,
|
|
4090
|
-
returned_chars: result.returnedChars,
|
|
4091
|
-
total_chars: result.totalChars,
|
|
4092
|
-
truncated: result.truncated,
|
|
4093
|
-
text: result.text,
|
|
4094
|
-
}),
|
|
5043
|
+
text: toolCompactOrJson("read_file_text", outputValue, compactReadTextFileText(outputValue), args.format),
|
|
4095
5044
|
},
|
|
4096
5045
|
],
|
|
4097
5046
|
};
|
|
@@ -4138,20 +5087,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4138
5087
|
total_chars: result.totalChars,
|
|
4139
5088
|
truncated: result.truncated,
|
|
4140
5089
|
});
|
|
5090
|
+
const outputValue = {
|
|
5091
|
+
ok: true,
|
|
5092
|
+
file_path: resolved.displayPath,
|
|
5093
|
+
allowed_root: resolved.allowedRoot,
|
|
5094
|
+
offset: args.offset,
|
|
5095
|
+
returned_chars: result.returnedChars,
|
|
5096
|
+
total_chars: result.totalChars,
|
|
5097
|
+
truncated: result.truncated,
|
|
5098
|
+
text: result.text,
|
|
5099
|
+
};
|
|
4141
5100
|
return {
|
|
4142
5101
|
content: [
|
|
4143
5102
|
{
|
|
4144
5103
|
type: "text",
|
|
4145
|
-
text:
|
|
4146
|
-
ok: true,
|
|
4147
|
-
file_path: resolved.displayPath,
|
|
4148
|
-
allowed_root: resolved.allowedRoot,
|
|
4149
|
-
offset: args.offset,
|
|
4150
|
-
returned_chars: result.returnedChars,
|
|
4151
|
-
total_chars: result.totalChars,
|
|
4152
|
-
truncated: result.truncated,
|
|
4153
|
-
text: result.text,
|
|
4154
|
-
}),
|
|
5104
|
+
text: toolCompactOrJson("read_codex_text_file", outputValue, compactReadTextFileText(outputValue), args.format),
|
|
4155
5105
|
},
|
|
4156
5106
|
],
|
|
4157
5107
|
};
|
|
@@ -4213,19 +5163,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4213
5163
|
returned: result.returned,
|
|
4214
5164
|
truncated: result.truncated,
|
|
4215
5165
|
});
|
|
5166
|
+
const outputValue = {
|
|
5167
|
+
ok: true,
|
|
5168
|
+
file_path: resolved.dbFilePath,
|
|
5169
|
+
from_line: fromLine,
|
|
5170
|
+
to_line: toLine,
|
|
5171
|
+
returned: result.returned,
|
|
5172
|
+
truncated: result.truncated,
|
|
5173
|
+
text: result.text,
|
|
5174
|
+
};
|
|
4216
5175
|
return {
|
|
4217
5176
|
content: [
|
|
4218
5177
|
{
|
|
4219
5178
|
type: "text",
|
|
4220
|
-
text:
|
|
4221
|
-
ok: true,
|
|
4222
|
-
file_path: resolved.dbFilePath,
|
|
4223
|
-
from_line: fromLine,
|
|
4224
|
-
to_line: toLine,
|
|
4225
|
-
returned: result.returned,
|
|
4226
|
-
truncated: result.truncated,
|
|
4227
|
-
text: result.text,
|
|
4228
|
-
}),
|
|
5179
|
+
text: toolCompactOrJson("read_file_lines", outputValue, compactReadFileLinesText(outputValue), args.format),
|
|
4229
5180
|
},
|
|
4230
5181
|
],
|
|
4231
5182
|
};
|
|
@@ -4242,11 +5193,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4242
5193
|
matches: filtered.length,
|
|
4243
5194
|
sample: filtered.slice(0, 10).map((m) => ({ name: m.name, type: m.type, file_path: m.file_path })),
|
|
4244
5195
|
});
|
|
5196
|
+
const outputValue = { ok: true, query: q, matches: filtered };
|
|
4245
5197
|
return {
|
|
4246
5198
|
content: [
|
|
4247
5199
|
{
|
|
4248
5200
|
type: "text",
|
|
4249
|
-
text:
|
|
5201
|
+
text: toolCompactOrJson("query_codebase", outputValue, compactQueryCodebaseText(outputValue), args.format),
|
|
4250
5202
|
},
|
|
4251
5203
|
],
|
|
4252
5204
|
};
|
|
@@ -4287,6 +5239,95 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4287
5239
|
],
|
|
4288
5240
|
};
|
|
4289
5241
|
}
|
|
5242
|
+
if (toolName === "upsert_decision") {
|
|
5243
|
+
const args = UpsertDecisionArgsSchema.parse(rawArgs);
|
|
5244
|
+
const key = args.key.trim();
|
|
5245
|
+
const title = args.title.trim() || key;
|
|
5246
|
+
const content = args.content.trim();
|
|
5247
|
+
const meta = {
|
|
5248
|
+
status: "current",
|
|
5249
|
+
key,
|
|
5250
|
+
title,
|
|
5251
|
+
tags: args.tags ?? [],
|
|
5252
|
+
supersedes_req_ids: args.supersedes_req_ids ?? [],
|
|
5253
|
+
supersedes_memory_ids: args.supersedes_memory_ids ?? [],
|
|
5254
|
+
related_files: (args.related_files ?? []).map((f) => normalizeToDbPath(f)),
|
|
5255
|
+
};
|
|
5256
|
+
upsertDecisionStmt.run(key, `${title}\n\n${content}`, safeJson(meta), sha256Hex(`${title}\n\n${content}`));
|
|
5257
|
+
const row = getDecisionByKeyStmt.get(key);
|
|
5258
|
+
if (row)
|
|
5259
|
+
enqueueEmbedding(row.id);
|
|
5260
|
+
const superseded_requirements = supersedeRequirementIds(args.supersedes_req_ids ?? [], {
|
|
5261
|
+
decision_id: row?.id,
|
|
5262
|
+
reason: `Superseded by decision ${key}: ${title}`,
|
|
5263
|
+
});
|
|
5264
|
+
const superseded_memory_items = supersedeMemoryItemIds(args.supersedes_memory_ids ?? [], {
|
|
5265
|
+
decision_id: row?.id,
|
|
5266
|
+
reason: `Superseded by decision ${key}: ${title}`,
|
|
5267
|
+
});
|
|
5268
|
+
logActivity("upsert_decision", {
|
|
5269
|
+
key,
|
|
5270
|
+
decision_id: row?.id ?? null,
|
|
5271
|
+
superseded_requirements,
|
|
5272
|
+
superseded_memory_items,
|
|
5273
|
+
});
|
|
5274
|
+
return {
|
|
5275
|
+
content: [
|
|
5276
|
+
{
|
|
5277
|
+
type: "text",
|
|
5278
|
+
text: toolJson({
|
|
5279
|
+
ok: true,
|
|
5280
|
+
decision: row ? { id: row.id, key, updated_at: row.updated_at } : null,
|
|
5281
|
+
superseded_requirements,
|
|
5282
|
+
superseded_memory_items,
|
|
5283
|
+
}),
|
|
5284
|
+
},
|
|
5285
|
+
],
|
|
5286
|
+
};
|
|
5287
|
+
}
|
|
5288
|
+
if (toolName === "supersede_memory") {
|
|
5289
|
+
const args = SupersedeMemoryArgsSchema.parse(rawArgs);
|
|
5290
|
+
const supersededReqIds = args.superseded_req_ids ?? [];
|
|
5291
|
+
const supersededMemoryIds = args.superseded_memory_ids ?? [];
|
|
5292
|
+
if (!supersededReqIds.length && !supersededMemoryIds.length) {
|
|
5293
|
+
return {
|
|
5294
|
+
isError: true,
|
|
5295
|
+
content: [
|
|
5296
|
+
{
|
|
5297
|
+
type: "text",
|
|
5298
|
+
text: toolJson({
|
|
5299
|
+
ok: false,
|
|
5300
|
+
error: "Provide superseded_req_ids and/or superseded_memory_ids.",
|
|
5301
|
+
}),
|
|
5302
|
+
},
|
|
5303
|
+
],
|
|
5304
|
+
};
|
|
5305
|
+
}
|
|
5306
|
+
const superseded_requirements = supersedeRequirementIds(supersededReqIds, {
|
|
5307
|
+
req_id: args.replacement_req_id,
|
|
5308
|
+
memory_id: args.replacement_memory_id,
|
|
5309
|
+
reason: args.reason,
|
|
5310
|
+
});
|
|
5311
|
+
const superseded_memory_items = supersedeMemoryItemIds(supersededMemoryIds, {
|
|
5312
|
+
req_id: args.replacement_req_id,
|
|
5313
|
+
memory_id: args.replacement_memory_id,
|
|
5314
|
+
reason: args.reason,
|
|
5315
|
+
});
|
|
5316
|
+
logActivity("supersede_memory", {
|
|
5317
|
+
superseded_requirements,
|
|
5318
|
+
superseded_memory_items,
|
|
5319
|
+
replacement_req_id: args.replacement_req_id ?? null,
|
|
5320
|
+
replacement_memory_id: args.replacement_memory_id ?? null,
|
|
5321
|
+
});
|
|
5322
|
+
return {
|
|
5323
|
+
content: [
|
|
5324
|
+
{
|
|
5325
|
+
type: "text",
|
|
5326
|
+
text: toolJson({ ok: true, superseded_requirements, superseded_memory_items }),
|
|
5327
|
+
},
|
|
5328
|
+
],
|
|
5329
|
+
};
|
|
5330
|
+
}
|
|
4290
5331
|
if (toolName === "upsert_convention") {
|
|
4291
5332
|
const args = UpsertConventionArgsSchema.parse(rawArgs);
|
|
4292
5333
|
const key = args.key.trim();
|
|
@@ -4345,11 +5386,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4345
5386
|
score: m.score,
|
|
4346
5387
|
})),
|
|
4347
5388
|
});
|
|
5389
|
+
const outputValue = { ok: true, ...result };
|
|
4348
5390
|
return {
|
|
4349
5391
|
content: [
|
|
4350
5392
|
{
|
|
4351
5393
|
type: "text",
|
|
4352
|
-
text:
|
|
5394
|
+
text: toolCompactOrJson("semantic_search", outputValue, compactSemanticSearchText(outputValue), args.format),
|
|
4353
5395
|
},
|
|
4354
5396
|
],
|
|
4355
5397
|
};
|