grepmax 0.7.4 → 0.7.6
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 +124 -33
- 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 +6 -3
package/dist/commands/mcp.js
CHANGED
|
@@ -61,6 +61,7 @@ const searcher_1 = require("../lib/search/searcher");
|
|
|
61
61
|
const retriever_1 = require("../lib/skeleton/retriever");
|
|
62
62
|
const skeletonizer_1 = require("../lib/skeleton/skeletonizer");
|
|
63
63
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
64
|
+
const file_utils_1 = require("../lib/utils/file-utils");
|
|
64
65
|
const filter_builder_1 = require("../lib/utils/filter-builder");
|
|
65
66
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
66
67
|
const project_root_1 = require("../lib/utils/project-root");
|
|
@@ -103,6 +104,14 @@ const TOOLS = [
|
|
|
103
104
|
type: "number",
|
|
104
105
|
description: "Max results per file (default: no cap). Useful to get diversity across files.",
|
|
105
106
|
},
|
|
107
|
+
file: {
|
|
108
|
+
type: "string",
|
|
109
|
+
description: "Filter to files matching this name (e.g. 'syncer.ts'). Matches the filename, not the full path.",
|
|
110
|
+
},
|
|
111
|
+
exclude: {
|
|
112
|
+
type: "string",
|
|
113
|
+
description: "Exclude files under this path prefix (e.g. 'tests/' or 'dist/').",
|
|
114
|
+
},
|
|
106
115
|
},
|
|
107
116
|
required: ["query"],
|
|
108
117
|
},
|
|
@@ -133,19 +142,31 @@ const TOOLS = [
|
|
|
133
142
|
type: "number",
|
|
134
143
|
description: "Max results per file (default: no cap).",
|
|
135
144
|
},
|
|
145
|
+
file: {
|
|
146
|
+
type: "string",
|
|
147
|
+
description: "Filter to files matching this name (e.g. 'syncer.ts').",
|
|
148
|
+
},
|
|
149
|
+
exclude: {
|
|
150
|
+
type: "string",
|
|
151
|
+
description: "Exclude files under this path prefix (e.g. 'tests/').",
|
|
152
|
+
},
|
|
136
153
|
},
|
|
137
154
|
required: ["query"],
|
|
138
155
|
},
|
|
139
156
|
},
|
|
140
157
|
{
|
|
141
158
|
name: "code_skeleton",
|
|
142
|
-
description: "Show the structure of
|
|
159
|
+
description: "Show the structure of source files — signatures with bodies collapsed (~4x fewer tokens). Accepts a file path, a directory path, or comma-separated file paths.",
|
|
143
160
|
inputSchema: {
|
|
144
161
|
type: "object",
|
|
145
162
|
properties: {
|
|
146
163
|
target: {
|
|
147
164
|
type: "string",
|
|
148
|
-
description: "File path
|
|
165
|
+
description: "File path, directory path (e.g. 'src/lib/search/'), or comma-separated files (e.g. 'src/a.ts,src/b.ts'). Relative to project root.",
|
|
166
|
+
},
|
|
167
|
+
limit: {
|
|
168
|
+
type: "number",
|
|
169
|
+
description: "Max files for directory mode (default 10, max 20). Ignored for single files.",
|
|
149
170
|
},
|
|
150
171
|
},
|
|
151
172
|
required: ["target"],
|
|
@@ -422,7 +443,14 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
422
443
|
pathPrefix += "/";
|
|
423
444
|
}
|
|
424
445
|
}
|
|
425
|
-
const
|
|
446
|
+
const filters = {};
|
|
447
|
+
if (typeof args.file === "string" && args.file) {
|
|
448
|
+
filters.file = args.file;
|
|
449
|
+
}
|
|
450
|
+
if (typeof args.exclude === "string" && args.exclude) {
|
|
451
|
+
filters.exclude = args.exclude;
|
|
452
|
+
}
|
|
453
|
+
const result = yield searcher.search(query, limit, { rerank: true }, Object.keys(filters).length > 0 ? filters : undefined, pathPrefix);
|
|
426
454
|
if (!result.data || result.data.length === 0) {
|
|
427
455
|
return ok("No matches found.");
|
|
428
456
|
}
|
|
@@ -510,36 +538,71 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
510
538
|
const target = String(args.target || "");
|
|
511
539
|
if (!target)
|
|
512
540
|
return err("Missing required parameter: target");
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
541
|
+
const fileLimit = Math.min(Math.max(Number(args.limit) || 10, 1), 20);
|
|
542
|
+
// Determine targets: comma-separated, directory, or single file
|
|
543
|
+
let targets;
|
|
544
|
+
if (target.includes(",")) {
|
|
545
|
+
targets = target
|
|
546
|
+
.split(",")
|
|
547
|
+
.map((t) => t.trim())
|
|
548
|
+
.filter(Boolean);
|
|
516
549
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
550
|
+
else {
|
|
551
|
+
const absPath = path.resolve(projectRoot, target);
|
|
552
|
+
if (fs.existsSync(absPath) && fs.statSync(absPath).isDirectory()) {
|
|
553
|
+
const entries = fs.readdirSync(absPath, { withFileTypes: true });
|
|
554
|
+
targets = entries
|
|
555
|
+
.filter((e) => e.isFile() &&
|
|
556
|
+
(0, file_utils_1.isIndexableFile)(path.join(absPath, e.name)))
|
|
557
|
+
.map((e) => path.relative(projectRoot, path.join(absPath, e.name)))
|
|
558
|
+
.slice(0, fileLimit);
|
|
559
|
+
if (targets.length === 0) {
|
|
560
|
+
return err(`No indexable files found in ${target}`);
|
|
561
|
+
}
|
|
524
562
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
// Index may not exist yet — fall through to live generation
|
|
528
|
-
}
|
|
529
|
-
// Generate skeleton from file
|
|
530
|
-
try {
|
|
531
|
-
const content = fs.readFileSync(absPath, "utf-8");
|
|
532
|
-
const skel = yield getSkeletonizer();
|
|
533
|
-
const result = yield skel.skeletonizeFile(absPath, content);
|
|
534
|
-
if (!result.success && result.error) {
|
|
535
|
-
return err(`Skeleton generation failed: ${result.error}`);
|
|
563
|
+
else {
|
|
564
|
+
targets = [target];
|
|
536
565
|
}
|
|
537
|
-
return ok(`// ${target} (~${result.tokenEstimate} tokens)\n\n${result.skeleton}`);
|
|
538
566
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
567
|
+
// Generate skeletons for all targets
|
|
568
|
+
const parts = [];
|
|
569
|
+
const skel = yield getSkeletonizer();
|
|
570
|
+
for (const t of targets) {
|
|
571
|
+
const absPath = path.resolve(projectRoot, t);
|
|
572
|
+
if (!fs.existsSync(absPath)) {
|
|
573
|
+
parts.push(`// ${t} — file not found`);
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
// Try cached skeleton first
|
|
577
|
+
try {
|
|
578
|
+
const db = getVectorDb();
|
|
579
|
+
const cached = yield (0, retriever_1.getStoredSkeleton)(db, absPath);
|
|
580
|
+
if (cached) {
|
|
581
|
+
const tokens = Math.ceil(cached.length / 4);
|
|
582
|
+
parts.push(`// ${t} (~${tokens} tokens)\n\n${cached}`);
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
catch (_a) {
|
|
587
|
+
// Index may not exist yet — fall through to live generation
|
|
588
|
+
}
|
|
589
|
+
// Generate live
|
|
590
|
+
try {
|
|
591
|
+
const content = fs.readFileSync(absPath, "utf-8");
|
|
592
|
+
const result = yield skel.skeletonizeFile(absPath, content);
|
|
593
|
+
if (result.success) {
|
|
594
|
+
parts.push(`// ${t} (~${result.tokenEstimate} tokens)\n\n${result.skeleton}`);
|
|
595
|
+
}
|
|
596
|
+
else {
|
|
597
|
+
parts.push(`// ${t} — skeleton failed: ${result.error}`);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
catch (e) {
|
|
601
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
602
|
+
parts.push(`// ${t} — error: ${msg}`);
|
|
603
|
+
}
|
|
542
604
|
}
|
|
605
|
+
return ok(parts.join("\n\n---\n\n"));
|
|
543
606
|
});
|
|
544
607
|
}
|
|
545
608
|
function handleTraceCalls(args) {
|
|
@@ -570,11 +633,23 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
570
633
|
else {
|
|
571
634
|
lines.push("Callers: none");
|
|
572
635
|
}
|
|
573
|
-
// Callees
|
|
636
|
+
// Callees with file paths
|
|
574
637
|
if (graph.callees.length > 0) {
|
|
575
|
-
|
|
576
|
-
const
|
|
577
|
-
|
|
638
|
+
lines.push("Calls:");
|
|
639
|
+
for (const callee of graph.callees.slice(0, 15)) {
|
|
640
|
+
if (callee.file) {
|
|
641
|
+
const rel = callee.file.startsWith(projectRoot)
|
|
642
|
+
? callee.file.slice(projectRoot.length + 1)
|
|
643
|
+
: callee.file;
|
|
644
|
+
lines.push(` -> ${callee.symbol} ${rel}:${callee.line + 1}`);
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
lines.push(` -> ${callee.symbol} (not indexed)`);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
if (graph.callees.length > 15) {
|
|
651
|
+
lines.push(` (+${graph.callees.length - 15} more)`);
|
|
652
|
+
}
|
|
578
653
|
}
|
|
579
654
|
else {
|
|
580
655
|
lines.push("Calls: none");
|
|
@@ -695,7 +770,23 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
695
770
|
indexingLine,
|
|
696
771
|
"",
|
|
697
772
|
"Indexed directories:",
|
|
698
|
-
...projects.map((p) =>
|
|
773
|
+
...(yield Promise.all(projects.map((p) => __awaiter(this, void 0, void 0, function* () {
|
|
774
|
+
var _a, _b;
|
|
775
|
+
const prefix = p.root.endsWith("/") ? p.root : `${p.root}/`;
|
|
776
|
+
try {
|
|
777
|
+
const table = yield db.ensureTable();
|
|
778
|
+
const rows = yield table
|
|
779
|
+
.query()
|
|
780
|
+
.select(["id"])
|
|
781
|
+
.where(`path LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`)
|
|
782
|
+
.limit(100000)
|
|
783
|
+
.toArray();
|
|
784
|
+
return ` ${p.name}\t${p.root}\t${(_a = p.lastIndexed) !== null && _a !== void 0 ? _a : "unknown"}\t(${rows.length} chunks)`;
|
|
785
|
+
}
|
|
786
|
+
catch (_c) {
|
|
787
|
+
return ` ${p.name}\t${p.root}\t${(_b = p.lastIndexed) !== null && _b !== void 0 ? _b : "unknown"}`;
|
|
788
|
+
}
|
|
789
|
+
})))),
|
|
699
790
|
].filter(Boolean);
|
|
700
791
|
return ok(lines.join("\n"));
|
|
701
792
|
}
|
|
@@ -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
|
|
@@ -53,11 +55,12 @@ Search ALL indexed code across every directory. Same parameters as semantic_sear
|
|
|
53
55
|
Use sparingly. Prefer `semantic_search` when you know which directory to search.
|
|
54
56
|
|
|
55
57
|
### code_skeleton
|
|
56
|
-
File structure — signatures with bodies collapsed (~4x fewer tokens).
|
|
57
|
-
- `target` (required): File path
|
|
58
|
+
File or directory structure — signatures with bodies collapsed (~4x fewer tokens).
|
|
59
|
+
- `target` (required): File path, directory path (e.g. "src/lib/search/"), or comma-separated files
|
|
60
|
+
- `limit` (optional): Max files for directory mode (default 10, max 20)
|
|
58
61
|
|
|
59
62
|
### trace_calls
|
|
60
|
-
Call graph — who calls a symbol and what it calls. Unscoped — follows calls across all indexed directories.
|
|
63
|
+
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
64
|
- `symbol` (required): Function/method/class name
|
|
62
65
|
|
|
63
66
|
### list_symbols
|