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.
- package/dist/commands/mcp.js +103 -21
- package/dist/lib/graph/graph-builder.js +34 -3
- package/dist/lib/output/formatter.js +6 -1
- package/dist/lib/search/searcher.js +13 -0
- package/package.json +1 -1
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
- package/plugins/grepmax/skills/grepmax/SKILL.md +3 -1
package/dist/commands/mcp.js
CHANGED
|
@@ -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,
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
596
|
+
// Callees with file paths
|
|
543
597
|
if (graph.callees.length > 0) {
|
|
544
|
-
|
|
545
|
-
const
|
|
546
|
-
|
|
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) =>
|
|
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
|
|
73
|
-
const
|
|
74
|
-
|
|
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
|
-
|
|
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
|
@@ -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
|