grepmax 0.26.11 → 0.26.12

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.
@@ -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.11",
13
+ "version": "0.26.12",
14
14
  "author": {
15
15
  "name": "Robert Owens",
16
16
  "email": "robowens@me.com"
@@ -88,7 +88,7 @@ exports.doctor = new commander_1.Command("doctor")
88
88
  .option("--fix", "Auto-fix detected issues (compact, prune, remove stale locks)", false)
89
89
  .option("--agent", "Compact output for AI agents", false)
90
90
  .action((opts) => __awaiter(void 0, void 0, void 0, function* () {
91
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
91
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
92
92
  if (!opts.agent)
93
93
  console.log("gmax Doctor\n");
94
94
  const root = config_1.PATHS.globalRoot;
@@ -281,7 +281,7 @@ exports.doctor = new commander_1.Command("doctor")
281
281
  ? "LOW"
282
282
  : "ok";
283
283
  }
284
- catch (_o) { }
284
+ catch (_p) { }
285
285
  const staleChunkerProjects = projects.filter((p) => {
286
286
  var _a;
287
287
  return p.status === "indexed" &&
@@ -486,7 +486,7 @@ exports.doctor = new commander_1.Command("doctor")
486
486
  yield mc.close();
487
487
  }
488
488
  }
489
- catch (_p) { }
489
+ catch (_q) { }
490
490
  }
491
491
  }
492
492
  // --fix auto-remediation
@@ -503,9 +503,39 @@ exports.doctor = new commander_1.Command("doctor")
503
503
  if (needsOptimize) {
504
504
  if (!opts.agent)
505
505
  console.log("... Running optimize (compact + prune)...");
506
- yield db.optimize(3, 0);
507
- if (!opts.agent)
508
- console.log("ok Optimize complete");
506
+ // Prefer the daemon. Lance's optimize deletes unverified files, which
507
+ // is only safe with a single writer on the store — optimizing here
508
+ // while the daemon compacts made this command a second writer. With a
509
+ // daemon up it owns compaction; without one we are the sole process
510
+ // and can do it in-process safely.
511
+ const { sendDaemonCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
512
+ const resp = yield sendDaemonCommand({ cmd: "optimize" }, { timeoutMs: 10 * 60 * 1000 });
513
+ let via = "daemon";
514
+ if (!resp.ok) {
515
+ // Only fall back when the daemon could not take it. A daemon that
516
+ // answered with a real failure should surface, not be retried here
517
+ // as a competing writer.
518
+ const reason = String((_k = resp.error) !== null && _k !== void 0 ? _k : "");
519
+ // Fall back ONLY when nothing is listening. A daemon that is merely
520
+ // too old ("unknown command") or busy ("timeout") is still a live
521
+ // writer on the store, so optimizing here would recreate the
522
+ // two-writer case this routing exists to prevent.
523
+ const daemonAbsent = reason.includes("ECONNREFUSED") || reason.includes("ENOENT");
524
+ if (daemonAbsent) {
525
+ yield db.optimize(3, 0);
526
+ via = "in-process (no daemon)";
527
+ }
528
+ else if (reason.includes("unknown command")) {
529
+ via = "";
530
+ console.log("WARN Optimize skipped: the running daemon predates the optimize command. Restart it (gmax watch --daemon -b) and re-run.");
531
+ }
532
+ else {
533
+ via = "";
534
+ console.log(`WARN Optimize failed via daemon: ${reason}`);
535
+ }
536
+ }
537
+ if (via && !opts.agent)
538
+ console.log(`ok Optimize complete (${via})`);
509
539
  fixed++;
510
540
  }
511
541
  if (orphanedProjects.length > 0) {
@@ -533,7 +563,7 @@ exports.doctor = new commander_1.Command("doctor")
533
563
  const done = yield sendStreamingCommand({ cmd: "index", root: p.root, reset: true }, () => { }).catch((e) => ({ ok: false, error: String(e) }));
534
564
  if (done.ok &&
535
565
  (!("degraded" in done) || done.degraded !== true)) {
536
- const chunks = (_k = done.indexed) !== null && _k !== void 0 ? _k : 0;
566
+ const chunks = (_l = done.indexed) !== null && _l !== void 0 ? _l : 0;
537
567
  if (opts.agent) {
538
568
  console.log(`stale_chunker_reindexed\tname=${name}\tchunks=${chunks}`);
539
569
  }
@@ -545,7 +575,7 @@ exports.doctor = new commander_1.Command("doctor")
545
575
  else if (!opts.agent) {
546
576
  const degraded = "degraded" in done && done.degraded === true;
547
577
  const reason = degraded
548
- ? `${(_l = ("scanErrors" in done ? done.scanErrors : 0)) !== null && _l !== void 0 ? _l : 0} scan errors, ${(_m = ("failedFiles" in done ? done.failedFiles : 0)) !== null && _m !== void 0 ? _m : 0} file failures`
578
+ ? `${(_m = ("scanErrors" in done ? done.scanErrors : 0)) !== null && _m !== void 0 ? _m : 0} scan errors, ${(_o = ("failedFiles" in done ? done.failedFiles : 0)) !== null && _o !== void 0 ? _o : 0} file failures`
549
579
  : done.error;
550
580
  console.log(`FAIL ${name}: reindex failed (${reason})`);
551
581
  }
@@ -486,6 +486,28 @@ class Daemon {
486
486
  runSharedOperation(name, signal, fn) {
487
487
  return this.operations.runShared(name, signal, fn);
488
488
  }
489
+ /**
490
+ * Compact + prune on behalf of a CLI caller.
491
+ *
492
+ * Lance's optimize runs with `deleteUnverified: true`, which its own docs say
493
+ * is only safe when no other process is working on the dataset. That held by
494
+ * accident, not by design: the daemon serializes its own maintenance, but
495
+ * `gmax doctor --fix` optimized in-process, so a user running it during a
496
+ * compaction was a second writer on a store that assumes one. Routing the
497
+ * request here makes it structural — with a daemon up, the daemon is the only
498
+ * optimizer, sharing the same serialization as the maintenance loop. Callers
499
+ * fall back to in-process only when no daemon answers, and are then the sole
500
+ * writer themselves.
501
+ */
502
+ runOptimize() {
503
+ return __awaiter(this, void 0, void 0, function* () {
504
+ const db = this.vectorDb;
505
+ if (!db)
506
+ throw new Error("daemon resources not ready");
507
+ yield this.runSharedOperation("store-maintenance", undefined, () => db.runMaintenance({ force: true }));
508
+ return { ok: true };
509
+ });
510
+ }
489
511
  operationStatus() {
490
512
  return this.operations.status;
491
513
  }
@@ -173,6 +173,9 @@ function handleCommand(daemon, cmd, conn) {
173
173
  diskPressure: daemon.getDiskPressure(),
174
174
  mlx: daemon.getMlxStatus(),
175
175
  };
176
+ case "optimize":
177
+ // Keeps compaction single-writer: see Daemon.runOptimize.
178
+ return yield daemon.runOptimize();
176
179
  case "project-stats": {
177
180
  const root = String(cmd.root || "");
178
181
  if (!root)
@@ -956,6 +956,15 @@ class VectorDB {
956
956
  yield this.drainWrites();
957
957
  try {
958
958
  const done = (0, logger_1.timer)("vectordb", "optimize");
959
+ // deleteUnverified deletes files Lance cannot prove are unreferenced.
960
+ // LanceDB's own docs: only safe "if you can guarantee that no other
961
+ // process is currently working on this dataset. Otherwise the dataset
962
+ // could be put into a corrupted state." gmax is multi-process, so that
963
+ // guarantee comes from routing compaction to a single writer — the
964
+ // daemon serializes it (Daemon.runOptimize + the maintenance loop),
965
+ // and CLI callers go through it rather than optimizing alongside it.
966
+ // Anything new that calls optimize() must preserve that: either be the
967
+ // daemon, or be the only process on the store.
959
968
  const stats = yield table.optimize({
960
969
  cleanupOlderThan: cutoff,
961
970
  deleteUnverified: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.26.11",
3
+ "version": "0.26.12",
4
4
  "author": "Robert Owens <78518764+reowens@users.noreply.github.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.26.11",
3
+ "version": "0.26.12",
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",