grepmax 0.26.1 → 0.26.3

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.1",
13
+ "version": "0.26.3",
14
14
  "author": {
15
15
  "name": "Robert Owens",
16
16
  "email": "robowens@me.com"
@@ -1472,8 +1472,11 @@ class Daemon {
1472
1472
  return;
1473
1473
  }
1474
1474
  const stopHeartbeat = (0, ipc_handler_1.startHeartbeat)(conn);
1475
+ const wasWatching = this.processors.has(root);
1476
+ let unwatched = false;
1475
1477
  try {
1476
1478
  yield this.unwatchProjectWithinOperation(root);
1479
+ unwatched = true;
1477
1480
  const rootPrefix = root.endsWith("/") ? root : `${root}/`;
1478
1481
  yield this.vectorDb.deletePathsWithPrefix(rootPrefix);
1479
1482
  const keys = yield this.metaCache.getKeysWithPrefix(rootPrefix);
@@ -1482,7 +1485,18 @@ class Daemon {
1482
1485
  (0, ipc_handler_1.writeDone)(conn, { ok: true });
1483
1486
  }
1484
1487
  catch (err) {
1485
- const msg = err instanceof Error ? err.message : String(err);
1488
+ let reportedError = err;
1489
+ if (unwatched && wasWatching && !this.shuttingDown) {
1490
+ try {
1491
+ yield this.watchProjectWithinOperation(root);
1492
+ }
1493
+ catch (watchError) {
1494
+ reportedError = new Error(`Project removal failed: ${String(err)}; watcher restoration failed: ${String(watchError)}`);
1495
+ }
1496
+ }
1497
+ const msg = reportedError instanceof Error
1498
+ ? reportedError.message
1499
+ : String(reportedError);
1486
1500
  console.error(`[daemon] removeProject failed for ${path.basename(root)}:`, msg);
1487
1501
  (0, ipc_handler_1.writeDone)(conn, { ok: false, error: msg });
1488
1502
  }
@@ -121,12 +121,14 @@ class WatcherManager {
121
121
  try {
122
122
  const filePolicy = new file_policy_1.ProjectFilePolicy(root);
123
123
  let processor;
124
+ let lastReindexAt;
124
125
  processor = new batch_processor_1.ProjectBatchProcessor(Object.assign(Object.assign({ projectRoot: root, vectorDb,
125
126
  metaCache }, (workerPool ? { workerPool } : {})), { filePolicy, onPolicyChange: () => {
126
127
  void this.runCatchup(root, processor).catch((err) => {
127
128
  console.error(`[daemon:${path.basename(root)}] Policy reconciliation failed:`, err);
128
129
  });
129
130
  }, onReindex: (files, ms) => __awaiter(this, void 0, void 0, function* () {
131
+ lastReindexAt = Date.now();
130
132
  console.log(`[daemon:${path.basename(root)}] Reindexed ${files} file${files !== 1 ? "s" : ""} (${(ms / 1000).toFixed(1)}s)`);
131
133
  // Update project registry so gmax status shows fresh data
132
134
  const proj = (0, project_registry_1.getProject)(root);
@@ -140,16 +142,13 @@ class WatcherManager {
140
142
  }
141
143
  (0, project_registry_1.registerProject)(Object.assign(Object.assign({}, proj), { lastIndexed: new Date().toISOString(), chunkCount }));
142
144
  }
143
- // Back to watching after batch completes
144
- (0, watcher_store_1.registerWatcher)({
145
- pid: process.pid,
146
- projectRoot: root,
147
- startTime: Date.now(),
148
- status: this.isRootDegraded(root) ? "degraded" : "watching",
149
- lastHeartbeat: Date.now(),
150
- lastReindex: Date.now(),
151
- });
152
- }), onActivity: () => {
145
+ }), onBatchSettled: () => {
146
+ (0, watcher_store_1.registerWatcher)(Object.assign({ pid: process.pid, projectRoot: root, startTime: Date.now(), status: this.isRootDegraded(root)
147
+ ? "degraded"
148
+ : processor.progress.pendingFiles > 0
149
+ ? "syncing"
150
+ : "watching", lastHeartbeat: Date.now() }, (lastReindexAt ? { lastReindex: lastReindexAt } : {})));
151
+ }, onActivity: () => {
153
152
  this.deps.touchActivity();
154
153
  // Mark as syncing while processing
155
154
  (0, watcher_store_1.registerWatcher)({
@@ -78,6 +78,7 @@ class ProjectBatchProcessor {
78
78
  this.workerPool = (_a = opts.workerPool) !== null && _a !== void 0 ? _a : (0, pool_1.getWorkerPool)();
79
79
  this.onReindex = opts.onReindex;
80
80
  this.onActivity = opts.onActivity;
81
+ this.onBatchSettled = opts.onBatchSettled;
81
82
  this.onPolicyChange = opts.onPolicyChange;
82
83
  this.onTerminalFailure = opts.onTerminalFailure;
83
84
  this.onPathSuccess = opts.onPathSuccess;
@@ -190,21 +191,12 @@ class ProjectBatchProcessor {
190
191
  }
191
192
  processBatch(operationSignal) {
192
193
  return __awaiter(this, void 0, void 0, function* () {
193
- var _a, _b, _c, _d, _e;
194
+ var _a, _b, _c, _d, _e, _f;
194
195
  if (this.closed || this.processing || this.pending.size === 0)
195
196
  return;
196
- // Circuit breaker: don't attempt writes when disk is critically low
197
- if (this.vectorDb.diskPressure === "critical") {
198
- (0, logger_1.log)(this.wtag, "Disk critically low — deferring batch processing");
199
- if (this.debounceTimer)
200
- clearTimeout(this.debounceTimer);
201
- this.debounceDueMs = Date.now() + 60000;
202
- this.debounceTimer = setTimeout(() => {
203
- this.debounceTimer = null;
204
- this.debounceDueMs = 0;
205
- this.startBatch();
206
- }, 60000);
207
- return;
197
+ const diskPressure = this.vectorDb.checkDiskPressure();
198
+ if (diskPressure === "critical") {
199
+ (0, logger_1.log)(this.wtag, "Disk critically low — applying deletions and deferring indexing");
208
200
  }
209
201
  const batch = new Map();
210
202
  const batchForceGenerations = new Map();
@@ -304,6 +296,9 @@ class ProjectBatchProcessor {
304
296
  continue;
305
297
  }
306
298
  const stats = classification.stat;
299
+ if (diskPressure === "critical") {
300
+ continue;
301
+ }
307
302
  const cached = this.metaCache.get(absPath);
308
303
  const forceReprocess = batchForceGenerations.has(absPath);
309
304
  if (!forceReprocess &&
@@ -423,6 +418,9 @@ class ProjectBatchProcessor {
423
418
  requeuePath(absPath, event, retryFailures.has(absPath));
424
419
  }
425
420
  }
421
+ if (diskPressure === "critical" && this.pending.size > 0) {
422
+ backoffOverrideMs = 60000;
423
+ }
426
424
  // Flush to VectorDB: insert first, then delete old (preserving new)
427
425
  const newIds = vectors.map((v) => v.id);
428
426
  if (vectors.length > 0) {
@@ -473,7 +471,7 @@ class ProjectBatchProcessor {
473
471
  (_c = this.onPathSuccess) === null || _c === void 0 ? void 0 : _c.call(this, absPath);
474
472
  }
475
473
  // Trigger compaction if fragments are accumulating
476
- if (reindexed > 0) {
474
+ if (reindexed > 0 && diskPressure !== "critical") {
477
475
  try {
478
476
  yield this.vectorDb.compactIfNeeded();
479
477
  }
@@ -484,7 +482,9 @@ class ProjectBatchProcessor {
484
482
  }
485
483
  catch (err) {
486
484
  // Disk pressure: requeue without counting as retries (not the file's fault)
487
- if (err instanceof vector_db_1.DiskPressureError) {
485
+ const code = err === null || err === void 0 ? void 0 : err.code;
486
+ if (err instanceof vector_db_1.DiskPressureError ||
487
+ (diskPressure === "critical" && code === "ENOSPC")) {
488
488
  for (const [absPath, event] of batch) {
489
489
  if (this.terminalFailures.has(absPath))
490
490
  continue;
@@ -575,6 +575,8 @@ class ProjectBatchProcessor {
575
575
  this.schedulePendingBatch();
576
576
  }
577
577
  }
578
+ if (!this.closed)
579
+ (_f = this.onBatchSettled) === null || _f === void 0 ? void 0 : _f.call(this);
578
580
  }
579
581
  });
580
582
  }
@@ -195,6 +195,7 @@ function startWatcher(opts) {
195
195
  processor = new batch_processor_1.ProjectBatchProcessor(Object.assign(Object.assign({}, opts), { filePolicy, onPolicyChange: reconcile, onReindex: (files, durationMs) => {
196
196
  var _a;
197
197
  (_a = opts.onReindex) === null || _a === void 0 ? void 0 : _a.call(opts, files, durationMs);
198
+ }, onBatchSettled: () => {
198
199
  reportHealthyIfSettled();
199
200
  }, onTerminalFailure: (absPath) => {
200
201
  var _a;
@@ -589,6 +589,19 @@ class VectorDB {
589
589
  return table;
590
590
  });
591
591
  }
592
+ openExistingTableUnsafe() {
593
+ return __awaiter(this, void 0, void 0, function* () {
594
+ const db = yield this.getDb();
595
+ try {
596
+ return yield db.openTable(TABLE_NAME);
597
+ }
598
+ catch (err) {
599
+ if (isMissingTableError(err))
600
+ return null;
601
+ throw err;
602
+ }
603
+ });
604
+ }
592
605
  insertBatch(records) {
593
606
  return __awaiter(this, void 0, void 0, function* () {
594
607
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
@@ -982,11 +995,12 @@ class VectorDB {
982
995
  return __awaiter(this, void 0, void 0, function* () {
983
996
  if (!paths.length)
984
997
  return;
985
- this.ensureDiskOk();
986
998
  const unique = Array.from(new Set(paths));
987
999
  const batchSize = 500;
988
1000
  yield this.withWriteGate(() => __awaiter(this, void 0, void 0, function* () {
989
- const table = yield this.ensureTableUnsafe();
1001
+ const table = yield this.openExistingTableUnsafe();
1002
+ if (!table)
1003
+ return;
990
1004
  for (let i = 0; i < unique.length; i += batchSize) {
991
1005
  const slice = unique.slice(i, i + batchSize);
992
1006
  const values = slice.map((p) => `'${(0, filter_builder_1.escapeSqlString)(p)}'`).join(",");
@@ -1026,14 +1040,15 @@ class VectorDB {
1026
1040
  return __awaiter(this, void 0, void 0, function* () {
1027
1041
  if (!paths.length)
1028
1042
  return;
1029
- this.ensureDiskOk();
1030
1043
  const unique = Array.from(new Set(paths));
1031
1044
  const batchSize = 500;
1032
1045
  const idExclusion = excludeIds.length > 0
1033
1046
  ? ` AND id NOT IN (${excludeIds.map((id) => `'${(0, filter_builder_1.escapeSqlString)(id)}'`).join(",")})`
1034
1047
  : "";
1035
1048
  yield this.withWriteGate(() => __awaiter(this, void 0, void 0, function* () {
1036
- const table = yield this.ensureTableUnsafe();
1049
+ const table = yield this.openExistingTableUnsafe();
1050
+ if (!table)
1051
+ return;
1037
1052
  for (let i = 0; i < unique.length; i += batchSize) {
1038
1053
  const slice = unique.slice(i, i + batchSize);
1039
1054
  const values = slice.map((p) => `'${(0, filter_builder_1.escapeSqlString)(p)}'`).join(",");
@@ -1053,14 +1068,15 @@ class VectorDB {
1053
1068
  }
1054
1069
  deletePathsWithPrefix(prefix) {
1055
1070
  return __awaiter(this, void 0, void 0, function* () {
1056
- this.ensureDiskOk();
1057
1071
  // Slash-terminate so a project root can't bleed into a sibling
1058
1072
  // (`/repo/app` must not delete `/repo/app2`), and use starts_with so `_`/`%`
1059
1073
  // in the path are literal, not LIKE wildcards. Destructive path — keep this
1060
1074
  // self-protective even if a caller forgets to normalize.
1061
1075
  const dirPrefix = prefix.endsWith("/") ? prefix : `${prefix}/`;
1062
1076
  yield this.withWriteGate(() => __awaiter(this, void 0, void 0, function* () {
1063
- const table = yield this.ensureTableUnsafe();
1077
+ const table = yield this.openExistingTableUnsafe();
1078
+ if (!table)
1079
+ return;
1064
1080
  yield table.delete((0, filter_builder_1.pathStartsWith)(dirPrefix));
1065
1081
  }));
1066
1082
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.26.1",
3
+ "version": "0.26.3",
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.1",
3
+ "version": "0.26.3",
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",