grepmax 0.24.0 → 0.25.0
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 +11 -6
- package/dist/commands/add.js +38 -31
- package/dist/commands/config.js +16 -15
- package/dist/commands/context.js +38 -13
- package/dist/commands/doctor.js +76 -83
- package/dist/commands/extract.js +12 -1
- package/dist/commands/index.js +23 -24
- package/dist/commands/list.js +23 -14
- package/dist/commands/mcp.js +278 -143
- package/dist/commands/peek.js +16 -5
- package/dist/commands/repair.js +54 -120
- package/dist/commands/search-output.js +30 -9
- package/dist/commands/search-run.js +75 -47
- package/dist/commands/search-skeletons.js +28 -18
- package/dist/commands/search.js +45 -49
- package/dist/commands/serve.js +415 -373
- package/dist/commands/setup.js +2 -2
- package/dist/commands/similar.js +5 -5
- package/dist/commands/skeleton.js +67 -41
- package/dist/commands/status.js +5 -2
- package/dist/commands/summarize.js +6 -0
- package/dist/commands/watch.js +102 -38
- package/dist/config.js +3 -0
- package/dist/lib/daemon/daemon.js +1101 -379
- package/dist/lib/daemon/ipc-handler.js +122 -13
- package/dist/lib/daemon/mlx-server-manager.js +268 -125
- package/dist/lib/daemon/process-manager.js +7 -4
- package/dist/lib/daemon/search-handler.js +23 -9
- package/dist/lib/daemon/watcher-manager.js +353 -110
- package/dist/lib/index/batch-processor.js +231 -67
- package/dist/lib/index/embedding-generation.js +109 -0
- package/dist/lib/index/embedding-status.js +23 -0
- package/dist/lib/index/file-policy.js +273 -0
- package/dist/lib/index/ignore-patterns.js +4 -0
- package/dist/lib/index/index-config.js +18 -4
- package/dist/lib/index/syncer.js +256 -79
- package/dist/lib/index/walker.js +66 -86
- package/dist/lib/index/watcher-batch.js +9 -0
- package/dist/lib/index/watcher.js +129 -3
- package/dist/lib/llm/server.js +6 -0
- package/dist/lib/search/searcher.js +30 -19
- package/dist/lib/store/store-lease.js +473 -0
- package/dist/lib/store/vector-db.js +302 -57
- package/dist/lib/utils/blocked-roots.js +63 -0
- package/dist/lib/utils/cross-project.js +7 -3
- package/dist/lib/utils/daemon-client.js +24 -1
- package/dist/lib/utils/daemon-launcher.js +38 -13
- package/dist/lib/utils/doctor-status.js +76 -0
- package/dist/lib/utils/file-utils.js +74 -4
- package/dist/lib/utils/keyed-mutex.js +101 -0
- package/dist/lib/utils/logger.js +57 -1
- package/dist/lib/utils/mlx-hf-cache.js +114 -0
- package/dist/lib/utils/operation-coordinator.js +146 -0
- package/dist/lib/utils/path-containment.js +106 -0
- package/dist/lib/utils/process.js +44 -0
- package/dist/lib/utils/project-registry.js +351 -3
- package/dist/lib/utils/scope-filter.js +3 -9
- package/dist/lib/utils/stale-hint.js +12 -19
- package/dist/lib/utils/watcher-launcher.js +5 -1
- package/dist/lib/utils/watcher-store.js +2 -2
- package/dist/lib/workers/colbert-math.js +15 -12
- package/dist/lib/workers/embeddings/colbert.js +3 -2
- package/dist/lib/workers/embeddings/granite.js +4 -3
- package/dist/lib/workers/embeddings/mlx-client.js +59 -16
- package/dist/lib/workers/orchestrator.js +39 -8
- package/dist/lib/workers/pool.js +146 -82
- package/dist/lib/workers/process-child.js +11 -2
- package/dist/lib/workers/serialized-handler.js +10 -0
- package/dist/lib/workers/worker.js +13 -4
- package/mlx-embed-server/server.py +21 -3
- package/mlx-embed-server/summarizer.py +8 -0
- package/package.json +4 -3
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
- package/plugins/grepmax/hooks/start.js +3 -170
package/dist/commands/setup.js
CHANGED
|
@@ -142,10 +142,10 @@ exports.setup = new commander_1.Command("setup")
|
|
|
142
142
|
const tierChanged = existingConfig.modelTier !== modelTier;
|
|
143
143
|
const modeChanged = existingConfig.embedMode !== embedMode;
|
|
144
144
|
if (tierChanged) {
|
|
145
|
-
p.log.warn(`Model tier changed (${(_d = existingConfig.vectorDim) !== null && _d !== void 0 ? _d : 384}d → ${selectedTier.vectorDim}d). Existing indexes
|
|
145
|
+
p.log.warn(`Model tier changed (${(_d = existingConfig.vectorDim) !== null && _d !== void 0 ? _d : 384}d → ${selectedTier.vectorDim}d). Existing indexes are preserved and search is blocked until you run 'gmax repair --rebuild'.`);
|
|
146
146
|
}
|
|
147
147
|
else if (modeChanged) {
|
|
148
|
-
p.log.warn("Embedding mode changed.
|
|
148
|
+
p.log.warn("Embedding mode changed. Restart the gmax daemon to apply the new settings.");
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
// Step 8: Install plugins for detected clients
|
package/dist/commands/similar.js
CHANGED
|
@@ -43,13 +43,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
43
43
|
};
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
45
|
exports.similar = void 0;
|
|
46
|
-
const path = __importStar(require("node:path"));
|
|
47
46
|
const commander_1 = require("commander");
|
|
48
47
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
49
48
|
const agent_errors_1 = require("../lib/utils/agent-errors");
|
|
50
49
|
const arrow_1 = require("../lib/utils/arrow");
|
|
51
50
|
const exit_1 = require("../lib/utils/exit");
|
|
52
51
|
const filter_builder_1 = require("../lib/utils/filter-builder");
|
|
52
|
+
const path_containment_1 = require("../lib/utils/path-containment");
|
|
53
53
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
54
54
|
const project_root_1 = require("../lib/utils/project-root");
|
|
55
55
|
const stale_hint_1 = require("../lib/utils/stale-hint");
|
|
@@ -81,9 +81,9 @@ exports.similar = new commander_1.Command("similar")
|
|
|
81
81
|
// Look up the source chunk's vector
|
|
82
82
|
let sourceRows;
|
|
83
83
|
if (isFile) {
|
|
84
|
-
const absPath =
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
const absPath = (0, path_containment_1.resolveContainedPath)(projectRoot, target, {
|
|
85
|
+
verifyExistingTarget: true,
|
|
86
|
+
});
|
|
87
87
|
sourceRows = yield table
|
|
88
88
|
.query()
|
|
89
89
|
.select(["vector", "path", "defined_symbols", "start_line"])
|
|
@@ -95,7 +95,7 @@ exports.similar = new commander_1.Command("similar")
|
|
|
95
95
|
sourceRows = yield table
|
|
96
96
|
.query()
|
|
97
97
|
.select(["vector", "path", "defined_symbols", "start_line"])
|
|
98
|
-
.where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(target)}')`)
|
|
98
|
+
.where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(target)}') AND ${(0, filter_builder_1.pathStartsWith)(`${projectRoot}/`)}`)
|
|
99
99
|
.limit(1)
|
|
100
100
|
.toArray();
|
|
101
101
|
}
|
|
@@ -52,8 +52,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
52
52
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
53
|
exports.skeleton = void 0;
|
|
54
54
|
const fs = __importStar(require("node:fs"));
|
|
55
|
-
const path = __importStar(require("node:path"));
|
|
56
55
|
const commander_1 = require("commander");
|
|
56
|
+
const config_1 = require("../config");
|
|
57
57
|
const sync_helpers_1 = require("../lib/index/sync-helpers");
|
|
58
58
|
const syncer_1 = require("../lib/index/syncer");
|
|
59
59
|
const searcher_1 = require("../lib/search/searcher");
|
|
@@ -62,20 +62,11 @@ const retriever_1 = require("../lib/skeleton/retriever");
|
|
|
62
62
|
const skeletonizer_1 = require("../lib/skeleton/skeletonizer");
|
|
63
63
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
64
64
|
const exit_1 = require("../lib/utils/exit");
|
|
65
|
+
const file_utils_1 = require("../lib/utils/file-utils");
|
|
66
|
+
const filter_builder_1 = require("../lib/utils/filter-builder");
|
|
67
|
+
const path_containment_1 = require("../lib/utils/path-containment");
|
|
65
68
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
66
69
|
const project_root_1 = require("../lib/utils/project-root");
|
|
67
|
-
/**
|
|
68
|
-
* Resolve a relative path across all indexed projects.
|
|
69
|
-
* Returns the first match found, or null.
|
|
70
|
-
*/
|
|
71
|
-
function resolveAcrossProjects(relativePath) {
|
|
72
|
-
for (const project of (0, project_registry_1.listProjects)()) {
|
|
73
|
-
const candidate = path.join(project.root, relativePath);
|
|
74
|
-
if (fs.existsSync(candidate))
|
|
75
|
-
return candidate;
|
|
76
|
-
}
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
70
|
/**
|
|
80
71
|
* Check if target looks like a file path.
|
|
81
72
|
*/
|
|
@@ -94,12 +85,16 @@ function isSymbolLike(target) {
|
|
|
94
85
|
/**
|
|
95
86
|
* Find a file by symbol name in the index.
|
|
96
87
|
*/
|
|
97
|
-
function findFileBySymbol(symbol, db) {
|
|
88
|
+
function findFileBySymbol(symbol, db, projectRoot) {
|
|
98
89
|
return __awaiter(this, void 0, void 0, function* () {
|
|
99
90
|
try {
|
|
100
91
|
const table = yield db.ensureTable();
|
|
101
92
|
// Search for files that define this symbol
|
|
102
|
-
const results = yield table
|
|
93
|
+
const results = yield table
|
|
94
|
+
.search(symbol)
|
|
95
|
+
.where((0, filter_builder_1.pathStartsWith)(`${projectRoot}/`))
|
|
96
|
+
.limit(10)
|
|
97
|
+
.toArray();
|
|
103
98
|
// Find a result where this symbol is defined
|
|
104
99
|
for (const result of results) {
|
|
105
100
|
const defined = result.defined_symbols;
|
|
@@ -144,8 +139,26 @@ Examples:
|
|
|
144
139
|
// Sync if requested
|
|
145
140
|
if (options.sync) {
|
|
146
141
|
const { spinner, onProgress } = (0, sync_helpers_1.createIndexingSpinner)(projectRoot, "Syncing...", { verbose: false });
|
|
147
|
-
yield (0, syncer_1.initialSync)({ projectRoot, onProgress });
|
|
148
|
-
|
|
142
|
+
const result = yield (0, syncer_1.initialSync)({ projectRoot, onProgress });
|
|
143
|
+
if (result.degraded) {
|
|
144
|
+
spinner.warn(`Sync incomplete: ${result.scanErrors} scan error(s), ${result.failedFiles} file failure(s)`);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
const prefix = projectRoot.endsWith("/")
|
|
148
|
+
? projectRoot
|
|
149
|
+
: `${projectRoot}/`;
|
|
150
|
+
const chunkCount = yield vectorDb.countRowsForPath(prefix);
|
|
151
|
+
(0, project_registry_1.stampProjectFullSync)({
|
|
152
|
+
root: projectRoot,
|
|
153
|
+
generation: result.generation,
|
|
154
|
+
embedMode: result.embedMode,
|
|
155
|
+
chunkCount,
|
|
156
|
+
chunkerVersion: config_1.CONFIG.CHUNKER_VERSION,
|
|
157
|
+
expectedFingerprint: result.registryExpectation.embeddingFingerprint,
|
|
158
|
+
expectedRebuildId: result.registryExpectation.rebuildId,
|
|
159
|
+
});
|
|
160
|
+
spinner.succeed("Sync complete");
|
|
161
|
+
}
|
|
149
162
|
}
|
|
150
163
|
// Initialize skeletonizer
|
|
151
164
|
const skeletonizer = new skeletonizer_1.Skeletonizer();
|
|
@@ -154,7 +167,9 @@ Examples:
|
|
|
154
167
|
includeSummary: !options.noSummary,
|
|
155
168
|
};
|
|
156
169
|
// Determine mode based on target
|
|
157
|
-
const resolvedTarget =
|
|
170
|
+
const resolvedTarget = (0, path_containment_1.resolveContainedPath)(projectRoot, target, {
|
|
171
|
+
verifyExistingTarget: true,
|
|
172
|
+
});
|
|
158
173
|
// Directory mode is unsupported. Auto-picking files from a directory
|
|
159
174
|
// was confusingly magical (and on '.' it fell through to the resolver
|
|
160
175
|
// path and skeletonized .gitignore). Refuse and point at the file form.
|
|
@@ -176,12 +191,14 @@ Examples:
|
|
|
176
191
|
.map((t) => t.trim())
|
|
177
192
|
.filter(Boolean);
|
|
178
193
|
for (const t of targets) {
|
|
179
|
-
const filePath =
|
|
194
|
+
const filePath = (0, path_containment_1.resolveContainedPath)(projectRoot, t, {
|
|
195
|
+
verifyExistingTarget: true,
|
|
196
|
+
});
|
|
180
197
|
if (!fs.existsSync(filePath)) {
|
|
181
198
|
console.error(`Not found: ${t}`);
|
|
182
199
|
continue;
|
|
183
200
|
}
|
|
184
|
-
const content =
|
|
201
|
+
const content = (0, file_utils_1.readContainedTextFileSync)(projectRoot, filePath);
|
|
185
202
|
const result = yield skeletonizer.skeletonizeFile(filePath, content, skeletonOpts);
|
|
186
203
|
outputResult(result, options);
|
|
187
204
|
}
|
|
@@ -189,16 +206,13 @@ Examples:
|
|
|
189
206
|
}
|
|
190
207
|
if (isFilePath(target)) {
|
|
191
208
|
// === FILE MODE ===
|
|
192
|
-
|
|
209
|
+
const filePath = (0, path_containment_1.resolveContainedPath)(projectRoot, target, {
|
|
210
|
+
verifyExistingTarget: true,
|
|
211
|
+
});
|
|
193
212
|
if (!fs.existsSync(filePath)) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
console.error(`File not found: ${filePath}`);
|
|
198
|
-
process.exitCode = 1;
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
filePath = found;
|
|
213
|
+
console.error(`File not found: ${filePath}`);
|
|
214
|
+
process.exitCode = 1;
|
|
215
|
+
return;
|
|
202
216
|
}
|
|
203
217
|
if (vectorDb) {
|
|
204
218
|
// Use absolute path for DB lookup (centralized index stores absolute paths)
|
|
@@ -212,13 +226,13 @@ Examples:
|
|
|
212
226
|
return;
|
|
213
227
|
}
|
|
214
228
|
}
|
|
215
|
-
const content =
|
|
229
|
+
const content = (0, file_utils_1.readContainedTextFileSync)(projectRoot, filePath);
|
|
216
230
|
const result = yield skeletonizer.skeletonizeFile(filePath, content, skeletonOpts);
|
|
217
231
|
outputResult(result, options);
|
|
218
232
|
}
|
|
219
233
|
else if (isSymbolLike(target) && !target.includes(" ")) {
|
|
220
234
|
// === SYMBOL MODE ===
|
|
221
|
-
const filePath = yield findFileBySymbol(target, vectorDb);
|
|
235
|
+
const filePath = yield findFileBySymbol(target, vectorDb, projectRoot);
|
|
222
236
|
if (!filePath) {
|
|
223
237
|
console.error(`Symbol not found in index: ${target}`);
|
|
224
238
|
console.error("Try running 'gmax index' first or use a search query.");
|
|
@@ -226,9 +240,9 @@ Examples:
|
|
|
226
240
|
return;
|
|
227
241
|
}
|
|
228
242
|
// filePath from DB is absolute (centralized index)
|
|
229
|
-
const absolutePath =
|
|
230
|
-
|
|
231
|
-
|
|
243
|
+
const absolutePath = (0, path_containment_1.resolveContainedPath)(projectRoot, filePath, {
|
|
244
|
+
verifyExistingTarget: true,
|
|
245
|
+
});
|
|
232
246
|
if (!fs.existsSync(absolutePath)) {
|
|
233
247
|
console.error(`File not found: ${absolutePath}`);
|
|
234
248
|
process.exitCode = 1;
|
|
@@ -243,7 +257,7 @@ Examples:
|
|
|
243
257
|
}, options);
|
|
244
258
|
return;
|
|
245
259
|
}
|
|
246
|
-
const content =
|
|
260
|
+
const content = (0, file_utils_1.readContainedTextFileSync)(projectRoot, absolutePath);
|
|
247
261
|
const result = yield skeletonizer.skeletonizeFile(absolutePath, content, skeletonOpts);
|
|
248
262
|
outputResult(result, options);
|
|
249
263
|
}
|
|
@@ -251,7 +265,7 @@ Examples:
|
|
|
251
265
|
// === QUERY MODE ===
|
|
252
266
|
const searcher = new searcher_1.Searcher(vectorDb);
|
|
253
267
|
const limit = Math.min(Number.parseInt(options.limit, 10) || 3, 10);
|
|
254
|
-
const searchResults = yield searcher.search(target, limit);
|
|
268
|
+
const searchResults = yield searcher.search(target, limit, {}, {}, `${projectRoot}/`);
|
|
255
269
|
if (!searchResults.data || searchResults.data.length === 0) {
|
|
256
270
|
console.error(`No results found for: ${target}`);
|
|
257
271
|
process.exitCode = 1;
|
|
@@ -273,9 +287,21 @@ Examples:
|
|
|
273
287
|
const results = [];
|
|
274
288
|
for (const filePath of filePaths) {
|
|
275
289
|
// Paths from search results are absolute (centralized index)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
290
|
+
let absolutePath;
|
|
291
|
+
try {
|
|
292
|
+
absolutePath = (0, path_containment_1.resolveContainedPath)(projectRoot, filePath, {
|
|
293
|
+
verifyExistingTarget: true,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
catch (_c) {
|
|
297
|
+
results.push({
|
|
298
|
+
file: filePath,
|
|
299
|
+
skeleton: `// File outside selected project: ${filePath}`,
|
|
300
|
+
tokens: 0,
|
|
301
|
+
error: "File outside selected project",
|
|
302
|
+
});
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
279
305
|
if (!fs.existsSync(absolutePath)) {
|
|
280
306
|
results.push({
|
|
281
307
|
file: filePath,
|
|
@@ -295,7 +321,7 @@ Examples:
|
|
|
295
321
|
});
|
|
296
322
|
continue;
|
|
297
323
|
}
|
|
298
|
-
const content =
|
|
324
|
+
const content = (0, file_utils_1.readContainedTextFileSync)(projectRoot, absolutePath);
|
|
299
325
|
const result = yield skeletonizer.skeletonizeFile(absolutePath, content, skeletonOpts);
|
|
300
326
|
results.push({
|
|
301
327
|
file: filePath,
|
|
@@ -326,7 +352,7 @@ Examples:
|
|
|
326
352
|
try {
|
|
327
353
|
yield vectorDb.close();
|
|
328
354
|
}
|
|
329
|
-
catch (
|
|
355
|
+
catch (_d) {
|
|
330
356
|
// Ignore close errors
|
|
331
357
|
}
|
|
332
358
|
}
|
package/dist/commands/status.js
CHANGED
|
@@ -46,6 +46,7 @@ exports.status = void 0;
|
|
|
46
46
|
const os = __importStar(require("node:os"));
|
|
47
47
|
const commander_1 = require("commander");
|
|
48
48
|
const config_1 = require("../config");
|
|
49
|
+
const embedding_status_1 = require("../lib/index/embedding-status");
|
|
49
50
|
const index_config_1 = require("../lib/index/index-config");
|
|
50
51
|
const exit_1 = require("../lib/utils/exit");
|
|
51
52
|
const filter_builder_1 = require("../lib/utils/filter-builder");
|
|
@@ -157,7 +158,8 @@ Examples:
|
|
|
157
158
|
st = "idle";
|
|
158
159
|
const isCurrent = project.root === currentRoot;
|
|
159
160
|
const count = (_b = chunkCounts.get(project.root)) !== null && _b !== void 0 ? _b : project.chunkCount;
|
|
160
|
-
|
|
161
|
+
const identity = (0, embedding_status_1.projectEmbeddingStatus)(project, globalConfig);
|
|
162
|
+
console.log(`${project.name}\t${formatChunks(count)}\t${formatAge(project.lastIndexed)}\t${st}\tembedding=${identity.state}${isCurrent ? "\tcurrent" : ""}`);
|
|
161
163
|
}
|
|
162
164
|
yield (0, exit_1.gracefulExit)();
|
|
163
165
|
return;
|
|
@@ -191,10 +193,11 @@ Examples:
|
|
|
191
193
|
const chunks = `${formatChunks(count)} chunks`;
|
|
192
194
|
// Age column
|
|
193
195
|
const age = formatAge(project.lastIndexed);
|
|
196
|
+
const embedding = (0, embedding_status_1.projectEmbeddingStatus)(project, globalConfig).state;
|
|
194
197
|
// Current marker
|
|
195
198
|
const marker = isCurrent ? style.cyan(" ←") : "";
|
|
196
199
|
const name = project.name.padEnd(nameWidth);
|
|
197
|
-
console.log(` ${name} ${chunks.padEnd(12)} ${age.padEnd(10)} ${statusStr}${marker}`);
|
|
200
|
+
console.log(` ${name} ${chunks.padEnd(12)} ${age.padEnd(10)} ${statusStr} embedding:${embedding}${marker}`);
|
|
198
201
|
}
|
|
199
202
|
if (currentRoot) {
|
|
200
203
|
console.log(`\n${style.dim("Current")}: ${shortenPath(currentRoot)}`);
|
|
@@ -50,6 +50,12 @@ const syncer_1 = require("../lib/index/syncer");
|
|
|
50
50
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
51
51
|
const exit_1 = require("../lib/utils/exit");
|
|
52
52
|
const project_root_1 = require("../lib/utils/project-root");
|
|
53
|
+
// ⛔ HARD STOP FOR AI AGENTS: The summarizer is decommissioned on purpose.
|
|
54
|
+
// `generateSummaries` (src/lib/index/syncer.ts) is a no-op stub, so this
|
|
55
|
+
// command intentionally does nothing. DO NOT re-enable it, and DO NOT start
|
|
56
|
+
// the summarizer server (mlx-embed-server/summarizer.py, port 8101) or load its
|
|
57
|
+
// ~16GB model to "measure"/"benchmark" — doing so has crashed the machine.
|
|
58
|
+
// Stop and get the user's explicit authorization first. See CLAUDE.md.
|
|
53
59
|
exports.summarize = new commander_1.Command("summarize")
|
|
54
60
|
.description("Generate LLM summaries for indexed chunks without re-indexing")
|
|
55
61
|
.option("-p, --path <dir>", "Only summarize chunks under this directory")
|
package/dist/commands/watch.js
CHANGED
|
@@ -48,7 +48,6 @@ const fs = __importStar(require("node:fs"));
|
|
|
48
48
|
const path = __importStar(require("node:path"));
|
|
49
49
|
const commander_1 = require("commander");
|
|
50
50
|
const config_1 = require("../config");
|
|
51
|
-
const index_config_1 = require("../lib/index/index-config");
|
|
52
51
|
const syncer_1 = require("../lib/index/syncer");
|
|
53
52
|
const watcher_1 = require("../lib/index/watcher");
|
|
54
53
|
const meta_cache_1 = require("../lib/store/meta-cache");
|
|
@@ -122,19 +121,51 @@ exports.watch = new commander_1.Command("watch")
|
|
|
122
121
|
}
|
|
123
122
|
const logFile = path.join(config_1.PATHS.logsDir, "daemon.log");
|
|
124
123
|
const out = (0, log_rotate_1.openRotatedLog)(logFile);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
124
|
+
let child;
|
|
125
|
+
try {
|
|
126
|
+
child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], "watch", "--daemon"], {
|
|
127
|
+
detached: true,
|
|
128
|
+
stdio: ["ignore", out, out],
|
|
129
|
+
cwd: process.cwd(),
|
|
130
|
+
env: Object.assign(Object.assign({}, process.env), { GMAX_BACKGROUND: "true" }),
|
|
131
|
+
});
|
|
132
|
+
yield new Promise((resolve, reject) => {
|
|
133
|
+
child.once("spawn", resolve);
|
|
134
|
+
child.once("error", reject);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
139
|
+
console.error(`Failed to start daemon: ${message}`);
|
|
140
|
+
process.exitCode = 1;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
try {
|
|
145
|
+
fs.closeSync(out);
|
|
146
|
+
}
|
|
147
|
+
catch (_c) { }
|
|
148
|
+
}
|
|
131
149
|
child.unref();
|
|
132
150
|
console.log(`Daemon started (PID: ${child.pid}, log: ${logFile})`);
|
|
133
151
|
process.exit(0);
|
|
134
152
|
}
|
|
135
153
|
// Daemon foreground
|
|
154
|
+
// Stamp every daemon.log line so incidents can be correlated with the
|
|
155
|
+
// timestamped MLX/LLM server logs. Workers inherit via env flag.
|
|
156
|
+
const { installTimestampedOutput } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/logger")));
|
|
157
|
+
installTimestampedOutput();
|
|
136
158
|
const { Daemon } = yield Promise.resolve().then(() => __importStar(require("../lib/daemon/daemon")));
|
|
137
159
|
const daemon = new Daemon();
|
|
160
|
+
process.on("SIGINT", () => daemon.shutdown().then(() => (0, exit_1.gracefulExit)()));
|
|
161
|
+
process.on("SIGTERM", () => daemon.shutdown().then(() => (0, exit_1.gracefulExit)()));
|
|
162
|
+
process.on("uncaughtException", (err) => {
|
|
163
|
+
console.error("[daemon] uncaughtException:", err);
|
|
164
|
+
daemon.shutdown().then(() => (0, exit_1.gracefulExit)(1));
|
|
165
|
+
});
|
|
166
|
+
process.on("unhandledRejection", (reason) => {
|
|
167
|
+
console.error("[daemon] unhandledRejection:", reason);
|
|
168
|
+
});
|
|
138
169
|
try {
|
|
139
170
|
yield daemon.start();
|
|
140
171
|
}
|
|
@@ -151,19 +182,10 @@ exports.watch = new commander_1.Command("watch")
|
|
|
151
182
|
try {
|
|
152
183
|
yield daemon.shutdown();
|
|
153
184
|
}
|
|
154
|
-
catch (
|
|
185
|
+
catch (_d) { }
|
|
155
186
|
process.exitCode = 1;
|
|
156
187
|
return;
|
|
157
188
|
}
|
|
158
|
-
process.on("SIGINT", () => daemon.shutdown().then(() => (0, exit_1.gracefulExit)()));
|
|
159
|
-
process.on("SIGTERM", () => daemon.shutdown().then(() => (0, exit_1.gracefulExit)()));
|
|
160
|
-
process.on("uncaughtException", (err) => {
|
|
161
|
-
console.error("[daemon] uncaughtException:", err);
|
|
162
|
-
daemon.shutdown().then(() => (0, exit_1.gracefulExit)(1));
|
|
163
|
-
});
|
|
164
|
-
process.on("unhandledRejection", (reason) => {
|
|
165
|
-
console.error("[daemon] unhandledRejection:", reason);
|
|
166
|
-
});
|
|
167
189
|
return;
|
|
168
190
|
}
|
|
169
191
|
// --- Per-project mode ---
|
|
@@ -185,12 +207,31 @@ exports.watch = new commander_1.Command("watch")
|
|
|
185
207
|
const safeName = projectName.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
186
208
|
const logFile = path.join(config_1.PATHS.logsDir, `watch-${safeName}.log`);
|
|
187
209
|
const out = (0, log_rotate_1.openRotatedLog)(logFile);
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
210
|
+
let child;
|
|
211
|
+
try {
|
|
212
|
+
child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], ...args], {
|
|
213
|
+
detached: true,
|
|
214
|
+
stdio: ["ignore", out, out],
|
|
215
|
+
cwd: process.cwd(),
|
|
216
|
+
env: Object.assign(Object.assign({}, process.env), { GMAX_BACKGROUND: "true" }),
|
|
217
|
+
});
|
|
218
|
+
yield new Promise((resolve, reject) => {
|
|
219
|
+
child.once("spawn", resolve);
|
|
220
|
+
child.once("error", reject);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
225
|
+
console.error(`Failed to start watcher for ${projectName}: ${message}`);
|
|
226
|
+
process.exitCode = 1;
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
finally {
|
|
230
|
+
try {
|
|
231
|
+
fs.closeSync(out);
|
|
232
|
+
}
|
|
233
|
+
catch (_e) { }
|
|
234
|
+
}
|
|
194
235
|
child.unref();
|
|
195
236
|
console.log(`Watcher started for ${projectName} (PID: ${child.pid}, log: ${logFile})`);
|
|
196
237
|
process.exit(0);
|
|
@@ -225,24 +266,36 @@ exports.watch = new commander_1.Command("watch")
|
|
|
225
266
|
.where((0, filter_builder_1.pathStartsWith)(prefix))
|
|
226
267
|
.limit(1)
|
|
227
268
|
.toArray();
|
|
269
|
+
let degraded = false;
|
|
270
|
+
let degradedErrors = 0;
|
|
271
|
+
let initialScanErrors = 0;
|
|
272
|
+
let initialFailedFiles = 0;
|
|
228
273
|
if (indexed.length === 0) {
|
|
229
274
|
console.log(`[watch:${projectName}] No index found for ${projectRoot}, running initial sync...`);
|
|
230
275
|
const syncResult = yield (0, syncer_1.initialSync)({ projectRoot });
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
276
|
+
if (syncResult.degraded) {
|
|
277
|
+
degraded = true;
|
|
278
|
+
degradedErrors = syncResult.scanErrors + syncResult.failedFiles;
|
|
279
|
+
initialScanErrors = syncResult.scanErrors;
|
|
280
|
+
initialFailedFiles = syncResult.failedFiles;
|
|
281
|
+
console.warn(`[watch:${projectName}] Initial sync incomplete: ${syncResult.scanErrors} scan error(s), ${syncResult.failedFiles} file failure(s). Preserving pending registry state.`);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
const chunkCount = yield vectorDb.countRowsForPath(prefix);
|
|
285
|
+
(0, project_registry_1.stampProjectFullSync)({
|
|
286
|
+
root: projectRoot,
|
|
287
|
+
name: projectName,
|
|
288
|
+
generation: syncResult.generation,
|
|
289
|
+
embedMode: syncResult.embedMode,
|
|
290
|
+
chunkCount,
|
|
291
|
+
chunkerVersion: config_1.CONFIG.CHUNKER_VERSION,
|
|
292
|
+
expectedFingerprint: syncResult.registryExpectation.embeddingFingerprint,
|
|
293
|
+
expectedRebuildId: syncResult.registryExpectation.rebuildId,
|
|
294
|
+
});
|
|
295
|
+
console.log(`[watch:${projectName}] Initial sync complete.`);
|
|
296
|
+
}
|
|
244
297
|
}
|
|
245
|
-
(0, watcher_store_1.updateWatcherStatus)(process.pid, "watching");
|
|
298
|
+
(0, watcher_store_1.updateWatcherStatus)(process.pid, degraded ? "degraded" : "watching", undefined, degraded ? `${degradedErrors} incomplete scan path(s)` : undefined);
|
|
246
299
|
// Open resources for watcher
|
|
247
300
|
const metaCache = new meta_cache_1.MetaCache(paths.lmdbPath);
|
|
248
301
|
// Start watching
|
|
@@ -251,10 +304,17 @@ exports.watch = new commander_1.Command("watch")
|
|
|
251
304
|
vectorDb,
|
|
252
305
|
metaCache,
|
|
253
306
|
dataDir: paths.dataDir,
|
|
307
|
+
initialScanErrors,
|
|
308
|
+
initialFailedFiles,
|
|
254
309
|
onReindex: (files, ms) => {
|
|
255
310
|
console.log(`[watch:${projectName}] Reindexed ${files} file${files !== 1 ? "s" : ""} (${(ms / 1000).toFixed(1)}s)`);
|
|
256
311
|
lastActivity = Date.now();
|
|
257
|
-
(0, watcher_store_1.updateWatcherStatus)(process.pid, "watching", Date.now());
|
|
312
|
+
(0, watcher_store_1.updateWatcherStatus)(process.pid, degraded ? "degraded" : "watching", Date.now(), degraded ? `${degradedErrors} incomplete scan path(s)` : undefined);
|
|
313
|
+
},
|
|
314
|
+
onHealthChange: (complete, errors) => {
|
|
315
|
+
degraded = !complete;
|
|
316
|
+
degradedErrors = errors;
|
|
317
|
+
(0, watcher_store_1.updateWatcherStatus)(process.pid, complete ? "watching" : "degraded", undefined, complete ? undefined : `${errors} incomplete scan path(s)`);
|
|
258
318
|
},
|
|
259
319
|
});
|
|
260
320
|
console.log(`[watch:${projectName}] File watcher active`);
|
|
@@ -300,6 +360,10 @@ exports.watch
|
|
|
300
360
|
const projects = resp.projects;
|
|
301
361
|
const uptime = Math.floor(resp.uptime / 60);
|
|
302
362
|
console.log(`Daemon (PID: ${resp.pid}, uptime: ${uptime}m):`);
|
|
363
|
+
const mlx = resp.mlx;
|
|
364
|
+
if (mlx) {
|
|
365
|
+
console.log(` MLX: ${mlx.state} (${mlx.model}${mlx.pid ? `, PID ${mlx.pid}` : ""})${mlx.error ? ` — ${mlx.error}` : ""}`);
|
|
366
|
+
}
|
|
303
367
|
for (const p of projects) {
|
|
304
368
|
console.log(` - ${p.root} [${p.status}]`);
|
|
305
369
|
}
|
package/dist/config.js
CHANGED
|
@@ -217,6 +217,9 @@ exports.PATHS = {
|
|
|
217
217
|
globalRoot: GLOBAL_ROOT,
|
|
218
218
|
models: path.join(GLOBAL_ROOT, "models"),
|
|
219
219
|
grammars: path.join(GLOBAL_ROOT, "grammars"),
|
|
220
|
+
// HF cache for the MLX embed server, pinned to internal disk so the server
|
|
221
|
+
// survives an unmounted external volume behind the user's HF_HOME.
|
|
222
|
+
hfDir: path.join(GLOBAL_ROOT, "hf"),
|
|
220
223
|
logsDir: path.join(GLOBAL_ROOT, "logs"),
|
|
221
224
|
daemonSocket: path.join(GLOBAL_ROOT, "daemon.sock"),
|
|
222
225
|
daemonPidFile: path.join(GLOBAL_ROOT, "daemon.pid"),
|