grepmax 0.6.1 → 0.6.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.
- package/dist/commands/index.js +17 -0
- package/dist/commands/mcp.js +6 -1
- package/dist/commands/search.js +6 -1
- package/dist/commands/serve.js +1 -1
- package/dist/commands/watch.js +1 -1
- package/dist/lib/index/syncer.js +13 -12
- package/dist/lib/index/watcher.js +40 -8
- package/dist/lib/search/searcher.js +34 -16
- package/dist/lib/store/meta-cache.js +10 -7
- package/dist/lib/store/vector-db.js +19 -0
- package/package.json +5 -2
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
package/dist/commands/index.js
CHANGED
|
@@ -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();
|
package/dist/commands/mcp.js
CHANGED
|
@@ -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");
|
|
@@ -434,7 +435,11 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
434
435
|
return true;
|
|
435
436
|
});
|
|
436
437
|
}
|
|
437
|
-
|
|
438
|
+
const output = results.map((r) => r.text).join("\n\n");
|
|
439
|
+
if ((_a = result.warnings) === null || _a === void 0 ? void 0 : _a.length) {
|
|
440
|
+
return ok(`${result.warnings.join("\n")}\n\n${output}`);
|
|
441
|
+
}
|
|
442
|
+
return ok(output);
|
|
438
443
|
}
|
|
439
444
|
catch (e) {
|
|
440
445
|
const msg = e instanceof Error ? e.message : String(e);
|
package/dist/commands/search.js
CHANGED
|
@@ -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);
|
package/dist/commands/serve.js
CHANGED
package/dist/commands/watch.js
CHANGED
|
@@ -154,7 +154,7 @@ exports.watch = new commander_1.Command("watch")
|
|
|
154
154
|
yield watcher.close();
|
|
155
155
|
}
|
|
156
156
|
catch (_a) { }
|
|
157
|
-
metaCache.close();
|
|
157
|
+
yield metaCache.close();
|
|
158
158
|
yield vectorDb.close();
|
|
159
159
|
(0, watcher_registry_1.unregisterWatcher)(process.pid);
|
|
160
160
|
yield (0, exit_1.gracefulExit)();
|
package/dist/lib/index/syncer.js
CHANGED
|
@@ -174,7 +174,7 @@ function createNoopMetaCache() {
|
|
|
174
174
|
delete: (filePath) => {
|
|
175
175
|
store.delete(filePath);
|
|
176
176
|
},
|
|
177
|
-
close: () => { },
|
|
177
|
+
close: () => __awaiter(this, void 0, void 0, function* () { }),
|
|
178
178
|
};
|
|
179
179
|
}
|
|
180
180
|
function initialSync(options) {
|
|
@@ -457,7 +457,17 @@ function initialSync(options) {
|
|
|
457
457
|
? flushError
|
|
458
458
|
: new Error(String(flushError));
|
|
459
459
|
}
|
|
460
|
-
|
|
460
|
+
// Stale cleanup: only remove paths scoped to this project's root
|
|
461
|
+
const stale = Array.from(cachedPaths).filter((p) => !seenPaths.has(p));
|
|
462
|
+
if (!dryRun && stale.length > 0 && !shouldSkipCleanup) {
|
|
463
|
+
(0, logger_1.log)("index", `Stale cleanup: ${stale.length} paths`);
|
|
464
|
+
yield vectorDb.deletePaths(stale);
|
|
465
|
+
stale.forEach((p) => {
|
|
466
|
+
metaCache.delete(p);
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
// Only rebuild FTS index if data actually changed
|
|
470
|
+
if (!dryRun && (indexed > 0 || stale.length > 0)) {
|
|
461
471
|
const ftsTimer = (0, logger_1.timer)("index", "FTS");
|
|
462
472
|
onProgress === null || onProgress === void 0 ? void 0 : onProgress({
|
|
463
473
|
processed,
|
|
@@ -468,15 +478,6 @@ function initialSync(options) {
|
|
|
468
478
|
yield vectorDb.createFTSIndex();
|
|
469
479
|
ftsTimer();
|
|
470
480
|
}
|
|
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
481
|
syncTimer();
|
|
481
482
|
// Write model config so future runs can detect model changes
|
|
482
483
|
if (!dryRun) {
|
|
@@ -501,7 +502,7 @@ function initialSync(options) {
|
|
|
501
502
|
if (lock) {
|
|
502
503
|
yield lock.release();
|
|
503
504
|
}
|
|
504
|
-
metaCache === null || metaCache === void 0 ? void 0 : metaCache.close();
|
|
505
|
+
yield (metaCache === null || metaCache === void 0 ? void 0 : metaCache.close());
|
|
505
506
|
yield vectorDb.close();
|
|
506
507
|
}
|
|
507
508
|
});
|
|
@@ -75,9 +75,12 @@ const FTS_REBUILD_INTERVAL_MS = 5 * 60 * 1000;
|
|
|
75
75
|
function startWatcher(opts) {
|
|
76
76
|
const { projectRoot, vectorDb, metaCache, dataDir, onReindex } = opts;
|
|
77
77
|
const pending = new Map();
|
|
78
|
+
const retryCount = new Map();
|
|
78
79
|
let debounceTimer = null;
|
|
79
80
|
let processing = false;
|
|
80
81
|
let closed = false;
|
|
82
|
+
let consecutiveLockFailures = 0;
|
|
83
|
+
const MAX_RETRIES = 5;
|
|
81
84
|
const watcher = (0, chokidar_1.watch)(projectRoot, {
|
|
82
85
|
ignored: exports.WATCHER_IGNORE_PATTERNS,
|
|
83
86
|
ignoreInitial: true,
|
|
@@ -98,6 +101,7 @@ function startWatcher(opts) {
|
|
|
98
101
|
debounceTimer = setTimeout(() => processBatch(), DEBOUNCE_MS);
|
|
99
102
|
};
|
|
100
103
|
const processBatch = () => __awaiter(this, void 0, void 0, function* () {
|
|
104
|
+
var _a;
|
|
101
105
|
if (closed || processing || pending.size === 0)
|
|
102
106
|
return;
|
|
103
107
|
processing = true;
|
|
@@ -175,13 +179,19 @@ function startWatcher(opts) {
|
|
|
175
179
|
}
|
|
176
180
|
}
|
|
177
181
|
}
|
|
178
|
-
// Flush to VectorDB
|
|
179
|
-
|
|
180
|
-
yield vectorDb.deletePaths(deletes);
|
|
181
|
-
}
|
|
182
|
+
// Flush to VectorDB: insert first, then delete old (preserving new)
|
|
183
|
+
const newIds = vectors.map((v) => v.id);
|
|
182
184
|
if (vectors.length > 0) {
|
|
183
185
|
yield vectorDb.insertBatch(vectors);
|
|
184
186
|
}
|
|
187
|
+
if (deletes.length > 0) {
|
|
188
|
+
if (newIds.length > 0) {
|
|
189
|
+
yield vectorDb.deletePathsExcludingIds(deletes, newIds);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
yield vectorDb.deletePaths(deletes);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
185
195
|
// Update MetaCache
|
|
186
196
|
for (const [p, entry] of metaUpdates) {
|
|
187
197
|
metaCache.put(p, entry);
|
|
@@ -222,7 +232,7 @@ function startWatcher(opts) {
|
|
|
222
232
|
}
|
|
223
233
|
}
|
|
224
234
|
}
|
|
225
|
-
catch (
|
|
235
|
+
catch (_b) {
|
|
226
236
|
// Summarizer unavailable — skip silently
|
|
227
237
|
}
|
|
228
238
|
}
|
|
@@ -230,16 +240,38 @@ function startWatcher(opts) {
|
|
|
230
240
|
const duration = Date.now() - start;
|
|
231
241
|
onReindex === null || onReindex === void 0 ? void 0 : onReindex(reindexed, duration);
|
|
232
242
|
}
|
|
243
|
+
consecutiveLockFailures = 0;
|
|
244
|
+
for (const absPath of batch.keys()) {
|
|
245
|
+
retryCount.delete(absPath);
|
|
246
|
+
}
|
|
233
247
|
}
|
|
234
248
|
catch (err) {
|
|
249
|
+
const isLockError = err instanceof Error && err.message.includes("lock already held");
|
|
250
|
+
if (isLockError) {
|
|
251
|
+
consecutiveLockFailures++;
|
|
252
|
+
}
|
|
235
253
|
console.error("[watch] Batch processing failed:", err);
|
|
236
|
-
|
|
254
|
+
let dropped = 0;
|
|
237
255
|
for (const [absPath, event] of batch) {
|
|
238
|
-
|
|
256
|
+
const count = ((_a = retryCount.get(absPath)) !== null && _a !== void 0 ? _a : 0) + 1;
|
|
257
|
+
if (count >= MAX_RETRIES) {
|
|
258
|
+
retryCount.delete(absPath);
|
|
259
|
+
dropped++;
|
|
260
|
+
}
|
|
261
|
+
else if (!pending.has(absPath)) {
|
|
239
262
|
pending.set(absPath, event);
|
|
263
|
+
retryCount.set(absPath, count);
|
|
240
264
|
}
|
|
241
265
|
}
|
|
242
|
-
|
|
266
|
+
if (dropped > 0) {
|
|
267
|
+
console.warn(`[watch] Dropped ${dropped} file(s) after ${MAX_RETRIES} failed retries`);
|
|
268
|
+
}
|
|
269
|
+
if (pending.size > 0) {
|
|
270
|
+
const backoffMs = Math.min(DEBOUNCE_MS * Math.pow(2, consecutiveLockFailures), 30000);
|
|
271
|
+
if (debounceTimer)
|
|
272
|
+
clearTimeout(debounceTimer);
|
|
273
|
+
debounceTimer = setTimeout(() => processBatch(), backoffMs);
|
|
274
|
+
}
|
|
243
275
|
}
|
|
244
276
|
finally {
|
|
245
277
|
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
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
ftsQuery =
|
|
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;
|
|
@@ -156,13 +156,16 @@ class MetaCache {
|
|
|
156
156
|
});
|
|
157
157
|
}
|
|
158
158
|
close() {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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;
|
|
@@ -344,6 +344,25 @@ class VectorDB {
|
|
|
344
344
|
}
|
|
345
345
|
});
|
|
346
346
|
}
|
|
347
|
+
deletePathsExcludingIds(paths, excludeIds) {
|
|
348
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
349
|
+
if (!paths.length)
|
|
350
|
+
return;
|
|
351
|
+
const table = yield this.ensureTable();
|
|
352
|
+
const unique = Array.from(new Set(paths));
|
|
353
|
+
const batchSize = 500;
|
|
354
|
+
const idExclusion = excludeIds.length > 0
|
|
355
|
+
? ` AND id NOT IN (${excludeIds.map((id) => `'${id.replace(/'/g, "''")}'`).join(",")})`
|
|
356
|
+
: "";
|
|
357
|
+
for (let i = 0; i < unique.length; i += batchSize) {
|
|
358
|
+
const slice = unique.slice(i, i + batchSize);
|
|
359
|
+
const values = slice
|
|
360
|
+
.map((p) => `'${p.replace(/'/g, "''")}'`)
|
|
361
|
+
.join(",");
|
|
362
|
+
yield table.delete(`path IN (${values})${idExclusion}`);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
}
|
|
347
366
|
deletePathsWithPrefix(prefix) {
|
|
348
367
|
return __awaiter(this, void 0, void 0, function* () {
|
|
349
368
|
const table = yield this.ensureTable();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grepmax",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
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
|
}
|