grepmax 0.24.0 → 0.25.1

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.
Files changed (75) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +15 -6
  3. package/dist/commands/add.js +38 -31
  4. package/dist/commands/config.js +22 -15
  5. package/dist/commands/context.js +38 -13
  6. package/dist/commands/doctor.js +86 -83
  7. package/dist/commands/extract.js +12 -1
  8. package/dist/commands/index.js +23 -24
  9. package/dist/commands/list.js +31 -14
  10. package/dist/commands/mcp.js +278 -143
  11. package/dist/commands/peek.js +16 -5
  12. package/dist/commands/repair.js +54 -120
  13. package/dist/commands/search-output.js +30 -9
  14. package/dist/commands/search-run.js +75 -47
  15. package/dist/commands/search-skeletons.js +28 -18
  16. package/dist/commands/search.js +45 -49
  17. package/dist/commands/serve.js +415 -373
  18. package/dist/commands/setup.js +2 -2
  19. package/dist/commands/similar.js +5 -5
  20. package/dist/commands/skeleton.js +67 -41
  21. package/dist/commands/status.js +11 -2
  22. package/dist/commands/summarize.js +6 -0
  23. package/dist/commands/watch.js +102 -38
  24. package/dist/config.js +3 -0
  25. package/dist/lib/daemon/daemon.js +1108 -379
  26. package/dist/lib/daemon/ipc-handler.js +122 -13
  27. package/dist/lib/daemon/mlx-server-manager.js +268 -125
  28. package/dist/lib/daemon/process-manager.js +7 -4
  29. package/dist/lib/daemon/search-handler.js +23 -9
  30. package/dist/lib/daemon/watcher-manager.js +353 -110
  31. package/dist/lib/index/batch-processor.js +231 -67
  32. package/dist/lib/index/embedding-generation.js +109 -0
  33. package/dist/lib/index/embedding-status.js +37 -0
  34. package/dist/lib/index/file-policy.js +273 -0
  35. package/dist/lib/index/ignore-patterns.js +4 -0
  36. package/dist/lib/index/index-config.js +18 -4
  37. package/dist/lib/index/syncer.js +256 -79
  38. package/dist/lib/index/walker.js +66 -86
  39. package/dist/lib/index/watcher-batch.js +9 -0
  40. package/dist/lib/index/watcher.js +129 -3
  41. package/dist/lib/llm/server.js +6 -0
  42. package/dist/lib/search/searcher.js +30 -19
  43. package/dist/lib/store/store-lease.js +473 -0
  44. package/dist/lib/store/vector-db.js +302 -57
  45. package/dist/lib/utils/blocked-roots.js +63 -0
  46. package/dist/lib/utils/cross-project.js +7 -3
  47. package/dist/lib/utils/daemon-client.js +24 -1
  48. package/dist/lib/utils/daemon-launcher.js +38 -13
  49. package/dist/lib/utils/doctor-status.js +76 -0
  50. package/dist/lib/utils/file-utils.js +74 -4
  51. package/dist/lib/utils/keyed-mutex.js +101 -0
  52. package/dist/lib/utils/logger.js +57 -1
  53. package/dist/lib/utils/mlx-hf-cache.js +114 -0
  54. package/dist/lib/utils/operation-coordinator.js +146 -0
  55. package/dist/lib/utils/path-containment.js +106 -0
  56. package/dist/lib/utils/process.js +44 -0
  57. package/dist/lib/utils/project-registry.js +351 -3
  58. package/dist/lib/utils/scope-filter.js +3 -9
  59. package/dist/lib/utils/stale-hint.js +12 -19
  60. package/dist/lib/utils/watcher-launcher.js +5 -1
  61. package/dist/lib/utils/watcher-store.js +2 -2
  62. package/dist/lib/workers/colbert-math.js +15 -12
  63. package/dist/lib/workers/embeddings/colbert.js +3 -2
  64. package/dist/lib/workers/embeddings/granite.js +4 -3
  65. package/dist/lib/workers/embeddings/mlx-client.js +59 -16
  66. package/dist/lib/workers/orchestrator.js +39 -8
  67. package/dist/lib/workers/pool.js +146 -82
  68. package/dist/lib/workers/process-child.js +11 -2
  69. package/dist/lib/workers/serialized-handler.js +10 -0
  70. package/dist/lib/workers/worker.js +13 -4
  71. package/mlx-embed-server/server.py +21 -3
  72. package/mlx-embed-server/summarizer.py +8 -0
  73. package/package.json +4 -3
  74. package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
  75. package/plugins/grepmax/hooks/start.js +3 -170
@@ -48,9 +48,12 @@ const os = __importStar(require("node:os"));
48
48
  const path = __importStar(require("node:path"));
49
49
  const commander_1 = require("commander");
50
50
  const config_1 = require("../config");
51
+ const embedding_status_1 = require("../lib/index/embedding-status");
51
52
  const index_config_1 = require("../lib/index/index-config");
53
+ const doctor_status_1 = require("../lib/utils/doctor-status");
52
54
  const exit_1 = require("../lib/utils/exit");
53
55
  const lock_1 = require("../lib/utils/lock");
56
+ const mlx_hf_cache_1 = require("../lib/utils/mlx-hf-cache");
54
57
  const project_registry_1 = require("../lib/utils/project-registry");
55
58
  const project_root_1 = require("../lib/utils/project-root");
56
59
  function formatSize(bytes) {
@@ -85,7 +88,7 @@ exports.doctor = new commander_1.Command("doctor")
85
88
  .option("--fix", "Auto-fix detected issues (compact, prune, remove stale locks)", false)
86
89
  .option("--agent", "Compact output for AI agents", false)
87
90
  .action((opts) => __awaiter(void 0, void 0, void 0, function* () {
88
- var _a, _b, _c, _d, _e, _f;
91
+ var _a, _b, _c, _d, _e, _f, _g, _h;
89
92
  if (!opts.agent)
90
93
  console.log("gmax Doctor\n");
91
94
  const root = config_1.PATHS.globalRoot;
@@ -106,33 +109,29 @@ exports.doctor = new commander_1.Command("doctor")
106
109
  if (!config_1.MODEL_TIERS[globalConfig.modelTier]) {
107
110
  console.log(`WARN Unknown model tier '${globalConfig.modelTier}', falling back to 'small'`);
108
111
  }
109
- const embedModel = globalConfig.embedMode === "gpu" ? tier.mlxModel : tier.onnxModel;
112
+ const embedModel = globalConfig.embedMode === "gpu"
113
+ ? ((_b = globalConfig.mlxModel) !== null && _b !== void 0 ? _b : tier.mlxModel)
114
+ : tier.onnxModel;
110
115
  if (!opts.agent) {
111
116
  console.log(`\nEmbed mode: ${globalConfig.embedMode} | Model tier: ${globalConfig.modelTier} (${tier.vectorDim}d)`);
112
117
  console.log(`Embed model: ${embedModel}`);
113
118
  console.log(`ColBERT model: ${config_1.MODEL_IDS.colbert}`);
114
- const modelStatuses = [embedModel, config_1.MODEL_IDS.colbert].map((id) => {
115
- const modelPath = path.join(models, ...id.split("/"));
116
- return { id, path: modelPath, exists: fs.existsSync(modelPath) };
117
- });
118
- modelStatuses.forEach(({ id, exists }) => {
119
- console.log(`${exists ? "ok" : "WARN"} ${id}: ${exists ? "downloaded" : "will download on first use"}`);
120
- });
121
- console.log(`\nLocal Project: ${process.cwd()}`);
122
- const projectRoot = (0, project_root_1.findProjectRoot)(process.cwd());
123
- if (projectRoot) {
124
- console.log(`ok Project root: ${projectRoot}`);
125
- console.log(` Centralized index at: ~/.gmax/lancedb/`);
126
- }
127
- else {
128
- console.log(`INFO No index found in current directory (run 'gmax index' to create one)`);
129
- }
130
- // Check MLX embed server
119
+ // Probe the MLX embed server once, up front: the gpu-mode embed model
120
+ // status is derived from it (the server, not the ONNX models dir, is the
121
+ // source of truth in gpu mode). Reused below for the "MLX Embed" line.
131
122
  let embedUp = false;
132
123
  let embedError = "";
133
124
  try {
134
125
  const res = yield fetch("http://127.0.0.1:8100/health");
135
- embedUp = res.ok;
126
+ const health = (yield res.json());
127
+ embedUp =
128
+ res.ok && health.status === "ok" && health.model === embedModel;
129
+ if (res.ok && health.model !== embedModel) {
130
+ embedError = `wrong model ${String(health.model)}; expected ${embedModel}`;
131
+ }
132
+ else if (res.ok && health.status !== "ok") {
133
+ embedError = "invalid health response";
134
+ }
136
135
  }
137
136
  catch (err) {
138
137
  embedError =
@@ -140,6 +139,28 @@ exports.doctor = new commander_1.Command("doctor")
140
139
  ? "connection refused"
141
140
  : err.message || String(err);
142
141
  }
142
+ // Embed model availability. In gpu mode the model is served by MLX from
143
+ // the pinned HF cache (~/.gmax/hf), never the ONNX models dir — report
144
+ // from the live server / HF cache. In cpu mode it's an ONNX model in
145
+ // ~/.gmax/models. ColBERT is always ONNX in-worker regardless of mode.
146
+ const onnxExists = (id) => fs.existsSync(path.join(models, ...id.split("/")));
147
+ const embedStatus = globalConfig.embedMode === "gpu"
148
+ ? (0, doctor_status_1.gpuEmbedModelStatus)(embedModel, { up: embedUp }, (0, mlx_hf_cache_1.isMlxModelCached)(embedModel))
149
+ : (0, doctor_status_1.onnxModelStatus)(embedModel, onnxExists(embedModel));
150
+ const colbertStatus = (0, doctor_status_1.onnxModelStatus)(config_1.MODEL_IDS.colbert, onnxExists(config_1.MODEL_IDS.colbert));
151
+ for (const s of [embedStatus, colbertStatus]) {
152
+ console.log(`${s.symbol} ${s.message}`);
153
+ }
154
+ console.log(`\nLocal Project: ${process.cwd()}`);
155
+ const projectRoot = (0, project_root_1.findProjectRoot)(process.cwd());
156
+ if (projectRoot) {
157
+ console.log(`ok Project root: ${projectRoot}`);
158
+ console.log(` Centralized index at: ~/.gmax/lancedb/`);
159
+ }
160
+ else {
161
+ console.log(`INFO No index found in current directory (run 'gmax index' to create one)`);
162
+ }
163
+ // MLX embed server (probed once above).
143
164
  console.log(`${embedUp ? "ok" : "WARN"} MLX Embed: ${embedUp ? "running (port 8100)" : `not running${embedError ? ` (${embedError})` : ""}`}`);
144
165
  if (embedUp) {
145
166
  try {
@@ -150,7 +171,7 @@ exports.doctor = new commander_1.Command("doctor")
150
171
  body: JSON.stringify({ texts: ["gmax health check"] }),
151
172
  });
152
173
  const embedData = yield embedRes.json();
153
- const dim = (_d = (_c = (_b = embedData === null || embedData === void 0 ? void 0 : embedData.vectors) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0;
174
+ const dim = (_e = (_d = (_c = embedData === null || embedData === void 0 ? void 0 : embedData.vectors) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.length) !== null && _e !== void 0 ? _e : 0;
154
175
  const ms = Date.now() - start;
155
176
  const expectedDim = tier.vectorDim || 384;
156
177
  if (dim === expectedDim) {
@@ -164,11 +185,13 @@ exports.doctor = new commander_1.Command("doctor")
164
185
  console.log(`FAIL Embedding: test failed (${err.message || err})`);
165
186
  }
166
187
  }
167
- // Check summarizer server
188
+ // Check summarizer server. Down is INFO, not WARN — opt-in, respawns on
189
+ // demand, idles out after 10min (see summarizerServerStatus).
168
190
  const summarizerUp = yield fetch("http://127.0.0.1:8101/health")
169
191
  .then((r) => r.ok)
170
192
  .catch(() => false);
171
- console.log(`${summarizerUp ? "ok" : "WARN"} Summarizer: ${summarizerUp ? "running (port 8101)" : "not running"}`);
193
+ const summarizerStatus = (0, doctor_status_1.summarizerServerStatus)(summarizerUp);
194
+ console.log(`${summarizerStatus.symbol} ${summarizerStatus.message}`);
172
195
  }
173
196
  // --- Index Health ---
174
197
  let needsOptimize = false;
@@ -193,9 +216,8 @@ exports.doctor = new commander_1.Command("doctor")
193
216
  .where("length(summary) > 5")
194
217
  .select(["id"])
195
218
  .toArray()).length;
196
- const pct = Math.round((withSummary / totalChunks) * 100);
197
- const symbol = pct >= 90 ? "ok" : pct > 0 ? "WARN" : "FAIL";
198
- console.log(`${symbol} Summary coverage: ${withSummary}/${totalChunks} (${pct}%)`);
219
+ const coverage = (0, doctor_status_1.summaryCoverageStatus)(withSummary, totalChunks);
220
+ console.log(`${coverage.symbol} ${coverage.message}`);
199
221
  }
200
222
  else if (!opts.agent && totalChunks === 0) {
201
223
  console.log("INFO No indexed chunks yet");
@@ -251,21 +273,15 @@ exports.doctor = new commander_1.Command("doctor")
251
273
  ? "LOW"
252
274
  : "ok";
253
275
  }
254
- catch (_g) { }
276
+ catch (_j) { }
255
277
  const staleChunkerProjects = projects.filter((p) => {
256
278
  var _a;
257
279
  return p.status === "indexed" &&
258
280
  ((_a = p.chunkerVersion) !== null && _a !== void 0 ? _a : 1) < config_1.CONFIG.CHUNKER_VERSION;
259
281
  });
260
- // Projects whose stored embedding model/dim no longer matches the global
261
- // config. Visibility only — recovery is a manual `gmax index --reset`
262
- // (a dim change can't be auto-fixed in the shared fixed-dim table; that's
263
- // the deferred Phase 1B re-embed work).
264
282
  const staleEmbeddingProjects = projects.filter((p) => p.status === "indexed" &&
265
- (0, config_1.describeEmbeddingGap)({ modelTier: p.modelTier, vectorDim: p.vectorDim }, {
266
- modelTier: globalConfig.modelTier,
267
- vectorDim: globalConfig.vectorDim,
268
- }) !== null);
283
+ (0, embedding_status_1.projectEmbeddingStatus)(p, globalConfig).state === "stale");
284
+ const legacyEmbeddingCount = (0, embedding_status_1.countLegacyEmbeddingProjects)(projects, globalConfig);
269
285
  if (opts.agent) {
270
286
  const fields = [
271
287
  "index_health",
@@ -282,6 +298,7 @@ exports.doctor = new commander_1.Command("doctor")
282
298
  `orphaned=${orphanedProjects.length}`,
283
299
  `stale_chunker=${staleChunkerProjects.length}`,
284
300
  `stale_embedding=${staleEmbeddingProjects.length}`,
301
+ `legacy_embedding=${legacyEmbeddingCount}`,
285
302
  `schema_dim=${physicalDim !== null && physicalDim !== void 0 ? physicalDim : "none"}`,
286
303
  `schema_dim_ok=${schemaGap ? "false" : "true"}`,
287
304
  ];
@@ -304,35 +321,35 @@ exports.doctor = new commander_1.Command("doctor")
304
321
  ].join("\t"));
305
322
  }
306
323
  for (const p of staleEmbeddingProjects) {
307
- const gap = (0, config_1.describeEmbeddingGap)({ modelTier: p.modelTier, vectorDim: p.vectorDim }, {
308
- modelTier: globalConfig.modelTier,
309
- vectorDim: globalConfig.vectorDim,
310
- });
311
- if (!gap)
324
+ const identity = (0, embedding_status_1.projectEmbeddingStatus)(p, globalConfig);
325
+ if (!identity.built)
312
326
  continue;
313
327
  console.log([
314
328
  "stale_embedding_project",
315
329
  `name=${p.name || path.basename(p.root)}`,
316
- `indexed_model=${gap.fromModel}`,
317
- `current_model=${gap.toModel}`,
318
- `indexed_dim=${gap.fromDim}`,
319
- `current_dim=${gap.toDim}`,
320
- `dim_changed=${gap.dimChanged}`,
321
- `severity=${gap.severity}`,
322
- // A dim change can't be fixed by a per-project reset (the shared
323
- // table is fixed-width) — point at the global rebuild instead.
324
- `fix=${gap.dimChanged ? config_1.REBUILD_COMMAND : `gmax index --reset (in ${p.root})`}`,
330
+ `indexed_model=${identity.built.tier}`,
331
+ `current_model=${identity.configured.tier}`,
332
+ `indexed_dim=${identity.built.vectorDim}`,
333
+ `current_dim=${identity.configured.vectorDim}`,
334
+ `indexed_fingerprint=${identity.built.fingerprint}`,
335
+ `current_fingerprint=${identity.configured.fingerprint}`,
336
+ "severity=breaking",
337
+ "fix=gmax repair --rebuild",
325
338
  ].join("\t"));
326
339
  }
340
+ const legacyNotice = (0, embedding_status_1.formatLegacyEmbeddingNotice)(legacyEmbeddingCount, {
341
+ agent: true,
342
+ });
343
+ if (legacyNotice)
344
+ console.log(legacyNotice);
327
345
  }
328
346
  else {
329
347
  console.log("\nIndex Health\n");
330
- // Physical schema width — a mismatch means every write throws until a
331
- // global rebuild (the shared fixed-width table can't be reshaped by a
332
- // per-project reset). Surfaced first because it's the most severe.
348
+ // Physical schema width mismatch remains fail-closed until the user
349
+ // explicitly runs the guarded whole-corpus rebuild.
333
350
  if (schemaGap) {
334
351
  console.log(`FAIL Schema: vector table is ${schemaGap.tableDim}d, config expects ${schemaGap.configDim}d`);
335
- console.log(` run '${config_1.REBUILD_COMMAND}' (drops + reindexes all projects at the new width)`);
352
+ console.log(" run 'gmax repair --rebuild' to rebuild the whole corpus");
336
353
  }
337
354
  else if (physicalDim) {
338
355
  console.log(`ok Schema: vector table is ${physicalDim}d`);
@@ -392,36 +409,18 @@ exports.doctor = new commander_1.Command("doctor")
392
409
  console.log(` run 'gmax index --reset' in ${p.root}`);
393
410
  }
394
411
  }
395
- // Index built with a different embedding model/dim than the current
396
- // config. A dim change is breaking (search scores are invalid until a
397
- // re-embed); a same-dim model swap is additive. Recovery differs by kind:
398
- // a same-dim model swap is fixed by a per-project `gmax index --reset`,
399
- // but a dim change can't be — the shared table is fixed-width, so it
400
- // needs the global rebuild (see the Schema check above).
401
412
  if (staleEmbeddingProjects.length > 0) {
402
- const gaps = staleEmbeddingProjects.map((p) => (0, config_1.describeEmbeddingGap)({ modelTier: p.modelTier, vectorDim: p.vectorDim }, {
403
- modelTier: globalConfig.modelTier,
404
- vectorDim: globalConfig.vectorDim,
405
- }));
406
- const anyBreaking = gaps.some((g) => (g === null || g === void 0 ? void 0 : g.severity) === "breaking");
407
- const anyDimChange = gaps.some((g) => g === null || g === void 0 ? void 0 : g.dimChanged);
408
- const headerFix = anyDimChange
409
- ? `run '${config_1.REBUILD_COMMAND}' (dim change needs a full rebuild)`
410
- : "run 'gmax index --reset' per project";
411
- console.log(`${anyBreaking ? "WARN" : "INFO"} Stale embedding: ${staleEmbeddingProjects.length} project(s) indexed with a different embedding model/dim — ${headerFix}`);
412
- staleEmbeddingProjects.forEach((p, i) => {
413
- const gap = gaps[i];
414
- if (!gap)
413
+ console.log(`WARN Stale embedding: ${staleEmbeddingProjects.length} project(s) use a different embedding generation run 'gmax repair --rebuild'`);
414
+ staleEmbeddingProjects.forEach((p) => {
415
+ const identity = (0, embedding_status_1.projectEmbeddingStatus)(p, globalConfig);
416
+ if (!identity.built)
415
417
  return;
416
- const change = gap.dimChanged
417
- ? `${gap.fromDim}d→${gap.toDim}d`
418
- : `model ${gap.fromModel}→${gap.toModel}`;
419
- console.log(` - ${p.name || path.basename(p.root)} (${change}, ${gap.severity})`);
420
- console.log(gap.dimChanged
421
- ? ` run '${config_1.REBUILD_COMMAND}'`
422
- : ` run 'gmax index --reset' in ${p.root}`);
418
+ console.log(` - ${p.name || path.basename(p.root)} (${identity.built.tier} ${identity.built.vectorDim}d → ${identity.configured.tier} ${identity.configured.vectorDim}d, breaking)`);
423
419
  });
424
420
  }
421
+ const legacyNotice = (0, embedding_status_1.formatLegacyEmbeddingNotice)(legacyEmbeddingCount);
422
+ if (legacyNotice)
423
+ console.log(legacyNotice);
425
424
  // Projects
426
425
  if (orphanedProjects.length > 0) {
427
426
  console.log(`WARN Orphaned projects: ${orphanedProjects.length} (directories no longer exist)`);
@@ -452,7 +451,7 @@ exports.doctor = new commander_1.Command("doctor")
452
451
  }
453
452
  yield mc.close();
454
453
  }
455
- catch (_h) { }
454
+ catch (_k) { }
456
455
  }
457
456
  }
458
457
  // --fix auto-remediation
@@ -497,8 +496,8 @@ exports.doctor = new commander_1.Command("doctor")
497
496
  if (!opts.agent)
498
497
  console.log(`... Reindexing ${name} (--reset)...`);
499
498
  const done = yield sendStreamingCommand({ cmd: "index", root: p.root, reset: true }, () => { }).catch((e) => ({ ok: false, error: String(e) }));
500
- if (done.ok) {
501
- (0, project_registry_1.registerProject)(Object.assign(Object.assign({}, p), { status: "indexed", chunkerVersion: config_1.CONFIG.CHUNKER_VERSION, lastIndexed: new Date().toISOString(), chunkCount: (_e = done.indexed) !== null && _e !== void 0 ? _e : p.chunkCount }));
499
+ if (done.ok &&
500
+ (!("degraded" in done) || done.degraded !== true)) {
502
501
  const chunks = (_f = done.indexed) !== null && _f !== void 0 ? _f : 0;
503
502
  if (opts.agent) {
504
503
  console.log(`stale_chunker_reindexed\tname=${name}\tchunks=${chunks}`);
@@ -509,7 +508,11 @@ exports.doctor = new commander_1.Command("doctor")
509
508
  fixed++;
510
509
  }
511
510
  else if (!opts.agent) {
512
- console.log(`FAIL ${name}: reindex failed (${done.error})`);
511
+ const degraded = "degraded" in done && done.degraded === true;
512
+ const reason = degraded
513
+ ? `${(_g = ("scanErrors" in done ? done.scanErrors : 0)) !== null && _g !== void 0 ? _g : 0} scan errors, ${(_h = ("failedFiles" in done ? done.failedFiles : 0)) !== null && _h !== void 0 ? _h : 0} file failures`
514
+ : done.error;
515
+ console.log(`FAIL ${name}: reindex failed (${reason})`);
513
516
  }
514
517
  }
515
518
  }
@@ -51,6 +51,7 @@ const exit_1 = require("../lib/utils/exit");
51
51
  const filter_builder_1 = require("../lib/utils/filter-builder");
52
52
  const import_extractor_1 = require("../lib/utils/import-extractor");
53
53
  const language_1 = require("../lib/utils/language");
54
+ const path_containment_1 = require("../lib/utils/path-containment");
54
55
  const project_registry_1 = require("../lib/utils/project-registry");
55
56
  const project_root_1 = require("../lib/utils/project-root");
56
57
  const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
@@ -129,7 +130,17 @@ exports.extract = new commander_1.Command("extract")
129
130
  exclude: opts.exclude,
130
131
  });
131
132
  const where = buildScopeWhere(scope, `array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbol)}')`);
132
- const chunks = yield findSymbolChunks(vectorDb, where);
133
+ const indexedChunks = yield findSymbolChunks(vectorDb, where);
134
+ const chunks = indexedChunks.flatMap((chunk) => {
135
+ try {
136
+ return [
137
+ Object.assign(Object.assign({}, chunk), { path: (0, path_containment_1.resolveContainedFile)(projectRoot, chunk.path) }),
138
+ ];
139
+ }
140
+ catch (_a) {
141
+ return [];
142
+ }
143
+ });
133
144
  if (chunks.length === 0) {
134
145
  console.log((0, agent_errors_1.symbolNotFoundLines)(symbol, {
135
146
  agent: opts.agent,
@@ -47,7 +47,6 @@ const path = __importStar(require("node:path"));
47
47
  const commander_1 = require("commander");
48
48
  const config_1 = require("../config");
49
49
  const grammar_loader_1 = require("../lib/index/grammar-loader");
50
- const index_config_1 = require("../lib/index/index-config");
51
50
  const sync_helpers_1 = require("../lib/index/sync-helpers");
52
51
  const syncer_1 = require("../lib/index/syncer");
53
52
  const setup_helpers_1 = require("../lib/setup/setup-helpers");
@@ -71,7 +70,7 @@ Examples:
71
70
  gmax index --reset Full re-index from scratch
72
71
  `)
73
72
  .action((_args, cmd) => __awaiter(void 0, void 0, void 0, function* () {
74
- var _a, _b, _c, _d, _e;
73
+ var _a, _b, _c, _d, _e, _f;
75
74
  const options = cmd.optsWithGlobals();
76
75
  let vectorDb = null;
77
76
  const ac = new AbortController();
@@ -134,19 +133,12 @@ Examples:
134
133
  spinner.succeed(`Dry run complete(${done.processed} / ${done.total}) • would have indexed ${done.indexed} `);
135
134
  return;
136
135
  }
137
- const globalConfig = (0, index_config_1.readGlobalConfig)();
138
- (0, project_registry_1.registerProject)({
139
- root: projectRoot,
140
- name: path.basename(projectRoot),
141
- vectorDim: globalConfig.vectorDim,
142
- modelTier: globalConfig.modelTier,
143
- embedMode: globalConfig.embedMode,
144
- lastIndexed: new Date().toISOString(),
145
- chunkCount: (_d = done.indexed) !== null && _d !== void 0 ? _d : 0,
146
- status: "indexed",
147
- chunkerVersion: stampedChunkerVersion,
148
- });
149
- const failedFiles = (_e = done.failedFiles) !== null && _e !== void 0 ? _e : 0;
136
+ if (done.degraded === true) {
137
+ spinner.warn(`Indexing incomplete • ${(_d = done.scanErrors) !== null && _d !== void 0 ? _d : 0} scan errors • ${(_e = done.failedFiles) !== null && _e !== void 0 ? _e : 0} failed`);
138
+ process.exitCode = 1;
139
+ return;
140
+ }
141
+ const failedFiles = (_f = done.failedFiles) !== null && _f !== void 0 ? _f : 0;
150
142
  const failedSuffix = failedFiles > 0 ? ` • ${failedFiles} failed` : "";
151
143
  spinner.succeed(`Indexing complete(${done.processed} / ${done.total}) • indexed ${done.indexed}${failedSuffix} `);
152
144
  }
@@ -166,7 +158,7 @@ Examples:
166
158
  try {
167
159
  process.kill(watcher.pid, "SIGTERM");
168
160
  }
169
- catch (_f) { }
161
+ catch (_g) { }
170
162
  for (let i = 0; i < 50; i++) {
171
163
  if (!(0, watcher_store_1.isProcessRunning)(watcher.pid))
172
164
  break;
@@ -196,17 +188,24 @@ Examples:
196
188
  }));
197
189
  return;
198
190
  }
199
- const globalConfig = (0, index_config_1.readGlobalConfig)();
200
- (0, project_registry_1.registerProject)({
191
+ if (result.degraded) {
192
+ spinner.warn(`Indexing incomplete • ${result.scanErrors} scan errors • ${result.failedFiles} failed`);
193
+ process.exitCode = 1;
194
+ return;
195
+ }
196
+ const prefix = projectRoot.endsWith("/")
197
+ ? projectRoot
198
+ : `${projectRoot}/`;
199
+ const chunkCount = yield vectorDb.countRowsForPath(prefix);
200
+ (0, project_registry_1.stampProjectFullSync)({
201
201
  root: projectRoot,
202
202
  name: path.basename(projectRoot),
203
- vectorDim: globalConfig.vectorDim,
204
- modelTier: globalConfig.modelTier,
205
- embedMode: globalConfig.embedMode,
206
- lastIndexed: new Date().toISOString(),
207
- chunkCount: result.indexed,
208
- status: "indexed",
203
+ generation: result.generation,
204
+ embedMode: result.embedMode,
205
+ chunkCount,
209
206
  chunkerVersion: stampedChunkerVersion,
207
+ expectedFingerprint: result.registryExpectation.embeddingFingerprint,
208
+ expectedRebuildId: result.registryExpectation.rebuildId,
210
209
  });
211
210
  const failedSuffix = result.failedFiles > 0 ? ` • ${result.failedFiles} failed` : "";
212
211
  spinner.succeed(`Indexing complete(${result.processed} / ${result.total}) • indexed ${result.indexed}${failedSuffix} `);
@@ -47,6 +47,7 @@ const fs = __importStar(require("node:fs"));
47
47
  const os = __importStar(require("node:os"));
48
48
  const path = __importStar(require("node:path"));
49
49
  const commander_1 = require("commander");
50
+ const embedding_status_1 = require("../lib/index/embedding-status");
50
51
  const index_config_1 = require("../lib/index/index-config");
51
52
  const exit_1 = require("../lib/utils/exit");
52
53
  const project_registry_1 = require("../lib/utils/project-registry");
@@ -123,7 +124,7 @@ exports.list = new commander_1.Command("list")
123
124
  }));
124
125
  function showCurrentProject() {
125
126
  return __awaiter(this, void 0, void 0, function* () {
126
- var _a, _b, _c, _d;
127
+ var _a;
127
128
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(process.cwd())) !== null && _a !== void 0 ? _a : process.cwd();
128
129
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
129
130
  const entries = [
@@ -132,13 +133,22 @@ function showCurrentProject() {
132
133
  ];
133
134
  console.log(`\n${style.bold("Project")}: ${style.green(projectRoot)}`);
134
135
  console.log(`${style.dim("Data directory")}: ${paths.dataDir}\n`);
135
- const config = (0, index_config_1.readIndexConfig)(paths.configPath);
136
- if (config) {
137
- console.log(`${style.dim("Model")}: ${(_b = config.modelTier) !== null && _b !== void 0 ? _b : "small"} (${(_c = config.vectorDim) !== null && _c !== void 0 ? _c : 384}d)`);
138
- console.log(`${style.dim("Mode")}: ${(_d = config.embedMode) !== null && _d !== void 0 ? _d : "cpu"}`);
139
- if (config.indexedAt) {
140
- console.log(`${style.dim("Indexed")}: ${formatDate(new Date(config.indexedAt))}`);
136
+ const globalConfig = (0, index_config_1.readGlobalConfig)();
137
+ const project = (0, project_registry_1.getProject)(projectRoot);
138
+ if (project) {
139
+ const identity = (0, embedding_status_1.projectEmbeddingStatus)(project, globalConfig);
140
+ console.log(`${style.dim("Configured embedding")}: ${identity.configured.tier} (${identity.configured.vectorDim}d)`);
141
+ if (identity.built) {
142
+ console.log(`${style.dim("Built embedding")}: ${identity.built.tier} (${identity.built.vectorDim}d, ${identity.state})`);
143
+ }
144
+ if (identity.state === "legacy") {
145
+ const notice = (0, embedding_status_1.formatLegacyEmbeddingNotice)(1);
146
+ if (notice)
147
+ console.log(style.yellow(notice));
141
148
  }
149
+ console.log(`${style.dim("Mode")}: ${globalConfig.embedMode}`);
150
+ if (project.lastIndexed)
151
+ console.log(`${style.dim("Indexed")}: ${formatDate(new Date(project.lastIndexed))}`);
142
152
  console.log();
143
153
  }
144
154
  for (const entry of entries) {
@@ -167,17 +177,24 @@ function showAllProjects() {
167
177
  // Header
168
178
  console.log(`${style.bold(pad("Project", nameWidth))} ${style.bold(pad("Path", pathWidth))} ${style.bold("Dims")} ${style.bold("Status")}`);
169
179
  for (const project of projects) {
170
- const dimMatch = project.vectorDim === globalConfig.vectorDim;
180
+ const identity = (0, embedding_status_1.projectEmbeddingStatus)(project, globalConfig);
171
181
  const dimsStr = `${project.vectorDim}d`;
172
- const status = dimMatch
173
- ? style.green("ok")
174
- : style.yellow("reindex needed");
182
+ const status = identity.state === "current"
183
+ ? style.green("current")
184
+ : identity.state === "legacy"
185
+ ? style.yellow("legacy")
186
+ : identity.state === "stale"
187
+ ? style.red("stale")
188
+ : style.dim("unbuilt");
175
189
  console.log(`${pad(project.name, nameWidth)} ${style.dim(pad(shortenPath(project.root), pathWidth))} ${pad(dimsStr, 4)} ${status}`);
176
190
  }
177
191
  console.log(`\n${style.dim("Global config")}: ${globalConfig.modelTier} (${globalConfig.vectorDim}d), ${globalConfig.embedMode}`);
178
- const needsReindex = projects.filter((p) => p.vectorDim !== globalConfig.vectorDim);
179
- if (needsReindex.length > 0) {
180
- console.log(style.yellow(`\n${needsReindex.length} project(s) need reindexing. Search will auto-reindex on first use.`));
192
+ const legacyNotice = (0, embedding_status_1.formatLegacyEmbeddingNotice)((0, embedding_status_1.countLegacyEmbeddingProjects)(projects, globalConfig));
193
+ if (legacyNotice)
194
+ console.log(style.yellow(`\n${legacyNotice}`));
195
+ const stale = projects.filter((project) => (0, embedding_status_1.projectEmbeddingStatus)(project, globalConfig).state === "stale");
196
+ if (stale.length > 0) {
197
+ console.log(style.yellow(`\n${stale.length} project(s) use a stale embedding generation. Existing indexes are preserved; run 'gmax repair --rebuild' to rebuild the whole corpus.`));
181
198
  }
182
199
  });
183
200
  }