grepmax 0.26.5 → 0.26.6
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 +1 -1
- package/README.md +6 -2
- package/dist/commands/doctor.js +20 -5
- package/dist/commands/mcp.js +60 -15
- package/dist/commands/search-run.js +4 -3
- package/dist/commands/similar.js +2 -2
- package/dist/eval-graph-recovery-probe.js +2 -2
- package/dist/eval-graph-sanity.js +2 -2
- package/dist/lib/analysis/surprising-connections.js +2 -2
- package/dist/lib/daemon/daemon.js +1 -0
- package/dist/lib/index/batch-processor.js +49 -22
- package/dist/lib/index/chunker.js +616 -602
- package/dist/lib/index/syncer.js +1 -1
- package/dist/lib/index/watcher.js +14 -3
- package/dist/lib/search/searcher.js +2 -2
- package/dist/lib/skeleton/skeletonizer.js +9 -1
- package/dist/lib/store/ann-config.js +34 -0
- package/dist/lib/store/vector-db.js +184 -33
- package/dist/lib/workers/orchestrator.js +5 -35
- package/dist/lib/workers/pool.js +15 -32
- package/package.json +3 -8
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
package/dist/lib/index/syncer.js
CHANGED
|
@@ -57,9 +57,14 @@ const batch_processor_1 = require("./batch-processor");
|
|
|
57
57
|
const cache_coherence_1 = require("./cache-coherence");
|
|
58
58
|
const file_policy_1 = require("./file-policy");
|
|
59
59
|
const walker_1 = require("./walker");
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
|
|
60
|
+
// Directory names ignored at ANY depth. @parcel/watcher treats a non-glob
|
|
61
|
+
// ignore entry as a literal path resolved against the watch root, so a bare
|
|
62
|
+
// "dist" only covered <root>/dist — nested build dirs (packages/*/dist,
|
|
63
|
+
// nested node_modules, .turbo) still flooded the daemon with events on every
|
|
64
|
+
// build. Glob entries are matched against the path relative to the watch
|
|
65
|
+
// root, and a leading "**/" matches zero or more segments, so the expansion
|
|
66
|
+
// below covers both the top-level and nested cases.
|
|
67
|
+
const IGNORED_DIR_NAMES = [
|
|
63
68
|
"node_modules",
|
|
64
69
|
".git",
|
|
65
70
|
".gmax",
|
|
@@ -77,10 +82,16 @@ exports.WATCHER_IGNORE_GLOBS = [
|
|
|
77
82
|
".pytest_cache",
|
|
78
83
|
".next",
|
|
79
84
|
".nuxt",
|
|
85
|
+
".turbo",
|
|
80
86
|
".gradle",
|
|
81
87
|
".m2",
|
|
82
88
|
"vendor",
|
|
83
89
|
"lancedb",
|
|
90
|
+
];
|
|
91
|
+
// Ignore patterns for @parcel/watcher (picomatch globs).
|
|
92
|
+
exports.WATCHER_IGNORE_GLOBS = [
|
|
93
|
+
// "**/<name>" ignores the directory entry itself, "**/<name>/**" its contents.
|
|
94
|
+
...IGNORED_DIR_NAMES.flatMap((name) => [`**/${name}`, `**/${name}/**`]),
|
|
84
95
|
"**/*.tmp.*", // editor atomic save artifacts
|
|
85
96
|
"**/*.sb-*", // Xcode swap files
|
|
86
97
|
];
|
|
@@ -13,6 +13,7 @@ exports.Searcher = void 0;
|
|
|
13
13
|
exports.asSymbolQuery = asSymbolQuery;
|
|
14
14
|
exports.buildWhereClause = buildWhereClause;
|
|
15
15
|
const config_1 = require("../../config");
|
|
16
|
+
const ann_config_1 = require("../store/ann-config");
|
|
16
17
|
const filter_builder_1 = require("../utils/filter-builder");
|
|
17
18
|
const pool_1 = require("../workers/pool");
|
|
18
19
|
const intent_1 = require("./intent");
|
|
@@ -475,8 +476,7 @@ class Searcher {
|
|
|
475
476
|
// in the respective query to suppress LanceDB deprecation warnings
|
|
476
477
|
const VECTOR_COLUMNS = [...LIGHTWEIGHT_COLUMNS, "_distance"];
|
|
477
478
|
const FTS_COLUMNS = [...LIGHTWEIGHT_COLUMNS, "_score"];
|
|
478
|
-
let vectorQuery = table
|
|
479
|
-
.vectorSearch(queryVector)
|
|
479
|
+
let vectorQuery = (0, ann_config_1.configureAnnVectorQuery)(table.vectorSearch(queryVector))
|
|
480
480
|
.select(VECTOR_COLUMNS)
|
|
481
481
|
.limit(PRE_RERANK_K);
|
|
482
482
|
if (whereClause) {
|
|
@@ -164,10 +164,14 @@ class Skeletonizer {
|
|
|
164
164
|
if (!language || !this.parser) {
|
|
165
165
|
return this.createFallbackResult(filePath, content, "Failed to load grammar");
|
|
166
166
|
}
|
|
167
|
+
let tree = null;
|
|
167
168
|
try {
|
|
168
169
|
// Parse the file
|
|
169
170
|
this.parser.setLanguage(language);
|
|
170
|
-
|
|
171
|
+
tree = this.parser.parse(content);
|
|
172
|
+
if (!tree) {
|
|
173
|
+
return this.createFallbackResult(filePath, content, "Parse returned no tree");
|
|
174
|
+
}
|
|
171
175
|
const root = tree.rootNode;
|
|
172
176
|
// Find all regions to elide (function/method bodies)
|
|
173
177
|
const elisions = [];
|
|
@@ -192,6 +196,10 @@ class Skeletonizer {
|
|
|
192
196
|
catch (err) {
|
|
193
197
|
return this.createFallbackResult(filePath, content, `Parse error: ${err instanceof Error ? err.message : String(err)}`);
|
|
194
198
|
}
|
|
199
|
+
finally {
|
|
200
|
+
// Nothing in the guarded block may retain a Node.
|
|
201
|
+
tree === null || tree === void 0 ? void 0 : tree.delete();
|
|
202
|
+
}
|
|
195
203
|
});
|
|
196
204
|
}
|
|
197
205
|
/**
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isAnnEnabled = isAnnEnabled;
|
|
4
|
+
exports.annMinRows = annMinRows;
|
|
5
|
+
exports.configureAnnVectorQuery = configureAnnVectorQuery;
|
|
6
|
+
function positiveInt(value, fallback) {
|
|
7
|
+
const parsed = Number.parseInt(value !== null && value !== void 0 ? value : "", 10);
|
|
8
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
9
|
+
}
|
|
10
|
+
function isAnnEnabled() {
|
|
11
|
+
return process.env.GMAX_ANN === "1";
|
|
12
|
+
}
|
|
13
|
+
function annMinRows() {
|
|
14
|
+
return positiveInt(process.env.GMAX_ANN_MIN_ROWS, 50000);
|
|
15
|
+
}
|
|
16
|
+
function configureAnnVectorQuery(query) {
|
|
17
|
+
let configured = query;
|
|
18
|
+
if (typeof configured.column === "function") {
|
|
19
|
+
configured = configured.column("vector");
|
|
20
|
+
}
|
|
21
|
+
if (!isAnnEnabled()) {
|
|
22
|
+
return typeof configured.bypassVectorIndex === "function"
|
|
23
|
+
? configured.bypassVectorIndex()
|
|
24
|
+
: configured;
|
|
25
|
+
}
|
|
26
|
+
const minimum = positiveInt(process.env.GMAX_ANN_NPROBES, 20);
|
|
27
|
+
const maximum = Math.max(minimum, positiveInt(process.env.GMAX_ANN_MAX_NPROBES, 200));
|
|
28
|
+
if (typeof configured.minimumNprobes === "function") {
|
|
29
|
+
configured = configured.minimumNprobes(minimum);
|
|
30
|
+
}
|
|
31
|
+
return typeof configured.maximumNprobes === "function"
|
|
32
|
+
? configured.maximumNprobes(maximum)
|
|
33
|
+
: configured;
|
|
34
|
+
}
|
|
@@ -53,6 +53,7 @@ const index_config_1 = require("../index/index-config");
|
|
|
53
53
|
const cleanup_1 = require("../utils/cleanup");
|
|
54
54
|
const filter_builder_1 = require("../utils/filter-builder");
|
|
55
55
|
const logger_1 = require("../utils/logger");
|
|
56
|
+
const ann_config_1 = require("./ann-config");
|
|
56
57
|
const store_lease_1 = require("./store-lease");
|
|
57
58
|
class DiskPressureError extends Error {
|
|
58
59
|
constructor(message = "Disk critically low — writes suspended") {
|
|
@@ -86,6 +87,7 @@ function isMissingTableError(err) {
|
|
|
86
87
|
}
|
|
87
88
|
const TABLE_NAME = "chunks";
|
|
88
89
|
const MAINTENANCE_INTERVAL_MS = 5 * 60 * 1000;
|
|
90
|
+
const CLEAN_MAINTENANCE_INTERVAL_MS = 60 * 60 * 1000;
|
|
89
91
|
class VectorDB {
|
|
90
92
|
constructor(lancedbDir, vectorDim, suppliedLease) {
|
|
91
93
|
this.lancedbDir = lancedbDir;
|
|
@@ -95,11 +97,18 @@ class VectorDB {
|
|
|
95
97
|
this.maintenanceRunning = false;
|
|
96
98
|
this.maintenancePromise = null;
|
|
97
99
|
this.maintenanceTimer = null;
|
|
100
|
+
this.writeEpoch = 0;
|
|
101
|
+
this.maintainedEpoch = -1;
|
|
102
|
+
this.maintainedTableVersion = null;
|
|
103
|
+
this.lastMaintenanceMs = 0;
|
|
104
|
+
this.ftsIndexEnsured = false;
|
|
105
|
+
this.lastOptimizeDidWork = false;
|
|
98
106
|
this.maintenanceRunner = (fn) => fn();
|
|
99
107
|
/** Latched when a from-scratch FTS rebuild failed to stop an optimize
|
|
100
108
|
* panic. Stops the (expensive) rebuild from re-running every maintenance
|
|
101
109
|
* tick for a panic it cannot fix; cleared when an optimize succeeds. */
|
|
102
110
|
this.ftsPanicRecoveryExhausted = false;
|
|
111
|
+
this.annPanicRecoveryExhausted = false;
|
|
103
112
|
this.lastCorruptionLogMs = 0;
|
|
104
113
|
this.diskPressure = "ok";
|
|
105
114
|
this.lastDiskCheckMs = 0;
|
|
@@ -141,6 +150,8 @@ class VectorDB {
|
|
|
141
150
|
// promise to await instead of a chain.
|
|
142
151
|
if (this.maintenancePromise)
|
|
143
152
|
return;
|
|
153
|
+
if (!this.maintenanceDue())
|
|
154
|
+
return;
|
|
144
155
|
const run = this.maintenanceRunner(() => __awaiter(this, void 0, void 0, function* () {
|
|
145
156
|
try {
|
|
146
157
|
yield this.runMaintenance();
|
|
@@ -179,6 +190,14 @@ class VectorDB {
|
|
|
179
190
|
isMaintenanceActive() {
|
|
180
191
|
return this.maintenancePromise !== null;
|
|
181
192
|
}
|
|
193
|
+
maintenanceDue() {
|
|
194
|
+
return (this.writeEpoch !== this.maintainedEpoch ||
|
|
195
|
+
!this.ftsIndexEnsured ||
|
|
196
|
+
Date.now() - this.lastMaintenanceMs >= CLEAN_MAINTENANCE_INTERVAL_MS);
|
|
197
|
+
}
|
|
198
|
+
markWriteCommitted() {
|
|
199
|
+
this.writeEpoch++;
|
|
200
|
+
}
|
|
182
201
|
/** Pause the maintenance timer (e.g. during a full index that calls runMaintenance itself). */
|
|
183
202
|
pauseMaintenanceLoop() {
|
|
184
203
|
if (this.maintenanceTimer) {
|
|
@@ -402,7 +421,9 @@ class VectorDB {
|
|
|
402
421
|
}
|
|
403
422
|
yield Promise.all([this.drainWrites(), this.drainCompactions()]);
|
|
404
423
|
const db = yield this.getDb();
|
|
405
|
-
|
|
424
|
+
const result = yield mutation(db);
|
|
425
|
+
this.markWriteCommitted();
|
|
426
|
+
return result;
|
|
406
427
|
}
|
|
407
428
|
finally {
|
|
408
429
|
yield (temporaryExclusiveLease === null || temporaryExclusiveLease === void 0 ? void 0 : temporaryExclusiveLease.release());
|
|
@@ -555,6 +576,7 @@ class VectorDB {
|
|
|
555
576
|
continue;
|
|
556
577
|
try {
|
|
557
578
|
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));
|
|
579
|
+
this.markWriteCommitted();
|
|
558
580
|
(0, logger_1.log)("db", `Added ${col} column to existing table`);
|
|
559
581
|
}
|
|
560
582
|
catch (err) {
|
|
@@ -586,6 +608,7 @@ class VectorDB {
|
|
|
586
608
|
schema,
|
|
587
609
|
});
|
|
588
610
|
yield table.delete('id = "seed"');
|
|
611
|
+
this.markWriteCommitted();
|
|
589
612
|
return table;
|
|
590
613
|
}
|
|
591
614
|
yield this.validateSchema(table);
|
|
@@ -606,6 +629,18 @@ class VectorDB {
|
|
|
606
629
|
}
|
|
607
630
|
});
|
|
608
631
|
}
|
|
632
|
+
readTableVersion(table) {
|
|
633
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
634
|
+
if (!table || typeof table.version !== "function")
|
|
635
|
+
return null;
|
|
636
|
+
try {
|
|
637
|
+
return yield table.version();
|
|
638
|
+
}
|
|
639
|
+
catch (_a) {
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
}
|
|
609
644
|
insertBatch(records) {
|
|
610
645
|
return __awaiter(this, void 0, void 0, function* () {
|
|
611
646
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
|
|
@@ -620,13 +655,6 @@ class VectorDB {
|
|
|
620
655
|
const view = val;
|
|
621
656
|
return Buffer.from(view.buffer, (_a = view.byteOffset) !== null && _a !== void 0 ? _a : 0, (_b = view.byteLength) !== null && _b !== void 0 ? _b : undefined);
|
|
622
657
|
}
|
|
623
|
-
if (val &&
|
|
624
|
-
typeof val === "object" &&
|
|
625
|
-
"type" in val &&
|
|
626
|
-
val.type === "Buffer" &&
|
|
627
|
-
Array.isArray(val.data)) {
|
|
628
|
-
return Buffer.from(val.data);
|
|
629
|
-
}
|
|
630
658
|
if (Array.isArray(val))
|
|
631
659
|
return Buffer.from(val);
|
|
632
660
|
return Buffer.alloc(0);
|
|
@@ -637,16 +665,6 @@ class VectorDB {
|
|
|
637
665
|
if (ArrayBuffer.isView(val) && val.buffer) {
|
|
638
666
|
return Array.from(val);
|
|
639
667
|
}
|
|
640
|
-
if (val &&
|
|
641
|
-
typeof val === "object" &&
|
|
642
|
-
!("length" in val) &&
|
|
643
|
-
Object.keys(val).length > 0) {
|
|
644
|
-
// Plain object with numeric keys (e.g., from IPC serialization)
|
|
645
|
-
return Object.entries(val)
|
|
646
|
-
.filter(([k]) => !Number.isNaN(Number(k)))
|
|
647
|
-
.sort((a, b) => Number(a[0]) - Number(b[0]))
|
|
648
|
-
.map(([, v]) => Number(v) || 0);
|
|
649
|
-
}
|
|
650
668
|
return [];
|
|
651
669
|
};
|
|
652
670
|
// Mutate records in-place to avoid doubling memory with a parallel rows array.
|
|
@@ -697,6 +715,7 @@ class VectorDB {
|
|
|
697
715
|
yield this.withWriteGate(() => __awaiter(this, void 0, void 0, function* () {
|
|
698
716
|
const table = yield this.ensureTableUnsafe();
|
|
699
717
|
yield table.add(records);
|
|
718
|
+
this.markWriteCommitted();
|
|
700
719
|
}));
|
|
701
720
|
}
|
|
702
721
|
catch (err) {
|
|
@@ -716,10 +735,105 @@ class VectorDB {
|
|
|
716
735
|
return this.withWriteGate(() => this.createFTSIndexUnsafe(rebuild, retries));
|
|
717
736
|
});
|
|
718
737
|
}
|
|
738
|
+
createVectorIndex() {
|
|
739
|
+
return __awaiter(this, arguments, void 0, function* (rebuild = false, retries = 5, checkOnly = false) {
|
|
740
|
+
if (this.checkDiskPressure() !== "ok") {
|
|
741
|
+
(0, logger_1.debug)("vectordb", "ANN index skipped under disk pressure");
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
744
|
+
return this.withWriteGate(() => this.createVectorIndexUnsafe(rebuild, retries, checkOnly));
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
createVectorIndexUnsafe(rebuild, retries, checkOnly) {
|
|
748
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
749
|
+
var _a, _b;
|
|
750
|
+
const table = yield this.ensureTableUnsafe();
|
|
751
|
+
const rowCount = yield table.countRows();
|
|
752
|
+
if (rowCount < (0, ann_config_1.annMinRows)())
|
|
753
|
+
return false;
|
|
754
|
+
const indices = yield table.listIndices();
|
|
755
|
+
const vectorIndex = indices.find((index) => index.name === "vector_idx" || index.columns.includes("vector"));
|
|
756
|
+
const pathIndex = indices.find((index) => index.name === "path_idx" || index.columns.includes("path"));
|
|
757
|
+
const vectorStats = vectorIndex
|
|
758
|
+
? yield table.indexStats(vectorIndex.name)
|
|
759
|
+
: undefined;
|
|
760
|
+
const staleRatio = vectorStats
|
|
761
|
+
? vectorStats.numUnindexedRows / Math.max(1, vectorStats.numIndexedRows)
|
|
762
|
+
: 0;
|
|
763
|
+
let needsVector = (0, ann_config_1.isAnnEnabled)() &&
|
|
764
|
+
(rebuild ||
|
|
765
|
+
!vectorIndex ||
|
|
766
|
+
((_a = vectorStats === null || vectorStats === void 0 ? void 0 : vectorStats.distanceType) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== "l2" ||
|
|
767
|
+
staleRatio > 0.2);
|
|
768
|
+
let needsPath = rebuild || !pathIndex;
|
|
769
|
+
if (!needsVector && !needsPath)
|
|
770
|
+
return false;
|
|
771
|
+
if (checkOnly) {
|
|
772
|
+
(0, logger_1.debug)("vectordb", `ANN maintenance due (vector=${needsVector}, path=${needsPath}, unindexed=${(_b = vectorStats === null || vectorStats === void 0 ? void 0 : vectorStats.numUnindexedRows) !== null && _b !== void 0 ? _b : 0})`);
|
|
773
|
+
return true;
|
|
774
|
+
}
|
|
775
|
+
let rebuiltAfterPanic = false;
|
|
776
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
777
|
+
try {
|
|
778
|
+
if (needsVector) {
|
|
779
|
+
const numPartitions = Math.max(64, Math.min(2048, Math.round(Math.sqrt(rowCount))));
|
|
780
|
+
yield table.createIndex("vector", {
|
|
781
|
+
config: lancedb.Index.ivfFlat({
|
|
782
|
+
distanceType: "l2",
|
|
783
|
+
numPartitions,
|
|
784
|
+
}),
|
|
785
|
+
name: "vector_idx",
|
|
786
|
+
replace: true,
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
if (needsPath) {
|
|
790
|
+
yield table.createIndex("path", {
|
|
791
|
+
config: lancedb.Index.btree(),
|
|
792
|
+
name: "path_idx",
|
|
793
|
+
replace: true,
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
this.annPanicRecoveryExhausted = false;
|
|
797
|
+
return true;
|
|
798
|
+
}
|
|
799
|
+
catch (err) {
|
|
800
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
801
|
+
if (attempt < retries &&
|
|
802
|
+
(message.includes("conflict") || message.includes("Retryable"))) {
|
|
803
|
+
const delay = 1000 * Math.pow(2, (attempt - 1));
|
|
804
|
+
(0, logger_1.log)("vectordb", `createVectorIndex conflict (attempt ${attempt}/${retries}), retrying in ${delay}ms`);
|
|
805
|
+
yield new Promise((resolve) => setTimeout(resolve, delay));
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
808
|
+
if (message.includes("Panic") &&
|
|
809
|
+
attempt < retries &&
|
|
810
|
+
!rebuiltAfterPanic &&
|
|
811
|
+
!this.annPanicRecoveryExhausted) {
|
|
812
|
+
rebuiltAfterPanic = true;
|
|
813
|
+
for (const name of ["vector_idx", "path_idx"]) {
|
|
814
|
+
try {
|
|
815
|
+
yield table.dropIndex(name);
|
|
816
|
+
}
|
|
817
|
+
catch (_c) { }
|
|
818
|
+
}
|
|
819
|
+
needsVector = (0, ann_config_1.isAnnEnabled)();
|
|
820
|
+
needsPath = true;
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
if (rebuiltAfterPanic)
|
|
824
|
+
this.annPanicRecoveryExhausted = true;
|
|
825
|
+
console.warn("Failed to create ANN indexes:", err);
|
|
826
|
+
return false;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
return false;
|
|
830
|
+
});
|
|
831
|
+
}
|
|
719
832
|
createFTSIndexUnsafe() {
|
|
720
833
|
return __awaiter(this, arguments, void 0, function* (rebuild = false, retries = 5) {
|
|
721
834
|
const table = yield this.ensureTableUnsafe();
|
|
722
835
|
if (rebuild) {
|
|
836
|
+
this.ftsIndexEnsured = false;
|
|
723
837
|
try {
|
|
724
838
|
yield table.dropIndex("content_idx");
|
|
725
839
|
}
|
|
@@ -730,11 +844,13 @@ class VectorDB {
|
|
|
730
844
|
yield table.createIndex("content", {
|
|
731
845
|
config: lancedb.Index.fts({ withPosition: true }),
|
|
732
846
|
});
|
|
847
|
+
this.ftsIndexEnsured = true;
|
|
733
848
|
return;
|
|
734
849
|
}
|
|
735
850
|
catch (e) {
|
|
736
851
|
const msg = e instanceof Error ? e.message : String(e);
|
|
737
852
|
if (msg.includes("already exists")) {
|
|
853
|
+
this.ftsIndexEnsured = true;
|
|
738
854
|
return;
|
|
739
855
|
}
|
|
740
856
|
// Retry on the same Lance commit-conflict pattern that optimize() handles —
|
|
@@ -748,18 +864,23 @@ class VectorDB {
|
|
|
748
864
|
}
|
|
749
865
|
// If position error, try dropping and recreating once
|
|
750
866
|
if (msg.includes("position")) {
|
|
867
|
+
this.ftsIndexEnsured = false;
|
|
751
868
|
try {
|
|
752
869
|
yield table.dropIndex("content_idx");
|
|
753
870
|
yield table.createIndex("content", {
|
|
754
871
|
config: lancedb.Index.fts({ withPosition: true }),
|
|
755
872
|
});
|
|
873
|
+
this.ftsIndexEnsured = true;
|
|
756
874
|
(0, logger_1.log)("vectordb", "Rebuilt FTS index with position support");
|
|
757
875
|
return;
|
|
758
876
|
}
|
|
759
|
-
catch (
|
|
877
|
+
catch (rebuildError) {
|
|
878
|
+
console.warn("Failed to rebuild positional FTS index:", rebuildError);
|
|
879
|
+
throw rebuildError;
|
|
880
|
+
}
|
|
760
881
|
}
|
|
761
882
|
console.warn("Failed to create FTS index:", e);
|
|
762
|
-
|
|
883
|
+
throw e;
|
|
763
884
|
}
|
|
764
885
|
}
|
|
765
886
|
});
|
|
@@ -777,6 +898,7 @@ class VectorDB {
|
|
|
777
898
|
yield this.compactingPromise;
|
|
778
899
|
return;
|
|
779
900
|
}
|
|
901
|
+
this.lastOptimizeDidWork = false;
|
|
780
902
|
let resolveCompacting;
|
|
781
903
|
this.compactingPromise = new Promise((resolve) => {
|
|
782
904
|
resolveCompacting = resolve;
|
|
@@ -800,6 +922,7 @@ class VectorDB {
|
|
|
800
922
|
if (compaction.fragmentsRemoved > 0 ||
|
|
801
923
|
prune.oldVersionsRemoved > 0 ||
|
|
802
924
|
prune.bytesRemoved > 0) {
|
|
925
|
+
this.lastOptimizeDidWork = true;
|
|
803
926
|
(0, logger_1.log)("vectordb", `Compacted: ${compaction.fragmentsRemoved} frags → ${compaction.fragmentsAdded}, ` +
|
|
804
927
|
`pruned ${prune.oldVersionsRemoved} versions, ` +
|
|
805
928
|
`freed ${(prune.bytesRemoved / 1024 / 1024).toFixed(1)}MB`);
|
|
@@ -842,6 +965,7 @@ class VectorDB {
|
|
|
842
965
|
rebuiltFts = true;
|
|
843
966
|
(0, logger_1.log)("vectordb", "Optimize panicked (likely corrupt FTS merge) — rebuilding FTS index from scratch and retrying");
|
|
844
967
|
try {
|
|
968
|
+
this.ftsIndexEnsured = false;
|
|
845
969
|
yield this.createFTSIndexUnsafe(true);
|
|
846
970
|
continue;
|
|
847
971
|
}
|
|
@@ -879,12 +1003,13 @@ class VectorDB {
|
|
|
879
1003
|
* Checks disk bloat ratio and retries optimize when bloat persists.
|
|
880
1004
|
*/
|
|
881
1005
|
runMaintenance() {
|
|
882
|
-
return __awaiter(this,
|
|
1006
|
+
return __awaiter(this, arguments, void 0, function* (options = {}) {
|
|
883
1007
|
if (this.maintenanceRunning) {
|
|
884
1008
|
(0, logger_1.debug)("vectordb", "Maintenance already running, skipping");
|
|
885
1009
|
return;
|
|
886
1010
|
}
|
|
887
1011
|
this.maintenanceRunning = true;
|
|
1012
|
+
const epochSnapshot = this.writeEpoch;
|
|
888
1013
|
try {
|
|
889
1014
|
const pressure = this.checkDiskPressure();
|
|
890
1015
|
if (pressure === "critical") {
|
|
@@ -892,25 +1017,47 @@ class VectorDB {
|
|
|
892
1017
|
(0, logger_1.log)("vectordb", `Maintenance skipped: disk critically low (${freeGb}GB free)`);
|
|
893
1018
|
return;
|
|
894
1019
|
}
|
|
1020
|
+
if (!options.force &&
|
|
1021
|
+
epochSnapshot === this.maintainedEpoch &&
|
|
1022
|
+
this.ftsIndexEnsured) {
|
|
1023
|
+
const table = yield this.openExistingTableUnsafe();
|
|
1024
|
+
const version = yield this.readTableVersion(table);
|
|
1025
|
+
if (version === this.maintainedTableVersion) {
|
|
1026
|
+
this.lastMaintenanceMs = Date.now();
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
(0, logger_1.log)("vectordb", `External store writes detected (v${this.maintainedTableVersion} → v${version})`);
|
|
1030
|
+
}
|
|
895
1031
|
yield this.createFTSIndex();
|
|
1032
|
+
yield this.createVectorIndex();
|
|
896
1033
|
if (pressure === "low") {
|
|
897
1034
|
(0, logger_1.log)("vectordb", `Low disk — single-pass optimize (no bloat retry)`);
|
|
898
1035
|
yield this.optimize(1, 0, true);
|
|
899
|
-
return;
|
|
900
1036
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
const table = yield this.ensureTable();
|
|
904
|
-
const stats = yield table.stats();
|
|
905
|
-
const diskSize = this.getDirectorySize(this.lancedbDir);
|
|
906
|
-
const logicalSize = stats.totalBytes;
|
|
907
|
-
const bloatRatio = logicalSize > 0 ? diskSize / logicalSize : 0;
|
|
908
|
-
// Only retry if disk is still ok after optimize (don't spiral)
|
|
909
|
-
if (bloatRatio > 2.0 && this.checkDiskPressure() === "ok") {
|
|
910
|
-
(0, logger_1.log)("vectordb", `Bloat detected after optimize: ${(diskSize / 1024 / 1024).toFixed(0)}MB disk vs ${(logicalSize / 1024 / 1024).toFixed(0)}MB logical (${bloatRatio.toFixed(1)}x) — retrying`);
|
|
911
|
-
yield new Promise((r) => setTimeout(r, 2000));
|
|
1037
|
+
else {
|
|
1038
|
+
// Normal maintenance: full optimize + bloat check
|
|
912
1039
|
yield this.optimize(5, 0, true);
|
|
1040
|
+
if (this.lastOptimizeDidWork) {
|
|
1041
|
+
const table = yield this.openExistingTableUnsafe();
|
|
1042
|
+
if (table) {
|
|
1043
|
+
const stats = yield table.stats();
|
|
1044
|
+
const diskSize = this.getDirectorySize(this.lancedbDir);
|
|
1045
|
+
const logicalSize = stats.totalBytes;
|
|
1046
|
+
const bloatRatio = logicalSize > 0 ? diskSize / logicalSize : 0;
|
|
1047
|
+
// Only retry if disk is still ok after optimize (don't spiral)
|
|
1048
|
+
if (bloatRatio > 2.0 && this.checkDiskPressure() === "ok") {
|
|
1049
|
+
(0, logger_1.log)("vectordb", `Bloat detected after optimize: ${(diskSize / 1024 / 1024).toFixed(0)}MB disk vs ${(logicalSize / 1024 / 1024).toFixed(0)}MB logical (${bloatRatio.toFixed(1)}x) — retrying`);
|
|
1050
|
+
yield new Promise((r) => setTimeout(r, 2000));
|
|
1051
|
+
yield this.optimize(5, 0, true);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
913
1055
|
}
|
|
1056
|
+
const maintainedTable = yield this.openExistingTableUnsafe();
|
|
1057
|
+
this.maintainedTableVersion =
|
|
1058
|
+
yield this.readTableVersion(maintainedTable);
|
|
1059
|
+
this.maintainedEpoch = epochSnapshot;
|
|
1060
|
+
this.lastMaintenanceMs = Date.now();
|
|
914
1061
|
}
|
|
915
1062
|
finally {
|
|
916
1063
|
this.maintenanceRunning = false;
|
|
@@ -1048,6 +1195,7 @@ class VectorDB {
|
|
|
1048
1195
|
.toArray();
|
|
1049
1196
|
if (existing.length > 0) {
|
|
1050
1197
|
yield table.delete(where);
|
|
1198
|
+
this.markWriteCommitted();
|
|
1051
1199
|
}
|
|
1052
1200
|
}
|
|
1053
1201
|
}));
|
|
@@ -1067,6 +1215,7 @@ class VectorDB {
|
|
|
1067
1215
|
values: { [field]: (_a = values[i]) !== null && _a !== void 0 ? _a : "" },
|
|
1068
1216
|
});
|
|
1069
1217
|
}
|
|
1218
|
+
this.markWriteCommitted();
|
|
1070
1219
|
}));
|
|
1071
1220
|
});
|
|
1072
1221
|
}
|
|
@@ -1095,6 +1244,7 @@ class VectorDB {
|
|
|
1095
1244
|
.toArray();
|
|
1096
1245
|
if (existing.length > 0) {
|
|
1097
1246
|
yield table.delete(where);
|
|
1247
|
+
this.markWriteCommitted();
|
|
1098
1248
|
}
|
|
1099
1249
|
}
|
|
1100
1250
|
}));
|
|
@@ -1112,6 +1262,7 @@ class VectorDB {
|
|
|
1112
1262
|
if (!table)
|
|
1113
1263
|
return;
|
|
1114
1264
|
yield table.delete((0, filter_builder_1.pathStartsWith)(dirPrefix));
|
|
1265
|
+
this.markWriteCommitted();
|
|
1115
1266
|
}));
|
|
1116
1267
|
});
|
|
1117
1268
|
}
|
|
@@ -62,40 +62,18 @@ const granite_1 = require("./embeddings/granite");
|
|
|
62
62
|
const mlx_client_1 = require("./embeddings/mlx-client");
|
|
63
63
|
let mlxFallbackWarned = false;
|
|
64
64
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* preserve TypedArrays. An Int8Array sent over IPC arrives as a plain
|
|
69
|
-
* object with numeric keys (`{0: byte, 1: byte, ...}`). Without explicitly
|
|
70
|
-
* handling that shape, the rerank pipeline silently no-ops (empty matrix
|
|
71
|
-
* → maxSim returns 0 → final ranking falls back to fusion-only). Caught
|
|
72
|
-
* 2026-05-25 when the eval harness showed rerank-on producing identical
|
|
73
|
-
* scores to rerank-off across 97 cases.
|
|
65
|
+
* Reinterpret a ColBERT byte payload as signed bytes without copying views.
|
|
66
|
+
* Arrow returns Uint8Array subarray views, so byteOffset and byteLength must
|
|
67
|
+
* be preserved or reranking can read unrelated bytes from the backing buffer.
|
|
74
68
|
*/
|
|
75
69
|
function coerceColbertBytes(col) {
|
|
76
70
|
if (col instanceof Int8Array)
|
|
77
71
|
return col;
|
|
78
|
-
if (
|
|
72
|
+
if (ArrayBuffer.isView(col)) {
|
|
79
73
|
return new Int8Array(col.buffer, col.byteOffset, col.byteLength);
|
|
80
74
|
}
|
|
81
75
|
if (Array.isArray(col))
|
|
82
76
|
return new Int8Array(col);
|
|
83
|
-
if (col && typeof col === "object") {
|
|
84
|
-
// {type:"Buffer", data:[...]} — Node Buffer.toJSON output
|
|
85
|
-
const asUnknown = col;
|
|
86
|
-
if (asUnknown.type === "Buffer" && Array.isArray(asUnknown.data)) {
|
|
87
|
-
return new Int8Array(asUnknown.data);
|
|
88
|
-
}
|
|
89
|
-
// {0: byte, 1: byte, ...} — Int8Array after IPC JSON serialization
|
|
90
|
-
const keys = Object.keys(col);
|
|
91
|
-
if (keys.length > 0 && keys.every((k) => /^\d+$/.test(k))) {
|
|
92
|
-
const arr = new Int8Array(keys.length);
|
|
93
|
-
for (const k of keys) {
|
|
94
|
-
arr[Number(k)] = col[k];
|
|
95
|
-
}
|
|
96
|
-
return arr;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
77
|
return new Int8Array(0);
|
|
100
78
|
}
|
|
101
79
|
const CACHE_DIR = config_1.PATHS.models;
|
|
@@ -313,15 +291,7 @@ class WorkerOrchestrator {
|
|
|
313
291
|
colbert: new Int8Array(),
|
|
314
292
|
scale: 1,
|
|
315
293
|
};
|
|
316
|
-
return Object.assign(Object.assign({}, chunk), { vector: hybrid.dense, colbert: Buffer.from(hybrid.colbert), colbert_scale: hybrid.scale,
|
|
317
|
-
// Convert the pooled Float32Array to a plain number[] so it survives
|
|
318
|
-
// the JSON IPC hop to the parent (process-child.ts → pool.ts). A typed
|
|
319
|
-
// array JSON-serializes to a length-less {"0":..} object, which then
|
|
320
|
-
// Array.from()s to [] on insert and pads to 48 zeros — silently making
|
|
321
|
-
// the stage-1 cosine prefilter a no-op (searcher.ts:732).
|
|
322
|
-
pooled_colbert_48d: hybrid.pooled_colbert_48d
|
|
323
|
-
? Array.from(hybrid.pooled_colbert_48d)
|
|
324
|
-
: undefined, doc_token_ids: hybrid.token_ids });
|
|
294
|
+
return Object.assign(Object.assign({}, chunk), { vector: hybrid.dense, colbert: Buffer.from(hybrid.colbert), colbert_scale: hybrid.scale, pooled_colbert_48d: hybrid.pooled_colbert_48d, doc_token_ids: hybrid.token_ids });
|
|
325
295
|
});
|
|
326
296
|
onProgress === null || onProgress === void 0 ? void 0 : onProgress();
|
|
327
297
|
(0, logger_1.debug)("orch", `processFile done: ${input.path} ${vectors.length} vectors ${(performance.now() - fileStart).toFixed(0)}ms`);
|