grepmax 0.21.3 → 0.22.0
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 +2 -4
- package/dist/commands/dead.js +2 -1
- package/dist/commands/impact.js +3 -3
- package/dist/commands/mcp.js +14 -7
- package/dist/commands/peek.js +17 -4
- package/dist/commands/plugin.js +34 -24
- package/dist/commands/search-run.js +2 -0
- package/dist/commands/search.js +21 -11
- package/dist/commands/test-find.js +2 -2
- package/dist/commands/trace.js +13 -5
- package/dist/lib/daemon/ipc-handler.js +1 -0
- package/dist/lib/daemon/mlx-server-manager.js +52 -8
- package/dist/lib/daemon/process-manager.js +16 -17
- package/dist/lib/graph/graph-builder.js +92 -13
- package/dist/lib/graph/impact.js +66 -16
- package/dist/lib/index/batch-processor.js +45 -9
- package/dist/lib/llm/tools.js +3 -3
- package/dist/lib/search/searcher.js +26 -21
- package/dist/lib/store/vector-db.js +20 -6
- package/dist/lib/utils/daemon-client.js +35 -9
- package/dist/lib/workers/pool.js +62 -20
- package/package.json +8 -3
- package/plugins/grepmax/.claude-plugin/plugin.json +2 -8
- package/scripts/postinstall.js +8 -115
|
@@ -374,7 +374,7 @@ class Searcher {
|
|
|
374
374
|
}
|
|
375
375
|
search(query, top_k, _search_options, _filters, pathPrefix, intent, signal) {
|
|
376
376
|
return __awaiter(this, void 0, void 0, function* () {
|
|
377
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
377
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
378
378
|
const finalLimit = top_k !== null && top_k !== void 0 ? top_k : 10;
|
|
379
379
|
// ColBERT rerank is opt-in as of v0.17.1. On the 97-case eval it
|
|
380
380
|
// regresses MRR@10 by ~3% and doubles query latency; sweep across
|
|
@@ -413,7 +413,7 @@ class Searcher {
|
|
|
413
413
|
try {
|
|
414
414
|
table = yield this.db.ensureTable();
|
|
415
415
|
}
|
|
416
|
-
catch (
|
|
416
|
+
catch (_r) {
|
|
417
417
|
return { data: [] };
|
|
418
418
|
}
|
|
419
419
|
// Ensure FTS index exists (lazy init, retry periodically on failure)
|
|
@@ -496,7 +496,7 @@ class Searcher {
|
|
|
496
496
|
this.ftsAvailable = true;
|
|
497
497
|
console.warn("[Searcher] Rebuilt FTS index with position support — retry search");
|
|
498
498
|
}
|
|
499
|
-
catch (
|
|
499
|
+
catch (_s) { }
|
|
500
500
|
}
|
|
501
501
|
else {
|
|
502
502
|
console.warn(`[Searcher] FTS search failed (will retry later): ${msg}`);
|
|
@@ -670,6 +670,10 @@ class Searcher {
|
|
|
670
670
|
if (stage2Candidates.length === 0) {
|
|
671
671
|
return { data: [] };
|
|
672
672
|
}
|
|
673
|
+
const envMaxPerFile = Number.parseInt((_m = process.env.GMAX_MAX_PER_FILE) !== null && _m !== void 0 ? _m : "", 10);
|
|
674
|
+
const MAX_PER_FILE = Number.isFinite(envMaxPerFile) && envMaxPerFile > 0 ? envMaxPerFile : 3;
|
|
675
|
+
const displayWindow = Math.min(stage2Candidates.length, Math.max(finalLimit * MAX_PER_FILE, finalLimit, RERANK_TOP));
|
|
676
|
+
const displayCandidates = stage2Candidates.slice(0, displayWindow);
|
|
673
677
|
const rerankCandidates = stage2Candidates.slice(0, RERANK_TOP);
|
|
674
678
|
// Symbol-definition promotion (1/2): membership. For a bare-symbol query,
|
|
675
679
|
// ensure the chunk(s) that actually DEFINE the symbol reach the rerank set
|
|
@@ -681,6 +685,7 @@ class Searcher {
|
|
|
681
685
|
// (2/2) below then lets them win dedup over their own method-child chunks.
|
|
682
686
|
if (symbolQuery && rerankCandidates.length > 0) {
|
|
683
687
|
const present = new Set(rerankCandidates.map((d) => d.id).filter(Boolean));
|
|
688
|
+
const displayPresent = new Set(displayCandidates.map((d) => d.id).filter(Boolean));
|
|
684
689
|
const MAX_INJECT = 5;
|
|
685
690
|
let injected = 0;
|
|
686
691
|
for (const d of topCandidates) {
|
|
@@ -691,6 +696,10 @@ class Searcher {
|
|
|
691
696
|
if (readSymbolArray(d.defined_symbols).includes(symbolQuery)) {
|
|
692
697
|
rerankCandidates.push(d);
|
|
693
698
|
present.add(d.id);
|
|
699
|
+
if (!displayPresent.has(d.id)) {
|
|
700
|
+
displayCandidates.push(d);
|
|
701
|
+
displayPresent.add(d.id);
|
|
702
|
+
}
|
|
694
703
|
injected++;
|
|
695
704
|
}
|
|
696
705
|
}
|
|
@@ -719,8 +728,9 @@ class Searcher {
|
|
|
719
728
|
}
|
|
720
729
|
}
|
|
721
730
|
}
|
|
722
|
-
const
|
|
723
|
-
|
|
731
|
+
const rerankScoreByKey = new Map();
|
|
732
|
+
if (doRerank && rerankCandidates.length > 0) {
|
|
733
|
+
const scores = yield pool.rerank({
|
|
724
734
|
query: queryMatrixRaw,
|
|
725
735
|
docs: rerankCandidates.map((doc) => {
|
|
726
736
|
var _a;
|
|
@@ -733,28 +743,25 @@ class Searcher {
|
|
|
733
743
|
});
|
|
734
744
|
}),
|
|
735
745
|
colbertDim,
|
|
736
|
-
}, signal)
|
|
737
|
-
|
|
738
|
-
var _a;
|
|
739
|
-
// If rerank is disabled, fall back to fusion ordering with structural boost
|
|
746
|
+
}, signal);
|
|
747
|
+
for (const [idx, doc] of rerankCandidates.entries()) {
|
|
740
748
|
const key = doc.id || `${doc.path}:${doc.chunk_index}`;
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
});
|
|
749
|
+
rerankScoreByKey.set(key, (_o = scores === null || scores === void 0 ? void 0 : scores[idx]) !== null && _o !== void 0 ? _o : 0);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
745
752
|
// Symbol-definition promotion (2/2): score. Multiplicatively boost any
|
|
746
753
|
// candidate that defines the queried symbol so the definition chunk outranks
|
|
747
754
|
// its own method-child chunks (e.g. the `BeyondError` class chunk vs its
|
|
748
755
|
// constructor/toJSON, which otherwise score higher on the literal and evict
|
|
749
756
|
// the parent in overlap dedup). Multiplicative keeps it scale-invariant
|
|
750
757
|
// across the rerank-on (ColBERT maxsim) and rerank-off (fusion) score ranges.
|
|
751
|
-
const envDefBoost = Number.parseFloat((
|
|
758
|
+
const envDefBoost = Number.parseFloat((_p = process.env.GMAX_DEF_BOOST) !== null && _p !== void 0 ? _p : "");
|
|
752
759
|
const DEF_MATCH_BOOST = Number.isFinite(envDefBoost) && envDefBoost >= 1 ? envDefBoost : 5;
|
|
753
|
-
const scored =
|
|
760
|
+
const scored = displayCandidates.map((doc, idx) => {
|
|
754
761
|
var _a, _b;
|
|
755
|
-
const base = (_a = scores === null || scores === void 0 ? void 0 : scores[idx]) !== null && _a !== void 0 ? _a : 0;
|
|
756
762
|
const key = doc.id || `${doc.path}:${doc.chunk_index}`;
|
|
757
|
-
const fusedScore = (
|
|
763
|
+
const fusedScore = (_a = candidateScores.get(key)) !== null && _a !== void 0 ? _a : 0;
|
|
764
|
+
const base = (_b = rerankScoreByKey.get(key)) !== null && _b !== void 0 ? _b : (fusedScore || 1 / (idx + 1));
|
|
758
765
|
const blended = base + FUSED_WEIGHT * fusedScore;
|
|
759
766
|
let boosted = this.applyStructureBoost(doc, blended, searchIntent);
|
|
760
767
|
if (symbolQuery &&
|
|
@@ -782,7 +789,7 @@ class Searcher {
|
|
|
782
789
|
try {
|
|
783
790
|
const { scores: prScores, max: prMax } = yield (0, pagerank_1.loadOrComputePageRank)(this.db, pathPrefix);
|
|
784
791
|
if (prMax > 0) {
|
|
785
|
-
const envWeight = Number.parseFloat((
|
|
792
|
+
const envWeight = Number.parseFloat((_q = process.env.GMAX_PR_WEIGHT) !== null && _q !== void 0 ? _q : "");
|
|
786
793
|
const PR_WEIGHT = Number.isFinite(envWeight) && envWeight >= 0 ? envWeight : 0.05;
|
|
787
794
|
for (const item of scored) {
|
|
788
795
|
const raw = item.record
|
|
@@ -799,7 +806,7 @@ class Searcher {
|
|
|
799
806
|
defs = arr.filter((v) => typeof v === "string");
|
|
800
807
|
}
|
|
801
808
|
}
|
|
802
|
-
catch (
|
|
809
|
+
catch (_t) { }
|
|
803
810
|
}
|
|
804
811
|
const norm = (0, pagerank_1.pageRankBoostForSymbols)(defs, prScores, prMax);
|
|
805
812
|
item.score += PR_WEIGHT * norm;
|
|
@@ -817,8 +824,6 @@ class Searcher {
|
|
|
817
824
|
// Item 10: Per-file diversification
|
|
818
825
|
const seenFiles = new Map();
|
|
819
826
|
const diversified = [];
|
|
820
|
-
const envMaxPerFile = Number.parseInt((_p = process.env.GMAX_MAX_PER_FILE) !== null && _p !== void 0 ? _p : "", 10);
|
|
821
|
-
const MAX_PER_FILE = Number.isFinite(envMaxPerFile) && envMaxPerFile > 0 ? envMaxPerFile : 3;
|
|
822
827
|
for (const item of uniqueScored) {
|
|
823
828
|
const path = item.record.path || "";
|
|
824
829
|
const count = seenFiles.get(path) || 0;
|
|
@@ -68,6 +68,17 @@ function isLanceCorruptionError(err) {
|
|
|
68
68
|
const msg = err instanceof Error ? err.message : String(err);
|
|
69
69
|
return /Not found:.*\.lance(?:[^a-z]|$)/i.test(msg);
|
|
70
70
|
}
|
|
71
|
+
function isMissingTableError(err) {
|
|
72
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
73
|
+
const tableMissing = /table.*chunks.*(?:not found|does not exist)/i.test(msg) ||
|
|
74
|
+
/(?:not found|does not exist).*table.*chunks/i.test(msg) ||
|
|
75
|
+
/no such table.*chunks/i.test(msg);
|
|
76
|
+
if (tableMissing)
|
|
77
|
+
return true;
|
|
78
|
+
if (isLanceCorruptionError(err))
|
|
79
|
+
return false;
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
71
82
|
const TABLE_NAME = "chunks";
|
|
72
83
|
const MAINTENANCE_INTERVAL_MS = 5 * 60 * 1000;
|
|
73
84
|
class VectorDB {
|
|
@@ -389,21 +400,24 @@ class VectorDB {
|
|
|
389
400
|
ensureTable() {
|
|
390
401
|
return __awaiter(this, void 0, void 0, function* () {
|
|
391
402
|
const db = yield this.getDb();
|
|
403
|
+
let table;
|
|
392
404
|
try {
|
|
393
|
-
|
|
394
|
-
yield this.validateSchema(table);
|
|
395
|
-
yield this.evolveSchema(table);
|
|
396
|
-
return table;
|
|
405
|
+
table = yield db.openTable(TABLE_NAME);
|
|
397
406
|
}
|
|
398
|
-
catch (
|
|
407
|
+
catch (err) {
|
|
408
|
+
if (!isMissingTableError(err))
|
|
409
|
+
throw err;
|
|
399
410
|
(0, logger_1.log)("db", `Creating table (${this.vectorDim}d)`);
|
|
400
411
|
const schema = this.buildSchema();
|
|
401
|
-
|
|
412
|
+
table = yield db.createTable(TABLE_NAME, [this.seedRow()], {
|
|
402
413
|
schema,
|
|
403
414
|
});
|
|
404
415
|
yield table.delete('id = "seed"');
|
|
405
416
|
return table;
|
|
406
417
|
}
|
|
418
|
+
yield this.validateSchema(table);
|
|
419
|
+
yield this.evolveSchema(table);
|
|
420
|
+
return table;
|
|
407
421
|
});
|
|
408
422
|
}
|
|
409
423
|
insertBatch(records) {
|
|
@@ -47,6 +47,7 @@ exports.isDaemonRunning = isDaemonRunning;
|
|
|
47
47
|
exports.isDaemonHeartbeatFresh = isDaemonHeartbeatFresh;
|
|
48
48
|
exports.readDaemonPid = readDaemonPid;
|
|
49
49
|
exports.waitForProcessExit = waitForProcessExit;
|
|
50
|
+
exports.waitForDaemonDrain = waitForDaemonDrain;
|
|
50
51
|
exports.writeDrainingMarker = writeDrainingMarker;
|
|
51
52
|
exports.clearDrainingMarker = clearDrainingMarker;
|
|
52
53
|
exports.isDaemonDraining = isDaemonDraining;
|
|
@@ -56,6 +57,8 @@ const fs = __importStar(require("node:fs"));
|
|
|
56
57
|
const net = __importStar(require("node:net"));
|
|
57
58
|
const config_1 = require("../../config");
|
|
58
59
|
const DEFAULT_TIMEOUT_MS = 5000;
|
|
60
|
+
const DAEMON_READY_TIMEOUT_MS = 30000;
|
|
61
|
+
const DAEMON_READY_POLL_MS = 200;
|
|
59
62
|
// A live daemon refreshes daemon.lock mtime every 60s (HEARTBEAT_INTERVAL_MS).
|
|
60
63
|
// Treat mtime younger than 2.5x that as proof of life, even if a ping times
|
|
61
64
|
// out — a busy daemon with a blocked event loop can still be heartbeating.
|
|
@@ -126,6 +129,15 @@ function isDaemonRunning(opts) {
|
|
|
126
129
|
return resp.ok === true;
|
|
127
130
|
});
|
|
128
131
|
}
|
|
132
|
+
function isDaemonReady(opts) {
|
|
133
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
134
|
+
var _a;
|
|
135
|
+
const resp = yield sendDaemonCommand({ cmd: "ping" }, { timeoutMs: (_a = opts === null || opts === void 0 ? void 0 : opts.timeoutMs) !== null && _a !== void 0 ? _a : 2000 });
|
|
136
|
+
// Old daemons do not include `ready`; treat missing as ready so an upgraded
|
|
137
|
+
// CLI does not hang on a still-running daemon from the previous release.
|
|
138
|
+
return resp.ok === true && resp.ready !== false;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
129
141
|
/**
|
|
130
142
|
* Lock-file-based liveness probe. A running daemon refreshes daemon.lock's
|
|
131
143
|
* mtime every 60s via its heartbeat loop; a fresh mtime means the daemon is
|
|
@@ -194,6 +206,11 @@ function waitForProcessExit(pid, timeoutMs) {
|
|
|
194
206
|
// LLM/MLX teardown. Treat a draining marker as authoritative for this long; past
|
|
195
207
|
// it, assume the draining daemon wedged and let killStaleProcesses reclaim it.
|
|
196
208
|
const DRAIN_GRACE_MS = 90000;
|
|
209
|
+
function waitForDaemonDrain(pid_1) {
|
|
210
|
+
return __awaiter(this, arguments, void 0, function* (pid, timeoutMs = DRAIN_GRACE_MS) {
|
|
211
|
+
return waitForProcessExit(pid, timeoutMs);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
197
214
|
/**
|
|
198
215
|
* Record that this daemon (pid) has begun graceful shutdown, so a successor's
|
|
199
216
|
* killStaleProcesses() won't SIGKILL it mid-cleanup after it drops its
|
|
@@ -240,20 +257,23 @@ function isDaemonDraining(pid) {
|
|
|
240
257
|
}
|
|
241
258
|
}
|
|
242
259
|
/**
|
|
243
|
-
* Ensure the daemon is running — start it if needed,
|
|
260
|
+
* Ensure the daemon is running — start it if needed, then wait for resources.
|
|
244
261
|
* Returns true if daemon is ready, false if it couldn't be started.
|
|
245
262
|
*/
|
|
246
263
|
function ensureDaemonRunning() {
|
|
247
264
|
return __awaiter(this, void 0, void 0, function* () {
|
|
248
|
-
if (yield
|
|
265
|
+
if (yield isDaemonReady())
|
|
249
266
|
return true;
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
267
|
+
if (!(yield isDaemonRunning())) {
|
|
268
|
+
const { spawnDaemon } = yield Promise.resolve().then(() => __importStar(require("./daemon-launcher")));
|
|
269
|
+
const pid = spawnDaemon();
|
|
270
|
+
if (!pid)
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
const deadline = Date.now() + DAEMON_READY_TIMEOUT_MS;
|
|
274
|
+
while (Date.now() < deadline) {
|
|
275
|
+
yield new Promise((r) => setTimeout(r, DAEMON_READY_POLL_MS));
|
|
276
|
+
if (yield isDaemonReady())
|
|
257
277
|
return true;
|
|
258
278
|
}
|
|
259
279
|
return false;
|
|
@@ -315,6 +335,12 @@ function sendStreamingCommand(cmd, onProgress, opts) {
|
|
|
315
335
|
// (DB flush, compaction). Reset the watchdog; do not surface.
|
|
316
336
|
resetTimer();
|
|
317
337
|
}
|
|
338
|
+
else if (msg.ok === false) {
|
|
339
|
+
const error = typeof msg.error === "string"
|
|
340
|
+
? msg.error
|
|
341
|
+
: "daemon command failed";
|
|
342
|
+
finish(new Error(error));
|
|
343
|
+
}
|
|
318
344
|
}
|
|
319
345
|
catch (_a) {
|
|
320
346
|
console.warn("[daemon-client] Malformed response line:", line.slice(0, 200));
|
package/dist/lib/workers/pool.js
CHANGED
|
@@ -203,6 +203,7 @@ class WorkerPool {
|
|
|
203
203
|
this.destroyed = false;
|
|
204
204
|
this.destroyPromise = null;
|
|
205
205
|
this.consecutiveRespawns = 0;
|
|
206
|
+
this.respawnLimitReached = false;
|
|
206
207
|
this.idleReapInterval = null;
|
|
207
208
|
const resolved = resolveProcessWorker();
|
|
208
209
|
this.modulePath = resolved.filename;
|
|
@@ -246,6 +247,37 @@ class WorkerPool {
|
|
|
246
247
|
if (idx !== -1)
|
|
247
248
|
this.taskQueue.splice(idx, 1);
|
|
248
249
|
}
|
|
250
|
+
hasUnassignedTasks() {
|
|
251
|
+
const hasUnassigned = (queue) => queue.some((id) => {
|
|
252
|
+
const t = this.tasks.get(id);
|
|
253
|
+
return t && !t.worker;
|
|
254
|
+
});
|
|
255
|
+
return hasUnassigned(this.priorityQueue) || hasUnassigned(this.taskQueue);
|
|
256
|
+
}
|
|
257
|
+
rejectUnassignedTasks(message) {
|
|
258
|
+
for (const task of Array.from(this.tasks.values())) {
|
|
259
|
+
if (task.worker)
|
|
260
|
+
continue;
|
|
261
|
+
task.reject(new Error(message));
|
|
262
|
+
this.completeTask(task, null);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
maybeRespawn(reason, hasPendingTasks) {
|
|
266
|
+
if (this.destroyed || this.respawnLimitReached)
|
|
267
|
+
return false;
|
|
268
|
+
this.consecutiveRespawns++;
|
|
269
|
+
(0, logger_1.log)("pool", `respawn #${this.consecutiveRespawns} after ${reason} (workers=${this.workers.length} pending=${hasPendingTasks})`);
|
|
270
|
+
if (this.consecutiveRespawns > WorkerPool.MAX_RESPAWNS) {
|
|
271
|
+
this.respawnLimitReached = true;
|
|
272
|
+
const message = `Worker respawn limit reached (${WorkerPool.MAX_RESPAWNS}). Not spawning more workers.`;
|
|
273
|
+
console.error(`[pool] ${message}`);
|
|
274
|
+
if (this.workers.length === 0)
|
|
275
|
+
this.rejectUnassignedTasks(message);
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
this.spawnWorker();
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
249
281
|
completeTask(task, worker) {
|
|
250
282
|
this.clearTaskTimeout(task);
|
|
251
283
|
this.tasks.delete(task.id);
|
|
@@ -280,19 +312,9 @@ class WorkerPool {
|
|
|
280
312
|
this.workers = this.workers.filter((w) => w !== worker);
|
|
281
313
|
if (!this.destroyed) {
|
|
282
314
|
// Only respawn if we have no workers left or there are pending tasks
|
|
283
|
-
const
|
|
284
|
-
const t = this.tasks.get(id);
|
|
285
|
-
return t && !t.worker;
|
|
286
|
-
});
|
|
287
|
-
const hasPendingTasks = hasUnassigned(this.priorityQueue) || hasUnassigned(this.taskQueue);
|
|
315
|
+
const hasPendingTasks = this.hasUnassignedTasks();
|
|
288
316
|
if (this.workers.length === 0 || hasPendingTasks) {
|
|
289
|
-
this.
|
|
290
|
-
(0, logger_1.log)("pool", `respawn #${this.consecutiveRespawns} after exit (workers=${this.workers.length} pending=${hasPendingTasks})`);
|
|
291
|
-
if (this.consecutiveRespawns > WorkerPool.MAX_RESPAWNS) {
|
|
292
|
-
console.error(`[pool] Worker respawn limit reached (${WorkerPool.MAX_RESPAWNS}). Not spawning more workers.`);
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
this.spawnWorker();
|
|
317
|
+
this.maybeRespawn(reason, hasPendingTasks);
|
|
296
318
|
}
|
|
297
319
|
this.dispatch();
|
|
298
320
|
}
|
|
@@ -350,6 +372,7 @@ class WorkerPool {
|
|
|
350
372
|
}
|
|
351
373
|
this.completeTask(task, worker);
|
|
352
374
|
this.consecutiveRespawns = 0;
|
|
375
|
+
this.respawnLimitReached = false;
|
|
353
376
|
this.recycleIfBloated(worker);
|
|
354
377
|
this.dispatch();
|
|
355
378
|
};
|
|
@@ -452,7 +475,7 @@ class WorkerPool {
|
|
|
452
475
|
catch (_e) { }
|
|
453
476
|
this.workers = this.workers.filter((w) => w !== worker);
|
|
454
477
|
if (!this.destroyed) {
|
|
455
|
-
this.
|
|
478
|
+
this.maybeRespawn(`timeout (${reason})`, this.hasUnassignedTasks());
|
|
456
479
|
}
|
|
457
480
|
this.dispatch();
|
|
458
481
|
}
|
|
@@ -472,6 +495,12 @@ class WorkerPool {
|
|
|
472
495
|
return;
|
|
473
496
|
// Lazy spawn: if no idle worker and below max, spawn one
|
|
474
497
|
if (!idle && this.workers.length < this.maxWorkers) {
|
|
498
|
+
if (this.respawnLimitReached) {
|
|
499
|
+
if (this.workers.length === 0) {
|
|
500
|
+
this.rejectUnassignedTasks(`Worker respawn limit reached (${WorkerPool.MAX_RESPAWNS}). Not spawning more workers.`);
|
|
501
|
+
}
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
475
504
|
this.spawnWorker();
|
|
476
505
|
idle = this.workers[this.workers.length - 1];
|
|
477
506
|
}
|
|
@@ -691,22 +720,35 @@ class WorkerPool {
|
|
|
691
720
|
this.taskQueue = [];
|
|
692
721
|
this.priorityQueue = [];
|
|
693
722
|
const killPromises = this.workers.map((w) => new Promise((resolve) => {
|
|
723
|
+
var _a, _b;
|
|
724
|
+
let settled = false;
|
|
725
|
+
let force;
|
|
726
|
+
let fallback;
|
|
727
|
+
const cleanup = () => {
|
|
728
|
+
if (settled)
|
|
729
|
+
return;
|
|
730
|
+
settled = true;
|
|
731
|
+
if (force)
|
|
732
|
+
clearTimeout(force);
|
|
733
|
+
if (fallback)
|
|
734
|
+
clearTimeout(fallback);
|
|
735
|
+
resolve();
|
|
736
|
+
};
|
|
694
737
|
w.cleanedUp = true;
|
|
695
738
|
w.child.removeAllListeners("message");
|
|
696
739
|
w.child.removeAllListeners("exit");
|
|
697
740
|
w.child.removeAllListeners("error");
|
|
698
|
-
w.child.once("exit",
|
|
699
|
-
|
|
700
|
-
const force = setTimeout(() => {
|
|
741
|
+
w.child.once("exit", cleanup);
|
|
742
|
+
force = setTimeout(() => {
|
|
701
743
|
try {
|
|
702
744
|
w.child.kill("SIGKILL");
|
|
703
745
|
}
|
|
704
746
|
catch (_a) { }
|
|
705
747
|
}, FORCE_KILL_GRACE_MS);
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
748
|
+
(_a = force.unref) === null || _a === void 0 ? void 0 : _a.call(force);
|
|
749
|
+
fallback = setTimeout(cleanup, config_1.WORKER_TIMEOUT_MS);
|
|
750
|
+
(_b = fallback.unref) === null || _b === void 0 ? void 0 : _b.call(fallback);
|
|
751
|
+
w.child.kill("SIGTERM");
|
|
710
752
|
}));
|
|
711
753
|
this.destroyPromise = Promise.allSettled(killPromises).then(() => {
|
|
712
754
|
this.workers = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grepmax",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"author": "Robert Owens <78518764+reowens@users.noreply.github.com>",
|
|
5
5
|
"homepage": "https://github.com/reowens/grepmax",
|
|
6
6
|
"bugs": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"type": "git",
|
|
11
11
|
"url": "https://github.com/reowens/grepmax.git"
|
|
12
12
|
},
|
|
13
|
-
"main": "index.js",
|
|
13
|
+
"main": "dist/index.js",
|
|
14
14
|
"bin": {
|
|
15
15
|
"gmax": "dist/index.js"
|
|
16
16
|
},
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
21
|
"postinstall": "node scripts/postinstall.js",
|
|
22
|
-
"prebuild": "mkdir -p dist",
|
|
22
|
+
"prebuild": "rm -rf dist tsconfig.tsbuildinfo && mkdir -p dist",
|
|
23
23
|
"build": "tsc",
|
|
24
24
|
"postbuild": "chmod +x dist/index.js",
|
|
25
25
|
"dev": "npx tsc && node dist/index.js",
|
|
@@ -96,6 +96,11 @@
|
|
|
96
96
|
"web-tree-sitter": "^0.26.9",
|
|
97
97
|
"zod": "^4.4.3"
|
|
98
98
|
},
|
|
99
|
+
"pnpm": {
|
|
100
|
+
"overrides": {
|
|
101
|
+
"mathjs": "15.2.0"
|
|
102
|
+
}
|
|
103
|
+
},
|
|
99
104
|
"devDependencies": {
|
|
100
105
|
"@biomejs/biome": "2.4.10",
|
|
101
106
|
"@types/node": "^26.0.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grepmax",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
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",
|
|
@@ -10,11 +10,5 @@
|
|
|
10
10
|
"homepage": "https://github.com/reowens/grepmax",
|
|
11
11
|
"repository": "https://github.com/reowens/grepmax",
|
|
12
12
|
"license": "Apache-2.0",
|
|
13
|
-
"keywords": [
|
|
14
|
-
"search",
|
|
15
|
-
"semantic",
|
|
16
|
-
"grep",
|
|
17
|
-
"code-search",
|
|
18
|
-
"semantic-search"
|
|
19
|
-
]
|
|
13
|
+
"keywords": ["search", "semantic", "grep", "code-search", "semantic-search"]
|
|
20
14
|
}
|
package/scripts/postinstall.js
CHANGED
|
@@ -1,121 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Postinstall
|
|
4
|
-
*
|
|
5
|
-
* skills, hooks, and configs without manual re-installation.
|
|
3
|
+
* Postinstall intentionally does not modify user-home agent configuration.
|
|
4
|
+
* Users can install or update integrations explicitly with:
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* - OpenCode: re-run installer (regenerates tool shim + plugin)
|
|
10
|
-
* - Codex: re-run installer (updates AGENTS.md + MCP registration)
|
|
11
|
-
* - Factory Droid: re-run installer (updates skills + hooks)
|
|
6
|
+
* gmax plugin add
|
|
7
|
+
* gmax plugin update
|
|
12
8
|
*/
|
|
13
|
-
const fs = require("node:fs");
|
|
14
|
-
const path = require("node:path");
|
|
15
|
-
const os = require("node:os");
|
|
16
|
-
const { execSync } = require("node:child_process");
|
|
17
9
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
os.homedir(),
|
|
23
|
-
".claude",
|
|
24
|
-
"plugins",
|
|
25
|
-
"cache",
|
|
26
|
-
"grepmax",
|
|
27
|
-
"grepmax",
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
if (fs.existsSync(pluginCacheBase) && fs.existsSync(sourcePlugin)) {
|
|
31
|
-
let entries;
|
|
32
|
-
try {
|
|
33
|
-
entries = fs.readdirSync(pluginCacheBase, { withFileTypes: true });
|
|
34
|
-
} catch {
|
|
35
|
-
entries = [];
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const versionDirs = entries
|
|
39
|
-
.filter((e) => e.isDirectory())
|
|
40
|
-
.map((e) => e.name);
|
|
41
|
-
|
|
42
|
-
function copyRecursive(src, dest) {
|
|
43
|
-
if (!fs.existsSync(src)) return;
|
|
44
|
-
const stat = fs.statSync(src);
|
|
45
|
-
if (stat.isDirectory()) {
|
|
46
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
47
|
-
for (const entry of fs.readdirSync(src)) {
|
|
48
|
-
copyRecursive(path.join(src, entry), path.join(dest, entry));
|
|
49
|
-
}
|
|
50
|
-
} else {
|
|
51
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
52
|
-
fs.copyFileSync(src, dest);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
for (const ver of versionDirs) {
|
|
57
|
-
const destDir = path.join(pluginCacheBase, ver);
|
|
58
|
-
try {
|
|
59
|
-
copyRecursive(
|
|
60
|
-
path.join(sourcePlugin, "skills"),
|
|
61
|
-
path.join(destDir, "skills"),
|
|
62
|
-
);
|
|
63
|
-
copyRecursive(
|
|
64
|
-
path.join(sourcePlugin, "hooks"),
|
|
65
|
-
path.join(destDir, "hooks"),
|
|
66
|
-
);
|
|
67
|
-
const hooksJson = path.join(sourcePlugin, "hooks.json");
|
|
68
|
-
if (fs.existsSync(hooksJson)) {
|
|
69
|
-
fs.copyFileSync(hooksJson, path.join(destDir, "hooks.json"));
|
|
70
|
-
}
|
|
71
|
-
} catch {
|
|
72
|
-
// Best-effort
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// --- OpenCode: re-run installer if tool shim or plugin exists ---
|
|
78
|
-
const ocToolPath = path.join(
|
|
79
|
-
os.homedir(),
|
|
80
|
-
".config",
|
|
81
|
-
"opencode",
|
|
82
|
-
"tool",
|
|
83
|
-
"gmax.ts",
|
|
84
|
-
);
|
|
85
|
-
const ocPluginPath = path.join(
|
|
86
|
-
os.homedir(),
|
|
87
|
-
".config",
|
|
88
|
-
"opencode",
|
|
89
|
-
"plugins",
|
|
90
|
-
"gmax.ts",
|
|
91
|
-
);
|
|
92
|
-
if (fs.existsSync(ocToolPath) || fs.existsSync(ocPluginPath)) {
|
|
93
|
-
try {
|
|
94
|
-
execSync("gmax install-opencode", { stdio: "ignore", timeout: 10000 });
|
|
95
|
-
} catch {}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// --- Codex: re-run installer if AGENTS.md has gmax skill ---
|
|
99
|
-
const codexAgentsPath = path.join(os.homedir(), ".codex", "AGENTS.md");
|
|
100
|
-
if (fs.existsSync(codexAgentsPath)) {
|
|
101
|
-
try {
|
|
102
|
-
const content = fs.readFileSync(codexAgentsPath, "utf-8");
|
|
103
|
-
if (content.includes("gmax")) {
|
|
104
|
-
execSync("gmax install-codex", { stdio: "ignore", timeout: 10000 });
|
|
105
|
-
}
|
|
106
|
-
} catch {}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// --- Factory Droid: re-run installer if skill exists ---
|
|
110
|
-
const droidSkillPath = path.join(
|
|
111
|
-
os.homedir(),
|
|
112
|
-
".factory",
|
|
113
|
-
"skills",
|
|
114
|
-
"gmax",
|
|
115
|
-
"SKILL.md",
|
|
116
|
-
);
|
|
117
|
-
if (fs.existsSync(droidSkillPath)) {
|
|
118
|
-
try {
|
|
119
|
-
execSync("gmax install-droid", { stdio: "ignore", timeout: 10000 });
|
|
120
|
-
} catch {}
|
|
10
|
+
if (process.env.GMAX_POSTINSTALL_QUIET !== "1") {
|
|
11
|
+
console.log(
|
|
12
|
+
"gmax installed. To install or update editor plugins, run: gmax plugin update",
|
|
13
|
+
);
|
|
121
14
|
}
|