grepmax 0.9.4 → 0.9.5

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.
@@ -441,7 +441,7 @@ exports.mcp = new commander_1.Command("mcp")
441
441
  }
442
442
  const result = yield searcher.search(query, limit, { rerank: true }, Object.keys(filters).length > 0 ? filters : undefined, pathPrefix);
443
443
  if (!result.data || result.data.length === 0) {
444
- return ok("No matches found.");
444
+ return ok("No matches found. Try broadening your query, using fewer keywords, or check `gmax status` to verify the project is indexed.");
445
445
  }
446
446
  const minScore = typeof args.min_score === "number" ? args.min_score : 0;
447
447
  const maxPerFile = typeof args.max_per_file === "number" ? args.max_per_file : 0;
@@ -743,7 +743,7 @@ exports.mcp = new commander_1.Command("mcp")
743
743
  const depth = Math.min(Math.max(Number(args.depth) || 1, 1), 3);
744
744
  const graph = yield builder.buildGraphMultiHop(symbol, depth);
745
745
  if (!graph.center) {
746
- return ok(`Symbol '${symbol}' not found in the index.`);
746
+ 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.`);
747
747
  }
748
748
  const lines = [];
749
749
  // Center
@@ -870,7 +870,7 @@ exports.mcp = new commander_1.Command("mcp")
870
870
  })
871
871
  .slice(0, limit);
872
872
  if (entries.length === 0) {
873
- return ok("No symbols found. Run 'gmax index' to build the index.");
873
+ return ok("No symbols found. Run `gmax status` to verify the project is indexed, or `gmax index` to rebuild.");
874
874
  }
875
875
  const lines = entries.map((e) => {
876
876
  const rel = e.path.startsWith(projectRoot)
@@ -1118,7 +1118,7 @@ exports.mcp = new commander_1.Command("mcp")
1118
1118
  .where(`path = '${(0, filter_builder_1.escapeSqlString)(absPath)}'`)
1119
1119
  .toArray();
1120
1120
  if (fileChunks.length === 0) {
1121
- return ok(`File not found in index: ${file}`);
1121
+ return ok(`File not found in index: ${file}. Check that the path is relative to the project root. Run \`gmax status\` to see indexed projects.`);
1122
1122
  }
1123
1123
  const definedHere = new Set();
1124
1124
  const referencedHere = new Set();
@@ -1236,7 +1236,7 @@ exports.mcp = new commander_1.Command("mcp")
1236
1236
  files.sort((a, b) => b.mtimeMs - a.mtimeMs);
1237
1237
  const top = files.slice(0, limit);
1238
1238
  if (top.length === 0) {
1239
- return ok(`No indexed files found for ${root}`);
1239
+ return ok(`No indexed files found for ${root}. Run \`gmax add\` to register and index this project.`);
1240
1240
  }
1241
1241
  const now = Date.now();
1242
1242
  const lines = [
@@ -59,8 +59,9 @@ function toArr(val) {
59
59
  exports.project = new commander_1.Command("project")
60
60
  .description("Show project overview — languages, structure, key symbols")
61
61
  .option("--root <dir>", "Project root (defaults to current directory)")
62
+ .option("--agent", "Compact output for AI agents", false)
62
63
  .action((opts) => __awaiter(void 0, void 0, void 0, function* () {
63
- var _a, _b, _c;
64
+ var _a, _b, _c, _d;
64
65
  let vectorDb = null;
65
66
  try {
66
67
  const root = opts.root
@@ -86,6 +87,7 @@ exports.project = new commander_1.Command("project")
86
87
  .toArray();
87
88
  if (rows.length === 0) {
88
89
  console.log(`No indexed data found for ${root}. Run: gmax index --path ${root}`);
90
+ process.exitCode = 1;
89
91
  return;
90
92
  }
91
93
  const files = new Set();
@@ -128,33 +130,50 @@ exports.project = new commander_1.Command("project")
128
130
  }
129
131
  const projects = (0, project_registry_1.listProjects)();
130
132
  const proj = projects.find((p) => p.root === root);
131
- console.log(`Project: ${projectName} (${root})`);
132
- console.log(`Last indexed: ${(_c = proj === null || proj === void 0 ? void 0 : proj.lastIndexed) !== null && _c !== void 0 ? _c : "unknown"} • ${rows.length} chunks • ${files.size} files\n`);
133
133
  const extEntries = Array.from(extCounts.entries())
134
134
  .sort((a, b) => b[1] - a[1])
135
135
  .slice(0, 8);
136
- console.log(`Languages: ${extEntries.map(([ext, count]) => `${ext} (${Math.round((count / rows.length) * 100)}%)`).join(", ")}\n`);
137
- console.log("Directory structure:");
138
- for (const [dir, data] of Array.from(dirCounts.entries())
139
- .sort((a, b) => b[1].chunks - a[1].chunks)
140
- .slice(0, 12)) {
141
- console.log(` ${dir.padEnd(25)} (${data.files.size} files, ${data.chunks} chunks)`);
142
- }
143
- const roleEntries = Array.from(roleCounts.entries()).sort((a, b) => b[1] - a[1]);
144
- console.log(`\nRoles: ${roleEntries.map(([r, c]) => `${Math.round((c / rows.length) * 100)}% ${r}`).join(", ")}\n`);
145
136
  const topSymbols = Array.from(symbolRefs.entries())
146
137
  .sort((a, b) => b[1] - a[1])
147
138
  .slice(0, 8);
148
- if (topSymbols.length > 0) {
149
- console.log("Key symbols (by reference count):");
150
- for (const [sym, count] of topSymbols) {
151
- console.log(` ${sym.padEnd(25)} (referenced ${count}x)`);
139
+ if (opts.agent) {
140
+ console.log(`name\t${projectName}`);
141
+ console.log(`root\t${root}`);
142
+ console.log(`chunks\t${rows.length}`);
143
+ console.log(`files\t${files.size}`);
144
+ console.log(`last_indexed\t${(_c = proj === null || proj === void 0 ? void 0 : proj.lastIndexed) !== null && _c !== void 0 ? _c : "unknown"}`);
145
+ console.log(`languages\t${extEntries.map(([ext]) => ext).join(",")}`);
146
+ 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(",")}`);
147
+ if (topSymbols.length > 0) {
148
+ console.log(`key_symbols\t${topSymbols.map(([s]) => s).join(",")}`);
149
+ }
150
+ if (entryPoints.length > 0) {
151
+ console.log(`entry_points\t${entryPoints.slice(0, 10).map((e) => e.symbol).join(",")}`);
152
152
  }
153
153
  }
154
- if (entryPoints.length > 0) {
155
- console.log("\nEntry points (exported orchestration):");
156
- for (const ep of entryPoints.slice(0, 10)) {
157
- console.log(` ${ep.symbol.padEnd(25)} ${ep.path}`);
154
+ else {
155
+ console.log(`Project: ${projectName} (${root})`);
156
+ console.log(`Last indexed: ${(_d = proj === null || proj === void 0 ? void 0 : proj.lastIndexed) !== null && _d !== void 0 ? _d : "unknown"} • ${rows.length} chunks • ${files.size} files\n`);
157
+ console.log(`Languages: ${extEntries.map(([ext, count]) => `${ext} (${Math.round((count / rows.length) * 100)}%)`).join(", ")}\n`);
158
+ console.log("Directory structure:");
159
+ for (const [dir, data] of Array.from(dirCounts.entries())
160
+ .sort((a, b) => b[1].chunks - a[1].chunks)
161
+ .slice(0, 12)) {
162
+ console.log(` ${dir.padEnd(25)} (${data.files.size} files, ${data.chunks} chunks)`);
163
+ }
164
+ const roleEntries = Array.from(roleCounts.entries()).sort((a, b) => b[1] - a[1]);
165
+ console.log(`\nRoles: ${roleEntries.map(([r, c]) => `${Math.round((c / rows.length) * 100)}% ${r}`).join(", ")}\n`);
166
+ if (topSymbols.length > 0) {
167
+ console.log("Key symbols (by reference count):");
168
+ for (const [sym, count] of topSymbols) {
169
+ console.log(` ${sym.padEnd(25)} (referenced ${count}x)`);
170
+ }
171
+ }
172
+ if (entryPoints.length > 0) {
173
+ console.log("\nEntry points (exported orchestration):");
174
+ for (const ep of entryPoints.slice(0, 10)) {
175
+ console.log(` ${ep.symbol.padEnd(25)} ${ep.path}`);
176
+ }
158
177
  }
159
178
  }
160
179
  }
@@ -168,7 +187,7 @@ exports.project = new commander_1.Command("project")
168
187
  try {
169
188
  yield vectorDb.close();
170
189
  }
171
- catch (_d) { }
190
+ catch (_e) { }
172
191
  }
173
192
  yield (0, exit_1.gracefulExit)();
174
193
  }
@@ -61,6 +61,7 @@ exports.recent = new commander_1.Command("recent")
61
61
  .description("Show recently modified indexed files")
62
62
  .option("-l, --limit <n>", "Max files (default 20)", "20")
63
63
  .option("--root <dir>", "Project root (defaults to current directory)")
64
+ .option("--agent", "Compact output for AI agents", false)
64
65
  .action((opts) => __awaiter(void 0, void 0, void 0, function* () {
65
66
  var _a, e_1, _b, _c;
66
67
  var _d, _e;
@@ -94,16 +95,28 @@ exports.recent = new commander_1.Command("recent")
94
95
  const top = files.slice(0, limit);
95
96
  if (top.length === 0) {
96
97
  console.log(`No indexed files found for ${root}`);
98
+ console.log("\nTry: `gmax add` to register and index this project, or `gmax status` to see what's indexed.");
99
+ process.exitCode = 1;
97
100
  return;
98
101
  }
99
102
  const now = Date.now();
100
- console.log(`Recent changes in ${path.basename(root)} (${top.length} most recent):\n`);
101
- for (const f of top) {
102
- const rel = f.path.startsWith(prefix)
103
- ? f.path.slice(prefix.length)
104
- : f.path;
105
- const ago = (0, format_helpers_1.formatTimeAgo)(now - f.mtimeMs);
106
- console.log(` ${ago.padEnd(10)} ${rel}`);
103
+ if (opts.agent) {
104
+ for (const f of top) {
105
+ const rel = f.path.startsWith(prefix)
106
+ ? f.path.slice(prefix.length)
107
+ : f.path;
108
+ console.log(`${rel}\t${(0, format_helpers_1.formatTimeAgo)(now - f.mtimeMs)}`);
109
+ }
110
+ }
111
+ else {
112
+ console.log(`Recent changes in ${path.basename(root)} (${top.length} most recent):\n`);
113
+ for (const f of top) {
114
+ const rel = f.path.startsWith(prefix)
115
+ ? f.path.slice(prefix.length)
116
+ : f.path;
117
+ const ago = (0, format_helpers_1.formatTimeAgo)(now - f.mtimeMs);
118
+ console.log(` ${ago.padEnd(10)} ${rel}`);
119
+ }
107
120
  }
108
121
  }
109
122
  finally {
@@ -60,6 +60,7 @@ exports.related = new commander_1.Command("related")
60
60
  .argument("<file>", "File path relative to project root")
61
61
  .option("-l, --limit <n>", "Max results per direction (default 10)", "10")
62
62
  .option("--root <dir>", "Project root directory")
63
+ .option("--agent", "Compact output for AI agents", false)
63
64
  .action((file, opts) => __awaiter(void 0, void 0, void 0, function* () {
64
65
  var _a;
65
66
  const limit = Math.min(Math.max(Number.parseInt(opts.limit || "10", 10), 1), 25);
@@ -79,6 +80,8 @@ exports.related = new commander_1.Command("related")
79
80
  .toArray();
80
81
  if (fileChunks.length === 0) {
81
82
  console.log(`File not found in index: ${file}`);
83
+ console.log("\nCheck that the path is relative to the project root. Run `gmax status` to see indexed projects.");
84
+ process.exitCode = 1;
82
85
  return;
83
86
  }
84
87
  const definedHere = new Set();
@@ -123,37 +126,53 @@ exports.related = new commander_1.Command("related")
123
126
  revCounts.set(p, (revCounts.get(p) || 0) + 1);
124
127
  }
125
128
  }
126
- console.log(`Related files for ${file}:\n`);
127
129
  const topDeps = Array.from(depCounts.entries())
128
130
  .sort((a, b) => b[1] - a[1])
129
131
  .slice(0, limit);
130
- if (topDeps.length > 0) {
131
- console.log("Dependencies (files this imports/calls):");
132
- for (const [p, count] of topDeps) {
133
- const rel = p.startsWith(`${projectRoot}/`)
134
- ? p.slice(projectRoot.length + 1)
135
- : p;
136
- console.log(` ${rel.padEnd(40)} (${count} shared symbol${count > 1 ? "s" : ""})`);
137
- }
138
- }
139
- else {
140
- console.log("Dependencies: none found");
141
- }
142
- console.log("");
143
132
  const topRevs = Array.from(revCounts.entries())
144
133
  .sort((a, b) => b[1] - a[1])
145
134
  .slice(0, limit);
146
- if (topRevs.length > 0) {
147
- console.log("Dependents (files that call this):");
135
+ if (opts.agent) {
136
+ const rel = (p) => p.startsWith(`${projectRoot}/`)
137
+ ? p.slice(projectRoot.length + 1)
138
+ : p;
139
+ for (const [p, count] of topDeps) {
140
+ console.log(`dep: ${rel(p)}\t${count}`);
141
+ }
148
142
  for (const [p, count] of topRevs) {
149
- const rel = p.startsWith(`${projectRoot}/`)
150
- ? p.slice(projectRoot.length + 1)
151
- : p;
152
- console.log(` ${rel.padEnd(40)} (${count} shared symbol${count > 1 ? "s" : ""})`);
143
+ console.log(`rev: ${rel(p)}\t${count}`);
144
+ }
145
+ if (!topDeps.length && !topRevs.length) {
146
+ console.log("(none)");
153
147
  }
154
148
  }
155
149
  else {
156
- console.log("Dependents: none found");
150
+ console.log(`Related files for ${file}:\n`);
151
+ if (topDeps.length > 0) {
152
+ console.log("Dependencies (files this imports/calls):");
153
+ for (const [p, count] of topDeps) {
154
+ const rel = p.startsWith(`${projectRoot}/`)
155
+ ? p.slice(projectRoot.length + 1)
156
+ : p;
157
+ console.log(` ${rel.padEnd(40)} (${count} shared symbol${count > 1 ? "s" : ""})`);
158
+ }
159
+ }
160
+ else {
161
+ console.log("Dependencies: none found");
162
+ }
163
+ console.log("");
164
+ if (topRevs.length > 0) {
165
+ console.log("Dependents (files that call this):");
166
+ for (const [p, count] of topRevs) {
167
+ const rel = p.startsWith(`${projectRoot}/`)
168
+ ? p.slice(projectRoot.length + 1)
169
+ : p;
170
+ console.log(` ${rel.padEnd(40)} (${count} shared symbol${count > 1 ? "s" : ""})`);
171
+ }
172
+ }
173
+ else {
174
+ console.log("Dependents: none found");
175
+ }
157
176
  }
158
177
  }
159
178
  catch (error) {
@@ -272,6 +272,8 @@ function outputSkeletons(results, projectRoot, limit, db) {
272
272
  }
273
273
  if (filesToProcess.length === 0) {
274
274
  console.log("No skeleton matches found.");
275
+ console.log("\nTry: broaden your query, or use `gmax skeleton <path>` to view a specific file's structure.");
276
+ process.exitCode = 1;
275
277
  return;
276
278
  }
277
279
  // Reuse or init skeletonizer for fallbacks
@@ -326,6 +328,19 @@ function outputSkeletons(results, projectRoot, limit, db) {
326
328
  }
327
329
  });
328
330
  }
331
+ function resultCountHeader(results, maxCount) {
332
+ var _a, _b, _c;
333
+ const files = new Set();
334
+ for (const r of results) {
335
+ const p = (_c = (_a = r.path) !== null && _a !== void 0 ? _a : (_b = r.metadata) === null || _b === void 0 ? void 0 : _b.path) !== null && _c !== void 0 ? _c : "";
336
+ if (p)
337
+ files.add(p);
338
+ }
339
+ const showing = results.length < maxCount
340
+ ? `${results.length}`
341
+ : `top ${results.length}`;
342
+ return `Found ${results.length} match${results.length === 1 ? "" : "es"} (showing ${showing}) across ${files.size} file${files.size === 1 ? "" : "s"}`;
343
+ }
329
344
  exports.search = new commander_1.Command("search")
330
345
  .description("Search code by meaning (default command)")
331
346
  .option("-m <max_count>, --max-count <max_count>", "The maximum number of results to return (total)", "5")
@@ -403,21 +418,31 @@ Examples:
403
418
  ? toCompactHits(filteredData)
404
419
  : [];
405
420
  if (options.compact) {
406
- const compactText = compactHits.length
407
- ? formatCompactTable(compactHits, projectRootForServer, pattern, {
421
+ if (!compactHits.length) {
422
+ console.log("No matches found.");
423
+ console.log("\nTry: broaden your query, use fewer keywords, or check `gmax status` to verify the project is indexed.");
424
+ process.exitCode = 1;
425
+ }
426
+ else {
427
+ console.log(formatCompactTable(compactHits, projectRootForServer, pattern, {
408
428
  isTTY: !!process.stdout.isTTY,
409
429
  plain: !!options.plain,
410
- })
411
- : "No matches found.";
412
- console.log(compactText);
430
+ }));
431
+ }
413
432
  return; // EXIT
414
433
  }
415
434
  if (!filteredData.length) {
416
435
  console.log("No matches found.");
436
+ console.log("\nTry: broaden your query, use fewer keywords, or check `gmax status` to verify the project is indexed.");
437
+ process.exitCode = 1;
417
438
  return; // EXIT
418
439
  }
419
440
  const isTTY = process.stdout.isTTY;
420
441
  const shouldBePlain = options.plain || !isTTY;
442
+ if (!options.agent && !options.compact) {
443
+ console.log(resultCountHeader(filteredData, parseInt(options.m, 10)));
444
+ console.log();
445
+ }
421
446
  if (shouldBePlain) {
422
447
  const mappedResults = toTextResults(filteredData);
423
448
  const output = (0, formatter_1.formatTextResults)(mappedResults, pattern, projectRootForServer, {
@@ -592,6 +617,7 @@ Examples:
592
617
  if (options.agent) {
593
618
  if (!filteredData.length) {
594
619
  console.log("(none)");
620
+ process.exitCode = 1;
595
621
  }
596
622
  else {
597
623
  // In agent mode, print imports header per file
@@ -685,25 +711,26 @@ Examples:
685
711
  yield outputSkeletons(filteredData, projectRoot, parseInt(options.m, 10), vectorDb);
686
712
  return;
687
713
  }
688
- const compactHits = options.compact ? toCompactHits(filteredData) : [];
689
- const compactText = options.compact && compactHits.length
690
- ? formatCompactTable(compactHits, projectRoot, pattern, {
691
- isTTY: !!process.stdout.isTTY,
692
- plain: !!options.plain,
693
- })
694
- : options.compact
695
- ? "No matches found."
696
- : "";
697
714
  if (!filteredData.length) {
698
715
  console.log("No matches found.");
716
+ console.log("\nTry: broaden your query, use fewer keywords, or check `gmax status` to verify the project is indexed.");
717
+ process.exitCode = 1;
699
718
  return;
700
719
  }
701
720
  if (options.compact) {
702
- console.log(compactText);
721
+ const compactHits = toCompactHits(filteredData);
722
+ console.log(formatCompactTable(compactHits, projectRoot, pattern, {
723
+ isTTY: !!process.stdout.isTTY,
724
+ plain: !!options.plain,
725
+ }));
703
726
  return;
704
727
  }
705
728
  const isTTY = process.stdout.isTTY;
706
729
  const shouldBePlain = options.plain || !isTTY;
730
+ if (!options.agent && !options.compact) {
731
+ console.log(resultCountHeader(filteredData, parseInt(options.m, 10)));
732
+ console.log();
733
+ }
707
734
  // Print imports per unique file before results when --imports is used
708
735
  if (options.imports) {
709
736
  const seenFiles = new Set();
@@ -91,21 +91,50 @@ function formatChunks(n) {
91
91
  }
92
92
  exports.status = new commander_1.Command("status")
93
93
  .description("Show gmax index status for all projects")
94
+ .option("--agent", "Compact output for AI agents", false)
94
95
  .addHelpText("after", `
95
96
  Examples:
96
97
  gmax status Show status of all indexed projects
97
98
  `)
98
- .action(() => __awaiter(void 0, void 0, void 0, function* () {
99
- var _a;
99
+ .action((opts) => __awaiter(void 0, void 0, void 0, function* () {
100
+ var _a, _b;
100
101
  const globalConfig = (0, index_config_1.readGlobalConfig)();
101
102
  const projects = (0, project_registry_1.listProjects)();
102
103
  (0, watcher_store_1.listWatchers)(); // cleans stale entries as side effect
103
104
  const indexing = (0, lock_1.isLocked)(config_1.PATHS.globalRoot);
104
105
  const currentRoot = (0, project_root_1.findProjectRoot)(process.cwd());
105
- // Header
106
- console.log(`\n${style.bold("gmax")} · ${globalConfig.modelTier} (${globalConfig.vectorDim}d, ${globalConfig.embedMode})${indexing ? style.yellow(" · indexing...") : ""}`);
106
+ if (!opts.agent) {
107
+ // Header
108
+ console.log(`\n${style.bold("gmax")} · ${globalConfig.modelTier} (${globalConfig.vectorDim}d, ${globalConfig.embedMode})${indexing ? style.yellow(" · indexing...") : ""}`);
109
+ }
107
110
  if (projects.length === 0) {
108
- console.log(`\nNo projects added yet. Run ${style.cyan("gmax add")} to get started.\n`);
111
+ if (opts.agent) {
112
+ console.log("(none)");
113
+ }
114
+ else {
115
+ console.log(`\nNo projects added yet. Run ${style.cyan("gmax add")} to get started.\n`);
116
+ }
117
+ yield (0, exit_1.gracefulExit)();
118
+ return;
119
+ }
120
+ if (opts.agent) {
121
+ for (const project of projects) {
122
+ const watcher = (0, watcher_store_1.getWatcherForProject)(project.root);
123
+ const projectStatus = (_a = project.status) !== null && _a !== void 0 ? _a : "indexed";
124
+ let st;
125
+ if (projectStatus === "pending")
126
+ st = "pending";
127
+ else if (projectStatus === "error")
128
+ st = "error";
129
+ else if ((watcher === null || watcher === void 0 ? void 0 : watcher.status) === "syncing")
130
+ st = "indexing";
131
+ else if (watcher)
132
+ st = "watching";
133
+ else
134
+ st = "idle";
135
+ const isCurrent = project.root === currentRoot;
136
+ console.log(`${project.name}\t${formatChunks(project.chunkCount)}\t${formatAge(project.lastIndexed)}\t${st}${isCurrent ? "\tcurrent" : ""}`);
137
+ }
109
138
  yield (0, exit_1.gracefulExit)();
110
139
  return;
111
140
  }
@@ -117,7 +146,7 @@ Examples:
117
146
  const watcher = (0, watcher_store_1.getWatcherForProject)(project.root);
118
147
  // Status column
119
148
  let statusStr;
120
- const projectStatus = (_a = project.status) !== null && _a !== void 0 ? _a : "indexed";
149
+ const projectStatus = (_b = project.status) !== null && _b !== void 0 ? _b : "indexed";
121
150
  if (projectStatus === "pending") {
122
151
  statusStr = style.yellow("pending");
123
152
  }
@@ -123,7 +123,13 @@ function collectSymbols(options) {
123
123
  }
124
124
  function formatTable(entries) {
125
125
  if (entries.length === 0) {
126
- return "No symbols found. Run `gmax index` to build the index.";
126
+ return [
127
+ "No symbols found.",
128
+ "",
129
+ "Try:",
130
+ " gmax status — verify the project is indexed",
131
+ " gmax index — rebuild the index",
132
+ ].join("\n");
127
133
  }
128
134
  const rows = entries.map((e) => ({
129
135
  symbol: e.symbol,
@@ -147,12 +153,25 @@ function formatTable(entries) {
147
153
  ];
148
154
  return lines.join("\n");
149
155
  }
156
+ function formatAgent(entries, projectRoot) {
157
+ if (entries.length === 0)
158
+ return "(none)";
159
+ return entries
160
+ .map((e) => {
161
+ const rel = e.path.startsWith(projectRoot)
162
+ ? e.path.slice(projectRoot.length + 1)
163
+ : e.path;
164
+ return `${e.symbol}\t${rel}:${Math.max(1, e.line + 1)}\t${e.count}`;
165
+ })
166
+ .join("\n");
167
+ }
150
168
  exports.symbols = new commander_1.Command("symbols")
151
169
  .description("List indexed symbols and where they are defined")
152
170
  .argument("[pattern]", "Optional pattern to filter symbols by name")
153
171
  .option("-l, --limit <number>", "Max symbols to list (default 20)", "20")
154
172
  .option("-p, --path <prefix>", "Only include symbols under this path prefix")
155
173
  .option("--root <dir>", "Project root directory")
174
+ .option("--agent", "Compact output for AI agents", false)
156
175
  .action((pattern, cmd) => __awaiter(void 0, void 0, void 0, function* () {
157
176
  var _a, _b;
158
177
  const root = cmd.root ? path.resolve(cmd.root) : process.cwd();
@@ -166,6 +185,14 @@ exports.symbols = new commander_1.Command("symbols")
166
185
  pathPrefix,
167
186
  pattern: pattern,
168
187
  });
169
- console.log(`${style.bold("Project")}: ${style.green(projectRoot)}\n${formatTable(entries)}`);
188
+ if (cmd.agent) {
189
+ console.log(formatAgent(entries, projectRoot));
190
+ }
191
+ else {
192
+ console.log(`${style.bold("Project")}: ${style.green(projectRoot)}\n${formatTable(entries)}`);
193
+ }
194
+ if (entries.length === 0) {
195
+ process.exitCode = 1;
196
+ }
170
197
  yield (0, exit_1.gracefulExit)();
171
198
  }));
@@ -50,11 +50,35 @@ const formatter_1 = require("../lib/output/formatter");
50
50
  const vector_db_1 = require("../lib/store/vector-db");
51
51
  const exit_1 = require("../lib/utils/exit");
52
52
  const project_root_1 = require("../lib/utils/project-root");
53
+ function formatTraceAgent(graph, projectRoot) {
54
+ if (!graph.center)
55
+ return "(not found)";
56
+ const rel = (p) => p.startsWith(projectRoot) ? p.slice(projectRoot.length + 1) : p;
57
+ const lines = [];
58
+ lines.push(`${graph.center.symbol}\t${rel(graph.center.file)}:${graph.center.line}\t${graph.center.role}`);
59
+ function walkCallers(tree, depth) {
60
+ for (const t of tree) {
61
+ lines.push(`${" ".repeat(depth)}<- ${t.node.symbol}\t${rel(t.node.file)}:${t.node.line}`);
62
+ walkCallers(t.callers, depth + 1);
63
+ }
64
+ }
65
+ walkCallers(graph.callerTree, 0);
66
+ for (const c of graph.callees) {
67
+ if (c.file) {
68
+ lines.push(`-> ${c.symbol}\t${rel(c.file)}:${c.line}`);
69
+ }
70
+ else {
71
+ lines.push(`-> ${c.symbol}\t(not indexed)`);
72
+ }
73
+ }
74
+ return lines.join("\n");
75
+ }
53
76
  exports.trace = new commander_1.Command("trace")
54
77
  .description("Trace the call graph for a symbol")
55
78
  .argument("<symbol>", "The symbol to trace")
56
79
  .option("-d, --depth <n>", "Caller traversal depth (default 1, max 3)", "1")
57
80
  .option("--root <dir>", "Project root directory")
81
+ .option("--agent", "Compact output for AI agents", false)
58
82
  .action((symbol, opts) => __awaiter(void 0, void 0, void 0, function* () {
59
83
  var _a;
60
84
  const depth = Math.min(Math.max(Number.parseInt(opts.depth || "1", 10), 1), 3);
@@ -66,7 +90,15 @@ exports.trace = new commander_1.Command("trace")
66
90
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
67
91
  const graphBuilder = new graph_builder_1.GraphBuilder(vectorDb, projectRoot);
68
92
  const graph = yield graphBuilder.buildGraphMultiHop(symbol, depth);
69
- console.log((0, formatter_1.formatTrace)(graph));
93
+ if (opts.agent) {
94
+ console.log(formatTraceAgent(graph, projectRoot));
95
+ }
96
+ else {
97
+ console.log((0, formatter_1.formatTrace)(graph, { symbol }));
98
+ }
99
+ if (!graph.center) {
100
+ process.exitCode = 1;
101
+ }
70
102
  }
71
103
  catch (error) {
72
104
  const message = error instanceof Error ? error.message : "Unknown error";
@@ -125,9 +125,23 @@ function formatResults(results, root, options = {}) {
125
125
  return "No results found.";
126
126
  return results.map((r) => formatResult(r, root, options)).join("\n\n");
127
127
  }
128
- function formatTrace(graph) {
128
+ function formatTrace(graph, options) {
129
+ var _a;
129
130
  if (!graph.center) {
130
- return style.dim("Symbol not found.");
131
+ const name = (_a = options === null || options === void 0 ? void 0 : options.symbol) !== null && _a !== void 0 ? _a : "unknown";
132
+ const lines = [
133
+ `Symbol not found: ${style.bold(name)}`,
134
+ "",
135
+ style.dim("Possible reasons:"),
136
+ style.dim(" • The symbol doesn't exist in any indexed project"),
137
+ style.dim(" • The containing file hasn't been indexed yet"),
138
+ style.dim(" • The name is spelled differently in the source"),
139
+ "",
140
+ style.dim("Try:"),
141
+ style.dim(" gmax status — see which projects are indexed"),
142
+ style.dim(" gmax search <name> — fuzzy search for similar symbols"),
143
+ ];
144
+ return lines.join("\n");
131
145
  }
132
146
  const lines = [];
133
147
  // 1. Importers
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.9.4",
3
+ "version": "0.9.5",
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.9.4",
3
+ "version": "0.9.5",
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",
@@ -121,7 +121,7 @@ async function main() {
121
121
  hookSpecificOutput: {
122
122
  hookEventName: "SessionStart",
123
123
  additionalContext:
124
- 'gmax ready. PREFER CLI over MCP — use Bash(gmax "query" --agent) for search (one line per result, 89% fewer tokens). Bash(gmax trace <symbol>) for call graphs. Bash(gmax skeleton <path>) for structure. Bash(gmax status) to check indexed projects. Use --agent flag on search commands only. If search says "not added yet", run Bash(gmax add).',
124
+ 'gmax ready. PREFER CLI over MCP — use Bash(gmax "query" --agent) for search (one line per result, 89% fewer tokens). Bash(gmax trace <symbol>) for call graphs. Bash(gmax skeleton <path>) for structure. Bash(gmax status) to check indexed projects. --agent flag works on search, trace, symbols, related, recent, status, project. If search says "not added yet", run Bash(gmax add).',
125
125
  },
126
126
  };
127
127
  process.stdout.write(JSON.stringify(response));
@@ -40,7 +40,7 @@ If search returns "This project hasn't been added to gmax yet", run `Bash(gmax a
40
40
 
41
41
  ### Search — `gmax "query" --agent`
42
42
 
43
- The `--agent` flag is **search-only**. Do NOT use it on `trace`, `skeleton`, or other commands.
43
+ The `--agent` flag produces compact, token-efficient output for AI agents. It is supported on: `search`, `trace`, `symbols`, `related`, `recent`, `status`, and `project`.
44
44
 
45
45
  ```
46
46
  gmax "where do we handle authentication" --agent
@@ -70,6 +70,7 @@ This shows function signatures, what each calls, and complexity — enough to de
70
70
  gmax trace handleAuth # 1-hop: callers + callees
71
71
  gmax trace handleAuth -d 2 # 2-hop: callers-of-callers
72
72
  gmax trace handleAuth --root ~/project # trace in a different project
73
+ gmax trace handleAuth --agent # compact: symbol\tpath:line, <- callers, -> callees
73
74
  ```
74
75
 
75
76
  ### Skeleton — `gmax skeleton <target>`
@@ -97,11 +98,20 @@ gmax related src/lib/index/syncer.ts --root ~/project
97
98
  gmax recent # recently modified files
98
99
  ```
99
100
 
100
- ### Other
101
+ ### Symbols — `gmax symbols`
101
102
  ```
102
103
  gmax symbols # list indexed symbols
103
104
  gmax symbols auth -p src/ --root ~/proj # filter by name, path, project
105
+ gmax symbols --agent # compact: symbol\tpath:line\tcount
106
+ ```
107
+
108
+ ### Other
109
+ ```
104
110
  gmax status # show all indexed projects
111
+ gmax status --agent # compact: name\tchunks\tage\tstatus
112
+ gmax recent --agent # compact: path\tage
113
+ gmax related src/file.ts --agent # compact: dep:/rev: path\tcount
114
+ gmax project --agent # compact: key\tvalue pairs
105
115
  gmax index # reindex current directory
106
116
  gmax config # view/change settings
107
117
  gmax doctor # health check
@@ -124,7 +134,7 @@ Use MCP only for `index_status` and `summarize_directory`. Use CLI for everythin
124
134
 
125
135
  ## Tips
126
136
 
127
- - **Use `--agent` on search only** — one line per result with signature hints.
137
+ - **Use `--agent` for compact output** — supported on search, trace, symbols, related, recent, status, project.
128
138
  - **Be specific.** 5+ words. "auth" returns noise. "where does the server validate JWT tokens" is specific.
129
139
  - **Use `--role ORCHESTRATION`** to skip type definitions and find the actual logic.
130
140
  - **Use `--symbol`** when the query is a function/class name — gets search + trace in one call.