grepmax 0.7.9 → 0.7.11

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.
@@ -124,6 +124,10 @@ const TOOLS = [
124
124
  type: "number",
125
125
  description: "Include N lines before and after the chunk (like grep -C). Only with detail 'code' or 'full'. Max 20.",
126
126
  },
127
+ mode: {
128
+ type: "string",
129
+ description: "Search mode: 'default' (semantic only) or 'symbol' (semantic + call graph trace appended). Use 'symbol' when query is a function/class name.",
130
+ },
127
131
  },
128
132
  required: ["query"],
129
133
  },
@@ -264,6 +268,19 @@ const TOOLS = [
264
268
  },
265
269
  },
266
270
  },
271
+ {
272
+ name: "summarize_project",
273
+ description: "High-level overview of an indexed project — languages, directory structure, role distribution, key symbols, and entry points. Use when first exploring a codebase.",
274
+ inputSchema: {
275
+ type: "object",
276
+ properties: {
277
+ root: {
278
+ type: "string",
279
+ description: "Project root (absolute path). Defaults to current project.",
280
+ },
281
+ },
282
+ },
283
+ },
267
284
  ];
268
285
  // ---------------------------------------------------------------------------
269
286
  // Helpers
@@ -609,7 +626,50 @@ exports.mcp = new commander_1.Command("mcp")
609
626
  return true;
610
627
  });
611
628
  }
612
- const output = results.map((r) => r.text).join("\n\n");
629
+ let output = results.map((r) => r.text).join("\n\n");
630
+ // Symbol mode: append call graph
631
+ const mode = typeof args.mode === "string" ? args.mode : "default";
632
+ if (mode === "symbol" && !searchAll) {
633
+ try {
634
+ const db = getVectorDb();
635
+ const builder = new graph_builder_1.GraphBuilder(db);
636
+ const graph = yield builder.buildGraph(query);
637
+ if (graph.center) {
638
+ const traceLines = ["", "--- Call graph ---"];
639
+ const centerRel = graph.center.file.startsWith(projectRoot)
640
+ ? graph.center.file.slice(projectRoot.length + 1)
641
+ : graph.center.file;
642
+ traceLines.push(`${graph.center.symbol} [${graph.center.role}] ${centerRel}:${graph.center.line + 1}`);
643
+ if (graph.callers.length > 0) {
644
+ traceLines.push("Callers:");
645
+ for (const caller of graph.callers) {
646
+ const rel = caller.file.startsWith(projectRoot)
647
+ ? caller.file.slice(projectRoot.length + 1)
648
+ : caller.file;
649
+ traceLines.push(` <- ${caller.symbol} ${rel}:${caller.line + 1}`);
650
+ }
651
+ }
652
+ if (graph.callees.length > 0) {
653
+ traceLines.push("Calls:");
654
+ for (const callee of graph.callees.slice(0, 15)) {
655
+ if (callee.file) {
656
+ const rel = callee.file.startsWith(projectRoot)
657
+ ? callee.file.slice(projectRoot.length + 1)
658
+ : callee.file;
659
+ traceLines.push(` -> ${callee.symbol} ${rel}:${callee.line + 1}`);
660
+ }
661
+ else {
662
+ traceLines.push(` -> ${callee.symbol} (not indexed)`);
663
+ }
664
+ }
665
+ }
666
+ output += `\n${traceLines.join("\n")}`;
667
+ }
668
+ }
669
+ catch (_b) {
670
+ // Trace failed — return search results without trace
671
+ }
672
+ }
613
673
  if ((_a = result.warnings) === null || _a === void 0 ? void 0 : _a.length) {
614
674
  return ok(`${result.warnings.join("\n")}\n\n${output}`);
615
675
  }
@@ -951,6 +1011,130 @@ exports.mcp = new commander_1.Command("mcp")
951
1011
  }
952
1012
  });
953
1013
  }
1014
+ function handleSummarizeProject(args) {
1015
+ return __awaiter(this, void 0, void 0, function* () {
1016
+ var _a;
1017
+ const root = typeof args.root === "string"
1018
+ ? path.resolve(args.root)
1019
+ : projectRoot;
1020
+ const prefix = root.endsWith("/") ? root : `${root}/`;
1021
+ const projectName = path.basename(root);
1022
+ try {
1023
+ const db = getVectorDb();
1024
+ const table = yield db.ensureTable();
1025
+ const rows = yield table
1026
+ .query()
1027
+ .select([
1028
+ "path",
1029
+ "role",
1030
+ "is_exported",
1031
+ "complexity",
1032
+ "defined_symbols",
1033
+ "referenced_symbols",
1034
+ ])
1035
+ .where(`path LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`)
1036
+ .limit(200000)
1037
+ .toArray();
1038
+ if (rows.length === 0) {
1039
+ return ok(`No indexed data found for ${root}. Run: gmax index --path ${root}`);
1040
+ }
1041
+ const files = new Set();
1042
+ const extCounts = new Map();
1043
+ const dirCounts = new Map();
1044
+ const roleCounts = new Map();
1045
+ const symbolRefs = new Map();
1046
+ const entryPoints = [];
1047
+ for (const row of rows) {
1048
+ const p = String(row.path || "");
1049
+ const role = String(row.role || "IMPLEMENTATION");
1050
+ const exported = Boolean(row.is_exported);
1051
+ const complexity = Number(row.complexity || 0);
1052
+ const defs = toStringArray(row.defined_symbols);
1053
+ const refs = toStringArray(row.referenced_symbols);
1054
+ files.add(p);
1055
+ const ext = path.extname(p).toLowerCase() || path.basename(p);
1056
+ extCounts.set(ext, (extCounts.get(ext) || 0) + 1);
1057
+ const rel = p.startsWith(prefix)
1058
+ ? p.slice(prefix.length)
1059
+ : p;
1060
+ const parts = rel.split("/");
1061
+ const dir = parts.length > 2
1062
+ ? `${parts.slice(0, 2).join("/")}/`
1063
+ : parts.length > 1
1064
+ ? `${parts[0]}/`
1065
+ : "(root)";
1066
+ if (!dirCounts.has(dir)) {
1067
+ dirCounts.set(dir, { files: new Set(), chunks: 0 });
1068
+ }
1069
+ const dc = dirCounts.get(dir);
1070
+ dc.files.add(p);
1071
+ dc.chunks++;
1072
+ roleCounts.set(role, (roleCounts.get(role) || 0) + 1);
1073
+ for (const ref of refs) {
1074
+ symbolRefs.set(ref, (symbolRefs.get(ref) || 0) + 1);
1075
+ }
1076
+ if (exported &&
1077
+ role === "ORCHESTRATION" &&
1078
+ complexity >= 5 &&
1079
+ defs.length > 0) {
1080
+ const relPath = p.startsWith(prefix)
1081
+ ? p.slice(prefix.length)
1082
+ : p;
1083
+ entryPoints.push({ symbol: defs[0], path: relPath });
1084
+ }
1085
+ }
1086
+ const lines = [];
1087
+ const projects = (0, project_registry_1.listProjects)();
1088
+ const proj = projects.find((p) => p.root === root);
1089
+ lines.push(`Project: ${projectName} (${root})`);
1090
+ lines.push(`Last indexed: ${(_a = proj === null || proj === void 0 ? void 0 : proj.lastIndexed) !== null && _a !== void 0 ? _a : "unknown"} • ${rows.length} chunks • ${files.size} files`);
1091
+ lines.push("");
1092
+ const extEntries = Array.from(extCounts.entries())
1093
+ .sort((a, b) => b[1] - a[1])
1094
+ .slice(0, 8);
1095
+ const langLine = extEntries
1096
+ .map(([ext, count]) => `${ext} (${Math.round((count / rows.length) * 100)}%)`)
1097
+ .join(", ");
1098
+ lines.push(`Languages: ${langLine}`);
1099
+ lines.push("");
1100
+ lines.push("Directory structure:");
1101
+ const dirEntries = Array.from(dirCounts.entries())
1102
+ .sort((a, b) => b[1].chunks - a[1].chunks)
1103
+ .slice(0, 12);
1104
+ for (const [dir, data] of dirEntries) {
1105
+ lines.push(` ${dir.padEnd(25)} (${data.files.size} files, ${data.chunks} chunks)`);
1106
+ }
1107
+ lines.push("");
1108
+ const roleEntries = Array.from(roleCounts.entries()).sort((a, b) => b[1] - a[1]);
1109
+ const roleLine = roleEntries
1110
+ .map(([role, count]) => `${Math.round((count / rows.length) * 100)}% ${role}`)
1111
+ .join(", ");
1112
+ lines.push(`Roles: ${roleLine}`);
1113
+ lines.push("");
1114
+ const topSymbols = Array.from(symbolRefs.entries())
1115
+ .sort((a, b) => b[1] - a[1])
1116
+ .slice(0, 8);
1117
+ if (topSymbols.length > 0) {
1118
+ lines.push("Key symbols (by reference count):");
1119
+ for (const [sym, count] of topSymbols) {
1120
+ lines.push(` ${sym.padEnd(25)} (referenced ${count}x)`);
1121
+ }
1122
+ lines.push("");
1123
+ }
1124
+ if (entryPoints.length > 0) {
1125
+ lines.push("Entry points (exported orchestration):");
1126
+ for (const ep of entryPoints.slice(0, 10)) {
1127
+ lines.push(` ${ep.symbol.padEnd(25)} ${ep.path}`);
1128
+ }
1129
+ }
1130
+ return ok(lines.join("\n"));
1131
+ }
1132
+ catch (e) {
1133
+ const msg = e instanceof Error ? e.message : String(e);
1134
+ return err(`Project summary failed: ${msg}`);
1135
+ }
1136
+ });
1137
+ }
954
1138
  // --- MCP server setup ---
955
1139
  const transport = new stdio_js_1.StdioServerTransport();
956
1140
  const server = new index_js_1.Server({
@@ -984,6 +1168,8 @@ exports.mcp = new commander_1.Command("mcp")
984
1168
  return handleIndexStatus();
985
1169
  case "summarize_directory":
986
1170
  return handleSummarizeDirectory(toolArgs);
1171
+ case "summarize_project":
1172
+ return handleSummarizeProject(toolArgs);
987
1173
  default:
988
1174
  return err(`Unknown tool: ${name}`);
989
1175
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.7.9",
3
+ "version": "0.7.11",
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.9",
3
+ "version": "0.7.11",
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",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: grepmax
3
3
  description: Semantic code search. Use alongside grep - grep for exact strings, gmax for concepts.
4
- allowed-tools: "mcp__grepmax__semantic_search, mcp__grepmax__search_all, mcp__grepmax__code_skeleton, mcp__grepmax__trace_calls, mcp__grepmax__list_symbols, mcp__grepmax__index_status, mcp__grepmax__summarize_directory, Bash(gmax:*), Read"
4
+ allowed-tools: "mcp__grepmax__semantic_search, mcp__grepmax__search_all, mcp__grepmax__code_skeleton, mcp__grepmax__trace_calls, mcp__grepmax__list_symbols, mcp__grepmax__index_status, mcp__grepmax__summarize_directory, mcp__grepmax__summarize_project, Bash(gmax:*), Read"
5
5
  ---
6
6
 
7
7
  ## What gmax does
@@ -47,6 +47,7 @@ Parameters:
47
47
  - `exclude` (optional): Exclude files under this path prefix (e.g. "tests/" or "dist/")
48
48
  - `language` (optional): Filter by file extension (e.g. "ts", "py", "go"). Omit the dot.
49
49
  - `role` (optional): Filter by chunk role: "ORCHESTRATION" (logic/flow), "DEFINITION" (types), or "IMPLEMENTATION"
50
+ - `mode` (optional): `"default"` (semantic only) or `"symbol"` (semantic + call graph appended). Use "symbol" when query is a function or class name — gets search results + callers/callees in one call.
50
51
 
51
52
  **When to use which mode:**
52
53
  - `pointer` — navigation, finding locations, understanding architecture
@@ -78,6 +79,10 @@ List indexed symbols with definition locations, role, and export status.
78
79
 
79
80
  Output: `symbolName [ORCH] exported src/path/file.ts:42`
80
81
 
82
+ ### summarize_project
83
+ High-level project overview — languages, directory structure, role distribution, key symbols, entry points. Use when first exploring a new codebase.
84
+ - `root` (optional): Project root path. Defaults to current project.
85
+
81
86
  ### index_status
82
87
  Check centralized index health — chunks, files, indexed directories, model info, watcher status.
83
88