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.
@@ -666,7 +666,7 @@ function initialSync(options) {
666
666
  total,
667
667
  filePath: "Creating FTS index...",
668
668
  });
669
- yield vectorDb.runMaintenance();
669
+ yield vectorDb.runMaintenance({ force: true });
670
670
  throwIfSyncAborted();
671
671
  ftsTimer();
672
672
  }
@@ -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
- // Ignore patterns for @parcel/watcher (micromatch globs + directory names).
61
- // Directory names are matched at any depth automatically.
62
- exports.WATCHER_IGNORE_GLOBS = [
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
- const tree = this.parser.parse(content);
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
- return yield mutation(db);
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 (_b) { }
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
- return;
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, void 0, void 0, function* () {
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
- // Normal maintenance: full optimize + bloat check
902
- yield this.optimize(5, 0, true);
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
- * Normalize a colbert payload received over IPC into an Int8Array.
66
- *
67
- * Node `child_process.send` serializes payloads as JSON, which doesn't
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 (Buffer.isBuffer(col)) {
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`);