grepmax 0.7.12 → 0.7.14

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.
@@ -226,6 +226,10 @@ const TOOLS = [
226
226
  type: "string",
227
227
  description: "The function, method, or class name to trace (e.g. 'handleAuth')",
228
228
  },
229
+ depth: {
230
+ type: "number",
231
+ description: "Traversal depth for callers (default 1, max 3). depth: 2 shows callers-of-callers.",
232
+ },
229
233
  },
230
234
  required: ["symbol"],
231
235
  },
@@ -289,6 +293,24 @@ const TOOLS = [
289
293
  },
290
294
  },
291
295
  },
296
+ {
297
+ name: "related_files",
298
+ description: "Find files related to a given file by shared symbol references. Shows dependencies (what this file calls) and dependents (what calls this file).",
299
+ inputSchema: {
300
+ type: "object",
301
+ properties: {
302
+ file: {
303
+ type: "string",
304
+ description: "File path relative to project root (e.g. 'src/lib/index/syncer.ts')",
305
+ },
306
+ limit: {
307
+ type: "number",
308
+ description: "Max related files per direction (default 10)",
309
+ },
310
+ },
311
+ required: ["file"],
312
+ },
313
+ },
292
314
  ];
293
315
  // ---------------------------------------------------------------------------
294
316
  // Helpers
@@ -861,20 +883,29 @@ exports.mcp = new commander_1.Command("mcp")
861
883
  try {
862
884
  const db = getVectorDb();
863
885
  const builder = new graph_builder_1.GraphBuilder(db);
864
- const graph = yield builder.buildGraph(symbol);
886
+ const depth = Math.min(Math.max(Number(args.depth) || 1, 1), 3);
887
+ const graph = yield builder.buildGraphMultiHop(symbol, depth);
865
888
  if (!graph.center) {
866
889
  return ok(`Symbol '${symbol}' not found in the index.`);
867
890
  }
868
891
  const lines = [];
869
892
  // Center
870
893
  lines.push(`${graph.center.symbol} [${graph.center.role}] ${graph.center.file}:${graph.center.line + 1}`);
871
- // Callers
872
- if (graph.callers.length > 0) {
873
- lines.push("Callers:");
874
- for (const caller of graph.callers) {
875
- lines.push(` <- ${caller.symbol} ${caller.file}:${caller.line + 1}`);
894
+ // Callers (recursive tree)
895
+ function formatCallerTree(trees, indent) {
896
+ for (const t of trees) {
897
+ const rel = t.node.file.startsWith(projectRoot)
898
+ ? t.node.file.slice(projectRoot.length + 1)
899
+ : t.node.file;
900
+ const pad = " ".repeat(indent);
901
+ lines.push(`${pad}<- ${t.node.symbol} ${rel}:${t.node.line + 1}`);
902
+ formatCallerTree(t.callers, indent + 1);
876
903
  }
877
904
  }
905
+ if (graph.callerTree.length > 0) {
906
+ lines.push("Callers:");
907
+ formatCallerTree(graph.callerTree, 1);
908
+ }
878
909
  else {
879
910
  lines.push("Callers: none");
880
911
  }
@@ -1197,6 +1228,107 @@ exports.mcp = new commander_1.Command("mcp")
1197
1228
  }
1198
1229
  });
1199
1230
  }
1231
+ function handleRelatedFiles(args) {
1232
+ return __awaiter(this, void 0, void 0, function* () {
1233
+ const file = String(args.file || "");
1234
+ if (!file)
1235
+ return err("Missing required parameter: file");
1236
+ const limit = Math.min(Math.max(Number(args.limit) || 10, 1), 25);
1237
+ const absPath = path.resolve(projectRoot, file);
1238
+ try {
1239
+ const db = getVectorDb();
1240
+ const table = yield db.ensureTable();
1241
+ const fileChunks = yield table
1242
+ .query()
1243
+ .select(["defined_symbols", "referenced_symbols"])
1244
+ .where(`path = '${(0, filter_builder_1.escapeSqlString)(absPath)}'`)
1245
+ .toArray();
1246
+ if (fileChunks.length === 0) {
1247
+ return ok(`File not found in index: ${file}`);
1248
+ }
1249
+ const definedHere = new Set();
1250
+ const referencedHere = new Set();
1251
+ for (const chunk of fileChunks) {
1252
+ for (const s of toStringArray(chunk.defined_symbols))
1253
+ definedHere.add(s);
1254
+ for (const s of toStringArray(chunk.referenced_symbols))
1255
+ referencedHere.add(s);
1256
+ }
1257
+ // Dependencies: files that DEFINE symbols this file REFERENCES
1258
+ const depCounts = new Map();
1259
+ for (const sym of referencedHere) {
1260
+ if (definedHere.has(sym))
1261
+ continue;
1262
+ const rows = yield table
1263
+ .query()
1264
+ .select(["path"])
1265
+ .where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(sym)}')`)
1266
+ .limit(3)
1267
+ .toArray();
1268
+ for (const row of rows) {
1269
+ const p = String(row.path || "");
1270
+ if (p === absPath)
1271
+ continue;
1272
+ depCounts.set(p, (depCounts.get(p) || 0) + 1);
1273
+ }
1274
+ }
1275
+ // Dependents: files that REFERENCE symbols this file DEFINES
1276
+ const revCounts = new Map();
1277
+ for (const sym of definedHere) {
1278
+ const rows = yield table
1279
+ .query()
1280
+ .select(["path"])
1281
+ .where(`array_contains(referenced_symbols, '${(0, filter_builder_1.escapeSqlString)(sym)}')`)
1282
+ .limit(20)
1283
+ .toArray();
1284
+ for (const row of rows) {
1285
+ const p = String(row.path || "");
1286
+ if (p === absPath)
1287
+ continue;
1288
+ revCounts.set(p, (revCounts.get(p) || 0) + 1);
1289
+ }
1290
+ }
1291
+ const lines = [];
1292
+ lines.push(`Related files for ${file}:\n`);
1293
+ const topDeps = Array.from(depCounts.entries())
1294
+ .sort((a, b) => b[1] - a[1])
1295
+ .slice(0, limit);
1296
+ if (topDeps.length > 0) {
1297
+ lines.push("Dependencies (files this imports/calls):");
1298
+ for (const [p, count] of topDeps) {
1299
+ const rel = p.startsWith(`${projectRoot}/`)
1300
+ ? p.slice(projectRoot.length + 1)
1301
+ : p;
1302
+ lines.push(` ${rel.padEnd(40)} (${count} shared symbol${count > 1 ? "s" : ""})`);
1303
+ }
1304
+ }
1305
+ else {
1306
+ lines.push("Dependencies: none found");
1307
+ }
1308
+ lines.push("");
1309
+ const topRevs = Array.from(revCounts.entries())
1310
+ .sort((a, b) => b[1] - a[1])
1311
+ .slice(0, limit);
1312
+ if (topRevs.length > 0) {
1313
+ lines.push("Dependents (files that call this):");
1314
+ for (const [p, count] of topRevs) {
1315
+ const rel = p.startsWith(`${projectRoot}/`)
1316
+ ? p.slice(projectRoot.length + 1)
1317
+ : p;
1318
+ lines.push(` ${rel.padEnd(40)} (${count} shared symbol${count > 1 ? "s" : ""})`);
1319
+ }
1320
+ }
1321
+ else {
1322
+ lines.push("Dependents: none found");
1323
+ }
1324
+ return ok(lines.join("\n"));
1325
+ }
1326
+ catch (e) {
1327
+ const msg = e instanceof Error ? e.message : String(e);
1328
+ return err(`Related files failed: ${msg}`);
1329
+ }
1330
+ });
1331
+ }
1200
1332
  // --- MCP server setup ---
1201
1333
  const transport = new stdio_js_1.StdioServerTransport();
1202
1334
  const server = new index_js_1.Server({
@@ -1232,6 +1364,8 @@ exports.mcp = new commander_1.Command("mcp")
1232
1364
  return handleSummarizeDirectory(toolArgs);
1233
1365
  case "summarize_project":
1234
1366
  return handleSummarizeProject(toolArgs);
1367
+ case "related_files":
1368
+ return handleRelatedFiles(toolArgs);
1235
1369
  default:
1236
1370
  return err(`Unknown tool: ${name}`);
1237
1371
  }
@@ -105,6 +105,40 @@ class GraphBuilder {
105
105
  return { center, callers, callees: calleeNodes };
106
106
  });
107
107
  }
108
+ buildGraphMultiHop(symbol, depth) {
109
+ return __awaiter(this, void 0, void 0, function* () {
110
+ const graph = yield this.buildGraph(symbol);
111
+ if (depth <= 1 || !graph.center) {
112
+ return {
113
+ center: graph.center,
114
+ callerTree: graph.callers.map((c) => ({ node: c, callers: [] })),
115
+ callees: graph.callees,
116
+ };
117
+ }
118
+ const visited = new Set([symbol]);
119
+ const callerTree = yield this.expandCallers(graph.callers, depth - 1, visited);
120
+ return { center: graph.center, callerTree, callees: graph.callees };
121
+ });
122
+ }
123
+ expandCallers(callers, remainingDepth, visited) {
124
+ return __awaiter(this, void 0, void 0, function* () {
125
+ const trees = [];
126
+ for (const caller of callers) {
127
+ if (visited.has(caller.symbol)) {
128
+ trees.push({ node: caller, callers: [] });
129
+ continue;
130
+ }
131
+ visited.add(caller.symbol);
132
+ let subCallers = [];
133
+ if (remainingDepth > 0) {
134
+ const upstreamCallers = yield this.getCallers(caller.symbol);
135
+ subCallers = yield this.expandCallers(upstreamCallers, remainingDepth - 1, visited);
136
+ }
137
+ trees.push({ node: caller, callers: subCallers });
138
+ }
139
+ return trees;
140
+ });
141
+ }
108
142
  mapRowToNode(row, targetSymbol, type) {
109
143
  // Helper to convert Arrow Vector to array if needed
110
144
  const toArray = (val) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.7.12",
3
+ "version": "0.7.14",
4
4
  "author": "Robert Owens <robowens@me.com>",
5
5
  "homepage": "https://github.com/reowens/grepmax",
6
6
  "bugs": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.7.12",
3
+ "version": "0.7.14",
4
4
  "description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
5
5
  "author": {
6
6
  "name": "Robert Owens",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: grepmax
3
3
  description: Semantic code search. Use alongside grep - grep for exact strings, gmax for concepts.
4
- allowed-tools: "mcp__grepmax__semantic_search, mcp__grepmax__search_all, mcp__grepmax__code_skeleton, mcp__grepmax__trace_calls, mcp__grepmax__list_symbols, mcp__grepmax__index_status, mcp__grepmax__summarize_directory, mcp__grepmax__summarize_project, Bash(gmax:*), Read"
4
+ allowed-tools: "mcp__grepmax__semantic_search, mcp__grepmax__search_all, mcp__grepmax__code_skeleton, mcp__grepmax__trace_calls, mcp__grepmax__list_symbols, mcp__grepmax__index_status, mcp__grepmax__summarize_directory, mcp__grepmax__summarize_project, mcp__grepmax__related_files, Bash(gmax:*), Read"
5
5
  ---
6
6
 
7
7
  ## What gmax does
@@ -71,6 +71,7 @@ File or directory structure — signatures with bodies collapsed (~4x fewer toke
71
71
  ### trace_calls
72
72
  Call graph — who calls a symbol and what it calls. Callers and callees include file:line locations. Unscoped — follows calls across all indexed directories.
73
73
  - `symbol` (required): Function/method/class name
74
+ - `depth` (optional): Traversal depth for callers (default 1, max 3). depth: 2 shows callers-of-callers with indentation.
74
75
 
75
76
  ### list_symbols
76
77
  List indexed symbols with definition locations, role, and export status.
@@ -84,6 +85,11 @@ Output: `symbolName [ORCH] exported src/path/file.ts:42`
84
85
  High-level project overview — languages, directory structure, role distribution, key symbols, entry points. Use when first exploring a new codebase.
85
86
  - `root` (optional): Project root path. Defaults to current project.
86
87
 
88
+ ### related_files
89
+ Find files related to a given file by shared symbol references. Shows dependencies (what this file calls) and dependents (what calls this file).
90
+ - `file` (required): File path relative to project root
91
+ - `limit` (optional): Max results per direction (default 10)
92
+
87
93
  ### index_status
88
94
  Check centralized index health — chunks, files, indexed directories, model info, watcher status.
89
95