grepmax 0.17.1 → 0.17.2

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.
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.dead = void 0;
46
+ const commander_1 = require("commander");
47
+ const graph_builder_1 = require("../lib/graph/graph-builder");
48
+ const vector_db_1 = require("../lib/store/vector-db");
49
+ const exit_1 = require("../lib/utils/exit");
50
+ const filter_builder_1 = require("../lib/utils/filter-builder");
51
+ const project_registry_1 = require("../lib/utils/project-registry");
52
+ const project_root_1 = require("../lib/utils/project-root");
53
+ const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
54
+ const style = {
55
+ bold: (s) => (useColors ? `\x1b[1m${s}\x1b[22m` : s),
56
+ dim: (s) => (useColors ? `\x1b[2m${s}\x1b[22m` : s),
57
+ red: (s) => (useColors ? `\x1b[31m${s}\x1b[39m` : s),
58
+ yellow: (s) => (useColors ? `\x1b[33m${s}\x1b[39m` : s),
59
+ green: (s) => (useColors ? `\x1b[32m${s}\x1b[39m` : s),
60
+ };
61
+ const TOP_CALLERS = 3;
62
+ function statusLabel(status) {
63
+ switch (status) {
64
+ case "DEAD":
65
+ return "DEAD";
66
+ case "PUBLIC_EXPORT":
67
+ return "PUBLIC EXPORT";
68
+ case "LIVE":
69
+ return "LIVE";
70
+ }
71
+ }
72
+ function formatHuman(r, projectRoot) {
73
+ const rel = (p) => p.startsWith(projectRoot) ? p.slice(projectRoot.length + 1) : p;
74
+ const defLoc = `${rel(r.defPath)}:${r.defLine + 1}`;
75
+ if (r.status === "DEAD") {
76
+ return `${style.red(style.bold("DEAD"))} ${defLoc} defines ${style.bold(r.symbol)}`;
77
+ }
78
+ if (r.status === "PUBLIC_EXPORT") {
79
+ return `${style.yellow(style.bold("PUBLIC EXPORT"))} ${defLoc} defines ${style.bold(r.symbol)} ${style.dim("— no internal callers found; check external usage")}`;
80
+ }
81
+ const header = `${style.green(style.bold("LIVE"))} ${defLoc} defines ${style.bold(r.symbol)} ${style.dim(`— ${r.callerCount} inbound caller${r.callerCount === 1 ? "" : "s"} (top ${Math.min(TOP_CALLERS, r.topCallers.length)}):`)}`;
82
+ const lines = [header];
83
+ for (const c of r.topCallers) {
84
+ lines.push(` ${rel(c.file)}:${c.line + 1}`);
85
+ }
86
+ return lines.join("\n");
87
+ }
88
+ function formatAgent(r, projectRoot) {
89
+ const rel = (p) => p.startsWith(projectRoot) ? p.slice(projectRoot.length + 1) : p;
90
+ const defLoc = `${rel(r.defPath)}:${r.defLine + 1}`;
91
+ const callerLocs = r.topCallers
92
+ .map((c) => `${rel(c.file)}:${c.line + 1}`)
93
+ .join(",");
94
+ return [
95
+ statusLabel(r.status),
96
+ defLoc,
97
+ String(r.callerCount),
98
+ callerLocs,
99
+ ].join("\t");
100
+ }
101
+ exports.dead = new commander_1.Command("dead")
102
+ .description("Report whether a symbol has zero inbound callers in the indexed call graph. " +
103
+ "The call graph reflects what tree-sitter chunked — dynamic dispatch, " +
104
+ "reflection, eval, and string-built call sites won't show up, so a 'DEAD' " +
105
+ "result is a hypothesis, not a proof. Exported public-API symbols " +
106
+ "legitimately have no in-project callers (reported as PUBLIC EXPORT).")
107
+ .argument("<symbol>", "The symbol to check")
108
+ .option("--root <dir>", "Project root directory")
109
+ .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
110
+ .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
111
+ .option("--agent", "Compact TSV output for AI agents", false)
112
+ .action((symbol, opts) => __awaiter(void 0, void 0, void 0, function* () {
113
+ var _a;
114
+ const root = (0, project_registry_1.resolveRootOrExit)(opts.root);
115
+ if (root === null)
116
+ return;
117
+ let vectorDb = null;
118
+ try {
119
+ const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
120
+ const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
121
+ vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
122
+ const { resolveScope, buildScopeWhere } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/scope-filter")));
123
+ const scope = resolveScope({
124
+ projectRoot,
125
+ in: opts.in,
126
+ exclude: opts.exclude,
127
+ });
128
+ // Resolve the defining chunk to get path, line, and is_exported.
129
+ const table = yield vectorDb.ensureTable();
130
+ const defRows = yield table
131
+ .query()
132
+ .select(["path", "start_line", "is_exported"])
133
+ .where(buildScopeWhere(scope, `array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbol)}')`))
134
+ .limit(1)
135
+ .toArray();
136
+ if (defRows.length === 0) {
137
+ console.log(opts.agent ? "(not found)" : `Symbol not found: ${symbol}`);
138
+ process.exitCode = 1;
139
+ return;
140
+ }
141
+ const defRow = defRows[0];
142
+ const defPath = String(defRow.path || "");
143
+ const defLine = Number(defRow.start_line || 0);
144
+ const isExported = Boolean(defRow.is_exported);
145
+ const builder = new graph_builder_1.GraphBuilder(vectorDb, scope.pathPrefix, scope.excludePrefixes);
146
+ const callers = yield builder.getCallers(symbol);
147
+ const status = callers.length === 0
148
+ ? isExported
149
+ ? "PUBLIC_EXPORT"
150
+ : "DEAD"
151
+ : "LIVE";
152
+ const topCallers = callers
153
+ .slice(0, TOP_CALLERS)
154
+ .map((c) => ({ file: c.file, line: c.line }));
155
+ const result = {
156
+ status,
157
+ symbol,
158
+ defPath,
159
+ defLine,
160
+ callerCount: callers.length,
161
+ topCallers,
162
+ };
163
+ console.log(opts.agent
164
+ ? formatAgent(result, projectRoot)
165
+ : formatHuman(result, projectRoot));
166
+ }
167
+ catch (error) {
168
+ const message = error instanceof Error ? error.message : "Unknown error";
169
+ console.error("Dead check failed:", message);
170
+ process.exitCode = 1;
171
+ }
172
+ finally {
173
+ if (vectorDb) {
174
+ try {
175
+ yield vectorDb.close();
176
+ }
177
+ catch (err) {
178
+ console.error("Failed to close VectorDB:", err);
179
+ }
180
+ }
181
+ yield (0, exit_1.gracefulExit)();
182
+ }
183
+ }));
@@ -161,6 +161,18 @@ const TOOLS = [
161
161
  required: ["symbol"],
162
162
  },
163
163
  },
164
+ {
165
+ name: "dead",
166
+ description: "Report whether a symbol has zero inbound callers in the indexed call graph. Returns DEAD, PUBLIC EXPORT (exported with no internal callers), or LIVE with caller count. Hypothesis, not proof: dynamic dispatch, reflection, and string-built call sites won't show up.",
167
+ inputSchema: {
168
+ type: "object",
169
+ properties: {
170
+ symbol: { type: "string", description: "Symbol name to check" },
171
+ root: { type: "string", description: "Project root (absolute path)" },
172
+ },
173
+ required: ["symbol"],
174
+ },
175
+ },
164
176
  {
165
177
  name: "list_symbols",
166
178
  description: "List indexed symbols with role and export status.",
@@ -1113,6 +1125,57 @@ exports.mcp = new commander_1.Command("mcp")
1113
1125
  }
1114
1126
  });
1115
1127
  }
1128
+ function handleDead(args) {
1129
+ return __awaiter(this, void 0, void 0, function* () {
1130
+ ensureWatcher();
1131
+ const symbol = String(args.symbol || "");
1132
+ if (!symbol)
1133
+ return err("Missing required parameter: symbol");
1134
+ try {
1135
+ const root = typeof args.root === "string" && args.root
1136
+ ? args.root
1137
+ : projectRoot;
1138
+ const db = getVectorDb();
1139
+ const table = yield db.ensureTable();
1140
+ const prefix = root.endsWith("/") ? root : `${root}/`;
1141
+ const defRows = yield table
1142
+ .query()
1143
+ .select(["path", "start_line", "is_exported"])
1144
+ .where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbol)}') AND path LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`)
1145
+ .limit(1)
1146
+ .toArray();
1147
+ if (defRows.length === 0) {
1148
+ return ok(`Symbol '${symbol}' not found in the index. Check \`gmax status\` to see which projects are indexed, or try \`gmax search ${symbol}\` to find similar symbols.`);
1149
+ }
1150
+ const defRow = defRows[0];
1151
+ const defPath = String(defRow.path || "");
1152
+ const defLine = Number(defRow.start_line || 0);
1153
+ const isExported = Boolean(defRow.is_exported);
1154
+ const builder = new graph_builder_1.GraphBuilder(db, root);
1155
+ const callers = yield builder.getCallers(symbol);
1156
+ const rel = (p) => p.startsWith(root) ? p.slice(root.length + 1) : p;
1157
+ const defLoc = `${rel(defPath)}:${defLine + 1}`;
1158
+ if (callers.length === 0) {
1159
+ if (isExported) {
1160
+ return ok(`PUBLIC EXPORT ${defLoc} defines ${symbol} — no internal callers found; check external usage`);
1161
+ }
1162
+ return ok(`DEAD ${defLoc} defines ${symbol}`);
1163
+ }
1164
+ const top = callers.slice(0, 3);
1165
+ const lines = [
1166
+ `LIVE ${defLoc} defines ${symbol} — ${callers.length} inbound caller${callers.length === 1 ? "" : "s"} (top ${top.length}):`,
1167
+ ];
1168
+ for (const c of top) {
1169
+ lines.push(` ${rel(c.file)}:${c.line + 1}`);
1170
+ }
1171
+ return ok(lines.join("\n"));
1172
+ }
1173
+ catch (e) {
1174
+ const msg = e instanceof Error ? e.message : String(e);
1175
+ return err(`Dead check failed: ${msg}`);
1176
+ }
1177
+ });
1178
+ }
1116
1179
  function handleListSymbols(args) {
1117
1180
  return __awaiter(this, void 0, void 0, function* () {
1118
1181
  ensureWatcher();
@@ -1831,6 +1894,9 @@ exports.mcp = new commander_1.Command("mcp")
1831
1894
  case "peek_symbol":
1832
1895
  result = yield handlePeekSymbol(toolArgs);
1833
1896
  break;
1897
+ case "dead":
1898
+ result = yield handleDead(toolArgs);
1899
+ break;
1834
1900
  case "list_symbols":
1835
1901
  result = yield handleListSymbols(toolArgs);
1836
1902
  break;
package/dist/index.js CHANGED
@@ -40,6 +40,7 @@ const path = __importStar(require("node:path"));
40
40
  const commander_1 = require("commander");
41
41
  const add_1 = require("./commands/add");
42
42
  const context_1 = require("./commands/context");
43
+ const dead_1 = require("./commands/dead");
43
44
  const diff_1 = require("./commands/diff");
44
45
  const claude_code_1 = require("./commands/claude-code");
45
46
  const codex_1 = require("./commands/codex");
@@ -114,6 +115,7 @@ commander_1.program.addCommand(symbols_1.symbols);
114
115
  commander_1.program.addCommand(trace_1.trace);
115
116
  commander_1.program.addCommand(extract_1.extract);
116
117
  commander_1.program.addCommand(peek_1.peek);
118
+ commander_1.program.addCommand(dead_1.dead);
117
119
  commander_1.program.addCommand(project_1.project);
118
120
  commander_1.program.addCommand(related_1.related);
119
121
  commander_1.program.addCommand(log_1.log);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.17.1",
3
+ "version": "0.17.2",
4
4
  "author": "Robert Owens <78518764+reowens@users.noreply.github.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.17.1",
3
+ "version": "0.17.2",
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",