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.
@@ -45,6 +45,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.peek = void 0;
46
46
  const fs = __importStar(require("node:fs"));
47
47
  const commander_1 = require("commander");
48
+ const callsites_1 = require("../lib/graph/callsites");
48
49
  const graph_builder_1 = require("../lib/graph/graph-builder");
49
50
  const vector_db_1 = require("../lib/store/vector-db");
50
51
  const agent_errors_1 = require("../lib/utils/agent-errors");
@@ -52,6 +53,7 @@ const exit_1 = require("../lib/utils/exit");
52
53
  const filter_builder_1 = require("../lib/utils/filter-builder");
53
54
  const language_1 = require("../lib/utils/language");
54
55
  const project_registry_1 = require("../lib/utils/project-registry");
56
+ const stale_hint_1 = require("../lib/utils/stale-hint");
55
57
  const project_root_1 = require("../lib/utils/project-root");
56
58
  const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
57
59
  const style = {
@@ -69,26 +71,45 @@ function extractSignature(filePath, startLine, endLine) {
69
71
  const lines = content.split("\n");
70
72
  const chunk = lines.slice(startLine, endLine + 1);
71
73
  const bodyLines = chunk.length;
72
- // Find the signature: everything up to and including the opening brace
74
+ // Find the signature: everything up to and including the opening brace.
75
+ // Only treat `{` / `=>` as the body boundary once the parameter list's
76
+ // parens are balanced — object-literal param types (`cached: { … }`)
77
+ // contain braces mid-signature and must not end it.
73
78
  const sigLines = [];
79
+ let parenDepth = 0;
74
80
  for (const line of chunk) {
75
81
  sigLines.push(line);
76
- if (line.includes("{") || line.includes("=>"))
82
+ for (const ch of line) {
83
+ if (ch === "(")
84
+ parenDepth++;
85
+ else if (ch === ")")
86
+ parenDepth--;
87
+ }
88
+ if (parenDepth <= 0 && (line.includes("{") || line.includes("=>"))) {
77
89
  break;
90
+ }
91
+ if (sigLines.length >= 12)
92
+ break; // degenerate input — bail
78
93
  }
79
94
  // If we only got one line and it's the whole function, collapse it
80
95
  if (sigLines.length >= bodyLines) {
81
- return { signature: chunk.join("\n"), bodyLines: 0 };
96
+ const whole = chunk.join("\n");
97
+ return { signature: whole, signatureOnly: whole, bodyLines: 0 };
82
98
  }
83
99
  const sig = sigLines.join("\n");
84
100
  const remaining = bodyLines - sigLines.length;
85
101
  return {
86
102
  signature: `${sig}\n // ... (${remaining} lines)\n }`,
103
+ signatureOnly: sig,
87
104
  bodyLines,
88
105
  };
89
106
  }
90
107
  catch (_a) {
91
- return { signature: "(source not available)", bodyLines: 0 };
108
+ return {
109
+ signature: "(source not available)",
110
+ signatureOnly: "(source not available)",
111
+ bodyLines: 0,
112
+ };
92
113
  }
93
114
  }
94
115
  exports.peek = new commander_1.Command("peek")
@@ -109,6 +130,7 @@ exports.peek = new commander_1.Command("peek")
109
130
  const depth = Math.min(Math.max(Number.parseInt(opts.depth || "1", 10), 1), 3);
110
131
  try {
111
132
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
133
+ (0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
112
134
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
113
135
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
114
136
  const { resolveScope, buildScopeWhere } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/scope-filter")));
@@ -122,6 +144,9 @@ exports.peek = new commander_1.Command("peek")
122
144
  // languages, refuse to silently pick one. The graph builder otherwise
123
145
  // picks one chunk arbitrarily and lists callers from a different
124
146
  // language — verified failure mode.
147
+ // Same-language multi-definition is reported as a note instead (below):
148
+ // the first definition still wins, but the agent learns it guessed.
149
+ let otherDefs = [];
125
150
  {
126
151
  const tableForCheck = yield vectorDb.ensureTable();
127
152
  const allDefs = yield tableForCheck
@@ -134,6 +159,14 @@ exports.peek = new commander_1.Command("peek")
134
159
  path: String(row.path || ""),
135
160
  startLine: Number(row.start_line || 0),
136
161
  }));
162
+ // Dedupe by file: split sub-chunks of one definition share a path,
163
+ // while genuine ambiguity (same name defined elsewhere) crosses files.
164
+ const distinct = new Map();
165
+ for (const c of chunks) {
166
+ if (!distinct.has(c.path))
167
+ distinct.set(c.path, c);
168
+ }
169
+ otherDefs = [...distinct.values()];
137
170
  const byLang = (0, language_1.groupByLanguage)(chunks);
138
171
  if (byLang.size >= 2) {
139
172
  const rel = (p) => p.startsWith(projectRoot) ? p.slice(projectRoot.length + 1) : p;
@@ -198,7 +231,30 @@ exports.peek = new commander_1.Command("peek")
198
231
  line: c.line,
199
232
  }));
200
233
  }
201
- const calleeList = graph.callees.map((c) => ({
234
+ // Re-anchor chunk-level caller rows to actual call sites and dedupe —
235
+ // getCallers() returns one row per chunk, which multiplies callers for
236
+ // classes split across many chunks (verified: 3 real call sites → 66).
237
+ const resolvedCallers = (0, callsites_1.resolveCallSites)(callerList, symbol).map((c) => {
238
+ var _a;
239
+ return ({
240
+ symbol: c.symbol,
241
+ file: c.file,
242
+ line: (_a = c.snippetLine) !== null && _a !== void 0 ? _a : c.line,
243
+ });
244
+ });
245
+ // Builtins listed as "(not indexed)" callees (trunc, now, filter, …)
246
+ // are noise; project symbols always resolve so they're unaffected.
247
+ // Dedupe by symbol — repeated references arrive once per chunk.
248
+ const seenCallees = new Set();
249
+ const calleeList = graph.callees
250
+ .filter((c) => c.file || !(0, callsites_1.isBuiltinCallee)(c.symbol))
251
+ .filter((c) => {
252
+ if (seenCallees.has(c.symbol))
253
+ return false;
254
+ seenCallees.add(c.symbol);
255
+ return true;
256
+ })
257
+ .map((c) => ({
202
258
  symbol: c.symbol,
203
259
  file: c.file,
204
260
  line: c.line,
@@ -207,16 +263,30 @@ exports.peek = new commander_1.Command("peek")
207
263
  // Compact TSV output
208
264
  const exportedStr = exported ? "exported" : "";
209
265
  console.log(`${center.symbol}\t${rel(center.file)}:${center.line + 1}\t${center.role}\t${exportedStr}`);
210
- // Signature (first line only)
211
- const { signature } = extractSignature(center.file, startLine, endLine);
212
- const firstLine = signature.split("\n")[0].trim();
213
- console.log(`sig: ${firstLine}`);
266
+ if (otherDefs.length > 1) {
267
+ const others = otherDefs
268
+ .filter((d) => d.path !== center.file)
269
+ .slice(0, 4)
270
+ .map((d) => `${rel(d.path)}:${d.startLine + 1}`);
271
+ if (others.length > 0) {
272
+ console.log(`also-defined: ${others.join(", ")} — showing the first; pin with --in <subpath>`);
273
+ }
274
+ }
275
+ // Signature — all lines up to the opening brace, collapsed to one
276
+ // line so parameters survive (first-line-only loses them).
277
+ const { signatureOnly } = extractSignature(center.file, startLine, endLine);
278
+ const sigOnly = signatureOnly
279
+ .split("\n")
280
+ .map((l) => l.trim())
281
+ .join(" ")
282
+ .replace(/\s+/g, " ");
283
+ console.log(`sig: ${sigOnly}`);
214
284
  // Callers
215
- for (const c of callerList.slice(0, MAX_CALLERS)) {
285
+ for (const c of resolvedCallers.slice(0, MAX_CALLERS)) {
216
286
  console.log(`<- ${c.symbol}\t${c.file ? `${rel(c.file)}:${c.line + 1}` : "(not indexed)"}`);
217
287
  }
218
- if (callerList.length > MAX_CALLERS) {
219
- console.log(`<- ... ${callerList.length - MAX_CALLERS} more`);
288
+ if (resolvedCallers.length > MAX_CALLERS) {
289
+ console.log(`<- ... ${resolvedCallers.length - MAX_CALLERS} more`);
220
290
  }
221
291
  // Callees
222
292
  for (const c of calleeList.slice(0, MAX_CALLEES)) {
@@ -239,6 +309,15 @@ exports.peek = new commander_1.Command("peek")
239
309
  // Rich output
240
310
  const exportedStr = exported ? ", exported" : "";
241
311
  console.log(`${style.bold(`peek: ${center.symbol}`)} ${style.dim(`${rel(center.file)}:${center.line + 1}`)} ${style.dim(`[${center.role}${exportedStr}]`)}`);
312
+ if (otherDefs.length > 1) {
313
+ const others = otherDefs
314
+ .filter((d) => d.path !== center.file)
315
+ .slice(0, 4)
316
+ .map((d) => `${rel(d.path)}:${d.startLine + 1}`);
317
+ if (others.length > 0) {
318
+ console.log(style.dim(` also defined in: ${others.join(", ")} — showing the first; pin with --in <subpath>`));
319
+ }
320
+ }
242
321
  console.log();
243
322
  // Signature with collapsed body
244
323
  const { signature } = extractSignature(center.file, startLine, endLine);
@@ -247,9 +326,9 @@ exports.peek = new commander_1.Command("peek")
247
326
  }
248
327
  console.log();
249
328
  // Callers
250
- if (callerList.length > 0) {
251
- const shown = callerList.slice(0, MAX_CALLERS);
252
- console.log(style.bold(`callers (${callerList.length}):`));
329
+ if (resolvedCallers.length > 0) {
330
+ const shown = resolvedCallers.slice(0, MAX_CALLERS);
331
+ console.log(style.bold(`callers (${resolvedCallers.length}):`));
253
332
  for (const c of shown) {
254
333
  if (c.file) {
255
334
  console.log(` ${style.blue("\u2190")} ${style.green(c.symbol.padEnd(25))} ${style.dim(`${rel(c.file)}:${c.line + 1}`)}`);
@@ -258,8 +337,8 @@ exports.peek = new commander_1.Command("peek")
258
337
  console.log(` ${style.blue("\u2190")} ${c.symbol.padEnd(25)} ${style.dim("(not indexed)")}`);
259
338
  }
260
339
  }
261
- if (callerList.length > MAX_CALLERS) {
262
- console.log(style.dim(` ... and ${callerList.length - MAX_CALLERS} more`));
340
+ if (resolvedCallers.length > MAX_CALLERS) {
341
+ console.log(style.dim(` ... and ${resolvedCallers.length - MAX_CALLERS} more`));
263
342
  }
264
343
  }
265
344
  else {
@@ -45,6 +45,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.project = void 0;
46
46
  const path = __importStar(require("node:path"));
47
47
  const commander_1 = require("commander");
48
+ const callsites_1 = require("../lib/graph/callsites");
48
49
  const vector_db_1 = require("../lib/store/vector-db");
49
50
  const filter_builder_1 = require("../lib/utils/filter-builder");
50
51
  const exit_1 = require("../lib/utils/exit");
@@ -91,7 +92,9 @@ exports.project = new commander_1.Command("project")
91
92
  const dirCounts = new Map();
92
93
  const roleCounts = new Map();
93
94
  const symbolRefs = new Map();
95
+ const definedInProject = new Set();
94
96
  const entryPoints = [];
97
+ const seenEntryPoints = new Set();
95
98
  for (const row of rows) {
96
99
  const p = String(row.path || "");
97
100
  const role = String(row.role || "IMPLEMENTATION");
@@ -115,13 +118,19 @@ exports.project = new commander_1.Command("project")
115
118
  dc.files.add(p);
116
119
  dc.chunks++;
117
120
  roleCounts.set(role, (roleCounts.get(role) || 0) + 1);
121
+ for (const d of defs)
122
+ definedInProject.add(d);
118
123
  for (const ref of refs)
119
124
  symbolRefs.set(ref, (symbolRefs.get(ref) || 0) + 1);
120
125
  if (exported && role === "ORCHESTRATION" && complexity >= 5 && defs.length > 0) {
121
- entryPoints.push({
122
- symbol: defs[0],
123
- path: p.startsWith(prefix) ? p.slice(prefix.length) : p,
124
- });
126
+ const epKey = `${defs[0]}:${p}`;
127
+ if (!seenEntryPoints.has(epKey)) {
128
+ seenEntryPoints.add(epKey);
129
+ entryPoints.push({
130
+ symbol: defs[0],
131
+ path: p.startsWith(prefix) ? p.slice(prefix.length) : p,
132
+ });
133
+ }
125
134
  }
126
135
  }
127
136
  const projects = (0, project_registry_1.listProjects)();
@@ -129,7 +138,11 @@ exports.project = new commander_1.Command("project")
129
138
  const extEntries = Array.from(extCounts.entries())
130
139
  .sort((a, b) => b[1] - a[1])
131
140
  .slice(0, 8);
141
+ // Key symbols must be the project's own: raw referenced_symbols counts
142
+ // are dominated by JS builtins (push, slice, map, …) which say nothing
143
+ // about the codebase.
132
144
  const topSymbols = Array.from(symbolRefs.entries())
145
+ .filter(([s]) => definedInProject.has(s) && !(0, callsites_1.isBuiltinCallee)(s))
133
146
  .sort((a, b) => b[1] - a[1])
134
147
  .slice(0, 8);
135
148
  if (opts.agent) {
@@ -51,6 +51,8 @@ const filter_builder_1 = require("../lib/utils/filter-builder");
51
51
  const exit_1 = require("../lib/utils/exit");
52
52
  const project_registry_1 = require("../lib/utils/project-registry");
53
53
  const project_root_1 = require("../lib/utils/project-root");
54
+ const query_timeout_1 = require("../lib/utils/query-timeout");
55
+ const stale_hint_1 = require("../lib/utils/stale-hint");
54
56
  const arrow_1 = require("../lib/utils/arrow");
55
57
  exports.related = new commander_1.Command("related")
56
58
  .description("Find files related by shared symbol references")
@@ -69,6 +71,7 @@ exports.related = new commander_1.Command("related")
69
71
  if (root === null)
70
72
  return;
71
73
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
74
+ (0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
72
75
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
73
76
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
74
77
  const absPath = path.resolve(projectRoot, file);
@@ -164,12 +167,13 @@ exports.related = new commander_1.Command("related")
164
167
  basenameRejected = true;
165
168
  }
166
169
  else {
167
- const rows = yield table
170
+ // No .limit() here: LIKE + limit deadlocks in @lancedb 0.27.x when
171
+ // more rows match than the limit (verified). The loop below caps.
172
+ const rows = yield (0, query_timeout_1.withQueryTimeout)(table
168
173
  .query()
169
174
  .select(["path"])
170
175
  .where(`content LIKE '%${(0, filter_builder_1.escapeSqlString)(basename)}%' AND ${pathScope}`)
171
- .limit(limit * 4)
172
- .toArray();
176
+ .toArray(), `content LIKE %${basename}% (related mentions)`);
173
177
  const seen = new Set();
174
178
  for (const row of rows) {
175
179
  const p = String(row.path || "");
@@ -56,6 +56,7 @@ const import_extractor_1 = require("../lib/utils/import-extractor");
56
56
  const project_registry_1 = require("../lib/utils/project-registry");
57
57
  const project_root_1 = require("../lib/utils/project-root");
58
58
  const server_registry_1 = require("../lib/utils/server-registry");
59
+ const stale_hint_1 = require("../lib/utils/stale-hint");
59
60
  const search_run_1 = require("./search-run");
60
61
  const search_skeletons_1 = require("./search-skeletons");
61
62
  exports.search = new commander_1.Command("search")
@@ -217,6 +218,7 @@ Examples:
217
218
  };
218
219
  console.log((0, agent_search_formatter_1.formatAgentSearchResults)(filteredData, projectRootForServer, {
219
220
  includeImports: options.imports,
221
+ query: pattern,
220
222
  getImportsForFile,
221
223
  explain: options.explain,
222
224
  }));
@@ -284,6 +286,7 @@ Examples:
284
286
  if (project.status === "pending") {
285
287
  console.warn("This project is still being indexed. Results may be incomplete.\n");
286
288
  }
289
+ (0, stale_hint_1.maybeWarnStaleChunker)(checkRoot, { agent: options.agent });
287
290
  // Compute effective paths + filters early — both the daemon-mediated
288
291
  // and in-process search paths need them. Reuse the resolved checkRoot
289
292
  // so --root <name> only resolves once per invocation.
@@ -444,6 +447,7 @@ Examples:
444
447
  if (options.agent) {
445
448
  body = (0, agent_search_formatter_1.formatAgentSearchResults)(g.items, g.root, {
446
449
  includeImports: options.imports,
450
+ query: pattern,
447
451
  getImportsForFile,
448
452
  explain: options.explain,
449
453
  });
@@ -487,6 +491,7 @@ Examples:
487
491
  else {
488
492
  console.log((0, agent_search_formatter_1.formatAgentSearchResults)(filteredData, effectiveRoot, {
489
493
  includeImports: options.imports,
494
+ query: pattern,
490
495
  getImportsForFile,
491
496
  explain: options.explain,
492
497
  }));
@@ -51,6 +51,7 @@ const filter_builder_1 = require("../lib/utils/filter-builder");
51
51
  const exit_1 = require("../lib/utils/exit");
52
52
  const project_registry_1 = require("../lib/utils/project-registry");
53
53
  const project_root_1 = require("../lib/utils/project-root");
54
+ const stale_hint_1 = require("../lib/utils/stale-hint");
54
55
  const arrow_1 = require("../lib/utils/arrow");
55
56
  exports.similar = new commander_1.Command("similar")
56
57
  .description("Find semantically similar code to a symbol or file")
@@ -71,6 +72,7 @@ exports.similar = new commander_1.Command("similar")
71
72
  if (root === null)
72
73
  return;
73
74
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
75
+ (0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
74
76
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
75
77
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
76
78
  const table = yield vectorDb.ensureTable();
@@ -45,10 +45,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.testFind = void 0;
46
46
  const commander_1 = require("commander");
47
47
  const impact_1 = require("../lib/graph/impact");
48
+ const test_hits_1 = require("../lib/graph/test-hits");
48
49
  const vector_db_1 = require("../lib/store/vector-db");
49
50
  const exit_1 = require("../lib/utils/exit");
50
51
  const project_registry_1 = require("../lib/utils/project-registry");
51
52
  const project_root_1 = require("../lib/utils/project-root");
53
+ const stale_hint_1 = require("../lib/utils/stale-hint");
52
54
  exports.testFind = new commander_1.Command("test")
53
55
  .description("Find tests that exercise a symbol or file")
54
56
  .argument("<target>", "Symbol name or file path")
@@ -66,6 +68,7 @@ exports.testFind = new commander_1.Command("test")
66
68
  if (root === null)
67
69
  return;
68
70
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
71
+ (0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
69
72
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
70
73
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
71
74
  const { symbols, resolvedAsFile } = yield (0, impact_1.resolveTargetSymbols)(target, vectorDb, projectRoot);
@@ -91,25 +94,18 @@ exports.testFind = new commander_1.Command("test")
91
94
  return;
92
95
  }
93
96
  const rel = (p) => p.startsWith(`${projectRoot}/`) ? p.slice(projectRoot.length + 1) : p;
97
+ // One line per test file: the file is what the reader runs; caller
98
+ // symbols inside it (often internal helpers) are detail, not the lead.
99
+ const grouped = (0, test_hits_1.groupTestHitsByFile)(tests);
94
100
  if (opts.agent) {
95
- for (const t of tests) {
96
- const hopLabel = t.hops === -1
97
- ? "via-import"
98
- : t.hops === 0
99
- ? "direct"
100
- : `${t.hops}-hop`;
101
- console.log(`${rel(t.file)}:${t.line + 1}\t${t.symbol}\t${hopLabel}`);
101
+ for (const t of grouped) {
102
+ console.log(`${rel(t.file)}:${t.line + 1}\t${(0, test_hits_1.hopLabelAgent)(t.hops)}${(0, test_hits_1.formatViaAgent)(t.via)}`);
102
103
  }
103
104
  }
104
105
  else {
105
106
  console.log(`Tests for ${target}:\n`);
106
- for (const t of tests) {
107
- const hopLabel = t.hops === -1
108
- ? "via import"
109
- : t.hops === 0
110
- ? "calls directly"
111
- : `${t.hops} hop${t.hops > 1 ? "s" : ""} away`;
112
- console.log(` ${rel(t.file)}:${t.line + 1} ${t.symbol} (${hopLabel})`);
107
+ for (const t of grouped) {
108
+ console.log(` ${rel(t.file)}:${t.line + 1} (${(0, test_hits_1.hopLabelHuman)(t.hops)}${(0, test_hits_1.formatViaHuman)(t.via)})`);
113
109
  }
114
110
  }
115
111
  }
@@ -43,14 +43,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
43
43
  };
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.trace = void 0;
46
- const fs = __importStar(require("node:fs"));
47
46
  const commander_1 = require("commander");
47
+ const callsites_1 = require("../lib/graph/callsites");
48
48
  const graph_builder_1 = require("../lib/graph/graph-builder");
49
49
  const formatter_1 = require("../lib/output/formatter");
50
50
  const vector_db_1 = require("../lib/store/vector-db");
51
51
  const agent_errors_1 = require("../lib/utils/agent-errors");
52
52
  const exit_1 = require("../lib/utils/exit");
53
53
  const project_registry_1 = require("../lib/utils/project-registry");
54
+ const stale_hint_1 = require("../lib/utils/stale-hint");
54
55
  const project_root_1 = require("../lib/utils/project-root");
55
56
  const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
56
57
  const dim = (s) => (useColors ? `\x1b[2m${s}\x1b[22m` : s);
@@ -78,33 +79,6 @@ function formatTraceAgent(graph, projectRoot) {
78
79
  }
79
80
  return lines.join("\n");
80
81
  }
81
- function findCallSiteSnippet(fileCache, callerFile, callerLine, targetSymbol) {
82
- if (!callerFile)
83
- return null;
84
- let lines = fileCache.get(callerFile);
85
- if (!lines) {
86
- try {
87
- lines = fs.readFileSync(callerFile, "utf-8").split("\n");
88
- }
89
- catch (_a) {
90
- return null;
91
- }
92
- fileCache.set(callerFile, lines);
93
- }
94
- // Search a bounded window starting at the caller's definition line.
95
- const start = Math.max(0, callerLine);
96
- const end = Math.min(lines.length, callerLine + 200);
97
- const wordRe = new RegExp(`\\b${targetSymbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
98
- for (let i = start; i < end; i++) {
99
- if (wordRe.test(lines[i])) {
100
- return { snippet: lines[i].trim(), snippetLine: i };
101
- }
102
- }
103
- // Window didn't contain the symbol — chunker rolled the reference up to a
104
- // parent scope. Skip the snippet rather than showing a misleading default;
105
- // the caller's file:line is still emitted.
106
- return null;
107
- }
108
82
  function buildInboundTree(callerTree, targetSymbol, fileCache, withSnippets, limit) {
109
83
  var _a, _b, _c;
110
84
  const out = [];
@@ -114,7 +88,7 @@ function buildInboundTree(callerTree, targetSymbol, fileCache, withSnippets, lim
114
88
  const seen = new Set();
115
89
  for (const t of callerTree) {
116
90
  const snippet = withSnippets
117
- ? findCallSiteSnippet(fileCache, t.node.file, t.node.line, targetSymbol)
91
+ ? (0, callsites_1.findCallSiteSnippet)(fileCache, t.node.file, t.node.line, targetSymbol)
118
92
  : null;
119
93
  const dedupeKey = `${t.node.file}:${(_a = snippet === null || snippet === void 0 ? void 0 : snippet.snippetLine) !== null && _a !== void 0 ? _a : t.node.line}`;
120
94
  if (seen.has(dedupeKey))
@@ -210,6 +184,7 @@ exports.trace = new commander_1.Command("trace")
210
184
  let vectorDb = null;
211
185
  try {
212
186
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
187
+ (0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
213
188
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
214
189
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
215
190
  const { resolveScope } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/scope-filter")));
package/dist/config.js CHANGED
@@ -33,7 +33,8 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.INDEXABLE_EXTENSIONS = exports.FRAGMENT_COMPACT_THRESHOLD = exports.DISK_LOW_BYTES = exports.DISK_CRITICAL_BYTES = exports.MAX_FILE_SIZE_BYTES = exports.PATHS = exports.MAX_WORKER_MEMORY_MB = exports.WORKER_BOOT_TIMEOUT_MS = exports.WORKER_TIMEOUT_MS = exports.CONFIG = exports.MODEL_IDS = exports.DEFAULT_MODEL_TIER = exports.MODEL_TIERS = void 0;
36
+ exports.INDEXABLE_EXTENSIONS = exports.FRAGMENT_COMPACT_THRESHOLD = exports.DISK_LOW_BYTES = exports.DISK_CRITICAL_BYTES = exports.MAX_FILE_SIZE_BYTES = exports.PATHS = exports.MAX_WORKER_MEMORY_MB = exports.WORKER_BOOT_TIMEOUT_MS = exports.WORKER_TIMEOUT_MS = exports.CHUNKER_VERSION_HISTORY = exports.CONFIG = exports.MODEL_IDS = exports.DEFAULT_MODEL_TIER = exports.MODEL_TIERS = void 0;
37
+ exports.describeChunkerGap = describeChunkerGap;
37
38
  const os = __importStar(require("node:os"));
38
39
  const path = __importStar(require("node:path"));
39
40
  exports.MODEL_TIERS = {
@@ -76,7 +77,52 @@ exports.CONFIG = {
76
77
  EMBED_BATCH_SIZE: 24,
77
78
  WORKER_THREADS: DEFAULT_WORKER_THREADS,
78
79
  QUERY_PREFIX: "",
80
+ // Bump when chunk metadata semantics change in a way that requires a full
81
+ // reindex to take effect. Must equal the latest entry's `v` in
82
+ // CHUNKER_VERSION_HISTORY below — see that list for per-version severity and
83
+ // the user-facing note rendered by `gmax doctor` and the staleness hint.
84
+ CHUNKER_VERSION: 3,
79
85
  };
86
+ /**
87
+ * Per-version record of what changed in the chunker and how much it matters to
88
+ * an already-built index. `severity` drives tone: an "additive" change only
89
+ * adds new metadata (older indexes under-cover but aren't wrong), while a
90
+ * "breaking" change means older indexes carry incorrect metadata until a
91
+ * reindex. The `note` is shown verbatim to the user for the versions their
92
+ * index is missing. CONFIG.CHUNKER_VERSION must equal the highest `v` here.
93
+ */
94
+ exports.CHUNKER_VERSION_HISTORY = [
95
+ {
96
+ v: 2,
97
+ severity: "breaking",
98
+ note: "sub-chunk symbol scoping; graph overcounted callers before this.",
99
+ },
100
+ {
101
+ v: 3,
102
+ severity: "additive",
103
+ note: "type-position edges; dead/trace miss type-only callers until reindex.",
104
+ },
105
+ ];
106
+ /**
107
+ * Describe the gap between an index's stamped chunker version and the current
108
+ * one, or null when the index is already current. Shared by `gmax doctor` and
109
+ * the query-time staleness hint so both render the same severity + notes.
110
+ */
111
+ function describeChunkerGap(indexedVersion) {
112
+ const fromVersion = indexedVersion !== null && indexedVersion !== void 0 ? indexedVersion : 1;
113
+ if (fromVersion >= exports.CONFIG.CHUNKER_VERSION)
114
+ return null;
115
+ const missed = exports.CHUNKER_VERSION_HISTORY.filter((h) => h.v > fromVersion && h.v <= exports.CONFIG.CHUNKER_VERSION);
116
+ const severity = missed.some((h) => h.severity === "breaking")
117
+ ? "breaking"
118
+ : "additive";
119
+ return {
120
+ fromVersion,
121
+ toVersion: exports.CONFIG.CHUNKER_VERSION,
122
+ severity,
123
+ notes: missed.map((h) => h.note),
124
+ };
125
+ }
80
126
  exports.WORKER_TIMEOUT_MS = Number.parseInt(process.env.GMAX_WORKER_TIMEOUT_MS || "60000", 10);
81
127
  exports.WORKER_BOOT_TIMEOUT_MS = Number.parseInt(process.env.GMAX_WORKER_BOOT_TIMEOUT_MS || "300000", 10);
82
128
  exports.MAX_WORKER_MEMORY_MB = (() => {