grepmax 0.7.15 → 0.7.17
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
CHANGED
|
@@ -212,6 +212,10 @@ const TOOLS = [
|
|
|
212
212
|
type: "number",
|
|
213
213
|
description: "Max files for directory mode (default 10, max 20). Ignored for single files.",
|
|
214
214
|
},
|
|
215
|
+
format: {
|
|
216
|
+
type: "string",
|
|
217
|
+
description: "Output format: 'text' (default) or 'json' (structured symbol list with names, lines, signatures).",
|
|
218
|
+
},
|
|
215
219
|
},
|
|
216
220
|
required: ["target"],
|
|
217
221
|
},
|
|
@@ -821,13 +825,16 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
821
825
|
targets = [target];
|
|
822
826
|
}
|
|
823
827
|
}
|
|
828
|
+
const fmt = typeof args.format === "string" ? args.format : "text";
|
|
824
829
|
// Generate skeletons for all targets
|
|
825
830
|
const parts = [];
|
|
831
|
+
const jsonFiles = [];
|
|
826
832
|
const skel = yield getSkeletonizer();
|
|
827
833
|
for (const t of targets) {
|
|
828
834
|
const absPath = path.resolve(projectRoot, t);
|
|
829
835
|
if (!fs.existsSync(absPath)) {
|
|
830
|
-
|
|
836
|
+
if (fmt !== "json")
|
|
837
|
+
parts.push(`// ${t} — file not found`);
|
|
831
838
|
continue;
|
|
832
839
|
}
|
|
833
840
|
// Read source for line annotations
|
|
@@ -836,39 +843,93 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
836
843
|
sourceContent = fs.readFileSync(absPath, "utf-8");
|
|
837
844
|
}
|
|
838
845
|
catch (_a) { }
|
|
846
|
+
let skeleton = "";
|
|
847
|
+
let language = "";
|
|
848
|
+
let tokenEstimate = 0;
|
|
839
849
|
// Try cached skeleton first
|
|
840
850
|
try {
|
|
841
851
|
const db = getVectorDb();
|
|
842
852
|
const cached = yield (0, retriever_1.getStoredSkeleton)(db, absPath);
|
|
843
853
|
if (cached) {
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
? annotateSkeletonLines(cached, sourceContent)
|
|
847
|
-
: cached;
|
|
848
|
-
parts.push(`// ${t} (~${tokens} tokens)\n\n${annotated}`);
|
|
849
|
-
continue;
|
|
854
|
+
skeleton = cached;
|
|
855
|
+
tokenEstimate = Math.ceil(cached.length / 4);
|
|
850
856
|
}
|
|
851
857
|
}
|
|
852
|
-
catch (_b) {
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
858
|
+
catch (_b) { }
|
|
859
|
+
// Generate live if no cache
|
|
860
|
+
if (!skeleton) {
|
|
861
|
+
try {
|
|
862
|
+
const content = sourceContent || fs.readFileSync(absPath, "utf-8");
|
|
863
|
+
const result = yield skel.skeletonizeFile(absPath, content);
|
|
864
|
+
if (result.success) {
|
|
865
|
+
skeleton = result.skeleton;
|
|
866
|
+
tokenEstimate = result.tokenEstimate;
|
|
867
|
+
language = result.language;
|
|
868
|
+
}
|
|
869
|
+
else {
|
|
870
|
+
if (fmt !== "json")
|
|
871
|
+
parts.push(`// ${t} — skeleton failed: ${result.error}`);
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
862
874
|
}
|
|
863
|
-
|
|
864
|
-
|
|
875
|
+
catch (e) {
|
|
876
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
877
|
+
if (fmt !== "json")
|
|
878
|
+
parts.push(`// ${t} — error: ${msg}`);
|
|
879
|
+
continue;
|
|
865
880
|
}
|
|
866
881
|
}
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
882
|
+
if (fmt === "json") {
|
|
883
|
+
// Extract structured symbols from annotated skeleton
|
|
884
|
+
const annotated = sourceContent
|
|
885
|
+
? annotateSkeletonLines(skeleton, sourceContent)
|
|
886
|
+
: skeleton;
|
|
887
|
+
const symbols = annotated
|
|
888
|
+
.split("\n")
|
|
889
|
+
.filter((l) => /^\s*\d+│/.test(l))
|
|
890
|
+
.map((l) => {
|
|
891
|
+
var _a, _b, _c;
|
|
892
|
+
const m = l.match(/^\s*(\d+)│(.+)/);
|
|
893
|
+
if (!m)
|
|
894
|
+
return null;
|
|
895
|
+
const line = Number.parseInt(m[1], 10);
|
|
896
|
+
const sig = m[2].trim();
|
|
897
|
+
const exported = sig.includes("export ");
|
|
898
|
+
const type = ((_a = sig.match(/\b(class|interface|type|function|def|fn|func)\b/)) === null || _a === void 0 ? void 0 : _a[1]) || "other";
|
|
899
|
+
const name = ((_b = sig.match(/(?:function|class|interface|type|def|fn|func)\s+(\w+)/)) === null || _b === void 0 ? void 0 : _b[1]) ||
|
|
900
|
+
((_c = sig.match(/^(?:async\s+)?(\w+)\s*[(<]/)) === null || _c === void 0 ? void 0 : _c[1]) ||
|
|
901
|
+
"unknown";
|
|
902
|
+
return {
|
|
903
|
+
name,
|
|
904
|
+
line,
|
|
905
|
+
signature: sig
|
|
906
|
+
.replace(/\s*\{?\s*\/\/.*$/, "")
|
|
907
|
+
.trim(),
|
|
908
|
+
type,
|
|
909
|
+
exported,
|
|
910
|
+
};
|
|
911
|
+
})
|
|
912
|
+
.filter((s) => s !== null && s.name !== "unknown");
|
|
913
|
+
jsonFiles.push({
|
|
914
|
+
file: t,
|
|
915
|
+
language: language || path.extname(t).slice(1),
|
|
916
|
+
tokenEstimate,
|
|
917
|
+
symbols,
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
else {
|
|
921
|
+
const annotated = sourceContent
|
|
922
|
+
? annotateSkeletonLines(skeleton, sourceContent)
|
|
923
|
+
: skeleton;
|
|
924
|
+
parts.push(`// ${t} (~${tokenEstimate} tokens)\n\n${annotated}`);
|
|
870
925
|
}
|
|
871
926
|
}
|
|
927
|
+
if (fmt === "json") {
|
|
928
|
+
const output = jsonFiles.length === 1
|
|
929
|
+
? jsonFiles[0]
|
|
930
|
+
: { files: jsonFiles };
|
|
931
|
+
return ok(JSON.stringify(output, null, 2));
|
|
932
|
+
}
|
|
872
933
|
return ok(parts.join("\n\n---\n\n"));
|
|
873
934
|
});
|
|
874
935
|
}
|
|
@@ -891,6 +952,21 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
891
952
|
const lines = [];
|
|
892
953
|
// Center
|
|
893
954
|
lines.push(`${graph.center.symbol} [${graph.center.role}] ${graph.center.file}:${graph.center.line + 1}`);
|
|
955
|
+
// Importers
|
|
956
|
+
if (graph.importers.length > 0) {
|
|
957
|
+
// Filter out the file where the symbol is defined
|
|
958
|
+
const centerFile = graph.center.file;
|
|
959
|
+
const filteredImporters = graph.importers.filter((p) => p !== centerFile);
|
|
960
|
+
if (filteredImporters.length > 0) {
|
|
961
|
+
lines.push("Imported by:");
|
|
962
|
+
for (const imp of filteredImporters.slice(0, 10)) {
|
|
963
|
+
const rel = imp.startsWith(projectRoot)
|
|
964
|
+
? imp.slice(projectRoot.length + 1)
|
|
965
|
+
: imp;
|
|
966
|
+
lines.push(` ${rel}`);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
894
970
|
// Callers (recursive tree)
|
|
895
971
|
function formatCallerTree(trees, indent) {
|
|
896
972
|
for (const t of trees) {
|
|
@@ -105,19 +105,40 @@ class GraphBuilder {
|
|
|
105
105
|
return { center, callers, callees: calleeNodes };
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
|
+
getImporters(symbol) {
|
|
109
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
110
|
+
const table = yield this.db.ensureTable();
|
|
111
|
+
const escaped = (0, filter_builder_1.escapeSqlString)(symbol);
|
|
112
|
+
const rows = yield table
|
|
113
|
+
.query()
|
|
114
|
+
.select(["path"])
|
|
115
|
+
.where(`content LIKE '%import%${escaped}%'`)
|
|
116
|
+
.limit(100)
|
|
117
|
+
.toArray();
|
|
118
|
+
const files = new Set();
|
|
119
|
+
for (const row of rows) {
|
|
120
|
+
files.add(String(row.path || ""));
|
|
121
|
+
}
|
|
122
|
+
return Array.from(files);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
108
125
|
buildGraphMultiHop(symbol, depth) {
|
|
109
126
|
return __awaiter(this, void 0, void 0, function* () {
|
|
110
|
-
const graph = yield
|
|
127
|
+
const [graph, importers] = yield Promise.all([
|
|
128
|
+
this.buildGraph(symbol),
|
|
129
|
+
this.getImporters(symbol),
|
|
130
|
+
]);
|
|
111
131
|
if (depth <= 1 || !graph.center) {
|
|
112
132
|
return {
|
|
113
133
|
center: graph.center,
|
|
114
134
|
callerTree: graph.callers.map((c) => ({ node: c, callers: [] })),
|
|
115
135
|
callees: graph.callees,
|
|
136
|
+
importers,
|
|
116
137
|
};
|
|
117
138
|
}
|
|
118
139
|
const visited = new Set([symbol]);
|
|
119
140
|
const callerTree = yield this.expandCallers(graph.callers, depth - 1, visited);
|
|
120
|
-
return { center: graph.center, callerTree, callees: graph.callees };
|
|
141
|
+
return { center: graph.center, callerTree, callees: graph.callees, importers };
|
|
121
142
|
});
|
|
122
143
|
}
|
|
123
144
|
expandCallers(callers, remainingDepth, visited) {
|
package/package.json
CHANGED
|
@@ -67,12 +67,15 @@ Use sparingly. Prefer `semantic_search` when you know which directory to search.
|
|
|
67
67
|
File or directory structure — signatures with bodies collapsed (~4x fewer tokens).
|
|
68
68
|
- `target` (required): File path, directory path (e.g. "src/lib/search/"), or comma-separated files
|
|
69
69
|
- `limit` (optional): Max files for directory mode (default 10, max 20)
|
|
70
|
+
- `format` (optional): `"text"` (default) or `"json"` (structured symbol list with name, line, signature, type, exported)
|
|
70
71
|
|
|
71
72
|
### trace_calls
|
|
72
|
-
Call graph — who
|
|
73
|
+
Call graph — who imports a symbol, who calls it, and what it calls. Includes file:line locations. Unscoped — follows calls across all indexed directories.
|
|
73
74
|
- `symbol` (required): Function/method/class name
|
|
74
75
|
- `depth` (optional): Traversal depth for callers (default 1, max 3). depth: 2 shows callers-of-callers with indentation.
|
|
75
76
|
|
|
77
|
+
Output: definition, "Imported by" (files with import statements), "Callers" (functions that call it), "Calls" (what it calls).
|
|
78
|
+
|
|
76
79
|
### list_symbols
|
|
77
80
|
List indexed symbols with definition locations, role, and export status.
|
|
78
81
|
- `pattern` (optional): Filter by name (case-insensitive substring match)
|