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.
@@ -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
- parts.push(`// ${t} — file not found`);
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
- const tokens = Math.ceil(cached.length / 4);
845
- const annotated = sourceContent
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
- // Index may not exist yet — fall through to live generation
854
- }
855
- // Generate live
856
- try {
857
- const content = sourceContent || fs.readFileSync(absPath, "utf-8");
858
- const result = yield skel.skeletonizeFile(absPath, content);
859
- if (result.success) {
860
- const annotated = annotateSkeletonLines(result.skeleton, content);
861
- parts.push(`// ${t} (~${result.tokenEstimate} tokens)\n\n${annotated}`);
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
- else {
864
- parts.push(`// ${t} skeleton failed: ${result.error}`);
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
- catch (e) {
868
- const msg = e instanceof Error ? e.message : String(e);
869
- parts.push(`// ${t} error: ${msg}`);
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 this.buildGraph(symbol);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.7.15",
3
+ "version": "0.7.17",
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.15",
3
+ "version": "0.7.17",
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",
@@ -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 calls a symbol and what it calls. Callers and callees include file:line locations. Unscoped — follows calls across all indexed directories.
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)