grepmax 0.26.81 → 0.26.82

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.81",
13
+ "version": "0.26.82",
14
14
  "author": {
15
15
  "name": "Robert Owens",
16
16
  "email": "robowens@me.com"
package/README.md CHANGED
@@ -524,7 +524,7 @@ To serve bounded reads under an existing startup quarantine, run `gmax watch --d
524
524
 
525
525
  Full-table compaction and new FTS/ANN index builds are disabled in the containment release, including forced maintenance and doctor repair. Existing indexes remain readable; missing lexical indexes fall back to available retrieval. Do not repeatedly force repair, use future cutoffs or manually remove index fragments.
526
526
 
527
- The package includes a separate prune-only helper with pinned Lance 12 dependencies, plus an exclusively leased Node wrapper. Runtime preparation uses `uv >=0.12.18`, an existing Python interpreter and locked wheels; it does not download Python, build from source, load an embedding model or run during installation. The helper currently supports macOS and Linux. The daemon runs independent prune-only retention: at most 128 old versions per pass, keeping current, tagged and two-minute recent versions. It retries backlog every minute and checks settled history every five minutes; status polling and watched edits do not prevent cleanup. Runtime setup happens before writer suspension. Older readers and external store owners are drained under an exclusive lease; new read-only queries may use the protected current version while history is deleted. Writers resume and watchers reconcile edits after the pass. Full-table compaction admission never gates this reclamation. Normal configured host policy applies; explicit offline recovery retains its separate admission.
527
+ The package includes a separate prune-only helper with pinned Lance 12 dependencies, plus an exclusively leased Node wrapper. Runtime preparation uses `uv >=0.12.18`, an existing Python interpreter and locked wheels; it does not download Python, build from source, load an embedding model or run during installation. The helper currently supports macOS and Linux. The daemon runs independent prune-only retention: at most 128 old versions per pass, keeping current, tagged and two-minute recent versions. It retries backlog every minute and checks settled history every five minutes; status polling and watched edits do not prevent cleanup. Runtime setup happens before writer suspension. Bulk project indexing defers retention without stopping watchers or closing reads, with a one-minute retry. Reads also remain available while watcher work quiesces; admission closes briefly to drain old native readers before pruning starts. Older readers and external store owners are drained under an exclusive lease; new read-only queries may use the protected current version while history is deleted. Writers resume and watchers reconcile edits after the pass. Full-table compaction admission never gates this reclamation. Normal configured host policy applies; explicit offline recovery retains its separate admission.
528
528
 
529
529
  `gmax recover --table /path/to/lancedb/chunks.lance --json` reads the durable recovery receipt without starting a daemon or preparing Python. `--check` samples admission without pruning. `--prune --version <current-version> --cutoff <absolute-ISO-timestamp>` explicitly requests one offline prune. A prior uncertain attempt requires `--acknowledge-uncertain <attempt-id>` after inspection. Recovery never creates/clears quarantine, stops other owners, retries automatically or enables rewriting.
530
530
 
@@ -163,6 +163,7 @@ class Daemon {
163
163
  this.cleanupInterval = null;
164
164
  this.cleanupPromise = null;
165
165
  this.lastCleanupAttempt = 0;
166
+ this.cleanupRetrySoon = false;
166
167
  this.heartbeatTick = 0;
167
168
  this.shuttingDown = false;
168
169
  this.recycling = false;
@@ -854,7 +855,8 @@ class Daemon {
854
855
  return;
855
856
  this.cleanupInterval = setInterval(() => {
856
857
  var _a;
857
- const interval = ((_a = this.versionCleanupStatus()) === null || _a === void 0 ? void 0 : _a.eligibleVersionsRemaining) === 0
858
+ const interval = !this.cleanupRetrySoon &&
859
+ ((_a = this.versionCleanupStatus()) === null || _a === void 0 ? void 0 : _a.eligibleVersionsRemaining) === 0
858
860
  ? 5 * 60000
859
861
  : 60000;
860
862
  if (!this.ready ||
@@ -875,6 +877,7 @@ class Daemon {
875
877
  if (this.cleanupPromise)
876
878
  return this.cleanupPromise;
877
879
  this.lastCleanupAttempt = Date.now();
880
+ this.cleanupRetrySoon = false;
878
881
  const run = this.performVersionCleanup();
879
882
  const tracked = run.finally(() => {
880
883
  if (this.cleanupPromise === tracked)
@@ -891,9 +894,26 @@ class Daemon {
891
894
  this.recycling ||
892
895
  this.pausedReason !== null)
893
896
  throw new Error("daemon resources not ready for version cleanup");
897
+ const bulkIndexingActive = () => this.operations
898
+ .activeOperationNames()
899
+ .some((name) => [
900
+ "add-project",
901
+ "ensure-project",
902
+ "index-project",
903
+ "index-pending",
904
+ ].includes(name));
905
+ const deferForIndexing = () => {
906
+ this.cleanupRetrySoon = true;
907
+ return (0, compaction_result_1.skippedCompaction)("project indexing is active; version cleanup deferred without closing reads");
908
+ };
909
+ if (bulkIndexingActive())
910
+ return deferForIndexing();
894
911
  this.assertHeavyOperationAdmission("version-cleanup");
895
912
  const deadline = AbortSignal.timeout(60000);
896
913
  const runtime = yield (0, version_cleanup_1.prepareVersionCleanup)(config_1.PATHS.lancedbDir, deadline);
914
+ // Runtime setup yields: a bulk index can start before exclusive intent.
915
+ if (bulkIndexingActive())
916
+ return deferForIndexing();
897
917
  let roots = [];
898
918
  try {
899
919
  return yield this.operations.runExclusive("version-cleanup", () => __awaiter(this, void 0, void 0, function* () {
@@ -919,7 +939,11 @@ class Daemon {
919
939
  return (0, compaction_result_1.skippedCompaction)("no indexed table");
920
940
  (0, logger_1.log)("daemon", `Version cleanup completed: ${JSON.stringify(result)}`);
921
941
  return result;
922
- }), { queueShared: true });
942
+ }), {
943
+ queueShared: true,
944
+ pendingReads: (name) => (0, read_verbs_1.getReadVerb)(name) !== undefined ||
945
+ ["search", "keyword-search", "documents", "project-stats"].includes(name),
946
+ });
923
947
  }
924
948
  finally {
925
949
  if (!this.shuttingDown &&
@@ -117,6 +117,9 @@ class OperationCoordinator {
117
117
  }
118
118
  runExclusive(name, quiesce, fn, options = {}) {
119
119
  var _a;
120
+ if (options.pendingReads &&
121
+ (name !== "version-cleanup" || !options.queueShared))
122
+ return Promise.reject(new Error("pending reads require queued prune-only cleanup"));
120
123
  if (this.state.kind === "closing" || this.state.kind === "closed") {
121
124
  return Promise.reject(new OperationClosedError());
122
125
  }
@@ -126,11 +129,16 @@ class OperationCoordinator {
126
129
  }
127
130
  this.state = { kind: "exclusive-pending", name };
128
131
  this.queueShared = (_a = options.queueShared) !== null && _a !== void 0 ? _a : false;
132
+ this.readWindow = options.pendingReads;
129
133
  const controller = new AbortController();
130
134
  this.controllers.add(controller);
131
135
  const task = (() => __awaiter(this, void 0, void 0, function* () {
132
136
  try {
133
137
  yield quiesce();
138
+ // Reads remain available while writers quiesce. Close admission before
139
+ // taking the drain snapshot so every old native reader still settles
140
+ // before retention closes handles and deletes history.
141
+ this.readWindow = undefined;
134
142
  yield Promise.allSettled([...this.sharedTasks]);
135
143
  if (controller.signal.aborted) {
136
144
  throw abortError(controller.signal.reason);
@@ -201,7 +209,7 @@ class OperationCoordinator {
201
209
  }
202
210
  if (this.state.kind === "exclusive" ||
203
211
  this.state.kind === "exclusive-pending") {
204
- if (this.state.kind === "exclusive" && ((_a = this.readWindow) === null || _a === void 0 ? void 0 : _a.call(this, name)))
212
+ if ((_a = this.readWindow) === null || _a === void 0 ? void 0 : _a.call(this, name))
205
213
  return;
206
214
  throw new OperationBusyError(this.state.name);
207
215
  }
@@ -102,6 +102,10 @@ function requestMlxJSON(reqPath, body) {
102
102
  hostname: MLX_HOST,
103
103
  port: MLX_PORT,
104
104
  path: reqPath,
105
+ // A local connection is cheap. Avoid reusing a socket that uvicorn
106
+ // closed at its keep-alive boundary: that reset can incorrectly mark
107
+ // the healthy embedding backend unavailable and force CPU fallback.
108
+ agent: false,
105
109
  method: payload === undefined ? "GET" : "POST",
106
110
  headers: payload === undefined
107
111
  ? undefined
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.26.81",
3
+ "version": "0.26.82",
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.81",
3
+ "version": "0.26.82",
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",