grepmax 0.18.0 → 0.19.0

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.
Files changed (66) hide show
  1. package/README.md +27 -2
  2. package/dist/commands/add.js +4 -2
  3. package/dist/commands/claude-code.js +1 -1
  4. package/dist/commands/codex.js +3 -1
  5. package/dist/commands/dead.js +2 -6
  6. package/dist/commands/doctor.js +26 -7
  7. package/dist/commands/extract.js +3 -5
  8. package/dist/commands/impact.js +6 -4
  9. package/dist/commands/index.js +7 -2
  10. package/dist/commands/log.js +4 -2
  11. package/dist/commands/mcp.js +587 -715
  12. package/dist/commands/peek.js +15 -9
  13. package/dist/commands/plugin.js +1 -1
  14. package/dist/commands/project.js +15 -5
  15. package/dist/commands/related.js +8 -9
  16. package/dist/commands/review.js +1 -1
  17. package/dist/commands/search.js +1 -1
  18. package/dist/commands/similar.js +4 -4
  19. package/dist/commands/status.js +5 -3
  20. package/dist/commands/summarize.js +6 -4
  21. package/dist/commands/test-find.js +2 -2
  22. package/dist/commands/trace.js +51 -9
  23. package/dist/commands/watch.js +9 -5
  24. package/dist/eval-graph-nav.js +4 -1
  25. package/dist/eval-graph-recovery-probe.js +56 -14
  26. package/dist/eval-graph-spotcheck.js +10 -2
  27. package/dist/eval-graph-totals.js +5 -2
  28. package/dist/eval-oss.js +212 -37
  29. package/dist/eval-seed.js +13 -4
  30. package/dist/index.js +9 -9
  31. package/dist/lib/daemon/daemon.js +22 -13
  32. package/dist/lib/daemon/ipc-handler.js +18 -5
  33. package/dist/lib/daemon/mlx-server-manager.js +11 -3
  34. package/dist/lib/daemon/process-manager.js +1 -2
  35. package/dist/lib/daemon/watcher-manager.js +4 -4
  36. package/dist/lib/graph/callsites.js +151 -25
  37. package/dist/lib/help/agent-cheatsheet.js +1 -1
  38. package/dist/lib/index/batch-processor.js +6 -5
  39. package/dist/lib/index/chunker.js +2 -0
  40. package/dist/lib/index/syncer.js +7 -6
  41. package/dist/lib/llm/diff.js +56 -11
  42. package/dist/lib/llm/investigate.js +12 -5
  43. package/dist/lib/llm/review.js +21 -8
  44. package/dist/lib/llm/server.js +16 -7
  45. package/dist/lib/output/agent-search-formatter.js +25 -3
  46. package/dist/lib/output/index-state-footer.js +1 -1
  47. package/dist/lib/review/risk.js +2 -4
  48. package/dist/lib/search/searcher.js +37 -11
  49. package/dist/lib/search/seed-weight.js +4 -1
  50. package/dist/lib/skeleton/symbol-extractor.js +2 -1
  51. package/dist/lib/utils/cross-project.js +5 -1
  52. package/dist/lib/utils/daemon-client.js +5 -1
  53. package/dist/lib/utils/git.js +10 -2
  54. package/dist/lib/utils/project-registry.js +3 -2
  55. package/dist/lib/utils/scope-filter.js +3 -1
  56. package/dist/lib/utils/watcher-launcher.js +4 -1
  57. package/dist/lib/utils/watcher-store.js +2 -1
  58. package/dist/lib/workers/embeddings/mlx-client.js +7 -2
  59. package/dist/lib/workers/pool.js +9 -6
  60. package/dist/lib/workers/process-child.js +1 -1
  61. package/package.json +22 -19
  62. package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
  63. package/plugins/grepmax/hooks/cwd-changed.js +4 -1
  64. package/plugins/grepmax/hooks/pre-grep.js +2 -1
  65. package/plugins/grepmax/hooks/start.js +45 -10
  66. package/plugins/grepmax/hooks/subagent-start.js +1 -4
@@ -53,8 +53,8 @@ const exit_1 = require("../lib/utils/exit");
53
53
  const filter_builder_1 = require("../lib/utils/filter-builder");
54
54
  const language_1 = require("../lib/utils/language");
55
55
  const project_registry_1 = require("../lib/utils/project-registry");
56
- const stale_hint_1 = require("../lib/utils/stale-hint");
57
56
  const project_root_1 = require("../lib/utils/project-root");
57
+ const stale_hint_1 = require("../lib/utils/stale-hint");
58
58
  const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
59
59
  const style = {
60
60
  bold: (s) => (useColors ? `\x1b[1m${s}\x1b[22m` : s),
@@ -117,8 +117,8 @@ exports.peek = new commander_1.Command("peek")
117
117
  .argument("<symbol>", "The symbol to peek at")
118
118
  .option("-d, --depth <n>", "Caller traversal depth (default 1, max 3)", "1")
119
119
  .option("--root <dir>", "Project root directory")
120
- .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
121
- .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
120
+ .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
121
+ .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
122
122
  .option("--agent", "Compact output for AI agents", false)
123
123
  .option("--no-tests", "Suppress the tests footer")
124
124
  .action((symbol, opts) => __awaiter(void 0, void 0, void 0, function* () {
@@ -196,9 +196,7 @@ exports.peek = new commander_1.Command("peek")
196
196
  return;
197
197
  }
198
198
  const center = graph.center;
199
- const rel = (p) => p.startsWith(projectRoot)
200
- ? p.slice(projectRoot.length + 1)
201
- : p;
199
+ const rel = (p) => p.startsWith(projectRoot) ? p.slice(projectRoot.length + 1) : p;
202
200
  // Get chunk metadata for is_exported and end_line
203
201
  const table = yield vectorDb.ensureTable();
204
202
  const metaRows = yield table
@@ -208,8 +206,12 @@ exports.peek = new commander_1.Command("peek")
208
206
  .limit(1)
209
207
  .toArray();
210
208
  const exported = metaRows.length > 0 && Boolean(metaRows[0].is_exported);
211
- const startLine = metaRows.length > 0 ? Number(metaRows[0].start_line || 0) : center.line;
212
- const endLine = metaRows.length > 0 ? Number(metaRows[0].end_line || 0) : center.line;
209
+ const startLine = metaRows.length > 0
210
+ ? Number(metaRows[0].start_line || 0)
211
+ : center.line;
212
+ const endLine = metaRows.length > 0
213
+ ? Number(metaRows[0].end_line || 0)
214
+ : center.line;
213
215
  // Get multi-hop callers if depth > 1
214
216
  let callerList;
215
217
  if (depth > 1) {
@@ -218,7 +220,11 @@ exports.peek = new commander_1.Command("peek")
218
220
  const flat = [];
219
221
  function walkCallers(tree) {
220
222
  for (const t of tree) {
221
- flat.push({ symbol: t.node.symbol, file: t.node.file, line: t.node.line });
223
+ flat.push({
224
+ symbol: t.node.symbol,
225
+ file: t.node.file,
226
+ line: t.node.line,
227
+ });
222
228
  walkCallers(t.callers);
223
229
  }
224
230
  }
@@ -107,7 +107,7 @@ function getClients() {
107
107
  isInstalled: () => {
108
108
  const p = path.join(os.homedir(), ".codex", "AGENTS.md");
109
109
  try {
110
- return fs.existsSync(p) && fs.readFileSync(p, "utf-8").includes("gmax");
110
+ return (fs.existsSync(p) && fs.readFileSync(p, "utf-8").includes("gmax"));
111
111
  }
112
112
  catch (_a) {
113
113
  return false;
@@ -47,11 +47,11 @@ const path = __importStar(require("node:path"));
47
47
  const commander_1 = require("commander");
48
48
  const callsites_1 = require("../lib/graph/callsites");
49
49
  const vector_db_1 = require("../lib/store/vector-db");
50
- const filter_builder_1 = require("../lib/utils/filter-builder");
50
+ const arrow_1 = require("../lib/utils/arrow");
51
51
  const exit_1 = require("../lib/utils/exit");
52
+ const filter_builder_1 = require("../lib/utils/filter-builder");
52
53
  const project_registry_1 = require("../lib/utils/project-registry");
53
54
  const project_root_1 = require("../lib/utils/project-root");
54
- const arrow_1 = require("../lib/utils/arrow");
55
55
  exports.project = new commander_1.Command("project")
56
56
  .description("Show project overview — languages, structure, key symbols")
57
57
  .option("--root <dir>", "Project root (defaults to current directory)")
@@ -122,7 +122,10 @@ exports.project = new commander_1.Command("project")
122
122
  definedInProject.add(d);
123
123
  for (const ref of refs)
124
124
  symbolRefs.set(ref, (symbolRefs.get(ref) || 0) + 1);
125
- if (exported && role === "ORCHESTRATION" && complexity >= 5 && defs.length > 0) {
125
+ if (exported &&
126
+ role === "ORCHESTRATION" &&
127
+ complexity >= 5 &&
128
+ defs.length > 0) {
126
129
  const epKey = `${defs[0]}:${p}`;
127
130
  if (!seenEntryPoints.has(epKey)) {
128
131
  seenEntryPoints.add(epKey);
@@ -152,12 +155,19 @@ exports.project = new commander_1.Command("project")
152
155
  console.log(`files\t${files.size}`);
153
156
  console.log(`last_indexed\t${(_b = proj === null || proj === void 0 ? void 0 : proj.lastIndexed) !== null && _b !== void 0 ? _b : "unknown"}`);
154
157
  console.log(`languages\t${extEntries.map(([ext]) => ext).join(",")}`);
155
- console.log(`top_dirs\t${Array.from(dirCounts.entries()).sort((a, b) => b[1].chunks - a[1].chunks).slice(0, 8).map(([d]) => d).join(",")}`);
158
+ console.log(`top_dirs\t${Array.from(dirCounts.entries())
159
+ .sort((a, b) => b[1].chunks - a[1].chunks)
160
+ .slice(0, 8)
161
+ .map(([d]) => d)
162
+ .join(",")}`);
156
163
  if (topSymbols.length > 0) {
157
164
  console.log(`key_symbols\t${topSymbols.map(([s]) => s).join(",")}`);
158
165
  }
159
166
  if (entryPoints.length > 0) {
160
- console.log(`entry_points\t${entryPoints.slice(0, 10).map((e) => e.symbol).join(",")}`);
167
+ console.log(`entry_points\t${entryPoints
168
+ .slice(0, 10)
169
+ .map((e) => e.symbol)
170
+ .join(",")}`);
161
171
  }
162
172
  }
163
173
  else {
@@ -47,20 +47,20 @@ const path = __importStar(require("node:path"));
47
47
  const commander_1 = require("commander");
48
48
  const vector_db_1 = require("../lib/store/vector-db");
49
49
  const agent_errors_1 = require("../lib/utils/agent-errors");
50
- const filter_builder_1 = require("../lib/utils/filter-builder");
50
+ const arrow_1 = require("../lib/utils/arrow");
51
51
  const exit_1 = require("../lib/utils/exit");
52
+ const filter_builder_1 = require("../lib/utils/filter-builder");
52
53
  const project_registry_1 = require("../lib/utils/project-registry");
53
54
  const project_root_1 = require("../lib/utils/project-root");
54
55
  const query_timeout_1 = require("../lib/utils/query-timeout");
55
56
  const stale_hint_1 = require("../lib/utils/stale-hint");
56
- const arrow_1 = require("../lib/utils/arrow");
57
57
  exports.related = new commander_1.Command("related")
58
58
  .description("Find files related by shared symbol references")
59
59
  .argument("<file>", "File path relative to project root")
60
60
  .option("-l, --limit <n>", "Max results per direction (default 10)", "10")
61
61
  .option("--root <dir>", "Project root directory")
62
- .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
63
- .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
62
+ .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
63
+ .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
64
64
  .option("--agent", "Compact output for AI agents", false)
65
65
  .action((file, opts) => __awaiter(void 0, void 0, void 0, function* () {
66
66
  var _a;
@@ -158,13 +158,14 @@ exports.related = new commander_1.Command("related")
158
158
  "common",
159
159
  "shared",
160
160
  ]);
161
- let mentions = [];
161
+ const mentions = [];
162
162
  let basename = "";
163
163
  let basenameRejected = false;
164
164
  if (topDeps.length === 0 && topRevs.length === 0) {
165
165
  const ext = path.extname(absPath);
166
166
  basename = path.basename(absPath, ext);
167
- if (basename.length < 4 || GENERIC_BASENAMES.has(basename.toLowerCase())) {
167
+ if (basename.length < 4 ||
168
+ GENERIC_BASENAMES.has(basename.toLowerCase())) {
168
169
  basenameRejected = true;
169
170
  }
170
171
  else {
@@ -190,9 +191,7 @@ exports.related = new commander_1.Command("related")
190
191
  }
191
192
  }
192
193
  if (opts.agent) {
193
- const rel = (p) => p.startsWith(`${projectRoot}/`)
194
- ? p.slice(projectRoot.length + 1)
195
- : p;
194
+ const rel = (p) => p.startsWith(`${projectRoot}/`) ? p.slice(projectRoot.length + 1) : p;
196
195
  for (const [p, count] of topDeps) {
197
196
  console.log(`dep: ${rel(p)}\t${count}`);
198
197
  }
@@ -85,7 +85,7 @@ Subcommands:
85
85
  const { VectorDB } = yield Promise.resolve().then(() => __importStar(require("../lib/store/vector-db")));
86
86
  const { GraphBuilder } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/graph-builder")));
87
87
  const { ensureProjectPaths } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/project-root")));
88
- const { gatherRiskInputs, computeRiskTable, formatRiskTable, } = yield Promise.resolve().then(() => __importStar(require("../lib/review/risk")));
88
+ const { gatherRiskInputs, computeRiskTable, formatRiskTable } = yield Promise.resolve().then(() => __importStar(require("../lib/review/risk")));
89
89
  const paths = ensureProjectPaths(projectRoot);
90
90
  const vectorDb = new VectorDB(paths.lancedbDir);
91
91
  try {
@@ -63,7 +63,7 @@ exports.search = new commander_1.Command("search")
63
63
  .option("--explain", "Show scoring breakdown per result", false)
64
64
  .option("--context-for-llm", "Return full function body + imports per result", false)
65
65
  .option("--budget <tokens>", "Max tokens for --context-for-llm output (default 8000)", "8000")
66
- .option("--min-score <score>", "Minimum relevance score (0-1) to include in results", "0")
66
+ .option("--min-score <score>", "Minimum relevance score to include. Scores are per-query-normalized (top hit = 1.0), so this is relative to the best match in THIS query, not an absolute confidence threshold.", "0")
67
67
  .option("--compact", "Compact hits view (paths + line ranges + role/preview)", false)
68
68
  .option("--plain", "Disable ANSI colors and use simpler formatting", false)
69
69
  .option("-s, --sync", "Syncs the local files to the store before searching", false)
@@ -47,20 +47,20 @@ const path = __importStar(require("node:path"));
47
47
  const commander_1 = require("commander");
48
48
  const vector_db_1 = require("../lib/store/vector-db");
49
49
  const agent_errors_1 = require("../lib/utils/agent-errors");
50
- const filter_builder_1 = require("../lib/utils/filter-builder");
50
+ const arrow_1 = require("../lib/utils/arrow");
51
51
  const exit_1 = require("../lib/utils/exit");
52
+ const filter_builder_1 = require("../lib/utils/filter-builder");
52
53
  const project_registry_1 = require("../lib/utils/project-registry");
53
54
  const project_root_1 = require("../lib/utils/project-root");
54
55
  const stale_hint_1 = require("../lib/utils/stale-hint");
55
- const arrow_1 = require("../lib/utils/arrow");
56
56
  exports.similar = new commander_1.Command("similar")
57
57
  .description("Find semantically similar code to a symbol or file")
58
58
  .argument("<target>", "Symbol name or file path")
59
59
  .option("-m, --max-count <n>", "Max results (default 5)", "5")
60
60
  .option("--threshold <score>", "Min similarity 0-1 (default 0)")
61
61
  .option("--root <dir>", "Project root directory")
62
- .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
63
- .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
62
+ .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
63
+ .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
64
64
  .option("--agent", "Compact output for AI agents", false)
65
65
  .action((target, opts) => __awaiter(void 0, void 0, void 0, function* () {
66
66
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
@@ -47,8 +47,8 @@ const os = __importStar(require("node:os"));
47
47
  const commander_1 = require("commander");
48
48
  const config_1 = require("../config");
49
49
  const index_config_1 = require("../lib/index/index-config");
50
- const filter_builder_1 = require("../lib/utils/filter-builder");
51
50
  const exit_1 = require("../lib/utils/exit");
51
+ const filter_builder_1 = require("../lib/utils/filter-builder");
52
52
  const lock_1 = require("../lib/utils/lock");
53
53
  const project_registry_1 = require("../lib/utils/project-registry");
54
54
  const project_root_1 = require("../lib/utils/project-root");
@@ -115,7 +115,9 @@ Examples:
115
115
  const db = new VectorDB(config_1.PATHS.lancedbDir);
116
116
  const table = yield db.ensureTable();
117
117
  for (const project of projects) {
118
- const prefix = project.root.endsWith("/") ? project.root : `${project.root}/`;
118
+ const prefix = project.root.endsWith("/")
119
+ ? project.root
120
+ : `${project.root}/`;
119
121
  const rows = yield table
120
122
  .query()
121
123
  .select(["id"])
@@ -125,7 +127,7 @@ Examples:
125
127
  }
126
128
  yield db.close();
127
129
  }
128
- catch (err) {
130
+ catch (_e) {
129
131
  console.warn(`[status] Failed to query LanceDB for live chunk counts, using cached counts`);
130
132
  }
131
133
  if (projects.length === 0) {
@@ -56,15 +56,17 @@ exports.summarize = new commander_1.Command("summarize")
56
56
  .action((options) => __awaiter(void 0, void 0, void 0, function* () {
57
57
  var _a, _b, _c;
58
58
  const paths = (0, project_root_1.ensureProjectPaths)(process.cwd());
59
- const rootPrefix = options.path
60
- ? `${path.resolve(options.path)}/`
61
- : "";
59
+ const rootPrefix = options.path ? `${path.resolve(options.path)}/` : "";
62
60
  const { spinner } = (0, sync_helpers_1.createIndexingSpinner)("", "Summarizing...");
63
61
  const { isDaemonRunning, sendStreamingCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
64
62
  if (yield isDaemonRunning()) {
65
63
  // Daemon mode: IPC streaming
66
64
  try {
67
- const done = yield sendStreamingCommand({ cmd: "summarize", root: paths.root, pathPrefix: rootPrefix || undefined }, (msg) => {
65
+ const done = yield sendStreamingCommand({
66
+ cmd: "summarize",
67
+ root: paths.root,
68
+ pathPrefix: rootPrefix || undefined,
69
+ }, (msg) => {
68
70
  var _a, _b;
69
71
  spinner.text = `Summarizing... (${(_a = msg.summarized) !== null && _a !== void 0 ? _a : 0}/${(_b = msg.total) !== null && _b !== void 0 ? _b : 0})`;
70
72
  });
@@ -56,8 +56,8 @@ exports.testFind = new commander_1.Command("test")
56
56
  .argument("<target>", "Symbol name or file path")
57
57
  .option("-d, --depth <n>", "Caller traversal depth (default 1, max 3)", "1")
58
58
  .option("--root <dir>", "Project root directory")
59
- .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
60
- .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
59
+ .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
60
+ .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
61
61
  .option("--agent", "Compact output for AI agents", false)
62
62
  .action((target, opts) => __awaiter(void 0, void 0, void 0, function* () {
63
63
  var _a;
@@ -51,21 +51,60 @@ const vector_db_1 = require("../lib/store/vector-db");
51
51
  const agent_errors_1 = require("../lib/utils/agent-errors");
52
52
  const exit_1 = require("../lib/utils/exit");
53
53
  const project_registry_1 = require("../lib/utils/project-registry");
54
- const stale_hint_1 = require("../lib/utils/stale-hint");
55
54
  const project_root_1 = require("../lib/utils/project-root");
55
+ const stale_hint_1 = require("../lib/utils/stale-hint");
56
56
  const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
57
57
  const dim = (s) => (useColors ? `\x1b[2m${s}\x1b[22m` : s);
58
58
  const bold = (s) => (useColors ? `\x1b[1m${s}\x1b[22m` : s);
59
- function formatTraceAgent(graph, projectRoot) {
59
+ function formatTraceAgent(graph, projectRoot, raw = false) {
60
60
  if (!graph.center)
61
61
  return "(not found)";
62
+ const center = graph.center;
62
63
  const rel = (p) => p.startsWith(projectRoot) ? p.slice(projectRoot.length + 1) : p;
63
64
  const lines = [];
64
- lines.push(`${graph.center.symbol}\t${rel(graph.center.file)}:${graph.center.line}\t${graph.center.role}`);
65
+ lines.push(`${center.symbol}\t${rel(center.file)}:${center.line}\t${center.role}`);
66
+ // A "self edge" is the traced symbol referencing itself (recursion) or its own
67
+ // definition chunk surfacing as a caller — noise in nearly every trace. Hidden
68
+ // by default; --raw brings them back.
69
+ const isSelfEdge = (n) => n.symbol === center.symbol ||
70
+ (n.file === center.file && n.line === center.line);
65
71
  function walkCallers(tree, depth) {
72
+ if (raw) {
73
+ for (const t of tree) {
74
+ lines.push(`${" ".repeat(depth)}<- ${t.node.symbol}\t${rel(t.node.file)}:${t.node.line}`);
75
+ walkCallers(t.callers, depth + 1);
76
+ }
77
+ return;
78
+ }
79
+ // Collapse repeated callers: the common flood is ONE caller symbol matched at
80
+ // many call-sites (e.g. `mcp` ×9). Group by (symbol, file), merge their lines
81
+ // onto a single row, and recurse into the union of their sub-callers.
82
+ const groups = new Map();
83
+ const order = [];
66
84
  for (const t of tree) {
67
- lines.push(`${" ".repeat(depth)}<- ${t.node.symbol}\t${rel(t.node.file)}:${t.node.line}`);
68
- walkCallers(t.callers, depth + 1);
85
+ if (isSelfEdge(t.node))
86
+ continue;
87
+ const key = `${t.node.symbol}\t${t.node.file}`;
88
+ let g = groups.get(key);
89
+ if (!g) {
90
+ g = {
91
+ symbol: t.node.symbol,
92
+ file: t.node.file,
93
+ lineSet: new Set(),
94
+ sub: [],
95
+ };
96
+ groups.set(key, g);
97
+ order.push(key);
98
+ }
99
+ g.lineSet.add(t.node.line);
100
+ g.sub.push(...t.callers);
101
+ }
102
+ for (const key of order) {
103
+ const g = groups.get(key);
104
+ const locs = [...g.lineSet].sort((a, b) => a - b).join(",");
105
+ const loc = g.file ? `${rel(g.file)}:${locs}` : "(not indexed)";
106
+ lines.push(`${" ".repeat(depth)}<- ${g.symbol}\t${loc}`);
107
+ walkCallers(g.sub, depth + 1);
69
108
  }
70
109
  }
71
110
  walkCallers(graph.callerTree, 0);
@@ -115,7 +154,9 @@ function formatInboundAgent(center, tree, projectRoot, withSnippets) {
115
154
  var _a, _b;
116
155
  for (const n of nodes) {
117
156
  const prefix = " ".repeat(depth);
118
- const loc = n.file ? `${rel(n.file)}:${((_a = n.snippetLine) !== null && _a !== void 0 ? _a : n.line) + 1}` : "(not indexed)";
157
+ const loc = n.file
158
+ ? `${rel(n.file)}:${((_a = n.snippetLine) !== null && _a !== void 0 ? _a : n.line) + 1}`
159
+ : "(not indexed)";
119
160
  const cols = withSnippets
120
161
  ? `${loc}\t${n.symbol}\t${(_b = n.snippet) !== null && _b !== void 0 ? _b : ""}`
121
162
  : `${loc}\t${n.symbol}`;
@@ -168,12 +209,13 @@ exports.trace = new commander_1.Command("trace")
168
209
  .argument("<symbol>", "The symbol to trace")
169
210
  .option("-d, --depth <n>", "Caller traversal depth (default 1, max 3)", "1")
170
211
  .option("--root <dir>", "Project root directory")
171
- .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
172
- .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
212
+ .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
213
+ .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
173
214
  .option("--agent", "Compact output for AI agents", false)
174
215
  .option("--inbound", "Show only callers, with call-site snippets", false)
175
216
  .option("--no-snippets", "Suppress call-site snippets in --inbound output")
176
217
  .option("--limit <n>", "Max callers shown per node in --inbound (default 10)", "10")
218
+ .option("--raw", "In --agent mode, list every call-site without collapsing repeated callers or hiding self-edges", false)
177
219
  .action((symbol, opts) => __awaiter(void 0, void 0, void 0, function* () {
178
220
  var _a;
179
221
  const depth = Math.min(Math.max(Number.parseInt(opts.depth || "1", 10), 1), 3);
@@ -214,7 +256,7 @@ exports.trace = new commander_1.Command("trace")
214
256
  }
215
257
  }
216
258
  else if (opts.agent) {
217
- console.log(formatTraceAgent(graph, projectRoot));
259
+ console.log(formatTraceAgent(graph, projectRoot, opts.raw));
218
260
  if (!graph.center)
219
261
  process.exitCode = 1;
220
262
  }
@@ -43,18 +43,18 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
43
43
  };
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.watch = void 0;
46
- const fs = __importStar(require("node:fs"));
47
46
  const node_child_process_1 = require("node:child_process");
47
+ const fs = __importStar(require("node:fs"));
48
48
  const path = __importStar(require("node:path"));
49
49
  const commander_1 = require("commander");
50
50
  const config_1 = require("../config");
51
51
  const index_config_1 = require("../lib/index/index-config");
52
- const filter_builder_1 = require("../lib/utils/filter-builder");
53
52
  const syncer_1 = require("../lib/index/syncer");
54
53
  const watcher_1 = require("../lib/index/watcher");
55
54
  const meta_cache_1 = require("../lib/store/meta-cache");
56
55
  const vector_db_1 = require("../lib/store/vector-db");
57
56
  const exit_1 = require("../lib/utils/exit");
57
+ const filter_builder_1 = require("../lib/utils/filter-builder");
58
58
  const log_rotate_1 = require("../lib/utils/log-rotate");
59
59
  const process_1 = require("../lib/utils/process");
60
60
  const project_registry_1 = require("../lib/utils/project-registry");
@@ -148,7 +148,7 @@ exports.watch = new commander_1.Command("watch")
148
148
  // --- Per-project mode ---
149
149
  const projectRoot = options.path
150
150
  ? path.resolve(options.path)
151
- : (_a = (0, project_root_1.findProjectRoot)(process.cwd())) !== null && _a !== void 0 ? _a : process.cwd();
151
+ : ((_a = (0, project_root_1.findProjectRoot)(process.cwd())) !== null && _a !== void 0 ? _a : process.cwd());
152
152
  const projectName = path.basename(projectRoot);
153
153
  // Check if watcher already running (exact match or parent covering this dir)
154
154
  const existing = (_b = (0, watcher_store_1.getWatcherForProject)(projectRoot)) !== null && _b !== void 0 ? _b : (0, watcher_store_1.getWatcherCoveringPath)(projectRoot);
@@ -195,7 +195,9 @@ exports.watch = new commander_1.Command("watch")
195
195
  // Initial sync if this directory isn't indexed yet
196
196
  const vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
197
197
  const table = yield vectorDb.ensureTable();
198
- const prefix = projectRoot.endsWith("/") ? projectRoot : `${projectRoot}/`;
198
+ const prefix = projectRoot.endsWith("/")
199
+ ? projectRoot
200
+ : `${projectRoot}/`;
199
201
  const indexed = yield table
200
202
  .query()
201
203
  .select(["id"])
@@ -325,7 +327,9 @@ exports.watch
325
327
  let parentCmd = "?";
326
328
  try {
327
329
  const { execSync } = yield Promise.resolve().then(() => __importStar(require("node:child_process")));
328
- parentCmd = execSync(`ps -o command= -p ${process.ppid}`, { encoding: "utf8" }).trim();
330
+ parentCmd = execSync(`ps -o command= -p ${process.ppid}`, {
331
+ encoding: "utf8",
332
+ }).trim();
329
333
  }
330
334
  catch (_c) { }
331
335
  yield sendDaemonCommand({
@@ -93,7 +93,10 @@ const SELF = "src/eval-graph-nav.ts"; // exclude from grep + graph (self-referen
93
93
  // grep can't tell those apart from genuine uses, so they would manufacture
94
94
  // phantom "expected callers" that no correct graph ever surfaces. Excluded from
95
95
  // both the grep truth and the getCallers set so the exclusion stays symmetric.
96
- const FIXTURE_FILES = new Set([SELF, "tests/graph-edges.type-position.test.ts"]);
96
+ const FIXTURE_FILES = new Set([
97
+ SELF,
98
+ "tests/graph-edges.type-position.test.ts",
99
+ ]);
97
100
  const rel = (p) => p.replace(`${GMAX_ROOT}/`, "");
98
101
  const INBOUND_SYMBOLS = [
99
102
  // ── Baselines: called/constructed, so the graph captures them. ──────────────
@@ -60,8 +60,8 @@ const path = __importStar(require("node:path"));
60
60
  const config_1 = require("./config");
61
61
  const vector_db_1 = require("./lib/store/vector-db");
62
62
  const exit_1 = require("./lib/utils/exit");
63
- const pool_1 = require("./lib/workers/pool");
64
63
  const filter_builder_1 = require("./lib/utils/filter-builder");
64
+ const pool_1 = require("./lib/workers/pool");
65
65
  const PLATFORM_ROOT = path.join((_b = process.env.HOME) !== null && _b !== void 0 ? _b : "", "Development/beyond/platform");
66
66
  const PRE_K = 500;
67
67
  const STAGE1_K = 200;
@@ -89,7 +89,9 @@ function toStrArr(val) {
89
89
  if (typeof m.toArray === "function") {
90
90
  try {
91
91
  const a = m.toArray();
92
- return Array.isArray(a) ? a.filter((v) => typeof v === "string") : [];
92
+ return Array.isArray(a)
93
+ ? a.filter((v) => typeof v === "string")
94
+ : [];
93
95
  }
94
96
  catch (_a) {
95
97
  return [];
@@ -101,22 +103,50 @@ function probe(table, sym, expectedFile) {
101
103
  return __awaiter(this, void 0, void 0, function* () {
102
104
  const pool = (0, pool_1.getWorkerPool)();
103
105
  const { dense } = yield pool.encodeQuery(sym);
104
- const prefix = PLATFORM_ROOT.endsWith("/") ? PLATFORM_ROOT : `${PLATFORM_ROOT}/`;
106
+ const prefix = PLATFORM_ROOT.endsWith("/")
107
+ ? PLATFORM_ROOT
108
+ : `${PLATFORM_ROOT}/`;
105
109
  const where = `path LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`;
106
- const columns = ["id", "path", "chunk_index", "defined_symbols", "referenced_symbols"];
107
- const vectorRows = (yield table.vectorSearch(dense).select([...columns, "_distance"]).where(where).limit(PRE_K).toArray());
110
+ const columns = [
111
+ "id",
112
+ "path",
113
+ "chunk_index",
114
+ "defined_symbols",
115
+ "referenced_symbols",
116
+ ];
117
+ const vectorRows = (yield table
118
+ .vectorSearch(dense)
119
+ .select([...columns, "_distance"])
120
+ .where(where)
121
+ .limit(PRE_K)
122
+ .toArray());
108
123
  let ftsRows = [];
109
124
  try {
110
- ftsRows = (yield table.search(sym).select([...columns, "_score"]).where(where).limit(PRE_K).toArray());
125
+ ftsRows = (yield table
126
+ .search(sym)
127
+ .select([...columns, "_score"])
128
+ .where(where)
129
+ .limit(PRE_K)
130
+ .toArray());
111
131
  }
112
132
  catch (_a) { }
113
133
  const scores = new Map();
114
134
  const docMap = new Map();
115
135
  const keyOf = (d) => d.id || `${d.path}:${d.chunk_index}`;
116
- vectorRows.forEach((d, r) => { const k = keyOf(d); docMap.set(k, d); scores.set(k, (scores.get(k) || 0) + 1 / (RRF_K + r + 1)); });
117
- ftsRows.forEach((d, r) => { const k = keyOf(d); if (!docMap.has(k))
118
- docMap.set(k, d); scores.set(k, (scores.get(k) || 0) + 1 / (RRF_K + r + 1)); });
119
- const fusedKeys = Array.from(scores.entries()).sort((a, b) => b[1] - a[1]).map(([k]) => k);
136
+ vectorRows.forEach((d, r) => {
137
+ const k = keyOf(d);
138
+ docMap.set(k, d);
139
+ scores.set(k, (scores.get(k) || 0) + 1 / (RRF_K + r + 1));
140
+ });
141
+ ftsRows.forEach((d, r) => {
142
+ const k = keyOf(d);
143
+ if (!docMap.has(k))
144
+ docMap.set(k, d);
145
+ scores.set(k, (scores.get(k) || 0) + 1 / (RRF_K + r + 1));
146
+ });
147
+ const fusedKeys = Array.from(scores.entries())
148
+ .sort((a, b) => b[1] - a[1])
149
+ .map(([k]) => k);
120
150
  const fused = fusedKeys.map((k) => docMap.get(k));
121
151
  // Locate the expected definition chunk among retrieved rows.
122
152
  const isExpected = (d) => String(d.path).toLowerCase().endsWith(`/${expectedFile.toLowerCase()}`) &&
@@ -134,7 +164,12 @@ function probe(table, sym, expectedFile) {
134
164
  // If not in the union, query directly to confirm it exists in the index.
135
165
  let defExistsInIndex = defInUnion;
136
166
  if (!defInUnion) {
137
- const direct = (yield table.query().select(columns).where(`${where} AND array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(sym)}')`).limit(50).toArray());
167
+ const direct = (yield table
168
+ .query()
169
+ .select(columns)
170
+ .where(`${where} AND array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(sym)}')`)
171
+ .limit(50)
172
+ .toArray());
138
173
  defExistsInIndex = direct.some((d) => String(d.path).toLowerCase().endsWith(`/${expectedFile.toLowerCase()}`));
139
174
  }
140
175
  // ref->def reachability: among top-SEED_K fusion seeds, how many reference `sym`?
@@ -149,8 +184,12 @@ function probe(table, sym, expectedFile) {
149
184
  }
150
185
  });
151
186
  const loc = defRetrievalRank > 0
152
- ? (defInPool ? `pool#${defRetrievalRank}` : `union#${defRetrievalRank}(>200)`)
153
- : (defExistsInIndex ? "OUTSIDE-500" : "NOT-IN-INDEX?");
187
+ ? defInPool
188
+ ? `pool#${defRetrievalRank}`
189
+ : `union#${defRetrievalRank}(>200)`
190
+ : defExistsInIndex
191
+ ? "OUTSIDE-500"
192
+ : "NOT-IN-INDEX?";
154
193
  return { sym, expectedFile, loc, seedsRefSym, firstRefSeedRank };
155
194
  });
156
195
  }
@@ -167,4 +206,7 @@ function main() {
167
206
  yield (0, exit_1.gracefulExit)(0);
168
207
  });
169
208
  }
170
- main().catch((e) => { console.error(e); process.exit(1); });
209
+ main().catch((e) => {
210
+ console.error(e);
211
+ process.exit(1);
212
+ });
@@ -37,7 +37,9 @@ function toStrArr(v) {
37
37
  if (typeof m.toArray === "function") {
38
38
  try {
39
39
  const a = m.toArray();
40
- return Array.isArray(a) ? a.filter((x) => typeof x === "string") : [];
40
+ return Array.isArray(a)
41
+ ? a.filter((x) => typeof x === "string")
42
+ : [];
41
43
  }
42
44
  catch (_a) {
43
45
  return [];
@@ -53,7 +55,13 @@ function main() {
53
55
  console.log(`\n── ${file}`);
54
56
  const rows = (yield table
55
57
  .query()
56
- .select(["start_line", "end_line", "chunk_type", "defined_symbols", "referenced_symbols"])
58
+ .select([
59
+ "start_line",
60
+ "end_line",
61
+ "chunk_type",
62
+ "defined_symbols",
63
+ "referenced_symbols",
64
+ ])
57
65
  .where(`path = '${(0, filter_builder_1.escapeSqlString)(file)}'`)
58
66
  .limit(20)
59
67
  .toArray());
@@ -69,7 +69,9 @@ function main() {
69
69
  return __awaiter(this, void 0, void 0, function* () {
70
70
  const db = new vector_db_1.VectorDB(config_1.PATHS.lancedbDir);
71
71
  const table = yield db.ensureTable();
72
- const pathPrefix = PLATFORM_ROOT.endsWith("/") ? PLATFORM_ROOT : `${PLATFORM_ROOT}/`;
72
+ const pathPrefix = PLATFORM_ROOT.endsWith("/")
73
+ ? PLATFORM_ROOT
74
+ : `${PLATFORM_ROOT}/`;
73
75
  const scope = `path LIKE '${(0, filter_builder_1.escapeSqlString)(pathPrefix)}%'`;
74
76
  console.log(`Platform graph density check — pathPrefix=${PLATFORM_ROOT}\n`);
75
77
  for (const sym of TARGETS) {
@@ -103,7 +105,8 @@ function main() {
103
105
  let arr = [];
104
106
  if (Array.isArray(raw))
105
107
  arr = raw.filter((v) => typeof v === "string");
106
- else if (raw && typeof raw.toArray === "function") {
108
+ else if (raw &&
109
+ typeof raw.toArray === "function") {
107
110
  try {
108
111
  const a = raw.toArray();
109
112
  if (Array.isArray(a))