grepmax 0.7.3 → 0.7.5

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.
@@ -103,6 +103,14 @@ const TOOLS = [
103
103
  type: "number",
104
104
  description: "Max results per file (default: no cap). Useful to get diversity across files.",
105
105
  },
106
+ file: {
107
+ type: "string",
108
+ description: "Filter to files matching this name (e.g. 'syncer.ts'). Matches the filename, not the full path.",
109
+ },
110
+ exclude: {
111
+ type: "string",
112
+ description: "Exclude files under this path prefix (e.g. 'tests/' or 'dist/').",
113
+ },
106
114
  },
107
115
  required: ["query"],
108
116
  },
@@ -133,6 +141,14 @@ const TOOLS = [
133
141
  type: "number",
134
142
  description: "Max results per file (default: no cap).",
135
143
  },
144
+ file: {
145
+ type: "string",
146
+ description: "Filter to files matching this name (e.g. 'syncer.ts').",
147
+ },
148
+ exclude: {
149
+ type: "string",
150
+ description: "Exclude files under this path prefix (e.g. 'tests/').",
151
+ },
136
152
  },
137
153
  required: ["query"],
138
154
  },
@@ -304,10 +320,39 @@ exports.mcp = new commander_1.Command("mcp")
304
320
  });
305
321
  }
306
322
  // --- Index sync ---
323
+ let _indexChildPid = null;
324
+ function isIndexProcessRunning() {
325
+ if (!_indexChildPid)
326
+ return false;
327
+ try {
328
+ process.kill(_indexChildPid, 0);
329
+ return true;
330
+ }
331
+ catch (_a) {
332
+ return false;
333
+ }
334
+ }
307
335
  function ensureIndexReady() {
308
336
  return __awaiter(this, void 0, void 0, function* () {
337
+ var _a;
309
338
  if (_indexReady)
310
339
  return;
340
+ // Check if a previously spawned index process finished
341
+ if (_indexing && !isIndexProcessRunning()) {
342
+ _indexing = false;
343
+ _indexProgress = "";
344
+ _indexChildPid = null;
345
+ // Re-check if index now exists
346
+ try {
347
+ const db = getVectorDb();
348
+ if (yield db.hasRowsForPath(projectRoot)) {
349
+ _indexReady = true;
350
+ console.log("[MCP] Background indexing complete.");
351
+ return;
352
+ }
353
+ }
354
+ catch (_b) { }
355
+ }
311
356
  try {
312
357
  const db = getVectorDb();
313
358
  const hasIndex = yield db.hasRowsForPath(projectRoot);
@@ -316,23 +361,25 @@ exports.mcp = new commander_1.Command("mcp")
316
361
  return; // Already indexing in background
317
362
  _indexing = true;
318
363
  _indexProgress = "starting...";
319
- console.log("[MCP] No index found, running initial sync...");
320
- (0, syncer_1.initialSync)({
321
- projectRoot,
322
- onProgress: (info) => {
323
- _indexProgress = `${info.processed}/${info.total || "?"} files`;
324
- },
325
- })
326
- .then(() => {
327
- _indexReady = true;
328
- _indexing = false;
329
- _indexProgress = "";
330
- console.log("[MCP] Initial sync complete.");
331
- })
332
- .catch((e) => {
364
+ console.log("[MCP] No index found, spawning background index...");
365
+ // Spawn gmax index as a detached child process — doesn't hold
366
+ // the lock inside this MCP process, so CLI `gmax index` won't conflict.
367
+ const child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], "index", "--path", projectRoot], { detached: true, stdio: "ignore" });
368
+ _indexChildPid = (_a = child.pid) !== null && _a !== void 0 ? _a : null;
369
+ child.unref();
370
+ _indexProgress = `PID ${_indexChildPid}`;
371
+ console.log(`[MCP] Background index started (PID: ${_indexChildPid})`);
372
+ child.on("exit", (code) => {
333
373
  _indexing = false;
334
374
  _indexProgress = "";
335
- console.error("[MCP] Index sync failed:", e);
375
+ _indexChildPid = null;
376
+ if (code === 0) {
377
+ _indexReady = true;
378
+ console.log("[MCP] Background indexing complete.");
379
+ }
380
+ else {
381
+ console.error(`[MCP] Background indexing failed (exit code: ${code})`);
382
+ }
336
383
  });
337
384
  }
338
385
  else {
@@ -391,7 +438,14 @@ exports.mcp = new commander_1.Command("mcp")
391
438
  pathPrefix += "/";
392
439
  }
393
440
  }
394
- const result = yield searcher.search(query, limit, { rerank: true }, undefined, pathPrefix);
441
+ const filters = {};
442
+ if (typeof args.file === "string" && args.file) {
443
+ filters.file = args.file;
444
+ }
445
+ if (typeof args.exclude === "string" && args.exclude) {
446
+ filters.exclude = args.exclude;
447
+ }
448
+ const result = yield searcher.search(query, limit, { rerank: true }, Object.keys(filters).length > 0 ? filters : undefined, pathPrefix);
395
449
  if (!result.data || result.data.length === 0) {
396
450
  return ok("No matches found.");
397
451
  }
@@ -539,11 +593,23 @@ exports.mcp = new commander_1.Command("mcp")
539
593
  else {
540
594
  lines.push("Callers: none");
541
595
  }
542
- // Callees (cap at 15)
596
+ // Callees with file paths
543
597
  if (graph.callees.length > 0) {
544
- const capped = graph.callees.slice(0, 15);
545
- const suffix = graph.callees.length > 15 ? ` (+${graph.callees.length - 15} more)` : "";
546
- lines.push(`Calls: ${capped.join(", ")}${suffix}`);
598
+ lines.push("Calls:");
599
+ for (const callee of graph.callees.slice(0, 15)) {
600
+ if (callee.file) {
601
+ const rel = callee.file.startsWith(projectRoot)
602
+ ? callee.file.slice(projectRoot.length + 1)
603
+ : callee.file;
604
+ lines.push(` -> ${callee.symbol} ${rel}:${callee.line + 1}`);
605
+ }
606
+ else {
607
+ lines.push(` -> ${callee.symbol} (not indexed)`);
608
+ }
609
+ }
610
+ if (graph.callees.length > 15) {
611
+ lines.push(` (+${graph.callees.length - 15} more)`);
612
+ }
547
613
  }
548
614
  else {
549
615
  lines.push("Calls: none");
@@ -664,7 +730,23 @@ exports.mcp = new commander_1.Command("mcp")
664
730
  indexingLine,
665
731
  "",
666
732
  "Indexed directories:",
667
- ...projects.map((p) => { var _a; return ` ${p.name}\t${p.root}\t${(_a = p.lastIndexed) !== null && _a !== void 0 ? _a : "unknown"}`; }),
733
+ ...(yield Promise.all(projects.map((p) => __awaiter(this, void 0, void 0, function* () {
734
+ var _a, _b;
735
+ const prefix = p.root.endsWith("/") ? p.root : `${p.root}/`;
736
+ try {
737
+ const table = yield db.ensureTable();
738
+ const rows = yield table
739
+ .query()
740
+ .select(["id"])
741
+ .where(`path LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`)
742
+ .limit(100000)
743
+ .toArray();
744
+ return ` ${p.name}\t${p.root}\t${(_a = p.lastIndexed) !== null && _a !== void 0 ? _a : "unknown"}\t(${rows.length} chunks)`;
745
+ }
746
+ catch (_c) {
747
+ return ` ${p.name}\t${p.root}\t${(_b = p.lastIndexed) !== null && _b !== void 0 ? _b : "unknown"}`;
748
+ }
749
+ })))),
668
750
  ].filter(Boolean);
669
751
  return ok(lines.join("\n"));
670
752
  }
@@ -69,9 +69,40 @@ class GraphBuilder {
69
69
  : null;
70
70
  // 2. Get Callers
71
71
  const callers = yield this.getCallers(symbol);
72
- // 3. Get Callees (from center)
73
- const callees = center ? center.calls : [];
74
- return { center, callers, callees };
72
+ // 3. Get Callees resolve each to a GraphNode with file:line
73
+ const calleeNames = center ? center.calls.slice(0, 15) : [];
74
+ const calleeNodes = [];
75
+ for (const name of calleeNames) {
76
+ const esc = (0, filter_builder_1.escapeSqlString)(name);
77
+ const rows = yield table
78
+ .query()
79
+ .where(`array_contains(defined_symbols, '${esc}')`)
80
+ .select([
81
+ "path",
82
+ "start_line",
83
+ "defined_symbols",
84
+ "referenced_symbols",
85
+ "role",
86
+ "parent_symbol",
87
+ "complexity",
88
+ ])
89
+ .limit(1)
90
+ .toArray();
91
+ if (rows.length > 0) {
92
+ calleeNodes.push(this.mapRowToNode(rows[0], name, "center"));
93
+ }
94
+ else {
95
+ calleeNodes.push({
96
+ symbol: name,
97
+ file: "",
98
+ line: 0,
99
+ role: "",
100
+ calls: [],
101
+ calledBy: [],
102
+ });
103
+ }
104
+ }
105
+ return { center, callers, callees: calleeNodes };
75
106
  });
76
107
  }
77
108
  mapRowToNode(row, targetSymbol, type) {
@@ -151,7 +151,12 @@ function formatTrace(graph) {
151
151
  if (graph.callees.length > 0) {
152
152
  lines.push(style.bold("Callees (What does this call?):"));
153
153
  graph.callees.forEach((callee) => {
154
- lines.push(` ${style.cyan("↓")} ${callee}`);
154
+ if (callee.file) {
155
+ lines.push(` ${style.cyan("↓")} ${style.green(callee.symbol)} ${style.dim(`(${callee.file}:${callee.line})`)}`);
156
+ }
157
+ else {
158
+ lines.push(` ${style.cyan("↓")} ${callee.symbol} ${style.dim("(not indexed)")}`);
159
+ }
155
160
  });
156
161
  }
157
162
  else {
@@ -277,6 +277,19 @@ class Searcher {
277
277
  if (pathPrefix) {
278
278
  whereClauseParts.push(`path LIKE '${(0, filter_builder_1.escapeSqlString)((0, filter_builder_1.normalizePath)(pathPrefix))}%'`);
279
279
  }
280
+ // Handle file name filter
281
+ const fileFilter = _filters === null || _filters === void 0 ? void 0 : _filters.file;
282
+ if (typeof fileFilter === "string" && fileFilter) {
283
+ whereClauseParts.push(`path LIKE '%/${(0, filter_builder_1.escapeSqlString)(fileFilter)}'`);
284
+ }
285
+ // Handle exclude filter
286
+ const excludeFilter = _filters === null || _filters === void 0 ? void 0 : _filters.exclude;
287
+ if (typeof excludeFilter === "string" && excludeFilter) {
288
+ const absExclude = pathPrefix
289
+ ? (0, filter_builder_1.normalizePath)(pathPrefix + excludeFilter)
290
+ : excludeFilter;
291
+ whereClauseParts.push(`path NOT LIKE '${(0, filter_builder_1.escapeSqlString)(absExclude)}%'`);
292
+ }
280
293
  // Handle --def (definition) filter
281
294
  const defFilter = _filters === null || _filters === void 0 ? void 0 : _filters.def;
282
295
  if (typeof defFilter === "string" && defFilter) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.7.3",
3
+ "version": "0.7.5",
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.3",
3
+ "version": "0.7.5",
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",
@@ -42,6 +42,8 @@ Parameters:
42
42
  - `detail` (optional): `"pointer"` (default) or `"code"`
43
43
  - `min_score` (optional): Filter by minimum relevance score (0-1)
44
44
  - `max_per_file` (optional): Cap results per file for diversity
45
+ - `file` (optional): Filter to files matching this name (e.g. "syncer.ts"). Matches filename, not full path.
46
+ - `exclude` (optional): Exclude files under this path prefix (e.g. "tests/" or "dist/")
45
47
 
46
48
  **When to use which mode:**
47
49
  - `pointer` — navigation, finding locations, understanding architecture
@@ -57,7 +59,7 @@ File structure — signatures with bodies collapsed (~4x fewer tokens).
57
59
  - `target` (required): File path relative to project root
58
60
 
59
61
  ### trace_calls
60
- Call graph — who calls a symbol and what it calls. Unscoped — follows calls across all indexed directories.
62
+ Call graph — who calls a symbol and what it calls. Callers and callees include file:line locations. Unscoped — follows calls across all indexed directories.
61
63
  - `symbol` (required): Function/method/class name
62
64
 
63
65
  ### list_symbols