grepmax 0.6.1 → 0.6.4

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.
@@ -64,6 +64,16 @@ exports.index = new commander_1.Command("index")
64
64
  var _a;
65
65
  const options = cmd.optsWithGlobals();
66
66
  let vectorDb = null;
67
+ const ac = new AbortController();
68
+ let aborted = false;
69
+ const onSignal = () => {
70
+ if (aborted)
71
+ return;
72
+ aborted = true;
73
+ ac.abort();
74
+ };
75
+ process.on("SIGINT", onSignal);
76
+ process.on("SIGTERM", onSignal);
67
77
  try {
68
78
  yield (0, setup_helpers_1.ensureSetup)();
69
79
  const indexRoot = options.path
@@ -107,7 +117,12 @@ exports.index = new commander_1.Command("index")
107
117
  dryRun: options.dryRun,
108
118
  reset: options.reset,
109
119
  onProgress,
120
+ signal: ac.signal,
110
121
  });
122
+ if (aborted) {
123
+ spinner.warn(`Indexing interrupted — partial progress saved (${result.indexed} indexed)`);
124
+ return;
125
+ }
111
126
  if (options.dryRun) {
112
127
  spinner.succeed(`Dry run complete(${result.processed} / ${result.total}) • would have indexed ${result.indexed} `);
113
128
  console.log((0, sync_helpers_1.formatDryRunSummary)(result, {
@@ -143,6 +158,8 @@ exports.index = new commander_1.Command("index")
143
158
  process.exitCode = 1;
144
159
  }
145
160
  finally {
161
+ process.removeListener("SIGINT", onSignal);
162
+ process.removeListener("SIGTERM", onSignal);
146
163
  if (vectorDb) {
147
164
  try {
148
165
  yield vectorDb.close();
@@ -338,6 +338,7 @@ exports.mcp = new commander_1.Command("mcp")
338
338
  // --- Tool handlers ---
339
339
  function handleSemanticSearch(args_1) {
340
340
  return __awaiter(this, arguments, void 0, function* (args, searchAll = false) {
341
+ var _a;
341
342
  const query = String(args.query || "");
342
343
  if (!query)
343
344
  return err("Missing required parameter: query");
@@ -353,6 +354,9 @@ exports.mcp = new commander_1.Command("mcp")
353
354
  const searchRoot = typeof args.root === "string"
354
355
  ? path.resolve(args.root)
355
356
  : path.resolve(projectRoot);
357
+ if (typeof args.root === "string" && !fs.existsSync(searchRoot)) {
358
+ return err(`Directory not found: ${args.root}`);
359
+ }
356
360
  displayRoot = searchRoot;
357
361
  pathPrefix = searchRoot.endsWith("/")
358
362
  ? searchRoot
@@ -434,7 +438,11 @@ exports.mcp = new commander_1.Command("mcp")
434
438
  return true;
435
439
  });
436
440
  }
437
- return ok(results.map((r) => r.text).join("\n\n"));
441
+ const output = results.map((r) => r.text).join("\n\n");
442
+ if ((_a = result.warnings) === null || _a === void 0 ? void 0 : _a.length) {
443
+ return ok(`${result.warnings.join("\n")}\n\n${output}`);
444
+ }
445
+ return ok(output);
438
446
  }
439
447
  catch (e) {
440
448
  const msg = e instanceof Error ? e.message : String(e);
@@ -339,7 +339,7 @@ exports.search = new commander_1.Command("search")
339
339
  .argument("<pattern>", "The pattern to search for")
340
340
  .argument("[path]", "The path to search in")
341
341
  .action((pattern, exec_path, _options, cmd) => __awaiter(void 0, void 0, void 0, function* () {
342
- var _a, _b;
342
+ var _a, _b, _c;
343
343
  const options = cmd.optsWithGlobals();
344
344
  const root = process.cwd();
345
345
  const minScore = Number.isFinite(Number.parseFloat(options.minScore))
@@ -488,6 +488,11 @@ exports.search = new commander_1.Command("search")
488
488
  ? searchPathPrefix
489
489
  : `${searchPathPrefix}/`;
490
490
  const searchResult = yield searcher.search(pattern, parseInt(options.m, 10), { rerank: true }, undefined, pathFilter);
491
+ if ((_c = searchResult.warnings) === null || _c === void 0 ? void 0 : _c.length) {
492
+ for (const w of searchResult.warnings) {
493
+ console.warn(`Warning: ${w}`);
494
+ }
495
+ }
491
496
  const filteredData = searchResult.data.filter((r) => typeof r.score !== "number" || r.score >= minScore);
492
497
  if (options.skeleton) {
493
498
  yield outputSkeletons(filteredData, projectRoot, parseInt(options.m, 10), vectorDb);
@@ -426,7 +426,7 @@ exports.serve = new commander_1.Command("serve")
426
426
  });
427
427
  // Clean close of owned resources
428
428
  try {
429
- metaCache.close();
429
+ yield metaCache.close();
430
430
  }
431
431
  catch (e) {
432
432
  console.error("Error closing meta cache:", e);
@@ -48,6 +48,7 @@ const fs = __importStar(require("node:fs"));
48
48
  const path = __importStar(require("node:path"));
49
49
  const commander_1 = require("commander");
50
50
  const config_1 = require("../config");
51
+ const filter_builder_1 = require("../lib/utils/filter-builder");
51
52
  const syncer_1 = require("../lib/index/syncer");
52
53
  const watcher_1 = require("../lib/index/watcher");
53
54
  const meta_cache_1 = require("../lib/store/meta-cache");
@@ -113,7 +114,7 @@ exports.watch = new commander_1.Command("watch")
113
114
  const indexed = yield table
114
115
  .query()
115
116
  .select(["id"])
116
- .where(`path LIKE '${prefix}%'`)
117
+ .where(`path LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`)
117
118
  .limit(1)
118
119
  .toArray();
119
120
  if (indexed.length === 0) {
@@ -154,7 +155,7 @@ exports.watch = new commander_1.Command("watch")
154
155
  yield watcher.close();
155
156
  }
156
157
  catch (_a) { }
157
- metaCache.close();
158
+ yield metaCache.close();
158
159
  yield vectorDb.close();
159
160
  (0, watcher_registry_1.unregisterWatcher)(process.pid);
160
161
  yield (0, exit_1.gracefulExit)();
@@ -57,6 +57,7 @@ const config_1 = require("../../config");
57
57
  const logger_1 = require("../utils/logger");
58
58
  const meta_cache_1 = require("../store/meta-cache");
59
59
  const vector_db_1 = require("../store/vector-db");
60
+ const filter_builder_1 = require("../utils/filter-builder");
60
61
  const file_utils_1 = require("../utils/file-utils");
61
62
  const lock_1 = require("../utils/lock");
62
63
  const project_registry_1 = require("../utils/project-registry");
@@ -85,7 +86,7 @@ function generateSummaries(db, pathPrefix, onProgress, maxChunks) {
85
86
  const rows = yield table
86
87
  .query()
87
88
  .select(["id", "path", "content", "defined_symbols"])
88
- .where(`path LIKE '${pathPrefix}%' AND (summary IS NULL OR summary = '')`)
89
+ .where(`path LIKE '${(0, filter_builder_1.escapeSqlString)(pathPrefix)}%' AND (summary IS NULL OR summary = '')`)
89
90
  .limit(queryLimit)
90
91
  .toArray();
91
92
  if (rows.length === 0)
@@ -174,7 +175,7 @@ function createNoopMetaCache() {
174
175
  delete: (filePath) => {
175
176
  store.delete(filePath);
176
177
  },
177
- close: () => { },
178
+ close: () => __awaiter(this, void 0, void 0, function* () { }),
178
179
  };
179
180
  }
180
181
  function initialSync(options) {
@@ -457,7 +458,17 @@ function initialSync(options) {
457
458
  ? flushError
458
459
  : new Error(String(flushError));
459
460
  }
460
- if (!dryRun) {
461
+ // Stale cleanup: only remove paths scoped to this project's root
462
+ const stale = Array.from(cachedPaths).filter((p) => !seenPaths.has(p));
463
+ if (!dryRun && stale.length > 0 && !shouldSkipCleanup) {
464
+ (0, logger_1.log)("index", `Stale cleanup: ${stale.length} paths`);
465
+ yield vectorDb.deletePaths(stale);
466
+ stale.forEach((p) => {
467
+ metaCache.delete(p);
468
+ });
469
+ }
470
+ // Only rebuild FTS index if data actually changed
471
+ if (!dryRun && (indexed > 0 || stale.length > 0)) {
461
472
  const ftsTimer = (0, logger_1.timer)("index", "FTS");
462
473
  onProgress === null || onProgress === void 0 ? void 0 : onProgress({
463
474
  processed,
@@ -468,15 +479,6 @@ function initialSync(options) {
468
479
  yield vectorDb.createFTSIndex();
469
480
  ftsTimer();
470
481
  }
471
- // Stale cleanup: only remove paths scoped to this project's root
472
- const stale = Array.from(cachedPaths).filter((p) => !seenPaths.has(p));
473
- if (!dryRun && stale.length > 0 && !shouldSkipCleanup) {
474
- (0, logger_1.log)("index", `Stale cleanup: ${stale.length} paths`);
475
- yield vectorDb.deletePaths(stale);
476
- stale.forEach((p) => {
477
- metaCache.delete(p);
478
- });
479
- }
480
482
  syncTimer();
481
483
  // Write model config so future runs can detect model changes
482
484
  if (!dryRun) {
@@ -501,7 +503,7 @@ function initialSync(options) {
501
503
  if (lock) {
502
504
  yield lock.release();
503
505
  }
504
- metaCache === null || metaCache === void 0 ? void 0 : metaCache.close();
506
+ yield (metaCache === null || metaCache === void 0 ? void 0 : metaCache.close());
505
507
  yield vectorDb.close();
506
508
  }
507
509
  });
@@ -47,6 +47,7 @@ exports.startWatcher = startWatcher;
47
47
  const fs = __importStar(require("node:fs"));
48
48
  const path = __importStar(require("node:path"));
49
49
  const chokidar_1 = require("chokidar");
50
+ const filter_builder_1 = require("../utils/filter-builder");
50
51
  const file_utils_1 = require("../utils/file-utils");
51
52
  const logger_1 = require("../utils/logger");
52
53
  const lock_1 = require("../utils/lock");
@@ -75,9 +76,12 @@ const FTS_REBUILD_INTERVAL_MS = 5 * 60 * 1000;
75
76
  function startWatcher(opts) {
76
77
  const { projectRoot, vectorDb, metaCache, dataDir, onReindex } = opts;
77
78
  const pending = new Map();
79
+ const retryCount = new Map();
78
80
  let debounceTimer = null;
79
81
  let processing = false;
80
82
  let closed = false;
83
+ let consecutiveLockFailures = 0;
84
+ const MAX_RETRIES = 5;
81
85
  const watcher = (0, chokidar_1.watch)(projectRoot, {
82
86
  ignored: exports.WATCHER_IGNORE_PATTERNS,
83
87
  ignoreInitial: true,
@@ -98,6 +102,7 @@ function startWatcher(opts) {
98
102
  debounceTimer = setTimeout(() => processBatch(), DEBOUNCE_MS);
99
103
  };
100
104
  const processBatch = () => __awaiter(this, void 0, void 0, function* () {
105
+ var _a;
101
106
  if (closed || processing || pending.size === 0)
102
107
  return;
103
108
  processing = true;
@@ -175,13 +180,19 @@ function startWatcher(opts) {
175
180
  }
176
181
  }
177
182
  }
178
- // Flush to VectorDB
179
- if (deletes.length > 0) {
180
- yield vectorDb.deletePaths(deletes);
181
- }
183
+ // Flush to VectorDB: insert first, then delete old (preserving new)
184
+ const newIds = vectors.map((v) => v.id);
182
185
  if (vectors.length > 0) {
183
186
  yield vectorDb.insertBatch(vectors);
184
187
  }
188
+ if (deletes.length > 0) {
189
+ if (newIds.length > 0) {
190
+ yield vectorDb.deletePathsExcludingIds(deletes, newIds);
191
+ }
192
+ else {
193
+ yield vectorDb.deletePaths(deletes);
194
+ }
195
+ }
185
196
  // Update MetaCache
186
197
  for (const [p, entry] of metaUpdates) {
187
198
  metaCache.put(p, entry);
@@ -198,7 +209,7 @@ function startWatcher(opts) {
198
209
  try {
199
210
  const table = yield vectorDb.ensureTable();
200
211
  for (const id of changedIds) {
201
- const escaped = id.replace(/'/g, "''");
212
+ const escaped = (0, filter_builder_1.escapeSqlString)(id);
202
213
  const rows = yield table
203
214
  .query()
204
215
  .select(["id", "path", "content"])
@@ -222,7 +233,7 @@ function startWatcher(opts) {
222
233
  }
223
234
  }
224
235
  }
225
- catch (_a) {
236
+ catch (_b) {
226
237
  // Summarizer unavailable — skip silently
227
238
  }
228
239
  }
@@ -230,16 +241,38 @@ function startWatcher(opts) {
230
241
  const duration = Date.now() - start;
231
242
  onReindex === null || onReindex === void 0 ? void 0 : onReindex(reindexed, duration);
232
243
  }
244
+ consecutiveLockFailures = 0;
245
+ for (const absPath of batch.keys()) {
246
+ retryCount.delete(absPath);
247
+ }
233
248
  }
234
249
  catch (err) {
250
+ const isLockError = err instanceof Error && err.message.includes("lock already held");
251
+ if (isLockError) {
252
+ consecutiveLockFailures++;
253
+ }
235
254
  console.error("[watch] Batch processing failed:", err);
236
- // Re-queue failed items for retry
255
+ let dropped = 0;
237
256
  for (const [absPath, event] of batch) {
238
- if (!pending.has(absPath)) {
257
+ const count = ((_a = retryCount.get(absPath)) !== null && _a !== void 0 ? _a : 0) + 1;
258
+ if (count >= MAX_RETRIES) {
259
+ retryCount.delete(absPath);
260
+ dropped++;
261
+ }
262
+ else if (!pending.has(absPath)) {
239
263
  pending.set(absPath, event);
264
+ retryCount.set(absPath, count);
240
265
  }
241
266
  }
242
- scheduleBatch();
267
+ if (dropped > 0) {
268
+ console.warn(`[watch] Dropped ${dropped} file(s) after ${MAX_RETRIES} failed retries`);
269
+ }
270
+ if (pending.size > 0) {
271
+ const backoffMs = Math.min(DEBOUNCE_MS * Math.pow(2, consecutiveLockFailures), 30000);
272
+ if (debounceTimer)
273
+ clearTimeout(debounceTimer);
274
+ debounceTimer = setTimeout(() => processBatch(), backoffMs);
275
+ }
243
276
  }
244
277
  finally {
245
278
  processing = false;
@@ -18,6 +18,8 @@ class Searcher {
18
18
  constructor(db) {
19
19
  this.db = db;
20
20
  this.ftsIndexChecked = false;
21
+ this.ftsAvailable = false;
22
+ this.ftsLastCheckedAt = 0;
21
23
  }
22
24
  mapRecordToChunk(record, score) {
23
25
  var _a;
@@ -307,13 +309,19 @@ class Searcher {
307
309
  catch (_k) {
308
310
  return { data: [] };
309
311
  }
310
- // Ensure FTS index exists (lazy init on first search)
311
- if (!this.ftsIndexChecked) {
312
- this.ftsIndexChecked = true; // Set immediately to prevent retry spam
312
+ // Ensure FTS index exists (lazy init, retry periodically on failure)
313
+ const now = Date.now();
314
+ if (!this.ftsIndexChecked ||
315
+ (!this.ftsAvailable &&
316
+ now - this.ftsLastCheckedAt > Searcher.FTS_RETRY_INTERVAL_MS)) {
317
+ this.ftsIndexChecked = true;
318
+ this.ftsLastCheckedAt = now;
313
319
  try {
314
320
  yield this.db.createFTSIndex();
321
+ this.ftsAvailable = true;
315
322
  }
316
323
  catch (e) {
324
+ this.ftsAvailable = false;
317
325
  console.warn("[Searcher] Failed to ensure FTS index:", e);
318
326
  }
319
327
  }
@@ -323,16 +331,21 @@ class Searcher {
323
331
  }
324
332
  const vectorResults = (yield vectorQuery.toArray());
325
333
  let ftsResults = [];
326
- try {
327
- let ftsQuery = table.search(query).limit(PRE_RERANK_K);
328
- if (whereClause) {
329
- ftsQuery = ftsQuery.where(whereClause);
334
+ let ftsSearchFailed = false;
335
+ if (this.ftsAvailable) {
336
+ try {
337
+ let ftsQuery = table.search(query).limit(PRE_RERANK_K);
338
+ if (whereClause) {
339
+ ftsQuery = ftsQuery.where(whereClause);
340
+ }
341
+ ftsResults = (yield ftsQuery.toArray());
342
+ }
343
+ catch (e) {
344
+ ftsSearchFailed = true;
345
+ this.ftsAvailable = false;
346
+ const msg = e instanceof Error ? e.message : String(e);
347
+ console.warn(`[Searcher] FTS search failed (will retry later): ${msg}`);
330
348
  }
331
- ftsResults = (yield ftsQuery.toArray());
332
- }
333
- catch (e) {
334
- const msg = e instanceof Error ? e.message : String(e);
335
- console.warn(`[Searcher] FTS search failed: ${msg}`);
336
349
  }
337
350
  if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
338
351
  const err = new Error("Aborted");
@@ -453,8 +466,7 @@ class Searcher {
453
466
  const finalResults = diversified.map((item) => (Object.assign(Object.assign({}, item.record), { _score: item.score, vector: undefined, colbert: undefined })));
454
467
  // Item 12: Score Calibration
455
468
  const maxScore = finalResults.length > 0 ? finalResults[0]._score : 1.0;
456
- return {
457
- data: finalResults.map((r) => {
469
+ return Object.assign({ data: finalResults.map((r) => {
458
470
  const chunk = this.mapRecordToChunk(r, r._score || 0);
459
471
  // Normalize score relative to top result
460
472
  const normalized = maxScore > 0 ? r._score / maxScore : 0;
@@ -466,9 +478,15 @@ class Searcher {
466
478
  chunk.score = normalized;
467
479
  chunk.confidence = confidence;
468
480
  return chunk;
469
- }),
470
- };
481
+ }) }, (!this.ftsAvailable || ftsSearchFailed
482
+ ? {
483
+ warnings: [
484
+ "Full-text search unavailable — results may be less precise",
485
+ ],
486
+ }
487
+ : {}));
471
488
  });
472
489
  }
473
490
  }
474
491
  exports.Searcher = Searcher;
492
+ Searcher.FTS_RETRY_INTERVAL_MS = 5 * 60 * 1000;
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.getStoredSkeleton = getStoredSkeleton;
13
+ const filter_builder_1 = require("../utils/filter-builder");
13
14
  function getStoredSkeleton(db, filePath) {
14
15
  return __awaiter(this, void 0, void 0, function* () {
15
16
  try {
@@ -17,7 +18,7 @@ function getStoredSkeleton(db, filePath) {
17
18
  // LanceDB query
18
19
  const results = yield table
19
20
  .query()
20
- .where(`path = '${filePath.replace(/'/g, "''")}' AND is_anchor = true`)
21
+ .where(`path = '${(0, filter_builder_1.escapeSqlString)(filePath)}' AND is_anchor = true`)
21
22
  .limit(1)
22
23
  .toArray();
23
24
  if (results.length > 0) {
@@ -156,13 +156,16 @@ class MetaCache {
156
156
  });
157
157
  }
158
158
  close() {
159
- var _a;
160
- if (this.closed)
161
- return;
162
- this.closed = true;
163
- (_a = this.unregisterCleanup) === null || _a === void 0 ? void 0 : _a.call(this);
164
- this.unregisterCleanup = undefined;
165
- this.db.close();
159
+ return __awaiter(this, void 0, void 0, function* () {
160
+ var _a;
161
+ if (this.closed)
162
+ return;
163
+ this.closed = true;
164
+ (_a = this.unregisterCleanup) === null || _a === void 0 ? void 0 : _a.call(this);
165
+ this.unregisterCleanup = undefined;
166
+ yield this.db.flushed;
167
+ this.db.close();
168
+ });
166
169
  }
167
170
  }
168
171
  exports.MetaCache = MetaCache;
@@ -47,6 +47,7 @@ const fs = __importStar(require("node:fs"));
47
47
  const lancedb = __importStar(require("@lancedb/lancedb"));
48
48
  const apache_arrow_1 = require("apache-arrow");
49
49
  const config_1 = require("../../config");
50
+ const filter_builder_1 = require("../utils/filter-builder");
50
51
  const logger_1 = require("../utils/logger");
51
52
  const cleanup_1 = require("../utils/cleanup");
52
53
  const TABLE_NAME = "chunks";
@@ -292,7 +293,7 @@ class VectorDB {
292
293
  const rows = yield table
293
294
  .query()
294
295
  .select(["id"])
295
- .where(`path LIKE '${prefix.replace(/'/g, "''")}%'`)
296
+ .where(`path LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`)
296
297
  .limit(1)
297
298
  .toArray();
298
299
  return rows.length > 0;
@@ -324,7 +325,7 @@ class VectorDB {
324
325
  const batchSize = 500;
325
326
  for (let i = 0; i < unique.length; i += batchSize) {
326
327
  const slice = unique.slice(i, i + batchSize);
327
- const values = slice.map((p) => `'${p.replace(/'/g, "''")}'`).join(",");
328
+ const values = slice.map((p) => `'${(0, filter_builder_1.escapeSqlString)(p)}'`).join(",");
328
329
  yield table.delete(`path IN (${values})`);
329
330
  }
330
331
  });
@@ -336,7 +337,7 @@ class VectorDB {
336
337
  return;
337
338
  const table = yield this.ensureTable();
338
339
  for (let i = 0; i < ids.length; i++) {
339
- const escaped = ids[i].replace(/'/g, "''");
340
+ const escaped = (0, filter_builder_1.escapeSqlString)(ids[i]);
340
341
  yield table.update({
341
342
  where: `id = '${escaped}'`,
342
343
  values: { [field]: (_a = values[i]) !== null && _a !== void 0 ? _a : "" },
@@ -344,11 +345,29 @@ class VectorDB {
344
345
  }
345
346
  });
346
347
  }
348
+ deletePathsExcludingIds(paths, excludeIds) {
349
+ return __awaiter(this, void 0, void 0, function* () {
350
+ if (!paths.length)
351
+ return;
352
+ const table = yield this.ensureTable();
353
+ const unique = Array.from(new Set(paths));
354
+ const batchSize = 500;
355
+ const idExclusion = excludeIds.length > 0
356
+ ? ` AND id NOT IN (${excludeIds.map((id) => `'${(0, filter_builder_1.escapeSqlString)(id)}'`).join(",")})`
357
+ : "";
358
+ for (let i = 0; i < unique.length; i += batchSize) {
359
+ const slice = unique.slice(i, i + batchSize);
360
+ const values = slice
361
+ .map((p) => `'${(0, filter_builder_1.escapeSqlString)(p)}'`)
362
+ .join(",");
363
+ yield table.delete(`path IN (${values})${idExclusion}`);
364
+ }
365
+ });
366
+ }
347
367
  deletePathsWithPrefix(prefix) {
348
368
  return __awaiter(this, void 0, void 0, function* () {
349
369
  const table = yield this.ensureTable();
350
- const escaped = prefix.replace(/'/g, "''");
351
- yield table.delete(`path LIKE '${escaped}%'`);
370
+ yield table.delete(`path LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`);
352
371
  });
353
372
  }
354
373
  drop() {
@@ -114,6 +114,7 @@ class WorkerPool {
114
114
  this.nextId = 1;
115
115
  this.destroyed = false;
116
116
  this.destroyPromise = null;
117
+ this.consecutiveRespawns = 0;
117
118
  const resolved = resolveProcessWorker();
118
119
  this.modulePath = resolved.filename;
119
120
  this.execArgv = resolved.execArgv;
@@ -153,6 +154,11 @@ class WorkerPool {
153
154
  (0, logger_1.log)("pool", `Worker PID:${worker.child.pid} exited (code:${code} signal:${signal})`);
154
155
  this.workers = this.workers.filter((w) => w !== worker);
155
156
  if (!this.destroyed) {
157
+ this.consecutiveRespawns++;
158
+ if (this.consecutiveRespawns > WorkerPool.MAX_RESPAWNS) {
159
+ console.error(`[pool] Worker respawn limit reached (${WorkerPool.MAX_RESPAWNS}). Not spawning more workers.`);
160
+ return;
161
+ }
156
162
  this.spawnWorker();
157
163
  this.dispatch();
158
164
  }
@@ -183,6 +189,7 @@ class WorkerPool {
183
189
  task.resolve(result);
184
190
  }
185
191
  this.completeTask(task, worker);
192
+ this.consecutiveRespawns = 0;
186
193
  this.dispatch();
187
194
  };
188
195
  const onExit = (code, signal) => this.handleWorkerExit(worker, code, signal);
@@ -355,6 +362,7 @@ class WorkerPool {
355
362
  }
356
363
  }
357
364
  exports.WorkerPool = WorkerPool;
365
+ WorkerPool.MAX_RESPAWNS = 10;
358
366
  let singleton = null;
359
367
  function getWorkerPool() {
360
368
  if (!singleton) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.6.1",
3
+ "version": "0.6.4",
4
4
  "author": "Robert Owens <robowens@me.com>",
5
5
  "homepage": "https://github.com/reowens/grepmax",
6
6
  "bugs": {
@@ -75,6 +75,9 @@
75
75
  "format": "biome check --write .",
76
76
  "format:check": "biome check .",
77
77
  "lint": "biome lint .",
78
- "typecheck": "tsc --noEmit"
78
+ "typecheck": "tsc --noEmit",
79
+ "preversion": "pnpm test && pnpm typecheck",
80
+ "version": "bash scripts/sync-versions.sh && git add -A",
81
+ "postversion": "git push origin main --tags && gh release create v$npm_package_version --generate-notes --title v$npm_package_version && sleep 5 && gh run watch $(gh run list --workflow=release.yml --limit 1 --json databaseId --jq '.[0].databaseId') --exit-status && sleep 30 && npm cache clean --force && npm install -g grepmax@$npm_package_version"
79
82
  }
80
83
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.6.1",
3
+ "version": "0.6.4",
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",