grepmax 0.17.22 → 0.17.24

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.
@@ -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 config_1 = require("../config");
50
51
  const grammar_loader_1 = require("../lib/index/grammar-loader");
51
52
  const index_config_1 = require("../lib/index/index-config");
52
53
  const sync_helpers_1 = require("../lib/index/sync-helpers");
@@ -218,7 +219,7 @@ Examples:
218
219
  if (!done.ok) {
219
220
  throw new Error((_c = done.error) !== null && _c !== void 0 ? _c : "daemon add failed");
220
221
  }
221
- (0, project_registry_1.registerProject)(Object.assign(Object.assign({}, pendingEntry), { lastIndexed: new Date().toISOString(), chunkCount: (_d = done.indexed) !== null && _d !== void 0 ? _d : 0, status: "indexed" }));
222
+ (0, project_registry_1.registerProject)(Object.assign(Object.assign({}, pendingEntry), { lastIndexed: new Date().toISOString(), chunkCount: (_d = done.indexed) !== null && _d !== void 0 ? _d : 0, status: "indexed", chunkerVersion: config_1.CONFIG.CHUNKER_VERSION }));
222
223
  const failedFiles = (_e = done.failedFiles) !== null && _e !== void 0 ? _e : 0;
223
224
  const failedSuffix = failedFiles > 0 ? ` · ${failedFiles} failed` : "";
224
225
  spinner.succeed(`Added ${projectName} (${done.total} files, ${done.indexed} chunks${failedSuffix})`);
@@ -239,7 +240,7 @@ Examples:
239
240
  projectRoot,
240
241
  onProgress,
241
242
  });
242
- (0, project_registry_1.registerProject)(Object.assign(Object.assign({}, pendingEntry), { lastIndexed: new Date().toISOString(), chunkCount: result.indexed, status: "indexed" }));
243
+ (0, project_registry_1.registerProject)(Object.assign(Object.assign({}, pendingEntry), { lastIndexed: new Date().toISOString(), chunkCount: result.indexed, status: "indexed", chunkerVersion: config_1.CONFIG.CHUNKER_VERSION }));
243
244
  const failedSuffix = result.failedFiles > 0 ? ` · ${result.failedFiles} failed` : "";
244
245
  spinner.succeed(`Added ${projectName} (${result.total} files, ${result.indexed} chunks${failedSuffix})`);
245
246
  }
@@ -45,11 +45,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.audit = void 0;
46
46
  exports.computeAudit = computeAudit;
47
47
  const commander_1 = require("commander");
48
+ const callsites_1 = require("../lib/graph/callsites");
49
+ const vector_db_1 = require("../lib/store/vector-db");
48
50
  const arrow_1 = require("../lib/utils/arrow");
49
51
  const exit_1 = require("../lib/utils/exit");
50
52
  const project_registry_1 = require("../lib/utils/project-registry");
51
53
  const project_root_1 = require("../lib/utils/project-root");
52
- const vector_db_1 = require("../lib/store/vector-db");
53
54
  const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
54
55
  const style = {
55
56
  bold: (s) => (useColors ? `\x1b[1m${s}\x1b[22m` : s),
@@ -71,8 +72,12 @@ function rel(p, prefix) {
71
72
  * `top` caps each list; `deadTotal` reports the full pre-cap dead count.
72
73
  */
73
74
  function computeAudit(rows, prefix, top) {
75
+ var _a, _b;
74
76
  // First definition of a symbol wins (matches GraphBuilder semantics).
75
77
  const defs = new Map();
78
+ // Distinct files defining each name — name-based edges can't tell same-name
79
+ // symbols apart, so multi-file definitions get flagged in the output.
80
+ const defFileCounts = new Map();
76
81
  // Distinct files that reference a symbol (cross-file inbound edges).
77
82
  const inboundFiles = new Map();
78
83
  const inboundTotal = new Map();
@@ -91,6 +96,9 @@ function computeAudit(rows, prefix, top) {
91
96
  if (!defs.has(s)) {
92
97
  defs.set(s, { file, line, exported, complexity: 0 });
93
98
  }
99
+ if (!defFileCounts.has(s))
100
+ defFileCounts.set(s, new Set());
101
+ defFileCounts.get(s).add(file);
94
102
  if (!fileDefs.has(file))
95
103
  fileDefs.set(file, new Set());
96
104
  fileDefs.get(file).add(s);
@@ -110,6 +118,10 @@ function computeAudit(rows, prefix, top) {
110
118
  for (const [symbol, info] of defs) {
111
119
  if (symbol.length < MIN_GOD_NAME_LEN)
112
120
  continue;
121
+ // Builtin method names (get, set, push, …) leak in via prototype/member
122
+ // definitions and their inbound counts are meaningless name collisions.
123
+ if ((0, callsites_1.isBuiltinCallee)(symbol))
124
+ continue;
113
125
  const refFiles = inboundFiles.get(symbol);
114
126
  if (!refFiles)
115
127
  continue;
@@ -125,6 +137,7 @@ function computeAudit(rows, prefix, top) {
125
137
  line: info.line,
126
138
  inboundFiles: external,
127
139
  totalRefs: inboundTotal.get(symbol) || 0,
140
+ defFiles: (_b = (_a = defFileCounts.get(symbol)) === null || _a === void 0 ? void 0 : _a.size) !== null && _b !== void 0 ? _b : 1,
128
141
  });
129
142
  }
130
143
  godNodes.sort((a, b) => b.inboundFiles - a.inboundFiles || b.totalRefs - a.totalRefs);
@@ -187,7 +200,10 @@ function formatHuman(r) {
187
200
  }
188
201
  else {
189
202
  for (const g of r.godNodes) {
190
- out.push(` ${style.cyan(g.symbol.padEnd(28))} ${style.dim(`${g.inboundFiles} files`)}, ${g.totalRefs} refs ${style.dim(`${g.file}:${g.line + 1}`)}`);
203
+ const ambiguous = g.defFiles > 1
204
+ ? style.dim(` ~defined in ${g.defFiles} files, location is a guess`)
205
+ : "";
206
+ out.push(` ${style.cyan(g.symbol.padEnd(28))} ${style.dim(`${g.inboundFiles} files`)}, ${g.totalRefs} refs ${style.dim(`${g.file}:${g.line + 1}`)}${ambiguous}`);
191
207
  }
192
208
  }
193
209
  out.push("");
@@ -224,7 +240,8 @@ function formatAgent(r) {
224
240
  const lines = [];
225
241
  lines.push(`scanned\t${r.scannedChunks}\t${r.scannedFiles}`);
226
242
  for (const g of r.godNodes) {
227
- lines.push(`god\t${g.symbol}\t${g.file}:${g.line + 1}\t${g.inboundFiles}\t${g.totalRefs}`);
243
+ const ambiguous = g.defFiles > 1 ? `\tdefs=${g.defFiles}` : "";
244
+ lines.push(`god\t${g.symbol}\t${g.file}:${g.line + 1}\t${g.inboundFiles}\t${g.totalRefs}${ambiguous}`);
228
245
  }
229
246
  for (const h of r.hubFiles) {
230
247
  lines.push(`hub\t${h.file}\t${h.dependents}\t${h.defines}\t${h.fanOut}`);
@@ -274,6 +291,7 @@ exports.audit = new commander_1.Command("audit")
274
291
  "start_line",
275
292
  "defined_symbols",
276
293
  "referenced_symbols",
294
+ "type_referenced_symbols",
277
295
  "is_exported",
278
296
  ])
279
297
  .where(buildScopeWhere(scope))
@@ -291,7 +309,14 @@ exports.audit = new commander_1.Command("audit")
291
309
  start_line: Number(r.start_line || 0),
292
310
  is_exported: Boolean(r.is_exported),
293
311
  defined_symbols: (0, arrow_1.toArr)(r.defined_symbols),
294
- referenced_symbols: (0, arrow_1.toArr)(r.referenced_symbols),
312
+ // Union call-position + type-position edges so god-node ranking and
313
+ // dead-candidate detection see references made purely in type position.
314
+ referenced_symbols: [
315
+ ...new Set([
316
+ ...(0, arrow_1.toArr)(r.referenced_symbols),
317
+ ...(0, arrow_1.toArr)(r.type_referenced_symbols),
318
+ ]),
319
+ ],
295
320
  })), prefix, top);
296
321
  console.log(opts.agent ? formatAgent(result) : formatHuman(result));
297
322
  }
@@ -43,6 +43,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
43
43
  };
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.context = void 0;
46
+ exports.findEnclosingSignature = findEnclosingSignature;
46
47
  const fs = __importStar(require("node:fs"));
47
48
  const path = __importStar(require("node:path"));
48
49
  const commander_1 = require("commander");
@@ -82,6 +83,23 @@ function chunkEndLine(chunk) {
82
83
  const start = chunkStartLine(chunk);
83
84
  return Number((_d = (_b = (_a = chunk.end_line) !== null && _a !== void 0 ? _a : chunk.endLine) !== null && _b !== void 0 ? _b : (_c = chunk.generated_metadata) === null || _c === void 0 ? void 0 : _c.end_line) !== null && _d !== void 0 ? _d : start);
84
85
  }
86
+ /**
87
+ * The definition line of `parentSymbol` nearest above `startLine` — used to
88
+ * give a mid-function sub-chunk extract its enclosing signature. Requires a
89
+ * definition-shaped line, not just any mention, so recursive calls or
90
+ * references between the definition and the chunk don't win.
91
+ */
92
+ function findEnclosingSignature(lines, startLine, parentSymbol) {
93
+ if (!parentSymbol || !/^\w+$/.test(parentSymbol))
94
+ return null;
95
+ const defRe = new RegExp(`(?:\\b(?:class|function|interface|enum|struct|trait|impl|def|fn|type|const|let|var)\\b[^=]*\\b${parentSymbol}\\b|\\b${parentSymbol}\\b\\s*[(:=])`);
96
+ for (let i = Math.min(startLine, lines.length) - 1; i >= 0; i--) {
97
+ if (defRe.test(lines[i])) {
98
+ return { text: lines[i].trim(), line: i };
99
+ }
100
+ }
101
+ return null;
102
+ }
85
103
  function resolveExistingPath(target, root, projectRoot) {
86
104
  const candidates = [
87
105
  path.isAbsolute(target) ? target : path.resolve(root, target),
@@ -196,7 +214,8 @@ exports.context = new commander_1.Command("context")
196
214
  for (const r of entryPoints.slice(0, 5)) {
197
215
  const p = chunkPath(r);
198
216
  const line = chunkStartLine(r);
199
- const sym = (_c = (_b = (0, arrow_1.toArr)(r.defined_symbols)) === null || _b === void 0 ? void 0 : _b[0]) !== null && _c !== void 0 ? _c : "";
217
+ const parentSym = String(r.parent_symbol || "");
218
+ const sym = (_c = (_b = (0, arrow_1.toArr)(r.defined_symbols)) === null || _b === void 0 ? void 0 : _b[0]) !== null && _c !== void 0 ? _c : (parentSym ? `(in ${parentSym})` : "");
200
219
  const role = String(r.role || "IMPLEMENTATION");
201
220
  epSection.push(`${relPath(projectRoot, p)}:${line + 1} ${sym} [${role}]`);
202
221
  }
@@ -215,13 +234,26 @@ exports.context = new commander_1.Command("context")
215
234
  const startLine = chunkStartLine(r);
216
235
  const endLine = chunkEndLine(r);
217
236
  const sym = (_b = (_a = (0, arrow_1.toArr)(r.defined_symbols)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : "";
237
+ const parentSym = String(r.parent_symbol || "");
218
238
  try {
219
239
  const content = fs.readFileSync(absP, "utf-8");
220
240
  const allLines = content.split("\n");
221
241
  const body = allLines
222
242
  .slice(startLine, Math.min(endLine + 1, allLines.length))
223
243
  .join("\n");
224
- return `\n--- ${relPath(projectRoot, absP)}:${startLine + 1} ${sym} ---\n${body}`;
244
+ // A sub-chunk that starts mid-function has no defined symbol of its
245
+ // own — prepend the enclosing definition's signature so the extract
246
+ // isn't headless code.
247
+ let enclosing = "";
248
+ let label = sym;
249
+ if (!sym && parentSym) {
250
+ label = `(in ${parentSym})`;
251
+ const sig = findEnclosingSignature(allLines, startLine, parentSym);
252
+ if (sig) {
253
+ enclosing = `${sig.text} // :${sig.line + 1}\n// ...\n`;
254
+ }
255
+ }
256
+ return `\n--- ${relPath(projectRoot, absP)}:${startLine + 1} ${label} ---\n${enclosing}${body}`;
225
257
  }
226
258
  catch (_c) {
227
259
  return null; // File not readable — drop
@@ -50,6 +50,7 @@ const agent_errors_1 = require("../lib/utils/agent-errors");
50
50
  const exit_1 = require("../lib/utils/exit");
51
51
  const filter_builder_1 = require("../lib/utils/filter-builder");
52
52
  const project_registry_1 = require("../lib/utils/project-registry");
53
+ const stale_hint_1 = require("../lib/utils/stale-hint");
53
54
  const project_root_1 = require("../lib/utils/project-root");
54
55
  const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
55
56
  const style = {
@@ -118,6 +119,7 @@ exports.dead = new commander_1.Command("dead")
118
119
  let vectorDb = null;
119
120
  try {
120
121
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
122
+ (0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
121
123
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
122
124
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
123
125
  const { resolveScope, buildScopeWhere } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/scope-filter")));
@@ -85,7 +85,7 @@ exports.doctor = new commander_1.Command("doctor")
85
85
  .option("--fix", "Auto-fix detected issues (compact, prune, remove stale locks)", false)
86
86
  .option("--agent", "Compact output for AI agents", false)
87
87
  .action((opts) => __awaiter(void 0, void 0, void 0, function* () {
88
- var _a, _b, _c, _d;
88
+ var _a, _b, _c, _d, _e, _f;
89
89
  if (!opts.agent)
90
90
  console.log("gmax Doctor\n");
91
91
  const root = config_1.PATHS.globalRoot;
@@ -236,7 +236,12 @@ exports.doctor = new commander_1.Command("doctor")
236
236
  availBytes = diskStats.bavail * diskStats.bsize;
237
237
  diskLevel = availBytes < config_1.DISK_CRITICAL_BYTES ? "CRITICAL" : availBytes < config_1.DISK_LOW_BYTES ? "LOW" : "ok";
238
238
  }
239
- catch (_e) { }
239
+ catch (_g) { }
240
+ const staleChunkerProjects = projects.filter((p) => {
241
+ var _a;
242
+ return p.status === "indexed" &&
243
+ ((_a = p.chunkerVersion) !== null && _a !== void 0 ? _a : 1) < config_1.CONFIG.CHUNKER_VERSION;
244
+ });
240
245
  if (opts.agent) {
241
246
  const fields = [
242
247
  "index_health",
@@ -251,8 +256,23 @@ exports.doctor = new commander_1.Command("doctor")
251
256
  `lock=${lockStatus.split(" ")[0]}`,
252
257
  `daemon=${daemonUp ? "running" : "stopped"}`,
253
258
  `orphaned=${orphanedProjects.length}`,
259
+ `stale_chunker=${staleChunkerProjects.length}`,
254
260
  ];
255
261
  console.log(fields.join("\t"));
262
+ for (const p of staleChunkerProjects) {
263
+ const gap = (0, config_1.describeChunkerGap)(p.chunkerVersion);
264
+ if (!gap)
265
+ continue;
266
+ console.log([
267
+ "stale_chunker_project",
268
+ `name=${p.name || path.basename(p.root)}`,
269
+ `indexed_v=${gap.fromVersion}`,
270
+ `current_v=${gap.toVersion}`,
271
+ `severity=${gap.severity}`,
272
+ `note=${gap.notes.join("; ")}`,
273
+ `fix=gmax index --reset (in ${p.root})`,
274
+ ].join("\t"));
275
+ }
256
276
  }
257
277
  else {
258
278
  console.log("\nIndex Health\n");
@@ -296,6 +316,21 @@ exports.doctor = new commander_1.Command("doctor")
296
316
  }
297
317
  // Daemon
298
318
  console.log(`${daemonUp ? "ok" : "INFO"} Daemon: ${daemonUp ? "running" : "not running"}`);
319
+ // Index built by an older chunker — graph metadata fixes need a reindex.
320
+ // Severity (and the WARN/INFO label) is driven by CHUNKER_VERSION_HISTORY:
321
+ // a breaking gap means stale metadata is wrong, an additive gap only
322
+ // means newer edges are missing.
323
+ if (staleChunkerProjects.length > 0) {
324
+ const anyBreaking = staleChunkerProjects.some((p) => { var _a; return ((_a = (0, config_1.describeChunkerGap)(p.chunkerVersion)) === null || _a === void 0 ? void 0 : _a.severity) === "breaking"; });
325
+ console.log(`${anyBreaking ? "WARN" : "INFO"} Stale chunker: ${staleChunkerProjects.length} project(s) indexed before chunker v${config_1.CONFIG.CHUNKER_VERSION} — run 'gmax doctor --fix' to reindex`);
326
+ for (const p of staleChunkerProjects) {
327
+ const gap = (0, config_1.describeChunkerGap)(p.chunkerVersion);
328
+ if (!gap)
329
+ continue;
330
+ console.log(` - ${p.name || path.basename(p.root)} (v${gap.fromVersion}→v${gap.toVersion}, ${gap.severity}): ${gap.notes.join(" ")}`);
331
+ console.log(` run 'gmax index --reset' in ${p.root}`);
332
+ }
333
+ }
299
334
  // Projects
300
335
  if (orphanedProjects.length > 0) {
301
336
  console.log(`WARN Orphaned projects: ${orphanedProjects.length} (directories no longer exist)`);
@@ -324,7 +359,7 @@ exports.doctor = new commander_1.Command("doctor")
324
359
  }
325
360
  yield mc.close();
326
361
  }
327
- catch (_f) { }
362
+ catch (_h) { }
328
363
  }
329
364
  }
330
365
  // --fix auto-remediation
@@ -354,6 +389,38 @@ exports.doctor = new commander_1.Command("doctor")
354
389
  console.log(`ok Removed ${orphanedProjects.length} orphaned project(s) from registry`);
355
390
  fixed++;
356
391
  }
392
+ // Reindex projects whose index predates the current chunker. This is a
393
+ // full `--reset` reindex per project (routed through the daemon), so it
394
+ // can be slow on large repos — unlike the cheap fixes above.
395
+ if (staleChunkerProjects.length > 0) {
396
+ const { ensureDaemonRunning, sendStreamingCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
397
+ if (!(yield ensureDaemonRunning())) {
398
+ if (!opts.agent)
399
+ console.log("WARN Stale chunker: daemon not running — start it (gmax watch --daemon -b) or run 'gmax index --reset' per project");
400
+ }
401
+ else {
402
+ for (const p of staleChunkerProjects) {
403
+ const name = p.name || path.basename(p.root);
404
+ if (!opts.agent)
405
+ console.log(`... Reindexing ${name} (--reset)...`);
406
+ const done = yield sendStreamingCommand({ cmd: "index", root: p.root, reset: true }, () => { }).catch((e) => ({ ok: false, error: String(e) }));
407
+ if (done.ok) {
408
+ (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 }));
409
+ const chunks = (_f = done.indexed) !== null && _f !== void 0 ? _f : 0;
410
+ if (opts.agent) {
411
+ console.log(`stale_chunker_reindexed\tname=${name}\tchunks=${chunks}`);
412
+ }
413
+ else {
414
+ console.log(`ok ${name} reindexed (${chunks} chunks)`);
415
+ }
416
+ fixed++;
417
+ }
418
+ else if (!opts.agent) {
419
+ console.log(`FAIL ${name}: reindex failed (${done.error})`);
420
+ }
421
+ }
422
+ }
423
+ }
357
424
  if (fixed === 0) {
358
425
  if (!opts.agent)
359
426
  console.log("ok Nothing to fix");
@@ -46,10 +46,12 @@ exports.impact = void 0;
46
46
  const path = __importStar(require("node:path"));
47
47
  const commander_1 = require("commander");
48
48
  const impact_1 = require("../lib/graph/impact");
49
+ const test_hits_1 = require("../lib/graph/test-hits");
49
50
  const vector_db_1 = require("../lib/store/vector-db");
50
51
  const agent_errors_1 = require("../lib/utils/agent-errors");
51
52
  const exit_1 = require("../lib/utils/exit");
52
53
  const project_registry_1 = require("../lib/utils/project-registry");
54
+ const stale_hint_1 = require("../lib/utils/stale-hint");
53
55
  const project_root_1 = require("../lib/utils/project-root");
54
56
  exports.impact = new commander_1.Command("impact")
55
57
  .description("Analyze change impact: dependents and affected tests")
@@ -71,6 +73,7 @@ exports.impact = new commander_1.Command("impact")
71
73
  if (root === null)
72
74
  return;
73
75
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
76
+ (0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
74
77
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
75
78
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
76
79
  const { symbols, resolvedAsFile } = yield (0, impact_1.resolveTargetSymbols)(target, vectorDb, projectRoot);
@@ -110,14 +113,15 @@ exports.impact = new commander_1.Command("impact")
110
113
  ]);
111
114
  // Separate test files from non-test dependents
112
115
  const nonTestDeps = dependents.filter((d) => !(0, impact_1.isTestPath)(d.file));
116
+ // One line per test file; caller symbols inside it become `via` detail.
117
+ const groupedTests = (0, test_hits_1.groupTestHitsByFile)(tests);
113
118
  const rel = (p) => p.startsWith(`${projectRoot}/`) ? p.slice(projectRoot.length + 1) : p;
114
119
  if (opts.agent) {
115
120
  for (const d of nonTestDeps) {
116
121
  console.log(`dep: ${rel(d.file)}\t${d.sharedSymbols}`);
117
122
  }
118
- for (const t of tests) {
119
- const hopLabel = t.hops === 0 ? "direct" : `${t.hops}-hop`;
120
- console.log(`test: ${rel(t.file)}:${t.line + 1}\t${t.symbol}\t${hopLabel}`);
123
+ for (const t of groupedTests) {
124
+ console.log(`test: ${rel(t.file)}:${t.line + 1}\t${(0, test_hits_1.hopLabelAgent)(t.hops)}${(0, test_hits_1.formatViaAgent)(t.via)}`);
121
125
  }
122
126
  if (!nonTestDeps.length && !tests.length) {
123
127
  console.log("(no impact detected)");
@@ -136,11 +140,10 @@ exports.impact = new commander_1.Command("impact")
136
140
  }
137
141
  if (includeTests) {
138
142
  console.log("");
139
- if (tests.length > 0) {
140
- console.log(`Affected tests (${tests.length}):`);
141
- for (const t of tests) {
142
- const hopLabel = t.hops === 0 ? "calls directly" : `${t.hops} hop${t.hops > 1 ? "s" : ""} away`;
143
- console.log(` ${rel(t.file)}:${t.line + 1} ${t.symbol} (${hopLabel})`);
143
+ if (groupedTests.length > 0) {
144
+ console.log(`Affected tests (${groupedTests.length}):`);
145
+ for (const t of groupedTests) {
146
+ console.log(` ${rel(t.file)}:${t.line + 1} (${(0, test_hits_1.hopLabelHuman)(t.hops)}${(0, test_hits_1.formatViaHuman)(t.via)})`);
144
147
  }
145
148
  }
146
149
  else {
@@ -45,6 +45,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.index = void 0;
46
46
  const path = __importStar(require("node:path"));
47
47
  const commander_1 = require("commander");
48
+ const config_1 = require("../config");
48
49
  const index_config_1 = require("../lib/index/index-config");
49
50
  const grammar_loader_1 = require("../lib/index/grammar-loader");
50
51
  const sync_helpers_1 = require("../lib/index/sync-helpers");
@@ -70,7 +71,7 @@ Examples:
70
71
  gmax index --reset Full re-index from scratch
71
72
  `)
72
73
  .action((_args, cmd) => __awaiter(void 0, void 0, void 0, function* () {
73
- var _a, _b, _c, _d;
74
+ var _a, _b, _c, _d, _e;
74
75
  const options = cmd.optsWithGlobals();
75
76
  let vectorDb = null;
76
77
  const ac = new AbortController();
@@ -90,11 +91,18 @@ Examples:
90
91
  : process.cwd();
91
92
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(indexRoot)) !== null && _a !== void 0 ? _a : indexRoot;
92
93
  // Project must be registered before reindexing
93
- if (!(0, project_registry_1.getProject)(projectRoot)) {
94
+ const existingEntry = (0, project_registry_1.getProject)(projectRoot);
95
+ if (!existingEntry) {
94
96
  console.error(`This project hasn't been added yet.\n\nRun: gmax add ${projectRoot}\n`);
95
97
  process.exitCode = 1;
96
98
  return;
97
99
  }
100
+ // Only a reset rechunks cached files, so only a reset may claim the
101
+ // current chunker version — a plain index would clear the doctor
102
+ // stale-chunker warning without regenerating any chunks.
103
+ const stampedChunkerVersion = options.reset
104
+ ? config_1.CONFIG.CHUNKER_VERSION
105
+ : ((_b = existingEntry.chunkerVersion) !== null && _b !== void 0 ? _b : 1);
98
106
  if (options.reset) {
99
107
  console.log("Resetting index...");
100
108
  }
@@ -115,7 +123,7 @@ Examples:
115
123
  });
116
124
  });
117
125
  if (!done.ok) {
118
- throw new Error((_b = done.error) !== null && _b !== void 0 ? _b : "daemon index failed");
126
+ throw new Error((_c = done.error) !== null && _c !== void 0 ? _c : "daemon index failed");
119
127
  }
120
128
  if (options.dryRun) {
121
129
  spinner.succeed(`Dry run complete(${done.processed} / ${done.total}) • would have indexed ${done.indexed} `);
@@ -129,10 +137,11 @@ Examples:
129
137
  modelTier: globalConfig.modelTier,
130
138
  embedMode: globalConfig.embedMode,
131
139
  lastIndexed: new Date().toISOString(),
132
- chunkCount: (_c = done.indexed) !== null && _c !== void 0 ? _c : 0,
140
+ chunkCount: (_d = done.indexed) !== null && _d !== void 0 ? _d : 0,
133
141
  status: "indexed",
142
+ chunkerVersion: stampedChunkerVersion,
134
143
  });
135
- const failedFiles = (_d = done.failedFiles) !== null && _d !== void 0 ? _d : 0;
144
+ const failedFiles = (_e = done.failedFiles) !== null && _e !== void 0 ? _e : 0;
136
145
  const failedSuffix = failedFiles > 0 ? ` • ${failedFiles} failed` : "";
137
146
  spinner.succeed(`Indexing complete(${done.processed} / ${done.total}) • indexed ${done.indexed}${failedSuffix} `);
138
147
  }
@@ -152,7 +161,7 @@ Examples:
152
161
  try {
153
162
  process.kill(watcher.pid, "SIGTERM");
154
163
  }
155
- catch (_e) { }
164
+ catch (_f) { }
156
165
  for (let i = 0; i < 50; i++) {
157
166
  if (!(0, watcher_store_1.isProcessRunning)(watcher.pid))
158
167
  break;
@@ -192,6 +201,7 @@ Examples:
192
201
  lastIndexed: new Date().toISOString(),
193
202
  chunkCount: result.indexed,
194
203
  status: "indexed",
204
+ chunkerVersion: stampedChunkerVersion,
195
205
  });
196
206
  const failedSuffix = result.failedFiles > 0 ? ` • ${result.failedFiles} failed` : "";
197
207
  spinner.succeed(`Indexing complete(${result.processed} / ${result.total}) • indexed ${result.indexed}${failedSuffix} `);
@@ -623,6 +623,7 @@ function formatMcpPointerSearchResults(data, displayRoot, options = {}) {
623
623
  return (0, agent_search_formatter_1.formatAgentSearchResults)(filterMcpSearchResults(data, options), displayRoot, {
624
624
  includeImports: options.includeImports,
625
625
  getImportsForFile: options.getImportsForFile,
626
+ query: options.query,
626
627
  });
627
628
  }
628
629
  // ---------------------------------------------------------------------------
@@ -834,6 +835,7 @@ exports.mcp = new commander_1.Command("mcp")
834
835
  namePattern,
835
836
  includeImports,
836
837
  getImportsForFile,
838
+ query,
837
839
  });
838
840
  if ((_a = result.warnings) === null || _a === void 0 ? void 0 : _a.length) {
839
841
  return ok(`${result.warnings.join("\n")}\n\n${output}`);