grepmax 0.21.2 → 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/config.js +6 -1
- 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 +111 -12
- package/dist/lib/graph/impact.js +66 -16
- package/dist/lib/index/batch-processor.js +45 -9
- package/dist/lib/index/chunker.js +30 -3
- package/dist/lib/llm/tools.js +3 -3
- package/dist/lib/search/searcher.js +26 -21
- package/dist/lib/store/vector-db.js +52 -23
- package/dist/lib/utils/daemon-client.js +35 -9
- package/dist/lib/workers/orchestrator.js +1 -0
- 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
package/dist/lib/llm/tools.js
CHANGED
|
@@ -293,15 +293,15 @@ function executeImpact(args, ctx) {
|
|
|
293
293
|
if (!target)
|
|
294
294
|
return "(error: missing target)";
|
|
295
295
|
const depth = clampDepth(args.depth);
|
|
296
|
-
const { symbols, resolvedAsFile } = yield (0, impact_1.resolveTargetSymbols)(target, ctx.vectorDb, ctx.projectRoot);
|
|
296
|
+
const { symbols, resolvedAsFile, symbolFamilies } = yield (0, impact_1.resolveTargetSymbols)(target, ctx.vectorDb, ctx.projectRoot);
|
|
297
297
|
if (symbols.length === 0)
|
|
298
298
|
return "(not found)";
|
|
299
299
|
const excludePaths = resolvedAsFile
|
|
300
300
|
? new Set([path.resolve(ctx.projectRoot, target)])
|
|
301
301
|
: undefined;
|
|
302
302
|
const [deps, tests] = yield Promise.all([
|
|
303
|
-
(0, impact_1.findDependents)(symbols, ctx.vectorDb, ctx.projectRoot, excludePaths),
|
|
304
|
-
(0, impact_1.findTests)(symbols, ctx.vectorDb, ctx.projectRoot, depth),
|
|
303
|
+
(0, impact_1.findDependents)(symbols, ctx.vectorDb, ctx.projectRoot, excludePaths, undefined, undefined, symbolFamilies),
|
|
304
|
+
(0, impact_1.findTests)(symbols, ctx.vectorDb, ctx.projectRoot, depth, undefined, symbolFamilies),
|
|
305
305
|
]);
|
|
306
306
|
if (deps.length === 0 && tests.length === 0)
|
|
307
307
|
return "(no impact detected)";
|
|
@@ -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 {
|
|
@@ -271,6 +282,7 @@ class VectorDB {
|
|
|
271
282
|
defined_symbols: [],
|
|
272
283
|
referenced_symbols: [],
|
|
273
284
|
type_referenced_symbols: [],
|
|
285
|
+
member_referenced_symbols: [],
|
|
274
286
|
imports: [],
|
|
275
287
|
exports: [],
|
|
276
288
|
role: "",
|
|
@@ -340,6 +352,7 @@ class VectorDB {
|
|
|
340
352
|
new apache_arrow_1.Field("defined_symbols", new apache_arrow_1.List(new apache_arrow_1.Field("item", new apache_arrow_1.Utf8(), true)), true),
|
|
341
353
|
new apache_arrow_1.Field("referenced_symbols", new apache_arrow_1.List(new apache_arrow_1.Field("item", new apache_arrow_1.Utf8(), true)), true),
|
|
342
354
|
new apache_arrow_1.Field("type_referenced_symbols", new apache_arrow_1.List(new apache_arrow_1.Field("item", new apache_arrow_1.Utf8(), true)), true),
|
|
355
|
+
new apache_arrow_1.Field("member_referenced_symbols", new apache_arrow_1.List(new apache_arrow_1.Field("item", new apache_arrow_1.Utf8(), true)), true),
|
|
343
356
|
new apache_arrow_1.Field("imports", new apache_arrow_1.List(new apache_arrow_1.Field("item", new apache_arrow_1.Utf8(), true)), true),
|
|
344
357
|
new apache_arrow_1.Field("exports", new apache_arrow_1.List(new apache_arrow_1.Field("item", new apache_arrow_1.Utf8(), true)), true),
|
|
345
358
|
new apache_arrow_1.Field("role", new apache_arrow_1.Utf8(), true),
|
|
@@ -360,42 +373,56 @@ class VectorDB {
|
|
|
360
373
|
return __awaiter(this, void 0, void 0, function* () {
|
|
361
374
|
const schema = yield table.schema();
|
|
362
375
|
const fields = new Set(schema.fields.map((f) => f.name));
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
376
|
+
// Additive list columns that older tables may predate. Each is checked and
|
|
377
|
+
// added INDEPENDENTLY: a single early-return on the first existing column
|
|
378
|
+
// (as this used to do) would permanently strand every table that already has
|
|
379
|
+
// `type_referenced_symbols` — i.e. all of them — without ever gaining a newer
|
|
380
|
+
// column like `member_referenced_symbols`.
|
|
381
|
+
const additiveListColumns = [
|
|
382
|
+
"type_referenced_symbols",
|
|
383
|
+
"member_referenced_symbols",
|
|
384
|
+
];
|
|
385
|
+
for (const col of additiveListColumns) {
|
|
386
|
+
if (fields.has(col))
|
|
387
|
+
continue;
|
|
388
|
+
try {
|
|
389
|
+
yield table.addColumns(new apache_arrow_1.Field(col, new apache_arrow_1.List(new apache_arrow_1.Field("item", new apache_arrow_1.Utf8(), true)), true));
|
|
390
|
+
(0, logger_1.log)("db", `Added ${col} column to existing table`);
|
|
391
|
+
}
|
|
392
|
+
catch (err) {
|
|
393
|
+
// Lost a race with another writer that already added it, or a transient
|
|
394
|
+
// commit conflict — the next ensureTable() re-checks and no-ops.
|
|
395
|
+
(0, logger_1.debug)("vectordb", `evolveSchema ${col} skipped: ${err.message}`);
|
|
396
|
+
}
|
|
373
397
|
}
|
|
374
398
|
});
|
|
375
399
|
}
|
|
376
400
|
ensureTable() {
|
|
377
401
|
return __awaiter(this, void 0, void 0, function* () {
|
|
378
402
|
const db = yield this.getDb();
|
|
403
|
+
let table;
|
|
379
404
|
try {
|
|
380
|
-
|
|
381
|
-
yield this.validateSchema(table);
|
|
382
|
-
yield this.evolveSchema(table);
|
|
383
|
-
return table;
|
|
405
|
+
table = yield db.openTable(TABLE_NAME);
|
|
384
406
|
}
|
|
385
|
-
catch (
|
|
407
|
+
catch (err) {
|
|
408
|
+
if (!isMissingTableError(err))
|
|
409
|
+
throw err;
|
|
386
410
|
(0, logger_1.log)("db", `Creating table (${this.vectorDim}d)`);
|
|
387
411
|
const schema = this.buildSchema();
|
|
388
|
-
|
|
412
|
+
table = yield db.createTable(TABLE_NAME, [this.seedRow()], {
|
|
389
413
|
schema,
|
|
390
414
|
});
|
|
391
415
|
yield table.delete('id = "seed"');
|
|
392
416
|
return table;
|
|
393
417
|
}
|
|
418
|
+
yield this.validateSchema(table);
|
|
419
|
+
yield this.evolveSchema(table);
|
|
420
|
+
return table;
|
|
394
421
|
});
|
|
395
422
|
}
|
|
396
423
|
insertBatch(records) {
|
|
397
424
|
return __awaiter(this, void 0, void 0, function* () {
|
|
398
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
425
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
|
|
399
426
|
if (!records.length)
|
|
400
427
|
return;
|
|
401
428
|
this.ensureDiskOk();
|
|
@@ -473,12 +500,14 @@ class VectorDB {
|
|
|
473
500
|
rec.defined_symbols = (_g = rec.defined_symbols) !== null && _g !== void 0 ? _g : [];
|
|
474
501
|
rec.referenced_symbols = (_h = rec.referenced_symbols) !== null && _h !== void 0 ? _h : [];
|
|
475
502
|
rec.type_referenced_symbols = (_j = rec.type_referenced_symbols) !== null && _j !== void 0 ? _j : [];
|
|
476
|
-
rec.
|
|
477
|
-
|
|
478
|
-
rec.
|
|
479
|
-
rec.
|
|
480
|
-
rec.
|
|
481
|
-
rec.
|
|
503
|
+
rec.member_referenced_symbols =
|
|
504
|
+
(_k = rec.member_referenced_symbols) !== null && _k !== void 0 ? _k : [];
|
|
505
|
+
rec.imports = (_l = rec.imports) !== null && _l !== void 0 ? _l : [];
|
|
506
|
+
rec.exports = (_m = rec.exports) !== null && _m !== void 0 ? _m : [];
|
|
507
|
+
rec.role = (_o = rec.role) !== null && _o !== void 0 ? _o : "";
|
|
508
|
+
rec.parent_symbol = (_p = rec.parent_symbol) !== null && _p !== void 0 ? _p : "";
|
|
509
|
+
rec.file_skeleton = (_q = rec.file_skeleton) !== null && _q !== void 0 ? _q : "";
|
|
510
|
+
rec.summary = (_r = rec.summary) !== null && _r !== void 0 ? _r : null;
|
|
482
511
|
}
|
|
483
512
|
try {
|
|
484
513
|
yield this.withWriteGate(() => table.add(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));
|
|
@@ -227,6 +227,7 @@ class WorkerOrchestrator {
|
|
|
227
227
|
defined_symbols: chunk.definedSymbols,
|
|
228
228
|
referenced_symbols: chunk.referencedSymbols,
|
|
229
229
|
type_referenced_symbols: chunk.typeReferencedSymbols,
|
|
230
|
+
member_referenced_symbols: chunk.memberReferencedSymbols,
|
|
230
231
|
role: chunk.role,
|
|
231
232
|
parent_symbol: chunk.parentSymbol,
|
|
232
233
|
file_skeleton: chunk.isAnchor ? skeleton : undefined,
|
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
|
}
|