grepmax 0.10.2 â 0.10.4
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/doctor.js
CHANGED
|
@@ -50,81 +50,257 @@ const commander_1 = require("commander");
|
|
|
50
50
|
const config_1 = require("../config");
|
|
51
51
|
const index_config_1 = require("../lib/index/index-config");
|
|
52
52
|
const exit_1 = require("../lib/utils/exit");
|
|
53
|
+
const lock_1 = require("../lib/utils/lock");
|
|
54
|
+
const project_registry_1 = require("../lib/utils/project-registry");
|
|
53
55
|
const project_root_1 = require("../lib/utils/project-root");
|
|
56
|
+
function formatSize(bytes) {
|
|
57
|
+
if (bytes < 1024)
|
|
58
|
+
return `${bytes} B`;
|
|
59
|
+
if (bytes < 1024 * 1024)
|
|
60
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
61
|
+
if (bytes < 1024 * 1024 * 1024)
|
|
62
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
63
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
64
|
+
}
|
|
65
|
+
function getDirectorySize(dirPath) {
|
|
66
|
+
let totalSize = 0;
|
|
67
|
+
try {
|
|
68
|
+
const items = fs.readdirSync(dirPath);
|
|
69
|
+
for (const item of items) {
|
|
70
|
+
const itemPath = path.join(dirPath, item);
|
|
71
|
+
const stats = fs.statSync(itemPath);
|
|
72
|
+
if (stats.isDirectory()) {
|
|
73
|
+
totalSize += getDirectorySize(itemPath);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
totalSize += stats.size;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (_a) { }
|
|
81
|
+
return totalSize;
|
|
82
|
+
}
|
|
54
83
|
exports.doctor = new commander_1.Command("doctor")
|
|
55
84
|
.description("Check installation health, models, and index status")
|
|
56
|
-
.
|
|
85
|
+
.option("--fix", "Auto-fix detected issues (compact, prune, remove stale locks)", false)
|
|
86
|
+
.option("--agent", "Compact output for AI agents", false)
|
|
87
|
+
.action((opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
57
88
|
var _a;
|
|
58
|
-
|
|
89
|
+
if (!opts.agent)
|
|
90
|
+
console.log("gmax Doctor\n");
|
|
59
91
|
const root = config_1.PATHS.globalRoot;
|
|
60
92
|
const models = config_1.PATHS.models;
|
|
61
93
|
const grammars = config_1.PATHS.grammars;
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
94
|
+
if (!opts.agent) {
|
|
95
|
+
const checkDir = (name, p) => {
|
|
96
|
+
const exists = fs.existsSync(p);
|
|
97
|
+
const symbol = exists ? "ok" : "MISSING";
|
|
98
|
+
console.log(`${symbol} ${name}: ${p}`);
|
|
99
|
+
};
|
|
100
|
+
checkDir("Root", root);
|
|
101
|
+
checkDir("Models", models);
|
|
102
|
+
checkDir("Grammars", grammars);
|
|
103
|
+
}
|
|
70
104
|
const globalConfig = (0, index_config_1.readGlobalConfig)();
|
|
71
105
|
const tier = (_a = config_1.MODEL_TIERS[globalConfig.modelTier]) !== null && _a !== void 0 ? _a : config_1.MODEL_TIERS.small;
|
|
72
106
|
const embedModel = globalConfig.embedMode === "gpu" ? tier.mlxModel : tier.onnxModel;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
107
|
+
if (!opts.agent) {
|
|
108
|
+
console.log(`\nEmbed mode: ${globalConfig.embedMode} | Model tier: ${globalConfig.modelTier} (${tier.vectorDim}d)`);
|
|
109
|
+
console.log(`Embed model: ${embedModel}`);
|
|
110
|
+
console.log(`ColBERT model: ${config_1.MODEL_IDS.colbert}`);
|
|
111
|
+
const modelStatuses = [embedModel, config_1.MODEL_IDS.colbert].map((id) => {
|
|
112
|
+
const modelPath = path.join(models, ...id.split("/"));
|
|
113
|
+
return { id, path: modelPath, exists: fs.existsSync(modelPath) };
|
|
114
|
+
});
|
|
115
|
+
modelStatuses.forEach(({ id, exists }) => {
|
|
116
|
+
console.log(`${exists ? "ok" : "WARN"} ${id}: ${exists ? "downloaded" : "will download on first use"}`);
|
|
117
|
+
});
|
|
118
|
+
console.log(`\nLocal Project: ${process.cwd()}`);
|
|
119
|
+
const projectRoot = (0, project_root_1.findProjectRoot)(process.cwd());
|
|
120
|
+
if (projectRoot) {
|
|
121
|
+
console.log(`ok Project root: ${projectRoot}`);
|
|
122
|
+
console.log(` Centralized index at: ~/.gmax/lancedb/`);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
console.log(`INFO No index found in current directory (run 'gmax index' to create one)`);
|
|
126
|
+
}
|
|
127
|
+
// Check MLX embed server
|
|
128
|
+
const embedUp = yield fetch("http://127.0.0.1:8100/health")
|
|
129
|
+
.then((r) => r.ok)
|
|
130
|
+
.catch(() => false);
|
|
131
|
+
console.log(`${embedUp ? "ok" : "WARN"} MLX Embed: ${embedUp ? "running (port 8100)" : "not running"}`);
|
|
132
|
+
// Check summarizer server
|
|
133
|
+
const summarizerUp = yield fetch("http://127.0.0.1:8101/health")
|
|
134
|
+
.then((r) => r.ok)
|
|
135
|
+
.catch(() => false);
|
|
136
|
+
console.log(`${summarizerUp ? "ok" : "WARN"} Summarizer: ${summarizerUp ? "running (port 8101)" : "not running"}`);
|
|
92
137
|
}
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
console.log(`${embedUp ? "â
" : "â ī¸ "} MLX Embed: ${embedUp ? "running (port 8100)" : "not running"}`);
|
|
98
|
-
// Check summarizer server
|
|
99
|
-
const summarizerUp = yield fetch("http://127.0.0.1:8101/health")
|
|
100
|
-
.then((r) => r.ok)
|
|
101
|
-
.catch(() => false);
|
|
102
|
-
console.log(`${summarizerUp ? "â
" : "â ī¸ "} Summarizer: ${summarizerUp ? "running (port 8101)" : "not running"}`);
|
|
103
|
-
// Check summary coverage
|
|
138
|
+
// --- Index Health ---
|
|
139
|
+
let needsOptimize = false;
|
|
140
|
+
let staleLock = false;
|
|
141
|
+
const orphanedProjects = [];
|
|
104
142
|
try {
|
|
105
143
|
const { VectorDB } = yield Promise.resolve().then(() => __importStar(require("../lib/store/vector-db")));
|
|
106
144
|
const db = new VectorDB(config_1.PATHS.lancedbDir);
|
|
107
145
|
const table = yield db.ensureTable();
|
|
108
146
|
const totalChunks = yield table.countRows();
|
|
109
|
-
|
|
147
|
+
// Summary coverage (existing check)
|
|
148
|
+
if (!opts.agent && totalChunks > 0) {
|
|
110
149
|
const withSummary = (yield table
|
|
111
150
|
.query()
|
|
112
151
|
.where("length(summary) > 5")
|
|
113
152
|
.select(["id"])
|
|
114
153
|
.toArray()).length;
|
|
115
154
|
const pct = Math.round((withSummary / totalChunks) * 100);
|
|
116
|
-
const symbol = pct >= 90 ? "
|
|
117
|
-
console.log(`${symbol}
|
|
155
|
+
const symbol = pct >= 90 ? "ok" : pct > 0 ? "WARN" : "FAIL";
|
|
156
|
+
console.log(`${symbol} Summary coverage: ${withSummary}/${totalChunks} (${pct}%)`);
|
|
157
|
+
}
|
|
158
|
+
else if (!opts.agent && totalChunks === 0) {
|
|
159
|
+
console.log("INFO No indexed chunks yet");
|
|
160
|
+
}
|
|
161
|
+
// Index health checks
|
|
162
|
+
const tableStats = yield table.stats();
|
|
163
|
+
const diskSize = getDirectorySize(config_1.PATHS.lancedbDir);
|
|
164
|
+
const logicalSize = tableStats.totalBytes;
|
|
165
|
+
const { numFragments, numSmallFragments } = tableStats.fragmentStats;
|
|
166
|
+
const versions = yield table.listVersions();
|
|
167
|
+
// Lock status
|
|
168
|
+
const lockPath = path.join(config_1.PATHS.globalRoot, "LOCK");
|
|
169
|
+
let lockStatus = "none";
|
|
170
|
+
if (fs.existsSync(lockPath)) {
|
|
171
|
+
const { pid, startedAt } = (0, lock_1.parseLock)(lockPath);
|
|
172
|
+
const alive = (0, lock_1.isProcessAlive)(pid);
|
|
173
|
+
if (alive) {
|
|
174
|
+
lockStatus = `active (PID ${pid})`;
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
lockStatus = `stale (PID ${pid}${startedAt ? ` @ ${startedAt}` : ""})`;
|
|
178
|
+
staleLock = true;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Daemon status
|
|
182
|
+
const { isDaemonRunning } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
|
|
183
|
+
const daemonUp = yield isDaemonRunning();
|
|
184
|
+
// Project registry health
|
|
185
|
+
const projects = (0, project_registry_1.listProjects)();
|
|
186
|
+
for (const p of projects) {
|
|
187
|
+
if (!fs.existsSync(p.root)) {
|
|
188
|
+
orphanedProjects.push(p.root);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// Compute warning flags
|
|
192
|
+
const bloatRatio = logicalSize > 0 ? diskSize / logicalSize : 0;
|
|
193
|
+
if (bloatRatio > 2.0)
|
|
194
|
+
needsOptimize = true;
|
|
195
|
+
if (numSmallFragments > 10)
|
|
196
|
+
needsOptimize = true;
|
|
197
|
+
if (versions.length > 50)
|
|
198
|
+
needsOptimize = true;
|
|
199
|
+
if (opts.agent) {
|
|
200
|
+
const fields = [
|
|
201
|
+
"index_health",
|
|
202
|
+
`rows=${totalChunks}`,
|
|
203
|
+
`logical=${formatSize(logicalSize)}`,
|
|
204
|
+
`disk=${formatSize(diskSize)}`,
|
|
205
|
+
`fragments=${numFragments}`,
|
|
206
|
+
`small=${numSmallFragments}`,
|
|
207
|
+
`versions=${versions.length}`,
|
|
208
|
+
`lock=${lockStatus.split(" ")[0]}`,
|
|
209
|
+
`daemon=${daemonUp ? "running" : "stopped"}`,
|
|
210
|
+
`orphaned=${orphanedProjects.length}`,
|
|
211
|
+
];
|
|
212
|
+
console.log(fields.join("\t"));
|
|
118
213
|
}
|
|
119
214
|
else {
|
|
120
|
-
console.log("
|
|
215
|
+
console.log("\nIndex Health\n");
|
|
216
|
+
// Storage
|
|
217
|
+
if (bloatRatio > 2.0) {
|
|
218
|
+
console.log(`WARN Storage: ${totalChunks.toLocaleString()} rows, ${formatSize(logicalSize)} logical, ${formatSize(diskSize)} disk (${bloatRatio.toFixed(1)}x â orphaned files)`);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
console.log(`ok Storage: ${totalChunks.toLocaleString()} rows, ${formatSize(logicalSize)} logical, ${formatSize(diskSize)} disk`);
|
|
222
|
+
}
|
|
223
|
+
// Fragments
|
|
224
|
+
if (numSmallFragments > 10) {
|
|
225
|
+
console.log(`WARN Fragments: ${numFragments} total, ${numSmallFragments} small â needs compaction`);
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
console.log(`ok Fragments: ${numFragments} total, ${numSmallFragments} small`);
|
|
229
|
+
}
|
|
230
|
+
// Versions
|
|
231
|
+
if (versions.length > 50) {
|
|
232
|
+
console.log(`WARN Versions: ${versions.length} â pruning recommended`);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
console.log(`ok Versions: ${versions.length}`);
|
|
236
|
+
}
|
|
237
|
+
// Lock
|
|
238
|
+
if (staleLock) {
|
|
239
|
+
console.log(`WARN Lock: ${lockStatus}`);
|
|
240
|
+
}
|
|
241
|
+
else if (lockStatus === "none") {
|
|
242
|
+
console.log("ok Lock: none");
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
console.log(`ok Lock: ${lockStatus}`);
|
|
246
|
+
}
|
|
247
|
+
// Daemon
|
|
248
|
+
console.log(`${daemonUp ? "ok" : "INFO"} Daemon: ${daemonUp ? "running" : "not running"}`);
|
|
249
|
+
// Projects
|
|
250
|
+
if (orphanedProjects.length > 0) {
|
|
251
|
+
console.log(`WARN Orphaned projects: ${orphanedProjects.length} (directories no longer exist)`);
|
|
252
|
+
for (const op of orphanedProjects) {
|
|
253
|
+
console.log(` - ${op}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
else if (projects.length > 0) {
|
|
257
|
+
console.log(`ok Projects: ${projects.length} registered, all directories exist`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// --fix auto-remediation
|
|
261
|
+
if (opts.fix) {
|
|
262
|
+
if (!opts.agent)
|
|
263
|
+
console.log("\nAuto-fix\n");
|
|
264
|
+
let fixed = 0;
|
|
265
|
+
if (staleLock) {
|
|
266
|
+
yield (0, lock_1.removeLock)(lockPath);
|
|
267
|
+
if (!opts.agent)
|
|
268
|
+
console.log("ok Removed stale lock");
|
|
269
|
+
fixed++;
|
|
270
|
+
}
|
|
271
|
+
if (needsOptimize) {
|
|
272
|
+
if (!opts.agent)
|
|
273
|
+
console.log("... Running optimize (compact + prune)...");
|
|
274
|
+
yield db.optimize(3, 0);
|
|
275
|
+
if (!opts.agent)
|
|
276
|
+
console.log("ok Optimize complete");
|
|
277
|
+
fixed++;
|
|
278
|
+
}
|
|
279
|
+
if (orphanedProjects.length > 0) {
|
|
280
|
+
for (const op of orphanedProjects) {
|
|
281
|
+
(0, project_registry_1.removeProject)(op);
|
|
282
|
+
}
|
|
283
|
+
if (!opts.agent)
|
|
284
|
+
console.log(`ok Removed ${orphanedProjects.length} orphaned project(s) from registry`);
|
|
285
|
+
fixed++;
|
|
286
|
+
}
|
|
287
|
+
if (fixed === 0) {
|
|
288
|
+
if (!opts.agent)
|
|
289
|
+
console.log("ok Nothing to fix");
|
|
290
|
+
}
|
|
121
291
|
}
|
|
122
292
|
yield db.close();
|
|
123
293
|
}
|
|
124
294
|
catch (_b) {
|
|
125
|
-
|
|
295
|
+
if (opts.agent) {
|
|
296
|
+
console.log("index_health\terror=could_not_check");
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
console.log("\nWARN Could not check index health");
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (!opts.agent) {
|
|
303
|
+
console.log(`\nSystem: ${os.platform()} ${os.arch()} | Node: ${process.version}`);
|
|
126
304
|
}
|
|
127
|
-
console.log(`\nSystem: ${os.platform()} ${os.arch()} | Node: ${process.version}`);
|
|
128
|
-
console.log("\nIf you see â
everywhere, you are ready to search!");
|
|
129
305
|
yield (0, exit_1.gracefulExit)();
|
|
130
306
|
}));
|
|
@@ -81,11 +81,10 @@ class ProjectBatchProcessor {
|
|
|
81
81
|
if (this.closed || this.processing)
|
|
82
82
|
return;
|
|
83
83
|
try {
|
|
84
|
-
yield this.vectorDb.
|
|
85
|
-
yield this.vectorDb.optimize();
|
|
84
|
+
yield this.vectorDb.runMaintenance();
|
|
86
85
|
}
|
|
87
86
|
catch (err) {
|
|
88
|
-
console.error(`[${this.wtag}]
|
|
87
|
+
console.error(`[${this.wtag}] Maintenance failed:`, err);
|
|
89
88
|
}
|
|
90
89
|
}), FTS_REBUILD_INTERVAL_MS);
|
|
91
90
|
this.ftsInterval.unref();
|
package/dist/lib/index/syncer.js
CHANGED
|
@@ -56,6 +56,7 @@ class VectorDB {
|
|
|
56
56
|
this.lancedbDir = lancedbDir;
|
|
57
57
|
this.db = null;
|
|
58
58
|
this.closed = false;
|
|
59
|
+
this.maintenanceRunning = false;
|
|
59
60
|
this.vectorDim = vectorDim !== null && vectorDim !== void 0 ? vectorDim : config_1.CONFIG.VECTOR_DIM;
|
|
60
61
|
this.unregisterCleanup = (0, cleanup_1.registerCleanup)(() => this.close());
|
|
61
62
|
}
|
|
@@ -275,21 +276,69 @@ class VectorDB {
|
|
|
275
276
|
});
|
|
276
277
|
}
|
|
277
278
|
optimize() {
|
|
278
|
-
return __awaiter(this,
|
|
279
|
+
return __awaiter(this, arguments, void 0, function* (retries = 3, retentionMs = 0) {
|
|
279
280
|
const table = yield this.ensureTable();
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
281
|
+
const cutoff = new Date(Date.now() - retentionMs);
|
|
282
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
283
|
+
try {
|
|
284
|
+
const done = (0, logger_1.timer)("vectordb", "optimize");
|
|
285
|
+
const stats = yield table.optimize({
|
|
286
|
+
cleanupOlderThan: cutoff,
|
|
287
|
+
deleteUnverified: true,
|
|
288
|
+
});
|
|
289
|
+
done();
|
|
290
|
+
const { compaction, prune } = stats;
|
|
291
|
+
if (compaction.fragmentsRemoved > 0 ||
|
|
292
|
+
prune.oldVersionsRemoved > 0 ||
|
|
293
|
+
prune.bytesRemoved > 0) {
|
|
294
|
+
(0, logger_1.log)("vectordb", `Compacted: ${compaction.fragmentsRemoved} frags â ${compaction.fragmentsAdded}, ` +
|
|
295
|
+
`pruned ${prune.oldVersionsRemoved} versions, ` +
|
|
296
|
+
`freed ${(prune.bytesRemoved / 1024 / 1024).toFixed(1)}MB`);
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
(0, logger_1.debug)("vectordb", "Optimize: nothing to compact or prune");
|
|
300
|
+
}
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
catch (e) {
|
|
304
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
305
|
+
if (msg.includes("Nothing to do")) {
|
|
306
|
+
(0, logger_1.debug)("vectordb", "Optimize: nothing to do");
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (attempt < retries &&
|
|
310
|
+
(msg.includes("conflict") || msg.includes("Retryable"))) {
|
|
311
|
+
const delay = 1000 * Math.pow(2, (attempt - 1));
|
|
312
|
+
(0, logger_1.log)("vectordb", `Optimize conflict (attempt ${attempt}/${retries}), retrying in ${delay}ms`);
|
|
313
|
+
yield new Promise((r) => setTimeout(r, delay));
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
288
316
|
(0, logger_1.log)("vectordb", `Optimize failed: ${msg}`);
|
|
317
|
+
return;
|
|
289
318
|
}
|
|
290
319
|
}
|
|
291
320
|
});
|
|
292
321
|
}
|
|
322
|
+
/**
|
|
323
|
+
* Run FTS rebuild + optimize as a single serialized operation.
|
|
324
|
+
* Safe to call from multiple project processors â only one runs at a time.
|
|
325
|
+
*/
|
|
326
|
+
runMaintenance() {
|
|
327
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
328
|
+
if (this.maintenanceRunning) {
|
|
329
|
+
(0, logger_1.debug)("vectordb", "Maintenance already running, skipping");
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
this.maintenanceRunning = true;
|
|
333
|
+
try {
|
|
334
|
+
yield this.createFTSIndex();
|
|
335
|
+
yield this.optimize();
|
|
336
|
+
}
|
|
337
|
+
finally {
|
|
338
|
+
this.maintenanceRunning = false;
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
}
|
|
293
342
|
hasAnyRows() {
|
|
294
343
|
return __awaiter(this, void 0, void 0, function* () {
|
|
295
344
|
const table = yield this.ensureTable();
|
package/dist/lib/utils/lock.js
CHANGED
|
@@ -42,6 +42,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
42
42
|
});
|
|
43
43
|
};
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.parseLock = parseLock;
|
|
46
|
+
exports.isProcessAlive = isProcessAlive;
|
|
47
|
+
exports.removeLock = removeLock;
|
|
45
48
|
exports.acquireWriterLock = acquireWriterLock;
|
|
46
49
|
exports.acquireWriterLockWithRetry = acquireWriterLockWithRetry;
|
|
47
50
|
exports.isLocked = isLocked;
|
package/package.json
CHANGED