grepmax 0.26.8 → 0.26.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/.claude-plugin/marketplace.json +1 -1
- package/README.md +22 -0
- package/dist/config.js +22 -2
- package/dist/lib/daemon/daemon.js +6 -1
- package/dist/lib/search/searcher.js +22 -8
- package/dist/lib/store/vector-db.js +44 -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.9",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "Robert Owens",
|
|
16
16
|
"email": "robowens@me.com"
|
package/README.md
CHANGED
|
@@ -221,6 +221,15 @@ The daemon auto-starts when you run `gmax add`, `gmax index`, `gmax remove`, `gm
|
|
|
221
221
|
|
|
222
222
|
gmax can use a local LLM (via llama-server) for agentic codebase investigation. This is entirely opt-in and disabled by default — gmax works fine without it.
|
|
223
223
|
|
|
224
|
+
> **Memory footprint.** `gmax llm start`, `investigate`, `review`, and `summarize` load a multi-GB
|
|
225
|
+
> GGUF model (typical coding models run 16–21 GB). On a memory-constrained machine that can stall
|
|
226
|
+
> or hang the host. Start it deliberately, not as a reflex — and if you run coding agents against
|
|
227
|
+
> this repo, they should be told not to invoke these on their own initiative. The bundled skill
|
|
228
|
+
> already carries that instruction.
|
|
229
|
+
>
|
|
230
|
+
> Everything else in gmax — search, `trace`, `impact`, `context`, `--context-for-llm` — runs off
|
|
231
|
+
> the local index with no model load.
|
|
232
|
+
|
|
224
233
|
```bash
|
|
225
234
|
gmax llm on # Enable LLM features (persists to config)
|
|
226
235
|
gmax llm start # Start llama-server (auto-starts daemon too)
|
|
@@ -393,6 +402,19 @@ gmax watch stop && gmax watch --daemon -b # Restart daemon
|
|
|
393
402
|
|
|
394
403
|
`gmax doctor` reports ANN index state. `ANN: vector index not built` is normal with the default exact-search configuration.
|
|
395
404
|
|
|
405
|
+
### Known issues
|
|
406
|
+
|
|
407
|
+
Some log output looks alarming but is expected and self-healing. Before filing a bug, check
|
|
408
|
+
[`docs/known-limitations.md`](docs/known-limitations.md) — it covers what each case means and
|
|
409
|
+
whether you need to act.
|
|
410
|
+
|
|
411
|
+
| You see | What it means |
|
|
412
|
+
|---|---|
|
|
413
|
+
| `Optimize panicked ... inverted/builder.rs` | Upstream Lance bug in incremental FTS merge ([lance#8310](https://github.com/lance-format/lance/issues/8310)). gmax rebuilds the index and retries automatically; no data loss, no action needed. |
|
|
414
|
+
| `disabling auto-rebuild until an optimize succeeds` | Transient, not a wedge. The next successful optimize clears it. Search stays available. |
|
|
415
|
+
| `ANN: vector index not built` | Normal — exact search is the default. |
|
|
416
|
+
| `npm audit` reports advisories on install | Two transitive advisories (`sharp`, `adm-zip`) have no upstream fix yet; neither is on a code path gmax executes. |
|
|
417
|
+
|
|
396
418
|
## Contributing
|
|
397
419
|
|
|
398
420
|
See [CLAUDE.md](CLAUDE.md) for development setup, commands, and architecture details.
|
package/dist/config.js
CHANGED
|
@@ -251,8 +251,28 @@ exports.DISK_LOW_BYTES = (() => {
|
|
|
251
251
|
const gb = Number.parseFloat((_a = process.env.GMAX_DISK_LOW_GB) !== null && _a !== void 0 ? _a : "20");
|
|
252
252
|
return (Number.isFinite(gb) && gb > 0 ? gb : 20) * 1024 * 1024 * 1024;
|
|
253
253
|
})();
|
|
254
|
-
// Trigger compaction when small (uncompacted) fragment count exceeds this
|
|
255
|
-
|
|
254
|
+
// Trigger compaction when small (uncompacted) fragment count exceeds this.
|
|
255
|
+
//
|
|
256
|
+
// Deliberately high. Lance's optimize() merges the *whole* table into ~2
|
|
257
|
+
// fragments regardless of how many small ones it absorbs, so a compaction costs
|
|
258
|
+
// the same full-table rewrite at 51 fragments as at 400 — the threshold only
|
|
259
|
+
// decides how far that fixed cost is amortized. At 50 it was amortized as little
|
|
260
|
+
// as possible: on a continuously-watched project this fired every ~15-25min and
|
|
261
|
+
// rewrote ~10-14GB each time, measured at 40 compactions / 586GB over 26h.
|
|
262
|
+
//
|
|
263
|
+
// Fragmentation also does not cost what you would expect. With ANN off (the
|
|
264
|
+
// default), vector search is a brute-force scan that Lance parallelizes across
|
|
265
|
+
// fragments, so compacting to 2 fragments collapses it to 2-way parallelism.
|
|
266
|
+
// Interleaved benchmark, 395k rows / 384d, 96 samples per config:
|
|
267
|
+
//
|
|
268
|
+
// fragments | 2 | 50 | 200 | 800
|
|
269
|
+
// vector p50| 165.6 | 73.4 | 61.6 | 66.8 (ms)
|
|
270
|
+
//
|
|
271
|
+
// Flat from 50-800; 2 is 2.5x slower. So raising this wins on writes *and*
|
|
272
|
+
// latency. 400 sits inside the measured range with margin at both ends.
|
|
273
|
+
// Caveat: measured on the no-ANN path — revisit if GMAX_ANN=1 becomes default,
|
|
274
|
+
// since an ANN index partitions independently of fragments.
|
|
275
|
+
exports.FRAGMENT_COMPACT_THRESHOLD = 400;
|
|
256
276
|
// Extensions we consider for indexing to avoid binary noise and improve relevance.
|
|
257
277
|
exports.INDEXABLE_EXTENSIONS = new Set([
|
|
258
278
|
".ts",
|
|
@@ -174,7 +174,11 @@ class Daemon {
|
|
|
174
174
|
return new pool_1.WorkerPool(generation, embedMode);
|
|
175
175
|
}
|
|
176
176
|
createVectorDb(vectorDim, lease) {
|
|
177
|
-
|
|
177
|
+
const db = new vector_db_1.VectorDB(config_1.PATHS.lancedbDir, vectorDim, lease);
|
|
178
|
+
// The daemon owns the store, so it is the one process allowed to create or
|
|
179
|
+
// rebuild the shared FTS index. Readers adopt it; see VectorDB.adoptFTSIndex.
|
|
180
|
+
db.markIndexOwner();
|
|
181
|
+
return db;
|
|
178
182
|
}
|
|
179
183
|
mlxMode(config) {
|
|
180
184
|
if (config.embedMode !== "gpu")
|
|
@@ -318,6 +322,7 @@ class Daemon {
|
|
|
318
322
|
this.activeConfig = (0, index_config_1.readGlobalConfig)();
|
|
319
323
|
this.activeGeneration = (0, embedding_generation_1.resolveEmbeddingGeneration)(this.activeConfig);
|
|
320
324
|
this.vectorDb = new vector_db_1.VectorDB(config_1.PATHS.lancedbDir, this.activeGeneration.vectorDim);
|
|
325
|
+
this.vectorDb.markIndexOwner();
|
|
321
326
|
this.workerPool = this.createWorkerPool(this.activeGeneration, this.activeConfig.embedMode);
|
|
322
327
|
this.vectorDb.startMaintenanceLoop((fn) => this.runSharedOperation("store-maintenance", undefined, () => fn()));
|
|
323
328
|
console.log("[daemon] Opening MetaCache:", config_1.PATHS.lmdbPath);
|
|
@@ -15,6 +15,7 @@ exports.buildWhereClause = buildWhereClause;
|
|
|
15
15
|
const config_1 = require("../../config");
|
|
16
16
|
const ann_config_1 = require("../store/ann-config");
|
|
17
17
|
const filter_builder_1 = require("../utils/filter-builder");
|
|
18
|
+
const logger_1 = require("../utils/logger");
|
|
18
19
|
const pool_1 = require("../workers/pool");
|
|
19
20
|
const intent_1 = require("./intent");
|
|
20
21
|
const pagerank_1 = require("./pagerank");
|
|
@@ -436,12 +437,15 @@ class Searcher {
|
|
|
436
437
|
this.ftsIndexChecked = true;
|
|
437
438
|
this.ftsLastCheckedAt = now;
|
|
438
439
|
try {
|
|
439
|
-
|
|
440
|
+
// Adopt-only: search runs in every process, so building here raced
|
|
441
|
+
// CreateIndex across processes and duplicated a full-size index per
|
|
442
|
+
// winner. Owners build it (daemon pre-warm, `gmax index`, maintenance).
|
|
443
|
+
yield this.db.adoptFTSIndex();
|
|
440
444
|
this.ftsAvailable = true;
|
|
441
445
|
}
|
|
442
446
|
catch (e) {
|
|
443
447
|
this.ftsAvailable = false;
|
|
444
|
-
|
|
448
|
+
(0, logger_1.debug)("searcher", `FTS index unavailable, vector-only search: ${e}`);
|
|
445
449
|
}
|
|
446
450
|
}
|
|
447
451
|
// Phase A: Lightweight retrieval — only columns needed for RRF, cosine, boost, dedup
|
|
@@ -501,13 +505,23 @@ class Searcher {
|
|
|
501
505
|
this.ftsAvailable = false;
|
|
502
506
|
const msg = e instanceof Error ? e.message : String(e);
|
|
503
507
|
if (msg.includes("position")) {
|
|
504
|
-
// FTS index lacks positional data — rebuild it
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
508
|
+
// FTS index lacks positional data — rebuild it, but only from the
|
|
509
|
+
// index owner. This drops the index before recreating it, so a
|
|
510
|
+
// non-owner (CLI fallback, MCP session) would yank the shared index
|
|
511
|
+
// out from under every other process and, with several running at
|
|
512
|
+
// once, race CreateIndex on the way back up. Non-owners degrade to
|
|
513
|
+
// vector-only and let the daemon repair it.
|
|
514
|
+
if (this.db.canBuildIndexes()) {
|
|
515
|
+
try {
|
|
516
|
+
yield this.db.createFTSIndex(true);
|
|
517
|
+
this.ftsAvailable = true;
|
|
518
|
+
console.warn("[Searcher] Rebuilt FTS index with position support — retry search");
|
|
519
|
+
}
|
|
520
|
+
catch (_r) { }
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
(0, logger_1.debug)("searcher", "FTS index lacks positions; deferring rebuild to the index owner");
|
|
509
524
|
}
|
|
510
|
-
catch (_r) { }
|
|
511
525
|
}
|
|
512
526
|
else {
|
|
513
527
|
console.warn(`[Searcher] FTS search failed (will retry later): ${msg}`);
|
|
@@ -102,6 +102,8 @@ class VectorDB {
|
|
|
102
102
|
this.maintainedTableVersion = null;
|
|
103
103
|
this.lastMaintenanceMs = 0;
|
|
104
104
|
this.ftsIndexEnsured = false;
|
|
105
|
+
/** Only an index owner may create/rebuild shared indexes — see markIndexOwner. */
|
|
106
|
+
this.indexOwner = false;
|
|
105
107
|
this.lastOptimizeDidWork = false;
|
|
106
108
|
this.maintenanceRunner = (fn) => fn();
|
|
107
109
|
/** Latched when a from-scratch FTS rebuild failed to stop an optimize
|
|
@@ -735,6 +737,48 @@ class VectorDB {
|
|
|
735
737
|
return this.withWriteGate(() => this.createFTSIndexUnsafe(rebuild, retries));
|
|
736
738
|
});
|
|
737
739
|
}
|
|
740
|
+
/**
|
|
741
|
+
* Mark this instance as the store's index owner — the daemon, or a
|
|
742
|
+
* deliberately exclusive CLI operation. Only an owner may create or rebuild
|
|
743
|
+
* the shared FTS index; see `adoptFTSIndex`.
|
|
744
|
+
*/
|
|
745
|
+
markIndexOwner() {
|
|
746
|
+
this.indexOwner = true;
|
|
747
|
+
}
|
|
748
|
+
/** Whether this instance may create or rebuild shared indexes. */
|
|
749
|
+
canBuildIndexes() {
|
|
750
|
+
return this.indexOwner;
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Adopt the FTS index if one already exists, without ever building it.
|
|
754
|
+
*
|
|
755
|
+
* Search runs in *every* process — the daemon, each CLI invocation that falls
|
|
756
|
+
* back in-process, each MCP session. `createFTSIndex` on a miss meant N
|
|
757
|
+
* processes could issue `CreateIndex` against the same table version
|
|
758
|
+
* concurrently: Lance rejects the losers with a retryable commit conflict
|
|
759
|
+
* (observed 2026-08-17T00:03, all 5 retries lost) and every winner writes a
|
|
760
|
+
* full-size inverted index — ~1.4GB each on this store, which is how
|
|
761
|
+
* `_indices` reached 47GB across 35 near-identical copies.
|
|
762
|
+
*
|
|
763
|
+
* Building is therefore reserved for the explicit paths that already run
|
|
764
|
+
* under an owner: daemon pre-warm, `gmax index`, and post-optimize
|
|
765
|
+
* maintenance. Throws when no index exists so callers set `ftsAvailable =
|
|
766
|
+
* false` and degrade to vector-only search — never silently reports FTS as
|
|
767
|
+
* ready.
|
|
768
|
+
*/
|
|
769
|
+
adoptFTSIndex() {
|
|
770
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
771
|
+
if (this.ftsIndexEnsured)
|
|
772
|
+
return;
|
|
773
|
+
const table = yield this.ensureTable();
|
|
774
|
+
const indices = yield table.listIndices();
|
|
775
|
+
const existing = indices.find((index) => index.name === "content_idx" || index.columns.includes("content"));
|
|
776
|
+
if (!existing) {
|
|
777
|
+
throw new Error("[vector-db] FTS index not built yet; this process is not the index owner");
|
|
778
|
+
}
|
|
779
|
+
this.ftsIndexEnsured = true;
|
|
780
|
+
});
|
|
781
|
+
}
|
|
738
782
|
createVectorIndex() {
|
|
739
783
|
return __awaiter(this, arguments, void 0, function* (rebuild = false, retries = 5, checkOnly = false) {
|
|
740
784
|
if (this.checkDiskPressure() !== "ok") {
|
package/package.json
CHANGED