grepmax 0.16.5 → 0.16.9
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 +1 -3
- package/dist/commands/watch.js +35 -3
- package/dist/lib/daemon/daemon.js +24 -21
- package/dist/lib/utils/daemon-client.js +19 -2
- package/package.json +23 -22
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
- package/plugins/grepmax/hooks/start.js +3 -1
- package/plugins/grepmax/skills/grepmax/SKILL.md +4 -4
package/README.md
CHANGED
|
@@ -60,8 +60,7 @@ gmax symbols auth # List indexed symbols
|
|
|
60
60
|
### Analysis Commands
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
|
-
gmax
|
|
64
|
-
gmax diff main --query "auth changes" # Semantic search within changes
|
|
63
|
+
gmax log src/lib/auth.ts # Git commit history for a path or symbol
|
|
65
64
|
gmax test handleAuth # Find tests via reverse call graph
|
|
66
65
|
gmax impact handleAuth # Dependents + affected tests
|
|
67
66
|
gmax similar handleAuth # Find similar code patterns
|
|
@@ -73,7 +72,6 @@ gmax context "auth system" --budget 4000 # Token-budgeted topic summary
|
|
|
73
72
|
```bash
|
|
74
73
|
gmax project # Languages, structure, key symbols
|
|
75
74
|
gmax related src/lib/auth.ts # Dependencies + dependents
|
|
76
|
-
gmax recent # Recently modified files
|
|
77
75
|
gmax status # All indexed projects + chunk counts
|
|
78
76
|
```
|
|
79
77
|
|
package/dist/commands/watch.js
CHANGED
|
@@ -309,14 +309,28 @@ exports.watch
|
|
|
309
309
|
var _a;
|
|
310
310
|
const { isDaemonRunning, sendDaemonCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
|
|
311
311
|
let stoppedDaemon = false;
|
|
312
|
+
let daemonPid;
|
|
312
313
|
// Try shutting down daemon first
|
|
313
314
|
if (yield isDaemonRunning()) {
|
|
315
|
+
// Capture PID before IPC shutdown so the --all loop below can skip the
|
|
316
|
+
// daemon's own watcher-store entries (it registers itself under every
|
|
317
|
+
// watched project's PID — racing killProcess against the in-flight
|
|
318
|
+
// graceful shutdown is what produced "did not exit after SIGKILL"
|
|
319
|
+
// storms and the orphaned pid/sock/lock files this code path used to
|
|
320
|
+
// leave behind).
|
|
321
|
+
try {
|
|
322
|
+
const pidRaw = yield fs.promises.readFile(config_1.PATHS.daemonPidFile, "utf-8");
|
|
323
|
+
const parsed = parseInt(pidRaw.trim(), 10);
|
|
324
|
+
if (Number.isFinite(parsed) && parsed > 0)
|
|
325
|
+
daemonPid = parsed;
|
|
326
|
+
}
|
|
327
|
+
catch (_b) { }
|
|
314
328
|
let parentCmd = "?";
|
|
315
329
|
try {
|
|
316
330
|
const { execSync } = yield Promise.resolve().then(() => __importStar(require("node:child_process")));
|
|
317
331
|
parentCmd = execSync(`ps -o command= -p ${process.ppid}`, { encoding: "utf8" }).trim();
|
|
318
332
|
}
|
|
319
|
-
catch (
|
|
333
|
+
catch (_c) { }
|
|
320
334
|
yield sendDaemonCommand({
|
|
321
335
|
cmd: "shutdown",
|
|
322
336
|
reason: "gmax-watch-stop",
|
|
@@ -325,20 +339,38 @@ exports.watch
|
|
|
325
339
|
from_argv: process.argv.slice(0, 4),
|
|
326
340
|
from_parent_cmd: parentCmd,
|
|
327
341
|
});
|
|
342
|
+
// Wait for the daemon to actually exit before we start killing watcher
|
|
343
|
+
// entries — many of those entries share the daemon's PID. Poll for up
|
|
344
|
+
// to 10s; shutdown work can take a few seconds on large indexes.
|
|
345
|
+
if (daemonPid) {
|
|
346
|
+
for (let i = 0; i < 100; i++) {
|
|
347
|
+
if (!(0, watcher_store_1.isProcessRunning)(daemonPid))
|
|
348
|
+
break;
|
|
349
|
+
yield new Promise((r) => setTimeout(r, 100));
|
|
350
|
+
}
|
|
351
|
+
}
|
|
328
352
|
console.log("Daemon stopped.");
|
|
329
353
|
stoppedDaemon = true;
|
|
330
354
|
}
|
|
331
355
|
if (options.all) {
|
|
332
356
|
const watchers = (0, watcher_store_1.listWatchers)();
|
|
357
|
+
const seenPids = new Set();
|
|
358
|
+
let projectStops = 0;
|
|
333
359
|
for (const w of watchers) {
|
|
360
|
+
if (daemonPid && w.pid === daemonPid)
|
|
361
|
+
continue;
|
|
362
|
+
if (seenPids.has(w.pid))
|
|
363
|
+
continue;
|
|
364
|
+
seenPids.add(w.pid);
|
|
334
365
|
const killed = yield (0, process_1.killProcess)(w.pid);
|
|
335
366
|
(0, watcher_store_1.unregisterWatcher)(w.pid);
|
|
367
|
+
projectStops++;
|
|
336
368
|
if (!killed) {
|
|
337
369
|
console.warn(`Warning: PID ${w.pid} did not exit after SIGKILL`);
|
|
338
370
|
}
|
|
339
371
|
}
|
|
340
|
-
if (
|
|
341
|
-
console.log(`Stopped ${
|
|
372
|
+
if (projectStops > 0) {
|
|
373
|
+
console.log(`Stopped ${projectStops} per-project watcher(s).`);
|
|
342
374
|
}
|
|
343
375
|
else if (!stoppedDaemon) {
|
|
344
376
|
console.log("No running watchers.");
|
|
@@ -1310,6 +1310,24 @@ class Daemon {
|
|
|
1310
1310
|
return;
|
|
1311
1311
|
this.shuttingDown = true;
|
|
1312
1312
|
console.log("[daemon] Shutting down...");
|
|
1313
|
+
// Drop external liveness markers FIRST so the next daemon start isn't
|
|
1314
|
+
// fooled by leftover state if the long cleanup below is interrupted
|
|
1315
|
+
// (uncaught exception, second SIGTERM, OOM kill mid-shutdown). The
|
|
1316
|
+
// fresh-lock check in isDaemonHeartbeatFresh keyed on these — orphans
|
|
1317
|
+
// here used to cause silent no-op spawns for up to 150s.
|
|
1318
|
+
try {
|
|
1319
|
+
fs.unlinkSync(config_1.PATHS.daemonSocket);
|
|
1320
|
+
}
|
|
1321
|
+
catch (_e) { }
|
|
1322
|
+
try {
|
|
1323
|
+
fs.unlinkSync(config_1.PATHS.daemonPidFile);
|
|
1324
|
+
}
|
|
1325
|
+
catch (_f) { }
|
|
1326
|
+
if (this.releaseLock) {
|
|
1327
|
+
const release = this.releaseLock;
|
|
1328
|
+
this.releaseLock = null;
|
|
1329
|
+
release().catch(() => { });
|
|
1330
|
+
}
|
|
1313
1331
|
if (this.heartbeatInterval)
|
|
1314
1332
|
clearInterval(this.heartbeatInterval);
|
|
1315
1333
|
if (this.idleInterval)
|
|
@@ -1332,7 +1350,7 @@ class Daemon {
|
|
|
1332
1350
|
try {
|
|
1333
1351
|
yield ((_a = this.llmServer) === null || _a === void 0 ? void 0 : _a.stop());
|
|
1334
1352
|
}
|
|
1335
|
-
catch (
|
|
1353
|
+
catch (_g) { }
|
|
1336
1354
|
// Stop MLX embed server if we started it
|
|
1337
1355
|
this.stopMlxServer();
|
|
1338
1356
|
// Destroy worker pool to prevent orphaned child processes
|
|
@@ -1340,7 +1358,7 @@ class Daemon {
|
|
|
1340
1358
|
try {
|
|
1341
1359
|
yield (0, pool_1.destroyWorkerPool)();
|
|
1342
1360
|
}
|
|
1343
|
-
catch (
|
|
1361
|
+
catch (_h) { }
|
|
1344
1362
|
}
|
|
1345
1363
|
// Stop poll intervals + their FSEvents recovery probes
|
|
1346
1364
|
for (const interval of this.pollIntervals.values()) {
|
|
@@ -1356,26 +1374,11 @@ class Daemon {
|
|
|
1356
1374
|
try {
|
|
1357
1375
|
yield sub.unsubscribe();
|
|
1358
1376
|
}
|
|
1359
|
-
catch (
|
|
1377
|
+
catch (_j) { }
|
|
1360
1378
|
}
|
|
1361
1379
|
this.subscriptions.clear();
|
|
1362
|
-
// Close server
|
|
1380
|
+
// Close server (socket/pid/lock already dropped at the top of shutdown)
|
|
1363
1381
|
(_b = this.server) === null || _b === void 0 ? void 0 : _b.close();
|
|
1364
|
-
try {
|
|
1365
|
-
fs.unlinkSync(config_1.PATHS.daemonSocket);
|
|
1366
|
-
}
|
|
1367
|
-
catch (_h) { }
|
|
1368
|
-
try {
|
|
1369
|
-
fs.unlinkSync(config_1.PATHS.daemonPidFile);
|
|
1370
|
-
}
|
|
1371
|
-
catch (_j) { }
|
|
1372
|
-
if (this.releaseLock) {
|
|
1373
|
-
try {
|
|
1374
|
-
yield this.releaseLock();
|
|
1375
|
-
}
|
|
1376
|
-
catch (_k) { }
|
|
1377
|
-
this.releaseLock = null;
|
|
1378
|
-
}
|
|
1379
1382
|
// Unregister all
|
|
1380
1383
|
for (const root of this.processors.keys()) {
|
|
1381
1384
|
(0, watcher_store_1.unregisterWatcherByRoot)(root);
|
|
@@ -1386,11 +1389,11 @@ class Daemon {
|
|
|
1386
1389
|
try {
|
|
1387
1390
|
yield ((_c = this.metaCache) === null || _c === void 0 ? void 0 : _c.close());
|
|
1388
1391
|
}
|
|
1389
|
-
catch (
|
|
1392
|
+
catch (_k) { }
|
|
1390
1393
|
try {
|
|
1391
1394
|
yield ((_d = this.vectorDb) === null || _d === void 0 ? void 0 : _d.close());
|
|
1392
1395
|
}
|
|
1393
|
-
catch (
|
|
1396
|
+
catch (_l) { }
|
|
1394
1397
|
console.log("[daemon] Shutdown complete");
|
|
1395
1398
|
});
|
|
1396
1399
|
}
|
|
@@ -122,13 +122,30 @@ function isDaemonRunning(opts) {
|
|
|
122
122
|
* Lock-file-based liveness probe. A running daemon refreshes daemon.lock's
|
|
123
123
|
* mtime every 60s via its heartbeat loop; a fresh mtime means the daemon is
|
|
124
124
|
* alive even if its socket ping times out under load.
|
|
125
|
+
*
|
|
126
|
+
* A fresh mtime alone is not proof of life — a SIGKILL'd (or OOM-killed,
|
|
127
|
+
* panicked, power-lost) daemon leaves the lock file behind with its last
|
|
128
|
+
* heartbeat mtime, fooling this check for up to HEARTBEAT_FRESH_THRESHOLD_MS.
|
|
129
|
+
* Cross-check that the PID file points at an actually-running process.
|
|
125
130
|
*/
|
|
126
131
|
function isDaemonHeartbeatFresh() {
|
|
127
132
|
try {
|
|
128
133
|
const stats = fs.statSync(config_1.PATHS.daemonLockFile);
|
|
129
|
-
|
|
134
|
+
if (Date.now() - stats.mtimeMs >= HEARTBEAT_FRESH_THRESHOLD_MS)
|
|
135
|
+
return false;
|
|
136
|
+
const pidRaw = fs.readFileSync(config_1.PATHS.daemonPidFile, "utf-8").trim();
|
|
137
|
+
const pid = parseInt(pidRaw, 10);
|
|
138
|
+
if (!Number.isFinite(pid) || pid <= 0)
|
|
139
|
+
return false;
|
|
140
|
+
try {
|
|
141
|
+
process.kill(pid, 0);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
catch (_a) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
130
147
|
}
|
|
131
|
-
catch (
|
|
148
|
+
catch (_b) {
|
|
132
149
|
return false;
|
|
133
150
|
}
|
|
134
151
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grepmax",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.9",
|
|
4
4
|
"author": "Robert Owens <78518764+reowens@users.noreply.github.com>",
|
|
5
5
|
"homepage": "https://github.com/reowens/grepmax",
|
|
6
6
|
"bugs": {
|
|
@@ -14,6 +14,27 @@
|
|
|
14
14
|
"bin": {
|
|
15
15
|
"gmax": "dist/index.js"
|
|
16
16
|
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"postinstall": "node scripts/postinstall.js",
|
|
19
|
+
"prebuild": "mkdir -p dist",
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"postbuild": "chmod +x dist/index.js",
|
|
22
|
+
"dev": "npx tsc && node dist/index.js",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:watch": "vitest",
|
|
25
|
+
"benchmark": "./run-benchmark.sh",
|
|
26
|
+
"benchmark:index": "./run-benchmark.sh $HOME/gmax-benchmarks --index",
|
|
27
|
+
"benchmark:agent": "npx tsx src/bench/benchmark-agent.ts",
|
|
28
|
+
"benchmark:chart": "npx tsx src/bench/generate-benchmark-chart.ts",
|
|
29
|
+
"format": "biome check --write .",
|
|
30
|
+
"format:check": "biome check .",
|
|
31
|
+
"lint": "biome lint .",
|
|
32
|
+
"typecheck": "tsc --noEmit",
|
|
33
|
+
"prepublishOnly": "pnpm build",
|
|
34
|
+
"preversion": "pnpm test && pnpm typecheck",
|
|
35
|
+
"version": "bash scripts/sync-versions.sh && git add -A",
|
|
36
|
+
"postversion": "git push origin main && git push origin v$npm_package_version && gh release create v$npm_package_version --generate-notes --title v$npm_package_version && sleep 5 && gh run watch $(gh run list --workflow=release.yml --branch v$npm_package_version --limit 1 --json databaseId --jq '.[0].databaseId') --exit-status && sleep 30 && npm cache clean --force && npm install -g grepmax@$npm_package_version"
|
|
37
|
+
},
|
|
17
38
|
"keywords": [
|
|
18
39
|
"grepmax",
|
|
19
40
|
"grep",
|
|
@@ -65,25 +86,5 @@
|
|
|
65
86
|
"typescript": "^6.0.2",
|
|
66
87
|
"vite": "^8.0.3",
|
|
67
88
|
"vitest": "^4.1.2"
|
|
68
|
-
},
|
|
69
|
-
"scripts": {
|
|
70
|
-
"postinstall": "node scripts/postinstall.js",
|
|
71
|
-
"prebuild": "mkdir -p dist",
|
|
72
|
-
"build": "tsc",
|
|
73
|
-
"postbuild": "chmod +x dist/index.js",
|
|
74
|
-
"dev": "npx tsc && node dist/index.js",
|
|
75
|
-
"test": "vitest run",
|
|
76
|
-
"test:watch": "vitest",
|
|
77
|
-
"benchmark": "./run-benchmark.sh",
|
|
78
|
-
"benchmark:index": "./run-benchmark.sh $HOME/gmax-benchmarks --index",
|
|
79
|
-
"benchmark:agent": "npx tsx src/bench/benchmark-agent.ts",
|
|
80
|
-
"benchmark:chart": "npx tsx src/bench/generate-benchmark-chart.ts",
|
|
81
|
-
"format": "biome check --write .",
|
|
82
|
-
"format:check": "biome check .",
|
|
83
|
-
"lint": "biome lint .",
|
|
84
|
-
"typecheck": "tsc --noEmit",
|
|
85
|
-
"preversion": "pnpm test && pnpm typecheck",
|
|
86
|
-
"version": "bash scripts/sync-versions.sh && git add -A",
|
|
87
|
-
"postversion": "git push origin main && git push origin v$npm_package_version && gh release create v$npm_package_version --generate-notes --title v$npm_package_version && sleep 5 && gh run watch $(gh run list --workflow=release.yml --branch v$npm_package_version --limit 1 --json databaseId --jq '.[0].databaseId') --exit-status && sleep 30 && npm cache clean --force && npm install -g grepmax@$npm_package_version"
|
|
88
89
|
}
|
|
89
|
-
}
|
|
90
|
+
}
|
|
@@ -187,8 +187,10 @@ Understand:
|
|
|
187
187
|
gmax trace <symbol> call graph (--inbound = callers + snippets)
|
|
188
188
|
gmax test <symbol> tests for symbol
|
|
189
189
|
gmax impact <symbol> blast radius
|
|
190
|
+
gmax related <file> file deps + dependents
|
|
190
191
|
|
|
191
192
|
Survey:
|
|
193
|
+
gmax project codebase overview (langs, structure, key symbols)
|
|
192
194
|
gmax skeleton <file> file structure (file path, NOT a directory)
|
|
193
195
|
gmax context "topic" --budget 4000 multi-file topic summary
|
|
194
196
|
gmax log <path-or-symbol> git commits (replaces recent/diff)
|
|
@@ -196,7 +198,7 @@ Survey:
|
|
|
196
198
|
|
|
197
199
|
Scope flags: --root <name|path>, --in <subpath>, --exclude <subpath>.
|
|
198
200
|
Roles in results: [DEFI] [ORCH] [IMPL] [DOCS].
|
|
199
|
-
Recovery: "not added yet" → gmax add; stale
|
|
201
|
+
Recovery: "not added yet" → gmax add; stale → gmax index; broken → gmax doctor --fix.`,
|
|
200
202
|
},
|
|
201
203
|
};
|
|
202
204
|
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 produces compact, token-efficient output for AI agents. It
|
|
43
|
+
The `--agent` flag produces compact, token-efficient output for AI agents. It works on most commands — `search`, `peek`, `extract`, `trace`, `test`, `impact`, `similar`, `log`, `related`, `symbols`, `status`, `project`, `context`, `skeleton`, and `doctor`.
|
|
44
44
|
|
|
45
45
|
```
|
|
46
46
|
gmax "where do we handle authentication" --agent
|
|
@@ -196,12 +196,12 @@ Agentic Q&A: a local LLM autonomously uses gmax tools (search, trace, peek, impa
|
|
|
196
196
|
```
|
|
197
197
|
gmax status # show all indexed projects
|
|
198
198
|
gmax status --agent # compact: name\tchunks\tage\tstatus
|
|
199
|
-
gmax recent --agent # compact: path\tage
|
|
200
199
|
gmax related src/file.ts --agent # compact: dep:/rev: path\tcount
|
|
201
200
|
gmax project --agent # compact: key\tvalue pairs
|
|
202
201
|
gmax index # reindex current directory
|
|
203
202
|
gmax config # view/change settings
|
|
204
203
|
gmax doctor # health check
|
|
204
|
+
gmax doctor --fix # auto-repair (compact, prune, clear stale locks)
|
|
205
205
|
gmax llm on/off/start/stop/status # manage local LLM server
|
|
206
206
|
```
|
|
207
207
|
|
|
@@ -215,7 +215,7 @@ gmax llm on/off/start/stop/status # manage local LLM server
|
|
|
215
215
|
6. **Skeleton** — `Bash(gmax skeleton <path>)` before reading large files, or use `--skeleton` on search
|
|
216
216
|
7. **Read** — `Read file:line` for specific ranges identified by search/skeleton
|
|
217
217
|
8. **Trace** — `Bash(gmax trace <symbol>)` for deep call flow (multi-hop)
|
|
218
|
-
9. **
|
|
218
|
+
9. **Log** — `Bash(gmax log <path-or-symbol>)` for git commit history on a path or symbol
|
|
219
219
|
10. **Test** — `Bash(gmax test <symbol>)` to find tests covering a symbol before editing
|
|
220
220
|
11. **Impact** — `Bash(gmax impact <symbol>)` for blast radius before significant changes
|
|
221
221
|
12. **Similar** — `Bash(gmax similar <symbol>)` to find similar patterns for DRY analysis
|
|
@@ -225,7 +225,7 @@ gmax llm on/off/start/stop/status # manage local LLM server
|
|
|
225
225
|
|
|
226
226
|
## Tips
|
|
227
227
|
|
|
228
|
-
- **Use `--agent` for compact output** —
|
|
228
|
+
- **Use `--agent` for compact output** — works on most commands: search, peek, extract, trace, log, test, impact, similar, related, status, project, doctor.
|
|
229
229
|
- **Be specific.** 5+ words. "auth" returns noise. "where does the server validate JWT tokens" is specific.
|
|
230
230
|
- **Use `--role ORCHESTRATION`** to skip type definitions and find the actual logic.
|
|
231
231
|
- **Use `--symbol`** when the query is a function/class name — gets search + trace in one call.
|