@coreyuan/vector-mind 1.0.38 → 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_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.38";
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)
@@ -2527,6 +2953,9 @@ function buildServerInstructions() {
2527
2953
  "Built-in frontend output-purity policy:",
2528
2954
  BUILTIN_FRONTEND_OUTPUT_PURITY_INSTRUCTIONS,
2529
2955
  "",
2956
+ "Built-in git commit summary policy:",
2957
+ BUILTIN_GIT_COMMIT_SUMMARY_INSTRUCTIONS,
2958
+ "",
2530
2959
  "Built-in low-overhead execution and heavy-thread policy:",
2531
2960
  BUILTIN_LOW_OVERHEAD_WORKFLOW_INSTRUCTIONS,
2532
2961
  "",
@@ -2537,11 +2966,14 @@ function buildServerInstructions() {
2537
2966
  BUILTIN_THREAD_HANDOFF_SWITCH_INSTRUCTIONS,
2538
2967
  "",
2539
2968
  "Required workflow:",
2540
- "- 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).",
2541
2971
  " - Output is compact by default. Use include_content=true only when you truly need full text (it increases tokens).",
2542
2972
  " - Tune output size with: requirements_limit/changes_limit/notes_limit, preview_chars, pending_limit/pending_offset.",
2543
2973
  " - Prefer read_memory_item(id, offset, limit) to fetch full text on demand instead of returning large content in other tool outputs.",
2544
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.",
2545
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.",
2546
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.",
2547
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.",
@@ -2560,6 +2992,7 @@ function buildServerInstructions() {
2560
2992
  "- When asked to locate code (class/function/type): call query_codebase(query) instead of guessing.",
2561
2993
  "- When you need to recall relevant context from history/code/docs: call semantic_search(query, ...) instead of guessing.",
2562
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.",
2563
2996
  "",
2564
2997
  "If tool output conflicts with assumptions, trust the tool output.",
2565
2998
  ].join("\n");
@@ -2808,6 +3241,22 @@ function initDatabase() {
2808
3241
 
2809
3242
  CREATE INDEX IF NOT EXISTS idx_pending_changes_updated_at
2810
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);
2811
3260
  `);
2812
3261
  initMemoryItemsFts();
2813
3262
  insertRequirementStmt = db.prepare(`INSERT INTO requirements (title, context_data, status) VALUES (?, ?, 'active')`);
@@ -2929,6 +3378,30 @@ function initDatabase() {
2929
3378
  END,
2930
3379
  name
2931
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 ?`);
2932
3405
  indexFileSymbolsTx = db.transaction((filePath, symbols) => {
2933
3406
  deleteSymbolsForFileStmt.run(filePath);
2934
3407
  for (const s of symbols) {
@@ -3123,6 +3596,21 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
3123
3596
  description: "Clear the in-memory debug activity log. Enable logging with VECTORMIND_DEBUG_LOG=1.",
3124
3597
  inputSchema: toJsonSchemaCompat(ClearActivityLogArgsSchema),
3125
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
+ },
3126
3614
  {
3127
3615
  name: "grep",
3128
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.",
@@ -3525,43 +4013,45 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3525
4013
  semantic_mode: semantic?.mode ?? null,
3526
4014
  semantic_matches: semantic?.matches?.length ?? 0,
3527
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
+ };
3528
4050
  return {
3529
4051
  content: [
3530
4052
  {
3531
4053
  type: "text",
3532
- text: toolJson({
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
- }),
4054
+ text: toolText("bootstrap_context", outputValue, compactBootstrapText(outputValue), args.format),
3565
4055
  },
3566
4056
  ],
3567
4057
  };
@@ -3602,42 +4092,45 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3602
4092
  notes_returned: recent_notes.length,
3603
4093
  conventions_returned: conventions.length,
3604
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
+ };
3605
4129
  return {
3606
4130
  content: [
3607
4131
  {
3608
4132
  type: "text",
3609
- text: toolJson({
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
- }),
4133
+ text: toolText("get_brain_dump", outputValue, compactBrainDumpText(outputValue), args.format),
3641
4134
  },
3642
4135
  ],
3643
4136
  };
@@ -3818,6 +4311,31 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3818
4311
  clearActivityLog();
3819
4312
  return { content: [{ type: "text", text: toolJson({ ok: true }) }] };
3820
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
+ }
3821
4339
  if (toolName === "grep") {
3822
4340
  const args = GrepArgsSchema.parse(rawArgs);
3823
4341
  const q = args.query;
@@ -3851,24 +4369,25 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3851
4369
  total_matches: ripgrepResult.total_matches,
3852
4370
  truncated: ripgrepResult.truncated,
3853
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
+ };
3854
4386
  return {
3855
4387
  content: [
3856
4388
  {
3857
4389
  type: "text",
3858
- text: toolJson({
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
- }),
4390
+ text: toolCompactOrJson("grep", outputValue, compactGrepText(outputValue), args.format),
3872
4391
  },
3873
4392
  ],
3874
4393
  };
@@ -3945,28 +4464,29 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3945
4464
  matches: indexedResult.matches.length,
3946
4465
  truncated: indexedResult.truncated,
3947
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
+ };
3948
4485
  return {
3949
4486
  content: [
3950
4487
  {
3951
4488
  type: "text",
3952
- text: toolJson({
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
- }),
4489
+ text: toolCompactOrJson("grep", outputValue, compactGrepText(outputValue), args.format),
3970
4490
  },
3971
4491
  ],
3972
4492
  };
@@ -4018,28 +4538,29 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4018
4538
  truncated: result.truncated,
4019
4539
  path_kind: st.isFile() ? "file" : st.isDirectory() ? "dir" : "other",
4020
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
+ };
4021
4559
  return {
4022
4560
  content: [
4023
4561
  {
4024
4562
  type: "text",
4025
- text: toolJson({
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
- }),
4563
+ text: toolCompactOrJson("list_project_files", outputValue, compactListProjectFilesText(outputValue), args.format),
4043
4564
  },
4044
4565
  ],
4045
4566
  };
@@ -4079,19 +4600,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4079
4600
  total_chars: result.totalChars,
4080
4601
  truncated: result.truncated,
4081
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
+ };
4082
4612
  return {
4083
4613
  content: [
4084
4614
  {
4085
4615
  type: "text",
4086
- text: toolJson({
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
- }),
4616
+ text: toolCompactOrJson("read_file_text", outputValue, compactReadTextFileText(outputValue), args.format),
4095
4617
  },
4096
4618
  ],
4097
4619
  };
@@ -4138,20 +4660,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4138
4660
  total_chars: result.totalChars,
4139
4661
  truncated: result.truncated,
4140
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
+ };
4141
4673
  return {
4142
4674
  content: [
4143
4675
  {
4144
4676
  type: "text",
4145
- text: toolJson({
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
- }),
4677
+ text: toolCompactOrJson("read_codex_text_file", outputValue, compactReadTextFileText(outputValue), args.format),
4155
4678
  },
4156
4679
  ],
4157
4680
  };
@@ -4213,19 +4736,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4213
4736
  returned: result.returned,
4214
4737
  truncated: result.truncated,
4215
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
+ };
4216
4748
  return {
4217
4749
  content: [
4218
4750
  {
4219
4751
  type: "text",
4220
- text: toolJson({
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
- }),
4752
+ text: toolCompactOrJson("read_file_lines", outputValue, compactReadFileLinesText(outputValue), args.format),
4229
4753
  },
4230
4754
  ],
4231
4755
  };
@@ -4242,11 +4766,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4242
4766
  matches: filtered.length,
4243
4767
  sample: filtered.slice(0, 10).map((m) => ({ name: m.name, type: m.type, file_path: m.file_path })),
4244
4768
  });
4769
+ const outputValue = { ok: true, query: q, matches: filtered };
4245
4770
  return {
4246
4771
  content: [
4247
4772
  {
4248
4773
  type: "text",
4249
- text: toolJson({ ok: true, query: q, matches: filtered }),
4774
+ text: toolCompactOrJson("query_codebase", outputValue, compactQueryCodebaseText(outputValue), args.format),
4250
4775
  },
4251
4776
  ],
4252
4777
  };
@@ -4345,11 +4870,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4345
4870
  score: m.score,
4346
4871
  })),
4347
4872
  });
4873
+ const outputValue = { ok: true, ...result };
4348
4874
  return {
4349
4875
  content: [
4350
4876
  {
4351
4877
  type: "text",
4352
- text: toolJson({ ok: true, ...result }),
4878
+ text: toolCompactOrJson("semantic_search", outputValue, compactSemanticSearchText(outputValue), args.format),
4353
4879
  },
4354
4880
  ],
4355
4881
  };