grepmax 0.12.13 → 0.13.1
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/add.js +65 -43
- package/dist/commands/index.js +93 -62
- package/dist/commands/mcp.js +1 -0
- package/dist/commands/remove.js +19 -19
- package/dist/commands/summarize.js +52 -18
- package/dist/lib/daemon/daemon.js +214 -4
- package/dist/lib/daemon/ipc-handler.js +61 -1
- package/dist/lib/index/batch-processor.js +81 -97
- package/dist/lib/index/syncer.js +37 -27
- package/dist/lib/store/vector-db.js +11 -0
- package/dist/lib/utils/daemon-client.js +89 -0
- package/mlx-embed-server/uv.lock +50 -0
- package/package.json +2 -2
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
|
@@ -53,6 +53,7 @@ const watcher = __importStar(require("@parcel/watcher"));
|
|
|
53
53
|
const proper_lockfile_1 = __importDefault(require("proper-lockfile"));
|
|
54
54
|
const config_1 = require("../../config");
|
|
55
55
|
const batch_processor_1 = require("../index/batch-processor");
|
|
56
|
+
const syncer_1 = require("../index/syncer");
|
|
56
57
|
const watcher_1 = require("../index/watcher");
|
|
57
58
|
const meta_cache_1 = require("../store/meta-cache");
|
|
58
59
|
const vector_db_1 = require("../store/vector-db");
|
|
@@ -76,6 +77,7 @@ class Daemon {
|
|
|
76
77
|
this.idleInterval = null;
|
|
77
78
|
this.shuttingDown = false;
|
|
78
79
|
this.pendingOps = new Set();
|
|
80
|
+
this.projectLocks = new Map();
|
|
79
81
|
}
|
|
80
82
|
start() {
|
|
81
83
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -168,9 +170,12 @@ class Daemon {
|
|
|
168
170
|
conn.end();
|
|
169
171
|
return;
|
|
170
172
|
}
|
|
171
|
-
(0, ipc_handler_1.handleCommand)(this, cmd).then((resp) => {
|
|
172
|
-
|
|
173
|
-
|
|
173
|
+
(0, ipc_handler_1.handleCommand)(this, cmd, conn).then((resp) => {
|
|
174
|
+
// null means the handler is managing the connection (streaming)
|
|
175
|
+
if (resp !== null) {
|
|
176
|
+
conn.write(`${JSON.stringify(resp)}\n`);
|
|
177
|
+
conn.end();
|
|
178
|
+
}
|
|
174
179
|
});
|
|
175
180
|
});
|
|
176
181
|
conn.on("error", () => { });
|
|
@@ -207,7 +212,6 @@ class Daemon {
|
|
|
207
212
|
projectRoot: root,
|
|
208
213
|
vectorDb: this.vectorDb,
|
|
209
214
|
metaCache: this.metaCache,
|
|
210
|
-
dataDir: config_1.PATHS.globalRoot,
|
|
211
215
|
onReindex: (files, ms) => {
|
|
212
216
|
console.log(`[daemon:${path.basename(root)}] Reindexed ${files} file${files !== 1 ? "s" : ""} (${(ms / 1000).toFixed(1)}s)`);
|
|
213
217
|
// Update project registry so gmax status shows fresh data
|
|
@@ -286,6 +290,212 @@ class Daemon {
|
|
|
286
290
|
uptime() {
|
|
287
291
|
return Math.floor((Date.now() - this.startTime) / 1000);
|
|
288
292
|
}
|
|
293
|
+
/** Reset idle timer — call during long-running operations. */
|
|
294
|
+
resetActivity() {
|
|
295
|
+
this.lastActivity = Date.now();
|
|
296
|
+
}
|
|
297
|
+
// --- Per-project operation serialization ---
|
|
298
|
+
withProjectLock(root, fn) {
|
|
299
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
300
|
+
var _a;
|
|
301
|
+
const prev = (_a = this.projectLocks.get(root)) !== null && _a !== void 0 ? _a : Promise.resolve();
|
|
302
|
+
let release;
|
|
303
|
+
const next = new Promise((r) => { release = r; });
|
|
304
|
+
this.projectLocks.set(root, next);
|
|
305
|
+
yield prev;
|
|
306
|
+
try {
|
|
307
|
+
return yield fn();
|
|
308
|
+
}
|
|
309
|
+
finally {
|
|
310
|
+
release();
|
|
311
|
+
if (this.projectLocks.get(root) === next) {
|
|
312
|
+
this.projectLocks.delete(root);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
// --- Streaming write operations (IPC) ---
|
|
318
|
+
addProject(root, conn) {
|
|
319
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
320
|
+
yield this.withProjectLock(root, () => __awaiter(this, void 0, void 0, function* () {
|
|
321
|
+
var _a;
|
|
322
|
+
if (!this.vectorDb || !this.metaCache) {
|
|
323
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: "daemon resources not ready" });
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const ac = new AbortController();
|
|
327
|
+
conn.on("close", () => ac.abort());
|
|
328
|
+
this.vectorDb.pauseMaintenanceLoop();
|
|
329
|
+
let lastProgressTime = 0;
|
|
330
|
+
try {
|
|
331
|
+
const result = yield (0, syncer_1.initialSync)({
|
|
332
|
+
projectRoot: root,
|
|
333
|
+
vectorDb: this.vectorDb,
|
|
334
|
+
metaCache: this.metaCache,
|
|
335
|
+
signal: ac.signal,
|
|
336
|
+
onProgress: (info) => {
|
|
337
|
+
this.resetActivity();
|
|
338
|
+
const now = Date.now();
|
|
339
|
+
if (now - lastProgressTime < 100)
|
|
340
|
+
return;
|
|
341
|
+
lastProgressTime = now;
|
|
342
|
+
(0, ipc_handler_1.writeProgress)(conn, {
|
|
343
|
+
processed: info.processed,
|
|
344
|
+
indexed: info.indexed,
|
|
345
|
+
total: info.total,
|
|
346
|
+
filePath: info.filePath,
|
|
347
|
+
});
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
yield this.watchProject(root);
|
|
351
|
+
(0, ipc_handler_1.writeDone)(conn, {
|
|
352
|
+
ok: true,
|
|
353
|
+
processed: result.processed,
|
|
354
|
+
indexed: result.indexed,
|
|
355
|
+
total: result.total,
|
|
356
|
+
failedFiles: result.failedFiles,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
catch (err) {
|
|
360
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
361
|
+
console.error(`[daemon] addProject failed for ${path.basename(root)}:`, msg);
|
|
362
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: msg });
|
|
363
|
+
}
|
|
364
|
+
finally {
|
|
365
|
+
(_a = this.vectorDb) === null || _a === void 0 ? void 0 : _a.resumeMaintenanceLoop();
|
|
366
|
+
}
|
|
367
|
+
}));
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
indexProject(root, conn, opts) {
|
|
371
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
372
|
+
yield this.withProjectLock(root, () => __awaiter(this, void 0, void 0, function* () {
|
|
373
|
+
var _a;
|
|
374
|
+
if (!this.vectorDb || !this.metaCache) {
|
|
375
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: "daemon resources not ready" });
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
// Pause the project's batch processor during full index
|
|
379
|
+
const processor = this.processors.get(root);
|
|
380
|
+
if (processor) {
|
|
381
|
+
yield processor.close();
|
|
382
|
+
this.processors.delete(root);
|
|
383
|
+
}
|
|
384
|
+
const sub = this.subscriptions.get(root);
|
|
385
|
+
if (sub) {
|
|
386
|
+
yield sub.unsubscribe();
|
|
387
|
+
this.subscriptions.delete(root);
|
|
388
|
+
}
|
|
389
|
+
const ac = new AbortController();
|
|
390
|
+
conn.on("close", () => ac.abort());
|
|
391
|
+
this.vectorDb.pauseMaintenanceLoop();
|
|
392
|
+
let lastProgressTime = 0;
|
|
393
|
+
try {
|
|
394
|
+
const result = yield (0, syncer_1.initialSync)({
|
|
395
|
+
projectRoot: root,
|
|
396
|
+
reset: opts.reset,
|
|
397
|
+
dryRun: opts.dryRun,
|
|
398
|
+
vectorDb: this.vectorDb,
|
|
399
|
+
metaCache: this.metaCache,
|
|
400
|
+
signal: ac.signal,
|
|
401
|
+
onProgress: (info) => {
|
|
402
|
+
this.resetActivity();
|
|
403
|
+
const now = Date.now();
|
|
404
|
+
if (now - lastProgressTime < 100)
|
|
405
|
+
return;
|
|
406
|
+
lastProgressTime = now;
|
|
407
|
+
(0, ipc_handler_1.writeProgress)(conn, {
|
|
408
|
+
processed: info.processed,
|
|
409
|
+
indexed: info.indexed,
|
|
410
|
+
total: info.total,
|
|
411
|
+
filePath: info.filePath,
|
|
412
|
+
});
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
(0, ipc_handler_1.writeDone)(conn, {
|
|
416
|
+
ok: true,
|
|
417
|
+
processed: result.processed,
|
|
418
|
+
indexed: result.indexed,
|
|
419
|
+
total: result.total,
|
|
420
|
+
failedFiles: result.failedFiles,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
catch (err) {
|
|
424
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
425
|
+
console.error(`[daemon] indexProject failed for ${path.basename(root)}:`, msg);
|
|
426
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: msg });
|
|
427
|
+
}
|
|
428
|
+
finally {
|
|
429
|
+
(_a = this.vectorDb) === null || _a === void 0 ? void 0 : _a.resumeMaintenanceLoop();
|
|
430
|
+
// Re-enable watcher
|
|
431
|
+
try {
|
|
432
|
+
yield this.watchProject(root);
|
|
433
|
+
}
|
|
434
|
+
catch (err) {
|
|
435
|
+
console.error(`[daemon] Failed to re-watch ${path.basename(root)}:`, err);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}));
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
removeProject(root, conn) {
|
|
442
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
443
|
+
yield this.withProjectLock(root, () => __awaiter(this, void 0, void 0, function* () {
|
|
444
|
+
if (!this.vectorDb || !this.metaCache) {
|
|
445
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: "daemon resources not ready" });
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
try {
|
|
449
|
+
yield this.unwatchProject(root);
|
|
450
|
+
const rootPrefix = root.endsWith("/") ? root : `${root}/`;
|
|
451
|
+
yield this.vectorDb.deletePathsWithPrefix(rootPrefix);
|
|
452
|
+
const keys = yield this.metaCache.getKeysWithPrefix(rootPrefix);
|
|
453
|
+
for (const key of keys) {
|
|
454
|
+
this.metaCache.delete(key);
|
|
455
|
+
}
|
|
456
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: true });
|
|
457
|
+
}
|
|
458
|
+
catch (err) {
|
|
459
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
460
|
+
console.error(`[daemon] removeProject failed for ${path.basename(root)}:`, msg);
|
|
461
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: msg });
|
|
462
|
+
}
|
|
463
|
+
}));
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
summarizeProject(root, conn, opts) {
|
|
467
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
468
|
+
yield this.withProjectLock(root, () => __awaiter(this, void 0, void 0, function* () {
|
|
469
|
+
var _a;
|
|
470
|
+
if (!this.vectorDb) {
|
|
471
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: "daemon resources not ready" });
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
const rootPrefix = (_a = opts.pathPrefix) !== null && _a !== void 0 ? _a : (root.endsWith("/") ? root : `${root}/`);
|
|
475
|
+
let lastProgressTime = 0;
|
|
476
|
+
try {
|
|
477
|
+
const result = yield (0, syncer_1.generateSummaries)(this.vectorDb, rootPrefix, (done, total) => {
|
|
478
|
+
this.resetActivity();
|
|
479
|
+
const now = Date.now();
|
|
480
|
+
if (now - lastProgressTime < 100)
|
|
481
|
+
return;
|
|
482
|
+
lastProgressTime = now;
|
|
483
|
+
(0, ipc_handler_1.writeProgress)(conn, { summarized: done, total });
|
|
484
|
+
}, opts.limit);
|
|
485
|
+
(0, ipc_handler_1.writeDone)(conn, {
|
|
486
|
+
ok: true,
|
|
487
|
+
summarized: result.summarized,
|
|
488
|
+
remaining: result.remaining,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
catch (err) {
|
|
492
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
493
|
+
console.error(`[daemon] summarizeProject failed for ${path.basename(root)}:`, msg);
|
|
494
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: msg });
|
|
495
|
+
}
|
|
496
|
+
}));
|
|
497
|
+
});
|
|
498
|
+
}
|
|
289
499
|
shutdown() {
|
|
290
500
|
return __awaiter(this, void 0, void 0, function* () {
|
|
291
501
|
var _a, _b, _c;
|
|
@@ -9,8 +9,33 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.writeProgress = writeProgress;
|
|
13
|
+
exports.writeDone = writeDone;
|
|
12
14
|
exports.handleCommand = handleCommand;
|
|
13
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Write a streaming progress line to the IPC connection.
|
|
17
|
+
*/
|
|
18
|
+
function writeProgress(conn, data) {
|
|
19
|
+
if (!conn.writable)
|
|
20
|
+
return;
|
|
21
|
+
conn.write(`${JSON.stringify(Object.assign({ type: "progress" }, data))}\n`);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Write the final streaming done line and end the connection.
|
|
25
|
+
*/
|
|
26
|
+
function writeDone(conn, data) {
|
|
27
|
+
if (!conn.writable)
|
|
28
|
+
return;
|
|
29
|
+
conn.write(`${JSON.stringify(Object.assign({ type: "done" }, data))}\n`);
|
|
30
|
+
conn.end();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Handle a single IPC command.
|
|
34
|
+
*
|
|
35
|
+
* Returns a DaemonResponse for simple commands (caller writes + closes).
|
|
36
|
+
* Returns null for streaming commands (handler manages connection lifecycle).
|
|
37
|
+
*/
|
|
38
|
+
function handleCommand(daemon, cmd, conn) {
|
|
14
39
|
return __awaiter(this, void 0, void 0, function* () {
|
|
15
40
|
try {
|
|
16
41
|
switch (cmd.cmd) {
|
|
@@ -41,6 +66,41 @@ function handleCommand(daemon, cmd) {
|
|
|
41
66
|
// Respond before shutting down so the client gets the response
|
|
42
67
|
setImmediate(() => daemon.shutdown());
|
|
43
68
|
return { ok: true };
|
|
69
|
+
// --- Streaming commands (daemon manages connection) ---
|
|
70
|
+
case "add": {
|
|
71
|
+
const root = String(cmd.root || "");
|
|
72
|
+
if (!root)
|
|
73
|
+
return { ok: false, error: "missing root" };
|
|
74
|
+
daemon.addProject(root, conn);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
case "index": {
|
|
78
|
+
const root = String(cmd.root || "");
|
|
79
|
+
if (!root)
|
|
80
|
+
return { ok: false, error: "missing root" };
|
|
81
|
+
daemon.indexProject(root, conn, {
|
|
82
|
+
reset: !!cmd.reset,
|
|
83
|
+
dryRun: !!cmd.dryRun,
|
|
84
|
+
});
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
case "remove": {
|
|
88
|
+
const root = String(cmd.root || "");
|
|
89
|
+
if (!root)
|
|
90
|
+
return { ok: false, error: "missing root" };
|
|
91
|
+
daemon.removeProject(root, conn);
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
case "summarize": {
|
|
95
|
+
const root = String(cmd.root || "");
|
|
96
|
+
if (!root)
|
|
97
|
+
return { ok: false, error: "missing root" };
|
|
98
|
+
daemon.summarizeProject(root, conn, {
|
|
99
|
+
limit: typeof cmd.limit === "number" ? cmd.limit : undefined,
|
|
100
|
+
pathPrefix: typeof cmd.pathPrefix === "string" ? cmd.pathPrefix : undefined,
|
|
101
|
+
});
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
44
104
|
default:
|
|
45
105
|
return { ok: false, error: `unknown command: ${cmd.cmd}` };
|
|
46
106
|
}
|
|
@@ -49,7 +49,6 @@ const config_1 = require("../../config");
|
|
|
49
49
|
const cache_check_1 = require("../utils/cache-check");
|
|
50
50
|
const file_utils_1 = require("../utils/file-utils");
|
|
51
51
|
const logger_1 = require("../utils/logger");
|
|
52
|
-
const lock_1 = require("../utils/lock");
|
|
53
52
|
const pool_1 = require("../workers/pool");
|
|
54
53
|
const watcher_batch_1 = require("./watcher-batch");
|
|
55
54
|
const DEBOUNCE_MS = 2000;
|
|
@@ -61,12 +60,10 @@ class ProjectBatchProcessor {
|
|
|
61
60
|
this.debounceTimer = null;
|
|
62
61
|
this.processing = false;
|
|
63
62
|
this.closed = false;
|
|
64
|
-
this.consecutiveLockFailures = 0;
|
|
65
63
|
this.currentBatchAc = null;
|
|
66
64
|
this.projectRoot = opts.projectRoot;
|
|
67
65
|
this.vectorDb = opts.vectorDb;
|
|
68
66
|
this.metaCache = opts.metaCache;
|
|
69
|
-
this.dataDir = opts.dataDir;
|
|
70
67
|
this.onReindex = opts.onReindex;
|
|
71
68
|
this.onActivity = opts.onActivity;
|
|
72
69
|
this.wtag = `watch:${path.basename(opts.projectRoot)}`;
|
|
@@ -122,127 +119,114 @@ class ProjectBatchProcessor {
|
|
|
122
119
|
const start = Date.now();
|
|
123
120
|
let reindexed = 0;
|
|
124
121
|
try {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
122
|
+
// No lock needed — daemon is the single writer to LanceDB/MetaCache
|
|
123
|
+
const pool = (0, pool_1.getWorkerPool)();
|
|
124
|
+
const deletes = [];
|
|
125
|
+
const vectors = [];
|
|
126
|
+
const metaUpdates = new Map();
|
|
127
|
+
const metaDeletes = [];
|
|
128
|
+
const attempted = new Set();
|
|
129
|
+
for (const [absPath, event] of batch) {
|
|
130
|
+
if (batchAc.signal.aborted)
|
|
131
|
+
break;
|
|
132
|
+
attempted.add(absPath);
|
|
133
|
+
if (event === "unlink") {
|
|
134
|
+
deletes.push(absPath);
|
|
135
|
+
metaDeletes.push(absPath);
|
|
136
|
+
reindexed++;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
// change or add
|
|
140
|
+
try {
|
|
141
|
+
const stats = yield fs.promises.stat(absPath);
|
|
142
|
+
if (!(0, file_utils_1.isIndexableFile)(absPath, stats.size))
|
|
143
|
+
continue;
|
|
144
|
+
const cached = this.metaCache.get(absPath);
|
|
145
|
+
if ((0, cache_check_1.isFileCached)(cached, stats)) {
|
|
144
146
|
continue;
|
|
145
147
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
hash: result.hash,
|
|
161
|
-
mtimeMs: result.mtimeMs,
|
|
162
|
-
size: result.size,
|
|
163
|
-
};
|
|
164
|
-
if (cached && cached.hash === result.hash) {
|
|
165
|
-
metaUpdates.set(absPath, metaEntry);
|
|
166
|
-
continue;
|
|
167
|
-
}
|
|
168
|
-
if (result.shouldDelete) {
|
|
169
|
-
deletes.push(absPath);
|
|
170
|
-
metaUpdates.set(absPath, metaEntry);
|
|
171
|
-
reindexed++;
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
148
|
+
const result = yield pool.processFile({
|
|
149
|
+
path: absPath,
|
|
150
|
+
absolutePath: absPath,
|
|
151
|
+
}, batchAc.signal);
|
|
152
|
+
const metaEntry = {
|
|
153
|
+
hash: result.hash,
|
|
154
|
+
mtimeMs: result.mtimeMs,
|
|
155
|
+
size: result.size,
|
|
156
|
+
};
|
|
157
|
+
if (cached && cached.hash === result.hash) {
|
|
158
|
+
metaUpdates.set(absPath, metaEntry);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (result.shouldDelete) {
|
|
174
162
|
deletes.push(absPath);
|
|
175
|
-
if (result.vectors.length > 0) {
|
|
176
|
-
vectors.push(...result.vectors);
|
|
177
|
-
}
|
|
178
163
|
metaUpdates.set(absPath, metaEntry);
|
|
179
164
|
reindexed++;
|
|
165
|
+
continue;
|
|
180
166
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const code = err === null || err === void 0 ? void 0 : err.code;
|
|
185
|
-
if (code === "ENOENT") {
|
|
186
|
-
deletes.push(absPath);
|
|
187
|
-
metaDeletes.push(absPath);
|
|
188
|
-
reindexed++;
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
console.error(`[${this.wtag}] Failed to process ${absPath}:`, err);
|
|
192
|
-
if (!pool.isHealthy()) {
|
|
193
|
-
console.error(`[${this.wtag}] Worker pool unhealthy, aborting batch`);
|
|
194
|
-
break;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
// Requeue files that weren't attempted (aborted or pool unhealthy)
|
|
200
|
-
for (const [absPath, event] of batch) {
|
|
201
|
-
if (!attempted.has(absPath) && !this.pending.has(absPath)) {
|
|
202
|
-
this.pending.set(absPath, event);
|
|
167
|
+
deletes.push(absPath);
|
|
168
|
+
if (result.vectors.length > 0) {
|
|
169
|
+
vectors.push(...result.vectors);
|
|
203
170
|
}
|
|
171
|
+
metaUpdates.set(absPath, metaEntry);
|
|
172
|
+
reindexed++;
|
|
204
173
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
174
|
+
catch (err) {
|
|
175
|
+
if (batchAc.signal.aborted)
|
|
176
|
+
break;
|
|
177
|
+
const code = err === null || err === void 0 ? void 0 : err.code;
|
|
178
|
+
if (code === "ENOENT") {
|
|
179
|
+
deletes.push(absPath);
|
|
180
|
+
metaDeletes.push(absPath);
|
|
181
|
+
reindexed++;
|
|
213
182
|
}
|
|
214
183
|
else {
|
|
215
|
-
|
|
184
|
+
console.error(`[${this.wtag}] Failed to process ${absPath}:`, err);
|
|
185
|
+
if (!pool.isHealthy()) {
|
|
186
|
+
console.error(`[${this.wtag}] Worker pool unhealthy, aborting batch`);
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
216
189
|
}
|
|
217
190
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
191
|
+
}
|
|
192
|
+
// Requeue files that weren't attempted (aborted or pool unhealthy)
|
|
193
|
+
for (const [absPath, event] of batch) {
|
|
194
|
+
if (!attempted.has(absPath) && !this.pending.has(absPath)) {
|
|
195
|
+
this.pending.set(absPath, event);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Flush to VectorDB: insert first, then delete old (preserving new)
|
|
199
|
+
const newIds = vectors.map((v) => v.id);
|
|
200
|
+
if (vectors.length > 0) {
|
|
201
|
+
yield this.vectorDb.insertBatch(vectors);
|
|
202
|
+
}
|
|
203
|
+
if (deletes.length > 0) {
|
|
204
|
+
if (newIds.length > 0) {
|
|
205
|
+
yield this.vectorDb.deletePathsExcludingIds(deletes, newIds);
|
|
221
206
|
}
|
|
222
|
-
|
|
223
|
-
this.
|
|
207
|
+
else {
|
|
208
|
+
yield this.vectorDb.deletePaths(deletes);
|
|
224
209
|
}
|
|
225
210
|
}
|
|
226
|
-
|
|
227
|
-
|
|
211
|
+
// Update MetaCache
|
|
212
|
+
for (const [p, entry] of metaUpdates) {
|
|
213
|
+
this.metaCache.put(p, entry);
|
|
214
|
+
}
|
|
215
|
+
for (const p of metaDeletes) {
|
|
216
|
+
this.metaCache.delete(p);
|
|
228
217
|
}
|
|
229
218
|
const duration = Date.now() - start;
|
|
230
219
|
if (reindexed > 0) {
|
|
231
220
|
(_a = this.onReindex) === null || _a === void 0 ? void 0 : _a.call(this, reindexed, duration);
|
|
232
221
|
}
|
|
233
222
|
(0, logger_1.log)(this.wtag, `Batch complete: ${batch.size} files, ${reindexed} reindexed (${(duration / 1000).toFixed(1)}s)`);
|
|
234
|
-
this.consecutiveLockFailures = 0;
|
|
235
223
|
for (const absPath of batch.keys()) {
|
|
236
224
|
this.retryCount.delete(absPath);
|
|
237
225
|
}
|
|
238
226
|
}
|
|
239
227
|
catch (err) {
|
|
240
|
-
const isLockError = err instanceof Error && err.message.includes("lock already held");
|
|
241
|
-
if (isLockError) {
|
|
242
|
-
this.consecutiveLockFailures++;
|
|
243
|
-
}
|
|
244
228
|
console.error(`[${this.wtag}] Batch processing failed:`, err);
|
|
245
|
-
const { requeued, dropped, backoffMs } = (0, watcher_batch_1.computeRetryAction)(batch, this.retryCount, MAX_RETRIES,
|
|
229
|
+
const { requeued, dropped, backoffMs } = (0, watcher_batch_1.computeRetryAction)(batch, this.retryCount, MAX_RETRIES, false, 0, DEBOUNCE_MS);
|
|
246
230
|
for (const [absPath, event] of requeued) {
|
|
247
231
|
if (!this.pending.has(absPath)) {
|
|
248
232
|
this.pending.set(absPath, event);
|