grepmax 0.9.3 → 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.
- package/README.md +17 -8
- package/dist/commands/mcp.js +5 -5
- package/dist/commands/project.js +40 -21
- package/dist/commands/recent.js +20 -7
- package/dist/commands/related.js +40 -21
- package/dist/commands/search.js +42 -15
- package/dist/commands/skeleton.js +22 -4
- package/dist/commands/status.js +35 -6
- package/dist/commands/symbols.js +29 -2
- package/dist/commands/trace.js +33 -1
- package/dist/lib/output/formatter.js +16 -2
- package/package.json +1 -1
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
- package/plugins/grepmax/hooks/start.js +1 -1
- package/plugins/grepmax/skills/grepmax/SKILL.md +13 -3
package/README.md
CHANGED
|
@@ -186,16 +186,17 @@ gmax index --reset # Full re-index from scratch
|
|
|
186
186
|
|
|
187
187
|
### `gmax watch`
|
|
188
188
|
|
|
189
|
-
Background file watcher for live reindexing.
|
|
189
|
+
Background file watcher for live reindexing. A single daemon process watches all registered projects through native OS file system events (`@parcel/watcher` — FSEvents on macOS, inotify on Linux). File changes are detected in sub-second and incrementally reindexed.
|
|
190
190
|
|
|
191
191
|
```bash
|
|
192
|
-
gmax watch -b
|
|
193
|
-
gmax watch
|
|
194
|
-
gmax watch status # Show
|
|
195
|
-
gmax watch stop
|
|
192
|
+
gmax watch --daemon -b # Start daemon (watches all projects)
|
|
193
|
+
gmax watch -b # Per-project mode (fallback)
|
|
194
|
+
gmax watch status # Show daemon + watcher status
|
|
195
|
+
gmax watch stop # Stop daemon
|
|
196
|
+
gmax watch stop --all # Stop everything
|
|
196
197
|
```
|
|
197
198
|
|
|
198
|
-
The
|
|
199
|
+
The daemon auto-starts when you run `gmax search` or use MCP tools. It shuts down after 30 minutes of inactivity. CLI commands communicate with the daemon over a Unix domain socket at `~/.gmax/daemon.sock`.
|
|
199
200
|
|
|
200
201
|
### `gmax summarize`
|
|
201
202
|
|
|
@@ -295,6 +296,9 @@ gmax doctor
|
|
|
295
296
|
All data lives in `~/.gmax/`:
|
|
296
297
|
- `~/.gmax/lancedb/` — LanceDB vector store (one database for all indexed directories)
|
|
297
298
|
- `~/.gmax/cache/meta.lmdb` — file metadata cache (content hashes, mtimes)
|
|
299
|
+
- `~/.gmax/cache/watchers.lmdb` — watcher/daemon registry (LMDB, crash-safe)
|
|
300
|
+
- `~/.gmax/daemon.sock` — Unix domain socket for daemon IPC
|
|
301
|
+
- `~/.gmax/logs/` — daemon and watcher logs (5MB rotation)
|
|
298
302
|
- `~/.gmax/config.json` — global config (model tier, embed mode)
|
|
299
303
|
- `~/.gmax/models/` — embedding models
|
|
300
304
|
- `~/.gmax/grammars/` — Tree-sitter grammars
|
|
@@ -304,6 +308,10 @@ All chunks store **absolute file paths**. Search scoping is done via path prefix
|
|
|
304
308
|
|
|
305
309
|
### Performance
|
|
306
310
|
|
|
311
|
+
- **Single Daemon:** One process watches all projects via native OS events — no polling, sub-second file change detection. Shared VectorDB, MetaCache, and worker pool across projects.
|
|
312
|
+
- **Native File Watching:** `@parcel/watcher` uses FSEvents (macOS), inotify (Linux), ReadDirectoryChangesW (Windows) — zero CPU overhead, no file descriptor exhaustion.
|
|
313
|
+
- **Automatic Compaction:** LanceDB table fragments from incremental inserts are compacted every 5 minutes, preventing performance degradation over time.
|
|
314
|
+
- **LMDB Caching:** File metadata reads use LRU/LFU caching for the watcher's hot path.
|
|
307
315
|
- **Bounded Concurrency:** Worker threads scale to 50% of CPU cores (min 4). Override with `GMAX_WORKER_THREADS`.
|
|
308
316
|
- **Smart Chunking:** `tree-sitter` splits code by function/class boundaries for complete logical blocks.
|
|
309
317
|
- **Deduplication:** Identical code blocks are embedded once and cached.
|
|
@@ -389,11 +397,12 @@ See [CLAUDE.md](CLAUDE.md) for development setup, commands, and architecture det
|
|
|
389
397
|
|
|
390
398
|
## Troubleshooting
|
|
391
399
|
|
|
392
|
-
- **Index feels stale?** Run `gmax index` to refresh
|
|
400
|
+
- **Index feels stale?** Run `gmax index` to refresh. The daemon auto-reindexes on file changes.
|
|
393
401
|
- **Weird results?** Run `gmax doctor` to verify models.
|
|
394
402
|
- **Index getting stuck?** Run `gmax index --verbose` to see which file is being processed.
|
|
395
403
|
- **Need a fresh start?** `rm -rf ~/.gmax/lancedb ~/.gmax/cache` then `gmax index`.
|
|
396
|
-
- **
|
|
404
|
+
- **Daemon issues?** Check `~/.gmax/logs/daemon.log`. Run `gmax watch stop` then `gmax watch --daemon -b` to restart.
|
|
405
|
+
- **MLX server won't start?** Check `~/.gmax/logs/mlx-embed-server.log` for errors. Use `GMAX_EMBED_MODE=cpu` to fall back to CPU.
|
|
397
406
|
|
|
398
407
|
## Attribution
|
|
399
408
|
|
package/dist/commands/mcp.js
CHANGED
|
@@ -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
|
|
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 = [
|
package/dist/commands/project.js
CHANGED
|
@@ -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 (
|
|
149
|
-
console.log(
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
155
|
-
console.log(
|
|
156
|
-
|
|
157
|
-
|
|
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 (
|
|
190
|
+
catch (_e) { }
|
|
172
191
|
}
|
|
173
192
|
yield (0, exit_1.gracefulExit)();
|
|
174
193
|
}
|
package/dist/commands/recent.js
CHANGED
|
@@ -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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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 {
|
package/dist/commands/related.js
CHANGED
|
@@ -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 (
|
|
147
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
console.log(
|
|
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(
|
|
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) {
|
package/dist/commands/search.js
CHANGED
|
@@ -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
|
-
|
|
407
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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();
|
|
@@ -63,7 +63,20 @@ const skeletonizer_1 = require("../lib/skeleton/skeletonizer");
|
|
|
63
63
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
64
64
|
const file_utils_1 = require("../lib/utils/file-utils");
|
|
65
65
|
const exit_1 = require("../lib/utils/exit");
|
|
66
|
+
const project_registry_1 = require("../lib/utils/project-registry");
|
|
66
67
|
const project_root_1 = require("../lib/utils/project-root");
|
|
68
|
+
/**
|
|
69
|
+
* Resolve a relative path across all indexed projects.
|
|
70
|
+
* Returns the first match found, or null.
|
|
71
|
+
*/
|
|
72
|
+
function resolveAcrossProjects(relativePath) {
|
|
73
|
+
for (const project of (0, project_registry_1.listProjects)()) {
|
|
74
|
+
const candidate = path.join(project.root, relativePath);
|
|
75
|
+
if (fs.existsSync(candidate))
|
|
76
|
+
return candidate;
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
67
80
|
/**
|
|
68
81
|
* Check if target looks like a file path.
|
|
69
82
|
*/
|
|
@@ -185,11 +198,16 @@ Examples:
|
|
|
185
198
|
}
|
|
186
199
|
if (isFilePath(target)) {
|
|
187
200
|
// === FILE MODE ===
|
|
188
|
-
|
|
201
|
+
let filePath = path.resolve(target);
|
|
189
202
|
if (!fs.existsSync(filePath)) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
203
|
+
// Try resolving across indexed projects
|
|
204
|
+
const found = resolveAcrossProjects(target);
|
|
205
|
+
if (!found) {
|
|
206
|
+
console.error(`File not found: ${filePath}`);
|
|
207
|
+
process.exitCode = 1;
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
filePath = found;
|
|
193
211
|
}
|
|
194
212
|
if (vectorDb) {
|
|
195
213
|
// Use absolute path for DB lookup (centralized index stores absolute paths)
|
package/dist/commands/status.js
CHANGED
|
@@ -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
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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 = (
|
|
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
|
}
|
package/dist/commands/symbols.js
CHANGED
|
@@ -123,7 +123,13 @@ function collectSymbols(options) {
|
|
|
123
123
|
}
|
|
124
124
|
function formatTable(entries) {
|
|
125
125
|
if (entries.length === 0) {
|
|
126
|
-
return
|
|
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
|
-
|
|
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
|
}));
|
package/dist/commands/trace.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
@@ -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.
|
|
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
|
|
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
|
-
###
|
|
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`
|
|
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.
|