@coreyuan/vector-mind 1.0.37 → 1.0.39

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/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_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.37";
19
+ const SERVER_VERSION = "1.0.39";
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());
@@ -181,6 +181,10 @@ let deleteOldestPendingChangesStmt = null;
181
181
  let deleteSymbolsForFileStmt;
182
182
  let upsertSymbolStmt;
183
183
  let searchSymbolsStmt;
184
+ let insertTokenSavingsStmt;
185
+ let summarizeTokenSavingsStmt;
186
+ let summarizeTokenSavingsByToolStmt;
187
+ let listRecentTokenSavingsStmt;
184
188
  let indexFileSymbolsTx = null;
185
189
  let activitySeq = 0;
186
190
  const activityLog = [];
@@ -1149,6 +1153,9 @@ function removeFileIndexes(absPath) {
1149
1153
  const ProjectRootArgSchema = z.object({
1150
1154
  project_root: z.string().optional(),
1151
1155
  });
1156
+ const OutputFormatSchema = z.object({
1157
+ format: z.enum(["compact", "json"]).optional().default("compact"),
1158
+ });
1152
1159
  const StartRequirementArgsSchema = ProjectRootArgSchema.merge(z.object({
1153
1160
  title: z.string().min(1),
1154
1161
  background: z.string().optional().default(""),
@@ -1159,10 +1166,10 @@ const SyncChangeIntentArgsSchema = ProjectRootArgSchema.merge(z.object({
1159
1166
  files: z.array(z.string().min(1)).optional(),
1160
1167
  affected_files: z.array(z.string().min(1)).optional(),
1161
1168
  }));
1162
- const QueryCodebaseArgsSchema = ProjectRootArgSchema.merge(z.object({
1169
+ const QueryCodebaseArgsSchema = ProjectRootArgSchema.merge(OutputFormatSchema).merge(z.object({
1163
1170
  query: z.string().min(1),
1164
1171
  }));
1165
- const GrepArgsSchema = ProjectRootArgSchema.merge(z.object({
1172
+ const GrepArgsSchema = ProjectRootArgSchema.merge(OutputFormatSchema).merge(z.object({
1166
1173
  // Pattern to search for. Defaults to regex mode for parity with tools like ripgrep.
1167
1174
  query: z.string().min(1),
1168
1175
  mode: z.enum(["regex", "literal"]).optional().default("regex"),
@@ -1179,7 +1186,7 @@ const GrepArgsSchema = ProjectRootArgSchema.merge(z.object({
1179
1186
  // Compatibility knob for the indexed fallback when ripgrep is unavailable.
1180
1187
  max_candidates: z.number().int().min(1).max(50_000).optional(),
1181
1188
  }));
1182
- const ReadFileLinesArgsSchema = ProjectRootArgSchema.merge(z.object({
1189
+ const ReadFileLinesArgsSchema = ProjectRootArgSchema.merge(OutputFormatSchema).merge(z.object({
1183
1190
  // Relative to project_root, or an absolute path under project_root.
1184
1191
  path: z.string().min(1),
1185
1192
  from_line: z.number().int().min(1).optional().default(1),
@@ -1190,7 +1197,7 @@ const ReadFileLinesArgsSchema = ProjectRootArgSchema.merge(z.object({
1190
1197
  max_lines: z.number().int().min(1).max(2000).optional().default(400),
1191
1198
  max_chars: z.number().int().min(200).max(200_000).optional().default(20_000),
1192
1199
  }));
1193
- const ReadFileTextArgsSchema = ProjectRootArgSchema.merge(z.object({
1200
+ const ReadFileTextArgsSchema = ProjectRootArgSchema.merge(OutputFormatSchema).merge(z.object({
1194
1201
  // Relative to project_root, or an absolute path under project_root.
1195
1202
  path: z.string().min(1),
1196
1203
  // Character offset in the decoded UTF-8 text.
@@ -1200,14 +1207,14 @@ const ReadFileTextArgsSchema = ProjectRootArgSchema.merge(z.object({
1200
1207
  // Safety guard for raw reads; use read_file_lines on larger files.
1201
1208
  max_file_bytes: z.number().int().min(1_000).max(5_000_000).optional().default(1_000_000),
1202
1209
  }));
1203
- const ReadCodexTextFileArgsSchema = ProjectRootArgSchema.merge(z.object({
1210
+ const ReadCodexTextFileArgsSchema = ProjectRootArgSchema.merge(OutputFormatSchema).merge(z.object({
1204
1211
  // Absolute path, file:// URI, or a path under CODEX_HOME / AGENTS_HOME allowed roots.
1205
1212
  path: z.string().min(1),
1206
1213
  offset: z.number().int().min(0).optional().default(0),
1207
1214
  max_chars: z.number().int().min(1).max(200_000).optional().default(20_000),
1208
1215
  max_file_bytes: z.number().int().min(1_000).max(5_000_000).optional().default(1_000_000),
1209
1216
  }));
1210
- const ListProjectFilesArgsSchema = ProjectRootArgSchema.merge(z.object({
1217
+ const ListProjectFilesArgsSchema = ProjectRootArgSchema.merge(OutputFormatSchema).merge(z.object({
1211
1218
  // Relative directory/file path under project_root. "." means the project root.
1212
1219
  path: z.string().optional().default("."),
1213
1220
  recursive: z.boolean().optional().default(false),
@@ -1242,24 +1249,24 @@ const UpsertConventionArgsSchema = ProjectRootArgSchema.merge(z.object({
1242
1249
  content: z.string().min(1),
1243
1250
  tags: z.array(z.string().min(1)).optional(),
1244
1251
  }));
1245
- const DEFAULT_PENDING_LIMIT = 50;
1252
+ const DEFAULT_PENDING_LIMIT = 10;
1246
1253
  const MAX_PENDING_LIMIT = 2000;
1247
1254
  const PendingPagingSchema = z.object({
1248
1255
  pending_offset: z.number().int().min(0).optional().default(0),
1249
1256
  pending_limit: z.number().int().min(1).max(MAX_PENDING_LIMIT).optional().default(DEFAULT_PENDING_LIMIT),
1250
1257
  });
1251
- const DEFAULT_PREVIEW_CHARS = 200;
1258
+ const DEFAULT_PREVIEW_CHARS = 120;
1252
1259
  const PreviewSchema = z.object({
1253
1260
  preview_chars: z.number().int().min(50).max(10_000).optional().default(DEFAULT_PREVIEW_CHARS),
1254
1261
  });
1255
- const DEFAULT_CONTENT_MAX_CHARS = 2000;
1262
+ const DEFAULT_CONTENT_MAX_CHARS = 1200;
1256
1263
  const ContentMaxSchema = z.object({
1257
1264
  content_max_chars: z.number().int().min(0).max(200_000).optional().default(DEFAULT_CONTENT_MAX_CHARS),
1258
1265
  });
1259
- const DEFAULT_RECENT_REQUIREMENTS = 3;
1260
- const DEFAULT_RECENT_CHANGES_PER_REQ = 5;
1261
- const DEFAULT_RECENT_NOTES = 5;
1262
- const DEFAULT_CONVENTIONS_LIMIT = 20;
1266
+ const DEFAULT_RECENT_REQUIREMENTS = 2;
1267
+ const DEFAULT_RECENT_CHANGES_PER_REQ = 3;
1268
+ const DEFAULT_RECENT_NOTES = 3;
1269
+ const DEFAULT_CONVENTIONS_LIMIT = 0;
1263
1270
  const BrainDumpLimitsSchema = z.object({
1264
1271
  requirements_limit: z.number().int().min(1).max(20).optional().default(DEFAULT_RECENT_REQUIREMENTS),
1265
1272
  changes_limit: z.number().int().min(1).max(100).optional().default(DEFAULT_RECENT_CHANGES_PER_REQ),
@@ -1285,6 +1292,7 @@ const GetActivitySummaryArgsSchema = ProjectRootArgSchema.merge(z.object({
1285
1292
  }));
1286
1293
  const ClearActivityLogArgsSchema = ProjectRootArgSchema;
1287
1294
  const GetBrainDumpArgsSchema = ProjectRootArgSchema.merge(PendingPagingSchema)
1295
+ .merge(OutputFormatSchema)
1288
1296
  .merge(PreviewSchema)
1289
1297
  .merge(ContentMaxSchema)
1290
1298
  .merge(BrainDumpLimitsSchema)
@@ -1293,16 +1301,17 @@ const GetBrainDumpArgsSchema = ProjectRootArgSchema.merge(PendingPagingSchema)
1293
1301
  }));
1294
1302
  const BootstrapContextArgsSchema = ProjectRootArgSchema.merge(z.object({
1295
1303
  query: z.string().optional(),
1296
- top_k: z.number().int().min(1).max(50).optional().default(5),
1304
+ top_k: z.number().int().min(1).max(50).optional().default(3),
1297
1305
  kinds: z.array(z.string().min(1)).optional(),
1298
1306
  include_content: z.boolean().optional().default(false),
1299
1307
  pending_offset: z.number().int().min(0).optional().default(0),
1300
1308
  pending_limit: z.number().int().min(1).max(MAX_PENDING_LIMIT).optional().default(DEFAULT_PENDING_LIMIT),
1301
1309
  })
1310
+ .merge(OutputFormatSchema)
1302
1311
  .merge(PreviewSchema)
1303
1312
  .merge(ContentMaxSchema)
1304
1313
  .merge(BrainDumpLimitsSchema));
1305
- const SemanticSearchArgsSchema = ProjectRootArgSchema.merge(z.object({
1314
+ const SemanticSearchArgsSchema = ProjectRootArgSchema.merge(OutputFormatSchema).merge(z.object({
1306
1315
  query: z.string().min(1),
1307
1316
  top_k: z.number().int().min(1).max(50).optional().default(8),
1308
1317
  kinds: z.array(z.string().min(1)).optional(),
@@ -1311,6 +1320,21 @@ const SemanticSearchArgsSchema = ProjectRootArgSchema.merge(z.object({
1311
1320
  content_max_chars: z.number().int().min(0).max(200_000).optional().default(DEFAULT_CONTENT_MAX_CHARS),
1312
1321
  }));
1313
1322
  const ProjectRootOnlyArgsSchema = ProjectRootArgSchema;
1323
+ const GetTokenSavingsArgsSchema = ProjectRootArgSchema.merge(z.object({
1324
+ limit: z.number().int().min(1).max(100).optional().default(10),
1325
+ format: z.enum(["compact", "json"]).optional().default("compact"),
1326
+ }));
1327
+ const DetectRtkArgsSchema = ProjectRootArgSchema;
1328
+ const InstallRtkArgsSchema = ProjectRootArgSchema.merge(z.object({
1329
+ dry_run: z.boolean().optional().default(true),
1330
+ method: z.enum(["auto", "cargo", "brew", "shell_script"]).optional().default("auto"),
1331
+ init: z
1332
+ .enum(["none", "global_no_patch", "global_auto_patch", "global_hook_only", "local"])
1333
+ .optional()
1334
+ .default("none"),
1335
+ uninstall_wrong_cargo_rtk: z.boolean().optional().default(false),
1336
+ timeout_ms: z.number().int().min(10_000).max(1_800_000).optional().default(600_000),
1337
+ }));
1314
1338
  const ReadMemoryItemArgsSchema = ProjectRootArgSchema.merge(z.object({
1315
1339
  id: z.number().int().positive(),
1316
1340
  offset: z.number().int().min(0).optional().default(0),
@@ -1335,6 +1359,408 @@ function safeJson(value) {
1335
1359
  function toolJson(value) {
1336
1360
  return JSON.stringify(value, null, prettyJsonOutput ? 2 : undefined);
1337
1361
  }
1362
+ function estimateTokens(text) {
1363
+ if (!text)
1364
+ return 0;
1365
+ return Math.ceil(text.length / 4);
1366
+ }
1367
+ function recordTokenSavings(tool, rawText, outputText) {
1368
+ if (!db || !insertTokenSavingsStmt)
1369
+ return;
1370
+ const rawTokens = estimateTokens(rawText);
1371
+ const outputTokens = estimateTokens(outputText);
1372
+ const savedTokens = Math.max(0, rawTokens - outputTokens);
1373
+ const savingsPct = rawTokens > 0 ? (savedTokens / rawTokens) * 100 : 0;
1374
+ try {
1375
+ insertTokenSavingsStmt.run(tool, rawTokens, outputTokens, savedTokens, savingsPct);
1376
+ }
1377
+ catch (err) {
1378
+ console.error("[vectormind] token savings record failed:", err);
1379
+ }
1380
+ }
1381
+ function toolText(tool, rawValue, compactText, format = "compact") {
1382
+ const rawText = toolJson(rawValue);
1383
+ if (format === "json")
1384
+ return rawText;
1385
+ recordTokenSavings(tool, rawText, compactText);
1386
+ return compactText;
1387
+ }
1388
+ function toolCompactOrJson(tool, rawValue, compactText, format) {
1389
+ return toolText(tool, rawValue, compactText, format);
1390
+ }
1391
+ function oneLine(input, max = 120) {
1392
+ const text = (input ?? "").replace(/\s+/g, " ").trim();
1393
+ if (text.length <= max)
1394
+ return text;
1395
+ return `${text.slice(0, Math.max(0, max - 3))}...`;
1396
+ }
1397
+ function compactMemoryLabel(item, max = 120) {
1398
+ const title = item.title ? ` ${oneLine(item.title, 48)}` : "";
1399
+ const loc = item.file_path ? ` ${item.file_path}${item.start_line != null ? `:${item.start_line}` : ""}` : "";
1400
+ const body = item.preview ? ` — ${oneLine(item.preview, max)}` : "";
1401
+ return `#${item.id} ${item.kind}${title}${loc}${body}`;
1402
+ }
1403
+ function compactRequirementLabel(req) {
1404
+ const ctx = req.context_preview ? ` — ${oneLine(req.context_preview, 100)}` : "";
1405
+ const mem = req.memory_item_id ? ` mem#${req.memory_item_id}` : "";
1406
+ return `req#${req.id}${mem} [${req.status}] ${oneLine(req.title, 80)}${ctx}`;
1407
+ }
1408
+ function compactChangeLabel(change) {
1409
+ return `change#${change.id} ${change.file_path}: ${oneLine(change.intent_preview, 120)}`;
1410
+ }
1411
+ function compactPendingLabel(p) {
1412
+ return `${p.last_event} ${p.file_path}`;
1413
+ }
1414
+ function compactSemanticSearchText(data) {
1415
+ const lines = [
1416
+ `semantic ${data.mode} ${data.matches.length}/${data.top_k} q="${oneLine(data.query, 100)}"`,
1417
+ ];
1418
+ for (const m of data.matches.slice(0, data.top_k)) {
1419
+ lines.push(`- score=${m.score.toFixed(3)} ${compactMemoryLabel(m.item, 160)}`);
1420
+ }
1421
+ if (!data.matches.length)
1422
+ lines.push("- no matches");
1423
+ lines.push("hint: use format=json for full metadata; read_memory_item(id) for full content");
1424
+ return lines.join("\n");
1425
+ }
1426
+ function compactGrepText(data) {
1427
+ const total = data.total_matches ?? data.matches.length;
1428
+ const fallback = data.fallback_reason ? ` fallback=${data.fallback_reason}` : "";
1429
+ const candidateText = data.candidates ? ` candidates=${data.candidates.scanned}/${data.candidates.total}` : "";
1430
+ const lines = [
1431
+ `grep ${data.backend}${fallback} mode=${data.mode} matches=${data.matches.length}/${total} truncated=${data.truncated}${candidateText} q="${oneLine(data.query, 100)}"`,
1432
+ ];
1433
+ if (data.ripgrep_error)
1434
+ lines.push(`ripgrep_error ${oneLine(data.ripgrep_error, 180)}`);
1435
+ for (const m of data.matches.slice(0, 80)) {
1436
+ lines.push(`${m.file_path}:${m.line}:${m.col}: ${oneLine(m.preview, 220)}`);
1437
+ }
1438
+ if (!data.matches.length)
1439
+ lines.push("- no matches");
1440
+ if (data.truncated)
1441
+ lines.push("hint: refine query/include_paths or raise max_results; use format=json for full match objects");
1442
+ return lines.join("\n");
1443
+ }
1444
+ function compactListProjectFilesText(data) {
1445
+ const lines = [
1446
+ `files path=${data.path} kind=${data.path_kind} returned=${data.returned} scanned=${data.scanned} recursive=${data.recursive} depth=${data.max_depth} truncated=${data.truncated}`,
1447
+ ];
1448
+ for (const e of data.entries.slice(0, 200)) {
1449
+ const stat = e.size != null ? ` ${e.size}B` : "";
1450
+ lines.push(`${e.kind === "dir" ? "d" : "f"} ${e.path}${stat}`);
1451
+ }
1452
+ if (!data.entries.length)
1453
+ lines.push("- empty");
1454
+ if (data.truncated)
1455
+ lines.push("hint: narrow path/filters or raise max_results; use format=json for full entry metadata");
1456
+ return lines.join("\n");
1457
+ }
1458
+ function compactReadTextFileText(data) {
1459
+ const offset = data.offset != null ? ` offset=${data.offset}` : "";
1460
+ const header = `file ${data.file_path}${offset} chars=${data.returned_chars}/${data.total_chars} truncated=${data.truncated}`;
1461
+ const hint = data.truncated ? "\nhint: continue with offset or read_file_lines; use format=json for metadata fields" : "";
1462
+ return `${header}\n${data.text}${hint}`;
1463
+ }
1464
+ function compactReadFileLinesText(data) {
1465
+ const header = `lines ${data.file_path}:${data.from_line}-${data.to_line} returned=${data.returned} truncated=${data.truncated}`;
1466
+ const hint = data.truncated ? "\nhint: narrow range or raise max_lines/max_chars; use format=json for metadata fields" : "";
1467
+ return `${header}\n${data.text}${hint}`;
1468
+ }
1469
+ function compactQueryCodebaseText(data) {
1470
+ const lines = [`query_codebase matches=${data.matches.length} q="${oneLine(data.query, 100)}"`];
1471
+ for (const m of data.matches.slice(0, 50)) {
1472
+ lines.push(`${m.file_path}: ${m.type} ${m.name}${m.signature ? ` — ${oneLine(m.signature, 160)}` : ""}`);
1473
+ }
1474
+ if (!data.matches.length)
1475
+ lines.push("- no matches");
1476
+ return lines.join("\n");
1477
+ }
1478
+ function compactBootstrapText(data) {
1479
+ const lines = [];
1480
+ lines.push(`ok ctx ${data.root_source} watcher=${data.watcher_enabled ? (data.watcher_ready ? "ready" : "starting") : "off"} root=${data.project_root}`);
1481
+ if (data.project_summary)
1482
+ lines.push(`summary ${compactMemoryLabel(data.project_summary, 140)}`);
1483
+ if (data.pending_total) {
1484
+ lines.push(`pending ${data.pending_changes.length}/${data.pending_total}${data.pending_truncated ? " truncated" : ""}: ${data.pending_changes
1485
+ .slice(0, 8)
1486
+ .map(compactPendingLabel)
1487
+ .join("; ")}`);
1488
+ }
1489
+ else {
1490
+ lines.push("pending 0");
1491
+ }
1492
+ if (data.items.length) {
1493
+ lines.push("requirements:");
1494
+ for (const item of data.items) {
1495
+ lines.push(`- ${compactRequirementLabel(item.requirement)}`);
1496
+ for (const c of item.recent_changes.slice(0, 3))
1497
+ lines.push(` - ${compactChangeLabel(c)}`);
1498
+ }
1499
+ }
1500
+ else {
1501
+ lines.push("requirements: none");
1502
+ }
1503
+ if (data.recent_notes.length) {
1504
+ lines.push("notes:");
1505
+ for (const n of data.recent_notes.slice(0, 3))
1506
+ lines.push(`- ${compactMemoryLabel(n, 120)}`);
1507
+ }
1508
+ if (data.conventions.length) {
1509
+ lines.push(`conventions ${data.conventions.length}: ${data.conventions
1510
+ .slice(0, 5)
1511
+ .map((c) => c.title ?? `#${c.id}`)
1512
+ .join(", ")}`);
1513
+ }
1514
+ if (data.semantic) {
1515
+ lines.push(`semantic ${data.semantic.mode} ${data.semantic.matches.length}/${data.semantic.top_k} for "${oneLine(data.semantic.query, 80)}":`);
1516
+ for (const m of data.semantic.matches.slice(0, 5)) {
1517
+ lines.push(`- score=${m.score.toFixed(3)} ${compactMemoryLabel(m.item, 120)}`);
1518
+ }
1519
+ }
1520
+ lines.push("hint: use format=json for full structured output; read_memory_item(id) for full content");
1521
+ return lines.join("\n");
1522
+ }
1523
+ function compactBrainDumpText(data) {
1524
+ return compactBootstrapText(data);
1525
+ }
1526
+ function detectRtk() {
1527
+ const command = process.platform === "win32" ? "rtk.exe" : "rtk";
1528
+ const result = spawnSync(command, ["--version"], {
1529
+ encoding: "utf8",
1530
+ timeout: 2000,
1531
+ windowsHide: true,
1532
+ });
1533
+ if (result.status === 0) {
1534
+ const gain = spawnSync(command, ["gain"], {
1535
+ encoding: "utf8",
1536
+ timeout: 5000,
1537
+ windowsHide: true,
1538
+ });
1539
+ const whereCommand = process.platform === "win32" ? "where.exe" : "which";
1540
+ const whereResult = spawnSync(whereCommand, [process.platform === "win32" ? "rtk.exe" : "rtk"], {
1541
+ encoding: "utf8",
1542
+ timeout: 2000,
1543
+ windowsHide: true,
1544
+ });
1545
+ const gainText = `${gain.stdout}${gain.stderr}`.trim();
1546
+ return {
1547
+ available: gain.status === 0,
1548
+ command,
1549
+ version: `${result.stdout}${result.stderr}`.trim(),
1550
+ gain_ok: gain.status === 0,
1551
+ gain_preview: oneLine(gainText, 240),
1552
+ path: whereResult.status === 0 ? oneLine(whereResult.stdout, 240) : undefined,
1553
+ note: gain.status === 0
1554
+ ? "Prefer prefixing shell commands with rtk for compact outputs, e.g. rtk git status / rtk npm run build / rtk rg pattern ."
1555
+ : "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.",
1556
+ };
1557
+ }
1558
+ return {
1559
+ available: false,
1560
+ command,
1561
+ note: "rtk was not found on PATH. VectorMind compact MCP output still works; install rtk to compact shell command output too.",
1562
+ };
1563
+ }
1564
+ function commandExists(command) {
1565
+ const probe = process.platform === "win32" ? "where.exe" : "which";
1566
+ const result = spawnSync(probe, [command], { encoding: "utf8", timeout: 2000, windowsHide: true });
1567
+ return result.status === 0;
1568
+ }
1569
+ function runInstallStep(command, args, timeoutMs) {
1570
+ const result = spawnSync(command, args, {
1571
+ encoding: "utf8",
1572
+ timeout: timeoutMs,
1573
+ windowsHide: true,
1574
+ shell: false,
1575
+ });
1576
+ const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
1577
+ return {
1578
+ command: [command, ...args].join(" "),
1579
+ status: result.status,
1580
+ ok: result.status === 0,
1581
+ output: oneLine(output, 1200),
1582
+ };
1583
+ }
1584
+ function chooseRtkInstallMethod(method) {
1585
+ if (method !== "auto")
1586
+ return method;
1587
+ if (process.platform === "darwin" && commandExists("brew"))
1588
+ return "brew";
1589
+ if (commandExists("cargo"))
1590
+ return "cargo";
1591
+ return "shell_script";
1592
+ }
1593
+ function buildRtkInstallPlan(args) {
1594
+ const method = chooseRtkInstallMethod(args.method);
1595
+ const commands = [];
1596
+ const notes = [];
1597
+ if (args.uninstall_wrong_cargo_rtk) {
1598
+ commands.push("cargo uninstall rtk");
1599
+ notes.push("Only use uninstall_wrong_cargo_rtk after verifying the existing rtk is the wrong Cargo package.");
1600
+ }
1601
+ if (method === "brew") {
1602
+ commands.push("brew install rtk");
1603
+ }
1604
+ else if (method === "cargo") {
1605
+ commands.push("cargo install --git https://github.com/rtk-ai/rtk");
1606
+ }
1607
+ else {
1608
+ if (process.platform === "win32") {
1609
+ notes.push("shell_script install is Linux/macOS-oriented; on Windows prefer method=cargo after installing Rust/Cargo.");
1610
+ commands.push("cargo install --git https://github.com/rtk-ai/rtk");
1611
+ }
1612
+ else {
1613
+ commands.push("curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/master/install.sh | sh");
1614
+ }
1615
+ }
1616
+ commands.push("rtk --version");
1617
+ commands.push("rtk gain");
1618
+ if (args.init === "global_no_patch")
1619
+ commands.push("rtk init -g --no-patch");
1620
+ if (args.init === "global_auto_patch")
1621
+ commands.push("rtk init -g --auto-patch");
1622
+ if (args.init === "global_hook_only")
1623
+ commands.push("rtk init -g --hook-only --no-patch");
1624
+ if (args.init === "local")
1625
+ commands.push("rtk init");
1626
+ if (args.init !== "none") {
1627
+ notes.push("rtk init may modify Claude/RTK configuration. Use init=none for binary-only installation.");
1628
+ }
1629
+ return { method, commands, notes };
1630
+ }
1631
+ function installRtk(args) {
1632
+ const detectedBefore = detectRtk();
1633
+ const plan = buildRtkInstallPlan(args);
1634
+ const steps = [];
1635
+ const notes = [...plan.notes];
1636
+ if (detectedBefore.available) {
1637
+ notes.push("rtk is already installed and verified with `rtk gain`; installation skipped.");
1638
+ return {
1639
+ ok: true,
1640
+ dry_run: args.dry_run,
1641
+ already_available: true,
1642
+ method: plan.method,
1643
+ commands: plan.commands,
1644
+ notes,
1645
+ steps,
1646
+ detected_before: detectedBefore,
1647
+ detected_after: detectedBefore,
1648
+ };
1649
+ }
1650
+ if (args.dry_run) {
1651
+ notes.push("dry_run=true: no command was executed. Call install_rtk with dry_run=false to install.");
1652
+ return {
1653
+ ok: true,
1654
+ dry_run: true,
1655
+ already_available: false,
1656
+ method: plan.method,
1657
+ commands: plan.commands,
1658
+ notes,
1659
+ steps,
1660
+ detected_before: detectedBefore,
1661
+ };
1662
+ }
1663
+ if (plan.method === "brew") {
1664
+ steps.push(runInstallStep("brew", ["install", "rtk"], args.timeout_ms));
1665
+ }
1666
+ else if (plan.method === "cargo") {
1667
+ if (args.uninstall_wrong_cargo_rtk) {
1668
+ steps.push(runInstallStep("cargo", ["uninstall", "rtk"], args.timeout_ms));
1669
+ }
1670
+ steps.push(runInstallStep("cargo", ["install", "--git", "https://github.com/rtk-ai/rtk"], args.timeout_ms));
1671
+ }
1672
+ else if (process.platform === "win32") {
1673
+ notes.push("Windows fallback uses Cargo because the upstream shell installer targets POSIX shells.");
1674
+ if (args.uninstall_wrong_cargo_rtk) {
1675
+ steps.push(runInstallStep("cargo", ["uninstall", "rtk"], args.timeout_ms));
1676
+ }
1677
+ steps.push(runInstallStep("cargo", ["install", "--git", "https://github.com/rtk-ai/rtk"], args.timeout_ms));
1678
+ }
1679
+ else {
1680
+ const script = "curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/master/install.sh | sh";
1681
+ steps.push(runInstallStep("sh", ["-c", script], args.timeout_ms));
1682
+ }
1683
+ const detectedAfterInstall = detectRtk();
1684
+ if (detectedAfterInstall.available && args.init !== "none") {
1685
+ if (args.init === "global_no_patch")
1686
+ steps.push(runInstallStep("rtk", ["init", "-g", "--no-patch"], args.timeout_ms));
1687
+ if (args.init === "global_auto_patch")
1688
+ steps.push(runInstallStep("rtk", ["init", "-g", "--auto-patch"], args.timeout_ms));
1689
+ if (args.init === "global_hook_only") {
1690
+ steps.push(runInstallStep("rtk", ["init", "-g", "--hook-only", "--no-patch"], args.timeout_ms));
1691
+ }
1692
+ if (args.init === "local")
1693
+ steps.push(runInstallStep("rtk", ["init"], args.timeout_ms));
1694
+ }
1695
+ const detectedAfter = detectRtk();
1696
+ return {
1697
+ ok: detectedAfter.available,
1698
+ dry_run: false,
1699
+ already_available: false,
1700
+ method: plan.method,
1701
+ commands: plan.commands,
1702
+ notes,
1703
+ steps,
1704
+ detected_before: detectedBefore,
1705
+ detected_after: detectedAfter,
1706
+ };
1707
+ }
1708
+ function compactInstallRtkText(data) {
1709
+ const lines = [];
1710
+ lines.push(`install_rtk ok=${data.ok} dry_run=${data.dry_run} already_available=${data.already_available} method=${data.method}`);
1711
+ lines.push(`before available=${data.detected_before.available} version=${data.detected_before.version ?? "none"} gain_ok=${data.detected_before.gain_ok ?? false}`);
1712
+ if (data.detected_after) {
1713
+ lines.push(`after available=${data.detected_after.available} version=${data.detected_after.version ?? "none"} gain_ok=${data.detected_after.gain_ok ?? false}`);
1714
+ }
1715
+ if (data.commands.length) {
1716
+ lines.push("commands:");
1717
+ for (const command of data.commands)
1718
+ lines.push(`- ${command}`);
1719
+ }
1720
+ if (data.steps.length) {
1721
+ lines.push("steps:");
1722
+ for (const step of data.steps) {
1723
+ lines.push(`- ${step.ok ? "ok" : "fail"} [${step.status ?? "null"}] ${step.command}: ${oneLine(step.output, 240)}`);
1724
+ }
1725
+ }
1726
+ if (data.notes.length) {
1727
+ lines.push("notes:");
1728
+ for (const note of data.notes)
1729
+ lines.push(`- ${note}`);
1730
+ }
1731
+ return lines.join("\n");
1732
+ }
1733
+ function tokenSavingsSummary(limit) {
1734
+ const summary = summarizeTokenSavingsStmt.get();
1735
+ const by_tool = summarizeTokenSavingsByToolStmt.all(limit);
1736
+ const recent = listRecentTokenSavingsStmt.all(limit);
1737
+ return {
1738
+ ok: true,
1739
+ summary: summary ?? { calls: 0, raw_tokens: 0, output_tokens: 0, saved_tokens: 0, avg_savings_pct: 0 },
1740
+ by_tool,
1741
+ recent,
1742
+ };
1743
+ }
1744
+ function compactTokenSavingsText(data) {
1745
+ const s = data.summary;
1746
+ const pct = Number(s.raw_tokens) > 0 ? (Number(s.saved_tokens) / Number(s.raw_tokens)) * 100 : 0;
1747
+ const lines = [
1748
+ `token_savings calls=${s.calls} raw=${s.raw_tokens} out=${s.output_tokens} saved=${s.saved_tokens} (${pct.toFixed(1)}%)`,
1749
+ ];
1750
+ if (data.by_tool.length) {
1751
+ lines.push("by_tool:");
1752
+ for (const t of data.by_tool.slice(0, 10)) {
1753
+ 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)}%`);
1754
+ }
1755
+ }
1756
+ if (data.recent.length) {
1757
+ lines.push("recent:");
1758
+ for (const r of data.recent.slice(0, 10)) {
1759
+ lines.push(`- #${r.id} ${r.tool}: ${r.raw_tokens}->${r.output_tokens} saved=${r.saved_tokens}`);
1760
+ }
1761
+ }
1762
+ return lines.join("\n");
1763
+ }
1338
1764
  function sliceTextForOutput(input, maxChars) {
1339
1765
  const total = input.length;
1340
1766
  if (maxChars <= 0)
@@ -2524,6 +2950,12 @@ function buildServerInstructions() {
2524
2950
  "Built-in architecture and code-organization policy:",
2525
2951
  BUILTIN_ARCHITECTURE_AND_CODE_ORGANIZATION_INSTRUCTIONS,
2526
2952
  "",
2953
+ "Built-in frontend output-purity policy:",
2954
+ BUILTIN_FRONTEND_OUTPUT_PURITY_INSTRUCTIONS,
2955
+ "",
2956
+ "Built-in git commit summary policy:",
2957
+ BUILTIN_GIT_COMMIT_SUMMARY_INSTRUCTIONS,
2958
+ "",
2527
2959
  "Built-in low-overhead execution and heavy-thread policy:",
2528
2960
  BUILTIN_LOW_OVERHEAD_WORKFLOW_INSTRUCTIONS,
2529
2961
  "",
@@ -2534,11 +2966,14 @@ function buildServerInstructions() {
2534
2966
  BUILTIN_THREAD_HANDOFF_SWITCH_INSTRUCTIONS,
2535
2967
  "",
2536
2968
  "Required workflow:",
2537
- "- 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 context and retrieve relevant matches from the local memory store (vector if enabled; otherwise FTS/LIKE).",
2969
+ "- Tool outputs are compact by default. Pass format=json only when you need full structured data.",
2970
+ "- 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).",
2538
2971
  " - Output is compact by default. Use include_content=true only when you truly need full text (it increases tokens).",
2539
2972
  " - Tune output size with: requirements_limit/changes_limit/notes_limit, preview_chars, pending_limit/pending_offset.",
2540
2973
  " - Prefer read_memory_item(id, offset, limit) to fetch full text on demand instead of returning large content in other tool outputs.",
2541
2974
  "- 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.",
2975
+ "- If rtk is installed and verified (detect_rtk with gain_ok=true), prefix shell commands with rtk where possible (rtk git status, rtk npm run build, rtk rg ...) so command output is compact before it reaches the model.",
2976
+ "- 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.",
2542
2977
  "- 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.",
2543
2978
  "- 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.",
2544
2979
  "- 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.",
@@ -2557,6 +2992,7 @@ function buildServerInstructions() {
2557
2992
  "- When asked to locate code (class/function/type): call query_codebase(query) instead of guessing.",
2558
2993
  "- When you need to recall relevant context from history/code/docs: call semantic_search(query, ...) instead of guessing.",
2559
2994
  "- 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.",
2995
+ "- Use get_token_savings({ format: 'compact' }) when you need to verify how many tokens VectorMind compact outputs saved.",
2560
2996
  "",
2561
2997
  "If tool output conflicts with assumptions, trust the tool output.",
2562
2998
  ].join("\n");
@@ -2805,6 +3241,22 @@ function initDatabase() {
2805
3241
 
2806
3242
  CREATE INDEX IF NOT EXISTS idx_pending_changes_updated_at
2807
3243
  ON pending_changes(updated_at DESC);
3244
+
3245
+ CREATE TABLE IF NOT EXISTS token_savings (
3246
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
3247
+ tool TEXT NOT NULL,
3248
+ raw_tokens INTEGER NOT NULL,
3249
+ output_tokens INTEGER NOT NULL,
3250
+ saved_tokens INTEGER NOT NULL,
3251
+ savings_pct REAL NOT NULL,
3252
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
3253
+ );
3254
+
3255
+ CREATE INDEX IF NOT EXISTS idx_token_savings_created_at
3256
+ ON token_savings(created_at DESC);
3257
+
3258
+ CREATE INDEX IF NOT EXISTS idx_token_savings_tool
3259
+ ON token_savings(tool);
2808
3260
  `);
2809
3261
  initMemoryItemsFts();
2810
3262
  insertRequirementStmt = db.prepare(`INSERT INTO requirements (title, context_data, status) VALUES (?, ?, 'active')`);
@@ -2926,6 +3378,30 @@ function initDatabase() {
2926
3378
  END,
2927
3379
  name
2928
3380
  LIMIT ?`);
3381
+ insertTokenSavingsStmt = db.prepare(`INSERT INTO token_savings (tool, raw_tokens, output_tokens, saved_tokens, savings_pct)
3382
+ VALUES (?, ?, ?, ?, ?)`);
3383
+ summarizeTokenSavingsStmt = db.prepare(`SELECT
3384
+ COUNT(*) as calls,
3385
+ COALESCE(SUM(raw_tokens), 0) as raw_tokens,
3386
+ COALESCE(SUM(output_tokens), 0) as output_tokens,
3387
+ COALESCE(SUM(saved_tokens), 0) as saved_tokens,
3388
+ COALESCE(AVG(savings_pct), 0) as avg_savings_pct
3389
+ FROM token_savings`);
3390
+ summarizeTokenSavingsByToolStmt = db.prepare(`SELECT
3391
+ tool,
3392
+ COUNT(*) as calls,
3393
+ COALESCE(SUM(raw_tokens), 0) as raw_tokens,
3394
+ COALESCE(SUM(output_tokens), 0) as output_tokens,
3395
+ COALESCE(SUM(saved_tokens), 0) as saved_tokens,
3396
+ COALESCE(AVG(savings_pct), 0) as avg_savings_pct
3397
+ FROM token_savings
3398
+ GROUP BY tool
3399
+ ORDER BY saved_tokens DESC, calls DESC
3400
+ LIMIT ?`);
3401
+ listRecentTokenSavingsStmt = db.prepare(`SELECT id, tool, raw_tokens, output_tokens, saved_tokens, savings_pct, created_at
3402
+ FROM token_savings
3403
+ ORDER BY created_at DESC, id DESC
3404
+ LIMIT ?`);
2929
3405
  indexFileSymbolsTx = db.transaction((filePath, symbols) => {
2930
3406
  deleteSymbolsForFileStmt.run(filePath);
2931
3407
  for (const s of symbols) {
@@ -3120,6 +3596,21 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
3120
3596
  description: "Clear the in-memory debug activity log. Enable logging with VECTORMIND_DEBUG_LOG=1.",
3121
3597
  inputSchema: toJsonSchemaCompat(ClearActivityLogArgsSchema),
3122
3598
  },
3599
+ {
3600
+ name: "detect_rtk",
3601
+ description: "Detect whether rtk is available on PATH. When available, prefer rtk-prefixed shell commands to reduce command-output tokens.",
3602
+ inputSchema: toJsonSchemaCompat(DetectRtkArgsSchema),
3603
+ },
3604
+ {
3605
+ name: "install_rtk",
3606
+ 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.",
3607
+ inputSchema: toJsonSchemaCompat(InstallRtkArgsSchema),
3608
+ },
3609
+ {
3610
+ name: "get_token_savings",
3611
+ description: "Show VectorMind compact-output token savings recorded by MCP tools. Use this to verify raw-vs-compact output reduction.",
3612
+ inputSchema: toJsonSchemaCompat(GetTokenSavingsArgsSchema),
3613
+ },
3123
3614
  {
3124
3615
  name: "grep",
3125
3616
  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.",
@@ -3522,43 +4013,45 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3522
4013
  semantic_mode: semantic?.mode ?? null,
3523
4014
  semantic_matches: semantic?.matches?.length ?? 0,
3524
4015
  });
4016
+ const outputValue = {
4017
+ ok: true,
4018
+ generated_at: new Date().toISOString(),
4019
+ project_root: projectRoot,
4020
+ root_source: rootSource,
4021
+ db_path: dbPath,
4022
+ watcher_enabled: !!watcher,
4023
+ watcher_ready: watcherReady,
4024
+ embeddings: {
4025
+ enabled: embeddingsEnabled,
4026
+ model: embedModelName,
4027
+ embed_files: embedFilesMode,
4028
+ },
4029
+ output: {
4030
+ format: args.format,
4031
+ include_content: includeContent,
4032
+ preview_chars: previewChars,
4033
+ content_max_chars: contentMaxChars,
4034
+ requirements_limit: requirementsLimit,
4035
+ changes_limit: changesLimit,
4036
+ notes_limit: notesLimit,
4037
+ conventions_limit: conventionsLimit,
4038
+ },
4039
+ project_summary,
4040
+ conventions,
4041
+ recent_notes,
4042
+ pending_total,
4043
+ pending_offset,
4044
+ pending_limit,
4045
+ pending_truncated,
4046
+ pending_changes,
4047
+ items,
4048
+ semantic,
4049
+ };
3525
4050
  return {
3526
4051
  content: [
3527
4052
  {
3528
4053
  type: "text",
3529
- text: toolJson({
3530
- ok: true,
3531
- generated_at: new Date().toISOString(),
3532
- project_root: projectRoot,
3533
- root_source: rootSource,
3534
- db_path: dbPath,
3535
- watcher_enabled: !!watcher,
3536
- watcher_ready: watcherReady,
3537
- embeddings: {
3538
- enabled: embeddingsEnabled,
3539
- model: embedModelName,
3540
- embed_files: embedFilesMode,
3541
- },
3542
- output: {
3543
- include_content: includeContent,
3544
- preview_chars: previewChars,
3545
- content_max_chars: contentMaxChars,
3546
- requirements_limit: requirementsLimit,
3547
- changes_limit: changesLimit,
3548
- notes_limit: notesLimit,
3549
- conventions_limit: conventionsLimit,
3550
- },
3551
- project_summary,
3552
- conventions,
3553
- recent_notes,
3554
- pending_total,
3555
- pending_offset,
3556
- pending_limit,
3557
- pending_truncated,
3558
- pending_changes,
3559
- items,
3560
- semantic,
3561
- }),
4054
+ text: toolText("bootstrap_context", outputValue, compactBootstrapText(outputValue), args.format),
3562
4055
  },
3563
4056
  ],
3564
4057
  };
@@ -3599,42 +4092,45 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3599
4092
  notes_returned: recent_notes.length,
3600
4093
  conventions_returned: conventions.length,
3601
4094
  });
4095
+ const outputValue = {
4096
+ ok: true,
4097
+ generated_at: new Date().toISOString(),
4098
+ project_root: projectRoot,
4099
+ root_source: rootSource,
4100
+ db_path: dbPath,
4101
+ watcher_enabled: !!watcher,
4102
+ watcher_ready: watcherReady,
4103
+ embeddings: {
4104
+ enabled: embeddingsEnabled,
4105
+ model: embedModelName,
4106
+ embed_files: embedFilesMode,
4107
+ },
4108
+ output: {
4109
+ format: args.format,
4110
+ include_content: includeContent,
4111
+ preview_chars: previewChars,
4112
+ content_max_chars: contentMaxChars,
4113
+ requirements_limit: requirementsLimit,
4114
+ changes_limit: changesLimit,
4115
+ notes_limit: notesLimit,
4116
+ conventions_limit: conventionsLimit,
4117
+ },
4118
+ project_summary,
4119
+ conventions,
4120
+ recent_notes,
4121
+ pending_total,
4122
+ pending_offset,
4123
+ pending_limit,
4124
+ pending_truncated,
4125
+ pending_changes,
4126
+ items,
4127
+ semantic: null,
4128
+ };
3602
4129
  return {
3603
4130
  content: [
3604
4131
  {
3605
4132
  type: "text",
3606
- text: toolJson({
3607
- ok: true,
3608
- generated_at: new Date().toISOString(),
3609
- project_root: projectRoot,
3610
- root_source: rootSource,
3611
- db_path: dbPath,
3612
- watcher_enabled: !!watcher,
3613
- watcher_ready: watcherReady,
3614
- embeddings: {
3615
- enabled: embeddingsEnabled,
3616
- model: embedModelName,
3617
- embed_files: embedFilesMode,
3618
- },
3619
- output: {
3620
- include_content: includeContent,
3621
- preview_chars: previewChars,
3622
- content_max_chars: contentMaxChars,
3623
- requirements_limit: requirementsLimit,
3624
- changes_limit: changesLimit,
3625
- notes_limit: notesLimit,
3626
- conventions_limit: conventionsLimit,
3627
- },
3628
- project_summary,
3629
- conventions,
3630
- recent_notes,
3631
- pending_total,
3632
- pending_offset,
3633
- pending_limit,
3634
- pending_truncated,
3635
- pending_changes,
3636
- items,
3637
- }),
4133
+ text: toolText("get_brain_dump", outputValue, compactBrainDumpText(outputValue), args.format),
3638
4134
  },
3639
4135
  ],
3640
4136
  };
@@ -3815,6 +4311,31 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3815
4311
  clearActivityLog();
3816
4312
  return { content: [{ type: "text", text: toolJson({ ok: true }) }] };
3817
4313
  }
4314
+ if (toolName === "detect_rtk") {
4315
+ DetectRtkArgsSchema.parse(rawArgs);
4316
+ const result = detectRtk();
4317
+ const text = result.available
4318
+ ? `rtk available: ${result.version ?? result.command}\ngain_ok=${result.gain_ok ?? false}${result.path ? ` path=${result.path}` : ""}\n${result.note}`
4319
+ : `rtk unavailable: ${result.command}\ngain_ok=${result.gain_ok ?? false}${result.version ? ` version=${result.version}` : ""}\n${result.note}`;
4320
+ return { content: [{ type: "text", text }] };
4321
+ }
4322
+ if (toolName === "install_rtk") {
4323
+ const args = InstallRtkArgsSchema.parse(rawArgs);
4324
+ const result = installRtk(args);
4325
+ return { content: [{ type: "text", text: compactInstallRtkText(result) }] };
4326
+ }
4327
+ if (toolName === "get_token_savings") {
4328
+ const args = GetTokenSavingsArgsSchema.parse(rawArgs);
4329
+ const result = tokenSavingsSummary(args.limit);
4330
+ return {
4331
+ content: [
4332
+ {
4333
+ type: "text",
4334
+ text: args.format === "json" ? toolJson(result) : compactTokenSavingsText(result),
4335
+ },
4336
+ ],
4337
+ };
4338
+ }
3818
4339
  if (toolName === "grep") {
3819
4340
  const args = GrepArgsSchema.parse(rawArgs);
3820
4341
  const q = args.query;
@@ -3848,24 +4369,25 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3848
4369
  total_matches: ripgrepResult.total_matches,
3849
4370
  truncated: ripgrepResult.truncated,
3850
4371
  });
4372
+ const outputValue = {
4373
+ ok: true,
4374
+ backend: ripgrepResult.backend,
4375
+ rg_command: ripgrepResult.rg_command,
4376
+ query: q,
4377
+ mode,
4378
+ case_sensitive: caseSensitive,
4379
+ smart_case: smartCase,
4380
+ include_paths: includePaths ?? [],
4381
+ exclude_paths: excludePaths ?? [],
4382
+ matches: ripgrepResult.matches,
4383
+ total_matches: ripgrepResult.total_matches,
4384
+ truncated: ripgrepResult.truncated,
4385
+ };
3851
4386
  return {
3852
4387
  content: [
3853
4388
  {
3854
4389
  type: "text",
3855
- text: toolJson({
3856
- ok: true,
3857
- backend: ripgrepResult.backend,
3858
- rg_command: ripgrepResult.rg_command,
3859
- query: q,
3860
- mode,
3861
- case_sensitive: caseSensitive,
3862
- smart_case: smartCase,
3863
- include_paths: includePaths ?? [],
3864
- exclude_paths: excludePaths ?? [],
3865
- matches: ripgrepResult.matches,
3866
- total_matches: ripgrepResult.total_matches,
3867
- truncated: ripgrepResult.truncated,
3868
- }),
4390
+ text: toolCompactOrJson("grep", outputValue, compactGrepText(outputValue), args.format),
3869
4391
  },
3870
4392
  ],
3871
4393
  };
@@ -3942,28 +4464,29 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3942
4464
  matches: indexedResult.matches.length,
3943
4465
  truncated: indexedResult.truncated,
3944
4466
  });
4467
+ const outputValue = {
4468
+ ok: true,
4469
+ backend: indexedResult.backend,
4470
+ fallback_reason: "ripgrep_unavailable",
4471
+ ripgrep_error: ripgrepResult.error,
4472
+ ripgrep_attempts: ripgrepResult.attempts,
4473
+ query: q,
4474
+ mode,
4475
+ case_sensitive: caseSensitive,
4476
+ smart_case: smartCase,
4477
+ hint: indexedResult.hint,
4478
+ kinds,
4479
+ include_paths: includePaths ?? [],
4480
+ exclude_paths: excludePaths ?? [],
4481
+ candidates: indexedResult.candidates,
4482
+ matches: indexedResult.matches,
4483
+ truncated: indexedResult.truncated,
4484
+ };
3945
4485
  return {
3946
4486
  content: [
3947
4487
  {
3948
4488
  type: "text",
3949
- text: toolJson({
3950
- ok: true,
3951
- backend: indexedResult.backend,
3952
- fallback_reason: "ripgrep_unavailable",
3953
- ripgrep_error: ripgrepResult.error,
3954
- ripgrep_attempts: ripgrepResult.attempts,
3955
- query: q,
3956
- mode,
3957
- case_sensitive: caseSensitive,
3958
- smart_case: smartCase,
3959
- hint: indexedResult.hint,
3960
- kinds,
3961
- include_paths: includePaths ?? [],
3962
- exclude_paths: excludePaths ?? [],
3963
- candidates: indexedResult.candidates,
3964
- matches: indexedResult.matches,
3965
- truncated: indexedResult.truncated,
3966
- }),
4489
+ text: toolCompactOrJson("grep", outputValue, compactGrepText(outputValue), args.format),
3967
4490
  },
3968
4491
  ],
3969
4492
  };
@@ -4015,28 +4538,29 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4015
4538
  truncated: result.truncated,
4016
4539
  path_kind: st.isFile() ? "file" : st.isDirectory() ? "dir" : "other",
4017
4540
  });
4541
+ const outputValue = {
4542
+ ok: true,
4543
+ path: resolved.dbFilePath,
4544
+ path_kind: st.isFile() ? "file" : st.isDirectory() ? "dir" : "other",
4545
+ recursive: args.recursive,
4546
+ max_depth: args.recursive ? args.max_depth : 1,
4547
+ include_files: args.include_files,
4548
+ include_dirs: args.include_dirs,
4549
+ include_hidden: args.include_hidden,
4550
+ respect_ignore: args.respect_ignore,
4551
+ include_paths: includePaths ?? [],
4552
+ exclude_paths: excludePaths ?? [],
4553
+ extensions: extensions ?? [],
4554
+ returned: result.returned,
4555
+ scanned: result.scanned,
4556
+ truncated: result.truncated,
4557
+ entries: result.entries,
4558
+ };
4018
4559
  return {
4019
4560
  content: [
4020
4561
  {
4021
4562
  type: "text",
4022
- text: toolJson({
4023
- ok: true,
4024
- path: resolved.dbFilePath,
4025
- path_kind: st.isFile() ? "file" : st.isDirectory() ? "dir" : "other",
4026
- recursive: args.recursive,
4027
- max_depth: args.recursive ? args.max_depth : 1,
4028
- include_files: args.include_files,
4029
- include_dirs: args.include_dirs,
4030
- include_hidden: args.include_hidden,
4031
- respect_ignore: args.respect_ignore,
4032
- include_paths: includePaths ?? [],
4033
- exclude_paths: excludePaths ?? [],
4034
- extensions: extensions ?? [],
4035
- returned: result.returned,
4036
- scanned: result.scanned,
4037
- truncated: result.truncated,
4038
- entries: result.entries,
4039
- }),
4563
+ text: toolCompactOrJson("list_project_files", outputValue, compactListProjectFilesText(outputValue), args.format),
4040
4564
  },
4041
4565
  ],
4042
4566
  };
@@ -4076,19 +4600,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4076
4600
  total_chars: result.totalChars,
4077
4601
  truncated: result.truncated,
4078
4602
  });
4603
+ const outputValue = {
4604
+ ok: true,
4605
+ file_path: resolved.dbFilePath,
4606
+ offset: args.offset,
4607
+ returned_chars: result.returnedChars,
4608
+ total_chars: result.totalChars,
4609
+ truncated: result.truncated,
4610
+ text: result.text,
4611
+ };
4079
4612
  return {
4080
4613
  content: [
4081
4614
  {
4082
4615
  type: "text",
4083
- text: toolJson({
4084
- ok: true,
4085
- file_path: resolved.dbFilePath,
4086
- offset: args.offset,
4087
- returned_chars: result.returnedChars,
4088
- total_chars: result.totalChars,
4089
- truncated: result.truncated,
4090
- text: result.text,
4091
- }),
4616
+ text: toolCompactOrJson("read_file_text", outputValue, compactReadTextFileText(outputValue), args.format),
4092
4617
  },
4093
4618
  ],
4094
4619
  };
@@ -4135,20 +4660,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4135
4660
  total_chars: result.totalChars,
4136
4661
  truncated: result.truncated,
4137
4662
  });
4663
+ const outputValue = {
4664
+ ok: true,
4665
+ file_path: resolved.displayPath,
4666
+ allowed_root: resolved.allowedRoot,
4667
+ offset: args.offset,
4668
+ returned_chars: result.returnedChars,
4669
+ total_chars: result.totalChars,
4670
+ truncated: result.truncated,
4671
+ text: result.text,
4672
+ };
4138
4673
  return {
4139
4674
  content: [
4140
4675
  {
4141
4676
  type: "text",
4142
- text: toolJson({
4143
- ok: true,
4144
- file_path: resolved.displayPath,
4145
- allowed_root: resolved.allowedRoot,
4146
- offset: args.offset,
4147
- returned_chars: result.returnedChars,
4148
- total_chars: result.totalChars,
4149
- truncated: result.truncated,
4150
- text: result.text,
4151
- }),
4677
+ text: toolCompactOrJson("read_codex_text_file", outputValue, compactReadTextFileText(outputValue), args.format),
4152
4678
  },
4153
4679
  ],
4154
4680
  };
@@ -4210,19 +4736,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4210
4736
  returned: result.returned,
4211
4737
  truncated: result.truncated,
4212
4738
  });
4739
+ const outputValue = {
4740
+ ok: true,
4741
+ file_path: resolved.dbFilePath,
4742
+ from_line: fromLine,
4743
+ to_line: toLine,
4744
+ returned: result.returned,
4745
+ truncated: result.truncated,
4746
+ text: result.text,
4747
+ };
4213
4748
  return {
4214
4749
  content: [
4215
4750
  {
4216
4751
  type: "text",
4217
- text: toolJson({
4218
- ok: true,
4219
- file_path: resolved.dbFilePath,
4220
- from_line: fromLine,
4221
- to_line: toLine,
4222
- returned: result.returned,
4223
- truncated: result.truncated,
4224
- text: result.text,
4225
- }),
4752
+ text: toolCompactOrJson("read_file_lines", outputValue, compactReadFileLinesText(outputValue), args.format),
4226
4753
  },
4227
4754
  ],
4228
4755
  };
@@ -4239,11 +4766,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4239
4766
  matches: filtered.length,
4240
4767
  sample: filtered.slice(0, 10).map((m) => ({ name: m.name, type: m.type, file_path: m.file_path })),
4241
4768
  });
4769
+ const outputValue = { ok: true, query: q, matches: filtered };
4242
4770
  return {
4243
4771
  content: [
4244
4772
  {
4245
4773
  type: "text",
4246
- text: toolJson({ ok: true, query: q, matches: filtered }),
4774
+ text: toolCompactOrJson("query_codebase", outputValue, compactQueryCodebaseText(outputValue), args.format),
4247
4775
  },
4248
4776
  ],
4249
4777
  };
@@ -4342,11 +4870,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4342
4870
  score: m.score,
4343
4871
  })),
4344
4872
  });
4873
+ const outputValue = { ok: true, ...result };
4345
4874
  return {
4346
4875
  content: [
4347
4876
  {
4348
4877
  type: "text",
4349
- text: toolJson({ ok: true, ...result }),
4878
+ text: toolCompactOrJson("semantic_search", outputValue, compactSemanticSearchText(outputValue), args.format),
4350
4879
  },
4351
4880
  ],
4352
4881
  };