grepmax 0.26.12 → 0.26.13

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.
@@ -10,7 +10,7 @@
10
10
  "name": "grepmax",
11
11
  "source": "./plugins/grepmax",
12
12
  "description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
13
- "version": "0.26.12",
13
+ "version": "0.26.13",
14
14
  "author": {
15
15
  "name": "Robert Owens",
16
16
  "email": "robowens@me.com"
@@ -198,7 +198,7 @@ exports.doctor = new commander_1.Command("doctor")
198
198
  let staleLock = false;
199
199
  const orphanedProjects = [];
200
200
  try {
201
- const { VectorDB } = yield Promise.resolve().then(() => __importStar(require("../lib/store/vector-db")));
201
+ const { VectorDB, findStaleLanceTempFiles, sweepStaleLanceTempFiles } = yield Promise.resolve().then(() => __importStar(require("../lib/store/vector-db")));
202
202
  const db = new VectorDB(config_1.PATHS.lancedbDir);
203
203
  const table = yield db.ensureTable();
204
204
  const totalChunks = yield table.countRows();
@@ -258,7 +258,16 @@ exports.doctor = new commander_1.Command("doctor")
258
258
  }
259
259
  }
260
260
  // Compute warning flags
261
- const bloatRatio = logicalSize > 0 ? diskSize / logicalSize : 0;
261
+ //
262
+ // Abandoned `.tmp*` scratch files are disk that optimize provably cannot
263
+ // reclaim (no manifest entry, so `deleteUnverified` will not touch them).
264
+ // They get their own remediation — a sweep — and are excluded from the
265
+ // ratio that decides whether to run a whole-table optimize, because
266
+ // counting them there prescribes a slow rewrite for bytes it cannot free.
267
+ const staleTempFiles = findStaleLanceTempFiles(config_1.PATHS.lancedbDir);
268
+ const staleTempBytes = staleTempFiles.reduce((n, f) => n + f.size, 0);
269
+ const reclaimableDiskSize = Math.max(0, diskSize - staleTempBytes);
270
+ const bloatRatio = logicalSize > 0 ? reclaimableDiskSize / logicalSize : 0;
262
271
  if (bloatRatio > 2.0)
263
272
  needsOptimize = true;
264
273
  // Track the compactor's own threshold. Warning (and worse, --fix forcing a
@@ -304,6 +313,8 @@ exports.doctor = new commander_1.Command("doctor")
304
313
  `lock=${lockStatus.split(" ")[0]}`,
305
314
  `daemon=${daemonUp ? "running" : "stopped"}`,
306
315
  `orphaned=${orphanedProjects.length}`,
316
+ `stale_temp=${staleTempFiles.length}`,
317
+ `stale_temp_bytes=${staleTempBytes}`,
307
318
  `stale_chunker=${staleChunkerProjects.length}`,
308
319
  `stale_embedding=${staleEmbeddingProjects.length}`,
309
320
  `legacy_embedding=${legacyEmbeddingCount}`,
@@ -380,11 +391,15 @@ exports.doctor = new commander_1.Command("doctor")
380
391
  console.log(`ok Disk: ${formatSize(availBytes)} available`);
381
392
  }
382
393
  // Storage
394
+ const storageLine = `${totalChunks.toLocaleString()} rows, ${formatSize(logicalSize)} logical, ${formatSize(diskSize)} disk`;
383
395
  if (bloatRatio > 2.0) {
384
- console.log(`WARN Storage: ${totalChunks.toLocaleString()} rows, ${formatSize(logicalSize)} logical, ${formatSize(diskSize)} disk (${bloatRatio.toFixed(1)}x — orphaned files)`);
396
+ console.log(`WARN Storage: ${storageLine} (${bloatRatio.toFixed(1)}x — orphaned files)`);
385
397
  }
386
398
  else {
387
- console.log(`ok Storage: ${totalChunks.toLocaleString()} rows, ${formatSize(logicalSize)} logical, ${formatSize(diskSize)} disk`);
399
+ console.log(`ok Storage: ${storageLine}`);
400
+ }
401
+ if (staleTempFiles.length > 0) {
402
+ console.log(`WARN Abandoned temp files: ${staleTempFiles.length} (${formatSize(staleTempBytes)}) — leftover from interrupted compaction, run with --fix`);
388
403
  }
389
404
  // Fragments
390
405
  if (numSmallFragments > config_1.FRAGMENT_COMPACT_THRESHOLD) {
@@ -500,6 +515,16 @@ exports.doctor = new commander_1.Command("doctor")
500
515
  console.log("ok Removed stale lock");
501
516
  fixed++;
502
517
  }
518
+ // Sweep before optimize: these bytes are unreachable by optimize, and
519
+ // clearing them first means any bloat still visible afterwards is real.
520
+ // Unlike optimize this needs no single-writer routing — the age gate in
521
+ // findStaleLanceTempFiles already excludes anything a writer could hold.
522
+ if (staleTempFiles.length > 0) {
523
+ const swept = sweepStaleLanceTempFiles(config_1.PATHS.lancedbDir);
524
+ if (!opts.agent)
525
+ console.log(`ok Removed ${swept.filesRemoved} abandoned temp file(s), freed ${formatSize(swept.bytesFreed)}`);
526
+ fixed++;
527
+ }
503
528
  if (needsOptimize) {
504
529
  if (!opts.agent)
505
530
  console.log("... Running optimize (compact + prune)...");
package/dist/config.js CHANGED
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.INDEXABLE_EXTENSIONS = exports.VERSION_PRUNE_THRESHOLD = exports.FRAGMENT_COMPACT_THRESHOLD = exports.DISK_LOW_BYTES = exports.DISK_CRITICAL_BYTES = exports.MAX_FILE_SIZE_BYTES = exports.PATHS = exports.MAX_WORKER_MEMORY_MB = exports.WORKER_BOOT_TIMEOUT_MS = exports.WORKER_TIMEOUT_MS = exports.REBUILD_COMMAND = exports.CHUNKER_VERSION_HISTORY = exports.CONFIG = exports.MODEL_IDS = exports.DEFAULT_MODEL_TIER = exports.MODEL_TIERS = void 0;
36
+ exports.INDEXABLE_EXTENSIONS = exports.STALE_TEMP_FILE_AGE_MS = exports.VERSION_PRUNE_THRESHOLD = exports.FRAGMENT_COMPACT_THRESHOLD = exports.DISK_LOW_BYTES = exports.DISK_CRITICAL_BYTES = exports.MAX_FILE_SIZE_BYTES = exports.PATHS = exports.MAX_WORKER_MEMORY_MB = exports.WORKER_BOOT_TIMEOUT_MS = exports.WORKER_TIMEOUT_MS = exports.REBUILD_COMMAND = exports.CHUNKER_VERSION_HISTORY = exports.CONFIG = exports.MODEL_IDS = exports.DEFAULT_MODEL_TIER = exports.MODEL_TIERS = void 0;
37
37
  exports.describeChunkerGap = describeChunkerGap;
38
38
  exports.describeEmbeddingGap = describeEmbeddingGap;
39
39
  exports.describeSchemaDimGap = describeSchemaDimGap;
@@ -281,6 +281,19 @@ exports.FRAGMENT_COMPACT_THRESHOLD = 400;
281
281
  // fire on every healthy store — and make `doctor --fix` force the whole-table
282
282
  // rewrite that the fragment threshold exists to avoid.
283
283
  exports.VERSION_PRUNE_THRESHOLD = 2000;
284
+ // Age past which a Lance `.tmp*` scratch file in a `*.lance/data` directory is
285
+ // considered abandoned rather than in flight. Lance writes a new fragment to a
286
+ // dot-prefixed temp file and renames it on success, so a killed optimize leaves
287
+ // the temp behind forever: it is in no manifest, so `optimize({deleteUnverified})`
288
+ // never collects it, and the dot prefix hides it from a plain `ls`. Three such
289
+ // files (7.3 GB, up to 2.5 months old) were what kept this store pinned at a 2.1x
290
+ // disk/logical ratio that repeated optimize passes could not move.
291
+ //
292
+ // The age gate is the whole safety argument: an in-flight write keeps bumping its
293
+ // temp file's mtime, so anything this stale has no writer, and deleting it is safe
294
+ // without knowing how many processes are on the store. Observed optimize runs on a
295
+ // 325k-row store peak at ~1.4 min, so an hour is ~40x the worst case seen.
296
+ exports.STALE_TEMP_FILE_AGE_MS = 60 * 60 * 1000;
284
297
  // Extensions we consider for indexing to avoid binary noise and improve relevance.
285
298
  exports.INDEXABLE_EXTENSIONS = new Set([
286
299
  ".ts",
@@ -43,6 +43,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
43
43
  };
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.VectorDB = exports.DiskPressureError = void 0;
46
+ exports.findStaleLanceTempFiles = findStaleLanceTempFiles;
47
+ exports.sweepStaleLanceTempFiles = sweepStaleLanceTempFiles;
46
48
  exports.isLanceCorruptionError = isLanceCorruptionError;
47
49
  exports.isMissingTableError = isMissingTableError;
48
50
  const fs = __importStar(require("node:fs"));
@@ -55,6 +57,73 @@ const filter_builder_1 = require("../utils/filter-builder");
55
57
  const logger_1 = require("../utils/logger");
56
58
  const ann_config_1 = require("./ann-config");
57
59
  const store_lease_1 = require("./store-lease");
60
+ /**
61
+ * Find abandoned Lance scratch files under `<lancedbDir>/ *.lance/data`.
62
+ *
63
+ * Lance stages a new fragment in a dot-prefixed `.tmp*` file and renames it into
64
+ * place on success. A killed or panicking optimize leaves the temp behind, and
65
+ * nothing ever collects it: it appears in no manifest, so `deleteUnverified`
66
+ * cannot prove it is garbage, and the leading dot hides it from `ls`. The result
67
+ * is permanent disk bloat that no amount of optimizing reclaims.
68
+ *
69
+ * Only files older than `minAgeMs` are reported. An in-flight write keeps
70
+ * bumping its temp file's mtime, so the age gate — not a writer count — is what
71
+ * makes deleting the result safe from any process.
72
+ */
73
+ function findStaleLanceTempFiles(lancedbDir, minAgeMs = config_1.STALE_TEMP_FILE_AGE_MS) {
74
+ const found = [];
75
+ const cutoff = Date.now() - minAgeMs;
76
+ let tables;
77
+ try {
78
+ tables = fs.readdirSync(lancedbDir);
79
+ }
80
+ catch (_a) {
81
+ return found;
82
+ }
83
+ for (const tableDir of tables) {
84
+ if (!tableDir.endsWith(".lance"))
85
+ continue;
86
+ const dataDir = `${lancedbDir}/${tableDir}/data`;
87
+ let entries;
88
+ try {
89
+ entries = fs.readdirSync(dataDir);
90
+ }
91
+ catch (_b) {
92
+ continue;
93
+ }
94
+ for (const entry of entries) {
95
+ if (!entry.startsWith(".tmp"))
96
+ continue;
97
+ const full = `${dataDir}/${entry}`;
98
+ try {
99
+ const s = fs.statSync(full);
100
+ if (!s.isFile() || s.mtimeMs > cutoff)
101
+ continue;
102
+ found.push({ path: full, size: s.size, mtimeMs: s.mtimeMs });
103
+ }
104
+ catch (_c) { }
105
+ }
106
+ }
107
+ return found;
108
+ }
109
+ /**
110
+ * Delete abandoned Lance scratch files. Returns bytes actually reclaimed —
111
+ * a file that vanishes between the scan and the unlink is not counted, so the
112
+ * number reported is never larger than what was freed.
113
+ */
114
+ function sweepStaleLanceTempFiles(lancedbDir, minAgeMs = config_1.STALE_TEMP_FILE_AGE_MS) {
115
+ let filesRemoved = 0;
116
+ let bytesFreed = 0;
117
+ for (const f of findStaleLanceTempFiles(lancedbDir, minAgeMs)) {
118
+ try {
119
+ fs.unlinkSync(f.path);
120
+ filesRemoved++;
121
+ bytesFreed += f.size;
122
+ }
123
+ catch (_a) { }
124
+ }
125
+ return { filesRemoved, bytesFreed };
126
+ }
58
127
  class DiskPressureError extends Error {
59
128
  constructor(message = "Disk critically low — writes suspended") {
60
129
  super(message);
@@ -1093,6 +1162,15 @@ class VectorDB {
1093
1162
  if (this.lastOptimizeDidWork) {
1094
1163
  const table = yield this.openExistingTableUnsafe();
1095
1164
  if (table) {
1165
+ // Collect abandoned scratch files before measuring. They are pure
1166
+ // disk with no manifest entry, so they inflate the ratio while being
1167
+ // invisible to optimize — measuring first would blame the compactor
1168
+ // for bytes it has no way to reach and send us into a second
1169
+ // full-table pass that reclaims nothing.
1170
+ const swept = sweepStaleLanceTempFiles(this.lancedbDir);
1171
+ if (swept.filesRemoved > 0) {
1172
+ (0, logger_1.log)("vectordb", `Swept ${swept.filesRemoved} abandoned temp file(s), freed ${(swept.bytesFreed / 1024 / 1024).toFixed(1)}MB`);
1173
+ }
1096
1174
  const stats = yield table.stats();
1097
1175
  const diskSize = this.getDirectorySize(this.lancedbDir);
1098
1176
  const logicalSize = stats.totalBytes;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.26.12",
3
+ "version": "0.26.13",
4
4
  "author": "Robert Owens <78518764+reowens@users.noreply.github.com>",
5
5
  "homepage": "https://github.com/reowens/grepmax",
6
6
  "bugs": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.26.12",
3
+ "version": "0.26.13",
4
4
  "description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
5
5
  "author": {
6
6
  "name": "Robert Owens",