grepmax 0.26.66 → 0.26.68
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/.claude-plugin/marketplace.json +1 -1
- package/README.md +11 -3
- package/dist/bin.js +5 -2
- package/dist/index.js +2 -1
- package/dist/lib/daemon/daemon.js +26 -1
- package/dist/lib/daemon/watcher-manager.js +13 -2
- package/dist/lib/utils/cli-command.js +25 -0
- package/package.json +1 -1
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"name": "grepmax",
|
|
11
11
|
"source": "./plugins/grepmax",
|
|
12
12
|
"description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
|
|
13
|
-
"version": "0.26.
|
|
13
|
+
"version": "0.26.68",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "Robert Owens",
|
|
16
16
|
"email": "robowens@me.com"
|
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ gmax doctor # Health check
|
|
|
57
57
|
gmax doctor --fix # Repair checks; full-table maintenance is disabled
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
**Storage containment:** full-table compaction is disabled, including forced maintenance and doctor repair. Existing retained index copies are not reclaimed by this release. The verified prune-only recovery path is separate work; normal incremental indexing remains available when the host is healthy and not quarantined. A persistent host safety stop blocks mutation and normal daemon restart; an explicit read-only start can serve the retained index. Installing an update preserves existing quarantine. Speculative embedding warmup and maintenance timers are disabled.
|
|
60
|
+
**Storage containment:** full-table compaction is disabled, including forced maintenance and doctor repair. Existing retained index copies are not reclaimed by this release. The verified prune-only recovery path is separate work; normal incremental indexing remains available when the host is healthy and not quarantined. A persistent host safety stop blocks mutation and normal daemon restart; an explicit read-only start can serve the retained index. Installing an update preserves existing quarantine. Speculative embedding warmup and maintenance timers are disabled. Under the default `strict` host policy on macOS, startup, heavy queries, worker forks and MLX launches require known normal OS memory pressure and a healthy kernel-zone sample; warning or unavailable probes pause heavy work while the daemon serves bounded reads. The explicit `critical-only` policy keeps semantic search and indexing available under warning or unavailable probes. Known critical pressure still stops the daemon.
|
|
61
61
|
|
|
62
62
|
### Core Commands
|
|
63
63
|
|
|
@@ -240,7 +240,7 @@ gmax status # See all projects + watcher status
|
|
|
240
240
|
|
|
241
241
|
Adding or indexing a project can start the daemon automatically. MCP acquires primary-project watch leases lazily when an index-reading tool is used; discovery and catalog listing start no background work. It shuts down after 4 hours of inactivity, and hands off to a fresh daemon after 24 hours or once its memory footprint passes 2.5 GB. File-change batches are processed on up to four worker processes by default, added only when work backs up, with one kept free for searches; full-table compaction and new FTS/ANN index builds are disabled by containment. Existing indexes remain readable. Host safety stops persist across restart and block heavy operations.
|
|
242
242
|
|
|
243
|
-
`gmax watch status` reports failed files, native watcher recovery or polling, dropped-event counts, and the last complete filesystem reconciliation. FSEvents dropped-event warnings keep the native stream attached and preserve the accompanying events. A reconciliation covers the gap; repeated warnings coalesce into one later scan, with a 30-second scan interval. Recovery stays visible until a complete scan covers the latest gap. Other backend errors still trigger subscription recovery; repeated terminal failures fall back to five-minute polling, where recent edits can lag. Search and MCP responses retain health warnings even after the file queue drains. A complete reconciliation means the filesystem scan completed; queued embedding work can still be pending.
|
|
243
|
+
`gmax watch status` reports failed files, native watcher recovery or polling, dropped-event counts, and the last complete filesystem reconciliation. FSEvents dropped-event warnings keep the native stream attached and preserve the accompanying events. A reconciliation covers the gap; repeated warnings coalesce into one later scan, with a 30-second scan interval. Recovery stays visible until a complete scan covers the latest gap. Other backend errors still trigger subscription recovery; repeated terminal failures fall back to five-minute polling, where recent edits can lag. Search and MCP responses retain health warnings even after the file queue drains. Live edit batches can run during reconciliation instead of waiting for the full filesystem walk. Whole-project reindex and removal still quiesce the watcher and drain its work before changing the project. A complete reconciliation means the filesystem scan completed; queued embedding work can still be pending.
|
|
244
244
|
|
|
245
245
|
Live filesystem edits take priority over background catchup and cleanup. Background batches dispatch one file at a time, with at most four catchup files or 50 cleanup paths. They yield between files when a live edit arrives or the two-second dispatch window is reached; an active file or store commit finishes before yielding. Catchup and cleanup alternate, and one background file is admitted after three consecutive live batches so cleanup continues during sustained editing. Existing retry backoff and worker limits remain in effect.
|
|
246
246
|
|
|
@@ -495,6 +495,14 @@ Production recovery currently requires macOS, a persistent `autostart-disabled`
|
|
|
495
495
|
|
|
496
496
|
The child waits for launch and deletion admission tokens, with verified helper PID/start identity recorded as the primary exclusive owner. Existing readers retain exclusion if the parent dies while the helper is alive. Current/tagged/post-cutoff metadata is verified after pruning; recovery refuses more than 256 protected versions before deletion to bound verification. Interrupted attempts require verification and explicit acknowledgement before retry. Linux/APFS fixtures exercise native retention and interruption; supported production host admission is tested separately and still runs freshly for each operation.
|
|
497
497
|
|
|
498
|
+
For an operator-coordinated recovery window:
|
|
499
|
+
|
|
500
|
+
1. Check live host pressure and service availability first. Keep development online when recovery admission is already known to refuse. Preserve the configured normal-service policy and any pre-existing safety markers.
|
|
501
|
+
2. Once admission is plausible, create a private, durable task-owned autostart marker and gracefully close the identified daemon and its managed children. Verify shared store owners have drained. `--check` samples resource admission; it does not drain owners or acquire the exclusive prune lease.
|
|
502
|
+
3. Take a fresh bounded version/tag inventory after draining, with a shared lease and small native Session caches. Close that reader before pruning. Choose an explicit real-clock cutoff and verify the union of current, tagged and post-cutoff versions fits the 256-version verification bound. A snapshot from before resumed indexing is unsuitable; never silently move the cutoff to fit the bound.
|
|
503
|
+
4. Run one `recover --prune` with that current version and cutoff. Both `--store <value> recover` and `--store=<value> recover` preserve recovery's table-version option; the explicit `--table` selects the recovery store. Require a verified result and completed receipt before claiming recovery. Record signed table allocated-byte recovery separately from global filesystem free-space change.
|
|
504
|
+
5. Wait for the recovery child and helper to close before lifting exclusion. Clear only the unchanged marker created by this operation, then restore normal service and verify search/watcher health and unchanged configuration. If ownership or the marker changed, preserve containment and inspect it. An uncertain receipt requires retained-state inspection and explicit acknowledgement before another attempt; never automatically retry.
|
|
505
|
+
|
|
498
506
|
Explicit helper calls persist a private `.gmax-prune-state.json` receipt outside the table before deletion. Cancellation, invalid results, lost ownership and abandoned running receipts remain uncertain; a verified explicit retry records the prior uncertain attempt. Successful receipts include before/after file lengths, allocated bytes and filesystem free space. These measurements do not imply zero retained copies or a macOS physical-footprint cap. Current, tagged and post-cutoff versions are protected; unverified files are retained. Full-table rewriting remains disabled regardless of receipt state.
|
|
499
507
|
|
|
500
508
|
`~/.gmax/logs/daemon.log` records containment skips and retained historical compaction outcomes, including duration, logical size, disk size before/after, free space, and bytes reclaimed. `gmax status --json` exposes the last outcome under `daemon.compaction`; its `at` timestamp identifies when it was recorded. Five-minute resource snapshots track footprint, RSS, heap, external/ArrayBuffer memory, Lance cache, workers and pending work for memory investigations. Reading status uses retained snapshots and does not scan the index directory. Logs rotate to `daemon.log.prev`.
|
|
@@ -570,7 +578,7 @@ grepmax is built upon the foundation of [mgrep](https://github.com/mixedbread-ai
|
|
|
570
578
|
|
|
571
579
|
Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE).
|
|
572
580
|
|
|
573
|
-
On macOS, heavy work uses a shared resource ledger and a bounded batch footprint probe covering every gmax process and its children. Native stores, workers and model launches reserve headroom before allocating it.
|
|
581
|
+
On macOS, heavy work uses a shared resource ledger and a bounded batch footprint probe covering every gmax process and its children. Native stores, workers and model launches reserve headroom before allocating it. Under the default `strict` policy, warning or unknown measurements pause heavy work; known critical pressure persists a stop under either policy. The 6 GiB admission budget is a conservative scheduling threshold, not an OS-enforced memory cap. It may be lowered with `GMAX_RESOURCE_BUDGET_MB`. Existing safety markers remain in place after upgrades.
|
|
574
582
|
|
|
575
583
|
MCP sessions started before this resource policy must reconnect after upgrading before heavy work can resume. Keyword search through the bounded read service stays available while those sessions are running. Cached resource snapshots in `gmax status --json` distinguish aggregate physical footprint from daemon RSS.
|
|
576
584
|
|
package/dist/bin.js
CHANGED
|
@@ -39,9 +39,10 @@ exports.registeredForm = registeredForm;
|
|
|
39
39
|
// Entry point. Picks the gmax home before anything reads config.ts: a command
|
|
40
40
|
// aimed at a project under a secondary store's prefix (src/lib/utils/stores.ts)
|
|
41
41
|
// runs against that store, in-process, so the drive it lives on can always
|
|
42
|
-
// eject. Only Node built-ins and
|
|
42
|
+
// eject. Only Node built-ins and dependency-free routing helpers may load here.
|
|
43
43
|
const fs = __importStar(require("node:fs"));
|
|
44
44
|
const path = __importStar(require("node:path"));
|
|
45
|
+
const cli_command_1 = require("./lib/utils/cli-command");
|
|
45
46
|
const stores_1 = require("./lib/utils/stores");
|
|
46
47
|
const DIR_ARG_COMMANDS = new Set(["add", "remove"]);
|
|
47
48
|
function optionValue(argv, names) {
|
|
@@ -77,7 +78,9 @@ function selectHome() {
|
|
|
77
78
|
const argv = process.argv.slice(2);
|
|
78
79
|
// These enumerate/route stores per request rather than pinning the process
|
|
79
80
|
// to its launch cwd's store. In particular an MCP session can serve both.
|
|
80
|
-
if (argv[0] === "mcp" ||
|
|
81
|
+
if (argv[0] === "mcp" ||
|
|
82
|
+
argv[0] === "status" ||
|
|
83
|
+
(0, cli_command_1.explicitCommand)(argv) === "recover")
|
|
81
84
|
return;
|
|
82
85
|
const match = (0, stores_1.matchStore)(commandTarget(argv, process.cwd()));
|
|
83
86
|
if (match.kind === "primary")
|
package/dist/index.js
CHANGED
|
@@ -79,6 +79,7 @@ const symbols_1 = require("./commands/symbols");
|
|
|
79
79
|
const test_find_1 = require("./commands/test-find");
|
|
80
80
|
const trace_1 = require("./commands/trace");
|
|
81
81
|
const watch_1 = require("./commands/watch");
|
|
82
|
+
const cli_command_1 = require("./lib/utils/cli-command");
|
|
82
83
|
commander_1.program
|
|
83
84
|
.name("gmax")
|
|
84
85
|
.description("Semantic code search — finds code by meaning, not just strings")
|
|
@@ -165,6 +166,6 @@ opencode_1.uninstallOpencode._hidden = true;
|
|
|
165
166
|
commander_1.program.addCommand(opencode_1.uninstallOpencode);
|
|
166
167
|
// Recovery owns --version as a required table-version value. Keep the root
|
|
167
168
|
// package-version flag from consuming it; other commands retain global options.
|
|
168
|
-
if (process.argv
|
|
169
|
+
if ((0, cli_command_1.explicitCommand)(process.argv.slice(2)) === "recover")
|
|
169
170
|
commander_1.program.enablePositionalOptions();
|
|
170
171
|
commander_1.program.parse();
|
|
@@ -192,7 +192,14 @@ class Daemon {
|
|
|
192
192
|
evictSearcher: (root) => {
|
|
193
193
|
this.searchers.delete(root);
|
|
194
194
|
},
|
|
195
|
-
runProjectOperation: (root, name, signal, fn) =>
|
|
195
|
+
runProjectOperation: (root, name, signal, fn) => {
|
|
196
|
+
// A filesystem walk can take minutes. Keep it lifecycle-tracked and
|
|
197
|
+
// admitted, but let live batches hold the project's write lock while
|
|
198
|
+
// the scan yields. Project removal/reindex quiesces the watcher first.
|
|
199
|
+
if (name === "watch-catchup")
|
|
200
|
+
return this.runSharedOperation(name, signal, fn);
|
|
201
|
+
return this.withProjectLock(root, signal, () => this.runSharedOperation(name, signal, fn));
|
|
202
|
+
},
|
|
196
203
|
});
|
|
197
204
|
this.projectMutex = new keyed_mutex_1.KeyedMutex();
|
|
198
205
|
// Tests construct daemons against the real HOME; keep their leases in memory.
|
|
@@ -1237,7 +1244,15 @@ class Daemon {
|
|
|
1237
1244
|
this.vectorDb.pauseMaintenanceLoop();
|
|
1238
1245
|
const stopHeartbeat = (0, ipc_handler_1.startHeartbeat)(conn);
|
|
1239
1246
|
let lastProgressTime = 0;
|
|
1247
|
+
const wasWatching = this.processors.has(root);
|
|
1248
|
+
let watcherQuiesced = false;
|
|
1240
1249
|
try {
|
|
1250
|
+
// Re-adding a watched project is also a full sync. A catchup scan no
|
|
1251
|
+
// longer holds the write lock, so explicitly drain it before syncing.
|
|
1252
|
+
if (wasWatching) {
|
|
1253
|
+
yield this.unwatchProjectWithinOperation(root);
|
|
1254
|
+
watcherQuiesced = true;
|
|
1255
|
+
}
|
|
1241
1256
|
const result = yield (0, syncer_1.initialSync)({
|
|
1242
1257
|
projectRoot: root,
|
|
1243
1258
|
vectorDb: this.vectorDb,
|
|
@@ -1287,6 +1302,7 @@ class Daemon {
|
|
|
1287
1302
|
});
|
|
1288
1303
|
}
|
|
1289
1304
|
yield this.watchProjectWithinOperation(root);
|
|
1305
|
+
watcherQuiesced = false;
|
|
1290
1306
|
(0, ipc_handler_1.writeDone)(conn, {
|
|
1291
1307
|
ok: true,
|
|
1292
1308
|
processed: result.processed,
|
|
@@ -1316,6 +1332,15 @@ class Daemon {
|
|
|
1316
1332
|
}
|
|
1317
1333
|
finally {
|
|
1318
1334
|
stopHeartbeat();
|
|
1335
|
+
if (watcherQuiesced && !this.shuttingDown) {
|
|
1336
|
+
try {
|
|
1337
|
+
this.assertHeavyOperationAdmission("watch");
|
|
1338
|
+
yield this.watchProjectWithinOperation(root);
|
|
1339
|
+
}
|
|
1340
|
+
catch (error) {
|
|
1341
|
+
console.error("[daemon] Could not restore watcher after add:", error);
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1319
1344
|
(_a = this.vectorDb) === null || _a === void 0 ? void 0 : _a.resumeMaintenanceLoop();
|
|
1320
1345
|
}
|
|
1321
1346
|
});
|
|
@@ -653,6 +653,7 @@ class WatcherManager {
|
|
|
653
653
|
// Fast path: if only mtime changed but size is identical and we have a hash,
|
|
654
654
|
// just verify the hash in-process instead of sending to a worker.
|
|
655
655
|
if ((cached === null || cached === void 0 ? void 0 : cached.hash) && cached.size === stats.size) {
|
|
656
|
+
const observed = Object.assign({}, cached);
|
|
656
657
|
const snapshot = yield (0, file_utils_1.readFileSnapshot)(absPath, {
|
|
657
658
|
projectRoot: root,
|
|
658
659
|
});
|
|
@@ -664,11 +665,21 @@ class WatcherManager {
|
|
|
664
665
|
continue;
|
|
665
666
|
}
|
|
666
667
|
const hash = (0, file_utils_1.computeContentHash)(snapshot.buffer, absPath);
|
|
667
|
-
if (hash ===
|
|
668
|
+
if (hash === observed.hash) {
|
|
668
669
|
// Content unchanged — update mtime in cache and skip worker
|
|
669
670
|
if (signal.aborted)
|
|
670
671
|
return false;
|
|
671
|
-
|
|
672
|
+
// A live batch can finish while this read yields. Never replace
|
|
673
|
+
// newer metadata or resurrect a cache entry it removed.
|
|
674
|
+
const current = metaCache.get(absPath);
|
|
675
|
+
if (current &&
|
|
676
|
+
current.hash === observed.hash &&
|
|
677
|
+
current.mtimeMs === observed.mtimeMs &&
|
|
678
|
+
current.size === observed.size &&
|
|
679
|
+
current.hashVersion === observed.hashVersion &&
|
|
680
|
+
current.hasVectors === observed.hasVectors) {
|
|
681
|
+
metaCache.put(absPath, Object.assign(Object.assign({}, observed), { mtimeMs: stats.mtimeMs }));
|
|
682
|
+
}
|
|
672
683
|
skipped++;
|
|
673
684
|
continue;
|
|
674
685
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.explicitCommand = explicitCommand;
|
|
4
|
+
/** Find an explicit command after the root's --store option, without treating
|
|
5
|
+
* its value (which can itself be a command name) as the command. Root help and
|
|
6
|
+
* version flags retain their normal early-exit behavior. */
|
|
7
|
+
function explicitCommand(argv) {
|
|
8
|
+
for (let i = 0; i < argv.length; i++) {
|
|
9
|
+
const arg = argv[i];
|
|
10
|
+
if (arg === "--store") {
|
|
11
|
+
if (argv[i + 1] === undefined)
|
|
12
|
+
return undefined;
|
|
13
|
+
i++;
|
|
14
|
+
}
|
|
15
|
+
else if (arg.startsWith("--store=")) {
|
|
16
|
+
}
|
|
17
|
+
else if (arg === "--") {
|
|
18
|
+
return argv[i + 1];
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
return arg.startsWith("-") ? undefined : arg;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
package/package.json
CHANGED