token-goat 2.8.3 → 2.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,7 +25,7 @@ import {
25
25
  runSkeleton,
26
26
  runSymbol,
27
27
  withPinnedReads
28
- } from "./token-goat-chunk-TX4JFJTD.mjs";
28
+ } from "./token-goat-chunk-RE7S7H26.mjs";
29
29
  import {
30
30
  buildProjectMap,
31
31
  embeddingsDepsAvailable,
@@ -34,7 +34,7 @@ import {
34
34
  getProjectIndexCounts,
35
35
  isWorkerRunning,
36
36
  mapLookupBytesSaved
37
- } from "./token-goat-chunk-PWVXXPCC.mjs";
37
+ } from "./token-goat-chunk-4OTIB7SB.mjs";
38
38
  import {
39
39
  VERSION,
40
40
  dataDir,
@@ -44,8 +44,9 @@ import {
44
44
  loadConfig,
45
45
  normalizePath,
46
46
  recordStat,
47
- resolveProjectRoot
48
- } from "./token-goat-chunk-6ODZ6PZK.mjs";
47
+ resolveProjectRoot,
48
+ savedTokensFromBytes
49
+ } from "./token-goat-chunk-E76UNTVK.mjs";
49
50
  import "./token-goat-chunk-AO2QD2AG.mjs";
50
51
  import "./token-goat-chunk-AEX54RUZ.mjs";
51
52
 
@@ -533,7 +534,7 @@ async function createMcpServer() {
533
534
  const map = buildProjectMap(resolveToolRoot(projectRoot), { compact: compact === true });
534
535
  const text = formatProjectMap(map, map.compact);
535
536
  const bytesSaved = mapLookupBytesSaved(map, text);
536
- recordStat("map_lookup", bytesSaved, Math.round(bytesSaved / 4));
537
+ recordStat("map_lookup", bytesSaved, savedTokensFromBytes(bytesSaved));
537
538
  return toCallToolResult({ text, code: 0 });
538
539
  }
539
540
  );
@@ -10,11 +10,11 @@ import {
10
10
  selectFilter,
11
11
  shlexSplit,
12
12
  wrappedShell
13
- } from "./token-goat-chunk-EFF2XCLB.mjs";
13
+ } from "./token-goat-chunk-CZALRRGN.mjs";
14
14
  import {
15
15
  loadConfig,
16
16
  recordStat
17
- } from "./token-goat-chunk-6ODZ6PZK.mjs";
17
+ } from "./token-goat-chunk-E76UNTVK.mjs";
18
18
  import "./token-goat-chunk-AO2QD2AG.mjs";
19
19
  import "./token-goat-chunk-AEX54RUZ.mjs";
20
20
 
@@ -39,6 +39,7 @@ import {
39
39
  locatePdfPages,
40
40
  mergeNearbyHits,
41
41
  ocrImage,
42
+ ocrIntegrityFailed,
42
43
  parseWhereSpecs,
43
44
  pathEqClause,
44
45
  probeImageMeta,
@@ -55,6 +56,7 @@ import {
55
56
  shrinkImage,
56
57
  storeBlob,
57
58
  stripLeadingAttributes,
59
+ stripTomlComment,
58
60
  tomlBracketDelta,
59
61
  trimToBudget,
60
62
  unzipBounded,
@@ -62,7 +64,7 @@ import {
62
64
  walkProject,
63
65
  yamlLineClosesQuote,
64
66
  yamlOpenQuoteAfter
65
- } from "./token-goat-chunk-PWVXXPCC.mjs";
67
+ } from "./token-goat-chunk-4OTIB7SB.mjs";
66
68
  import {
67
69
  Database,
68
70
  PER_FILE_COUNTERFACTUAL_CEILING,
@@ -82,6 +84,7 @@ import {
82
84
  excludeTestsHiddenNote,
83
85
  extractErrorMessage,
84
86
  fileIsAbsent,
87
+ filtersFilteredToEmptyNotice,
85
88
  findHtmlHeadingMatches,
86
89
  findProject,
87
90
  fingerprintContent,
@@ -107,6 +110,7 @@ import {
107
110
  resolveIndexPath,
108
111
  resolveProjectRoot,
109
112
  runGit,
113
+ savedTokensFromBytes,
110
114
  shortFingerprint,
111
115
  stripAnsi,
112
116
  stripBom,
@@ -114,7 +118,7 @@ import {
114
118
  unsupportedLanguageName,
115
119
  windowsCmdQuoteArg,
116
120
  withExtension
117
- } from "./token-goat-chunk-6ODZ6PZK.mjs";
121
+ } from "./token-goat-chunk-E76UNTVK.mjs";
118
122
  import {
119
123
  registerReset
120
124
  } from "./token-goat-chunk-AO2QD2AG.mjs";
@@ -198,18 +202,19 @@ function findTableHeaders(lines, isToml) {
198
202
  const restStart = closeIdx + openDelim.length;
199
203
  const m2 = TABLE_HEADER_RE.exec(line.slice(restStart));
200
204
  if (m2 !== null && m2[1] !== void 0) headers.push({ heading: m2[1].trim(), level: 1, index: i });
201
- openDelim = lineOpenDelimiterAfter(line, restStart);
205
+ openDelim = lineOpenDelimiterAfter(stripTomlComment(line.slice(restStart)), 0);
202
206
  continue;
203
207
  }
204
208
  if (isToml && arrayDepth > 0) {
205
- arrayDepth = Math.max(0, arrayDepth + tomlBracketDelta(line));
209
+ arrayDepth = Math.max(0, arrayDepth + tomlBracketDelta(stripTomlComment(line)));
206
210
  continue;
207
211
  }
208
212
  const m = TABLE_HEADER_RE.exec(line);
209
213
  if (m !== null && m[1] !== void 0) headers.push({ heading: m[1].trim(), level: 1, index: i });
210
214
  if (isToml) {
211
- openDelim = lineOpenDelimiterAfter(line, 0);
212
- if (openDelim === null) arrayDepth = Math.max(0, tomlBracketDelta(line));
215
+ const code = stripTomlComment(line);
216
+ openDelim = lineOpenDelimiterAfter(code, 0);
217
+ if (openDelim === null) arrayDepth = Math.max(0, tomlBracketDelta(code));
213
218
  }
214
219
  }
215
220
  return headers;
@@ -255,10 +260,11 @@ function findKeyValueHeaders(lines, language) {
255
260
  if (closed) openQuote = null;
256
261
  continue;
257
262
  }
258
- const m = (isEnv ? ENV_KEYVALUE_HEADER_RE : KEYVALUE_HEADER_RE).exec(line);
263
+ const scanned = isEnv ? line.replace(/^[ \t]+/, "") : line;
264
+ const m = (isEnv ? ENV_KEYVALUE_HEADER_RE : KEYVALUE_HEADER_RE).exec(scanned);
259
265
  if (m === null || m[1] === void 0) continue;
260
266
  headers.push({ heading: m[1], level: 1, index: i });
261
- openQuote = isEnv ? _detectOpenQuote(line.slice(m[0].length)) : yamlOpenQuoteAfter(line, m[0].length);
267
+ openQuote = isEnv ? _detectOpenQuote(scanned.slice(m[0].length)) : yamlOpenQuoteAfter(line, m[0].length);
262
268
  }
263
269
  return headers;
264
270
  }
@@ -5376,7 +5382,7 @@ function sumFileSizes(filePaths) {
5376
5382
  function recordReadStat(kind, fullSourceBytes, emittedText, detail) {
5377
5383
  const emittedBytes = Buffer.byteLength(emittedText, "utf8");
5378
5384
  const bytesSaved = Math.max(1, fullSourceBytes - emittedBytes);
5379
- recordStat(kind, bytesSaved, Math.round(bytesSaved / 4), void 0, detail);
5385
+ recordStat(kind, bytesSaved, savedTokensFromBytes(bytesSaved), void 0, detail);
5380
5386
  }
5381
5387
  function findSpecSeparator(spec) {
5382
5388
  return spec.lastIndexOf("::");
@@ -5659,10 +5665,21 @@ function runSymbol(opts) {
5659
5665
  if (opts.name !== void 0 && emptyIndexRoot !== null) {
5660
5666
  const rootDir = emptyIndexRoot;
5661
5667
  const rawSymbols = querySymbols({ limit: FIND_SCAN_LIMIT, rootDir });
5662
- const candidates = rankSimilarNames(rawSymbols.map((s) => s.name), opts.name);
5663
- text2 += candidates.length > 0 ? `
5668
+ const exactMatches = rawSymbols.filter((s) => s.name === opts.name);
5669
+ if (exactMatches.length > 0) {
5670
+ const shown = exactMatches.slice(0, DIDYOUMEAN_LIMIT);
5671
+ const where = shown.map((s) => `${s.kind} at ${toDisplayPath(rootDir, s.filePath)}:${s.lineStart}`).join("; ");
5672
+ const more = exactMatches.length > shown.length ? ` (+${exactMatches.length - shown.length} more)` : "";
5673
+ const flags = [opts.kind !== void 0 ? "--kind" : null, opts.file !== void 0 ? "--file" : null].filter((f) => f !== null);
5674
+ const widen = flags.length > 0 ? `drop ${flags.join("/")} to see it` : "widen the search scope to see it";
5675
+ text2 += `
5676
+ '${opts.name}' IS indexed (${where}${more}) -- ${widen}`;
5677
+ } else {
5678
+ const candidates = rankSimilarNames(rawSymbols.map((s) => s.name), opts.name);
5679
+ text2 += candidates.length > 0 ? `
5664
5680
  ${didYouMean(candidates)}` : indexEmpty ? "" : `
5665
5681
  Try: token-goat semantic "${opts.name}"`;
5682
+ }
5666
5683
  const structuredFiles = [...new Set(rawSymbols.map((s) => s.filePath))].sort();
5667
5684
  const hit = findStructuredKeyPath(opts.name, structuredFiles);
5668
5685
  if (hit !== null) {
@@ -5936,9 +5953,11 @@ function resolveSymbolSpec(spec, forceRefresh, projectRoot) {
5936
5953
  let candidates = querySymbols({ name: lookupName, filePath: resolved, limit: 10 });
5937
5954
  if (candidates.length === 0) {
5938
5955
  const foldedFile = foldPath(file);
5956
+ const baseName = file.slice(Math.max(file.lastIndexOf("/"), file.lastIndexOf("\\")) + 1);
5939
5957
  candidates = querySymbols({
5940
5958
  name: lookupName,
5941
5959
  limit: 50,
5960
+ ...baseName !== "" ? { fileBaseName: baseName } : {},
5942
5961
  ...projectRoot !== void 0 ? { rootDir: projectRoot } : {}
5943
5962
  }).filter((s) => {
5944
5963
  const foldedFilePath = foldPath(s.filePath);
@@ -6072,6 +6091,12 @@ ${sub.text}`);
6072
6091
  function resolveAgainstProjectRoot(file, projectRoot) {
6073
6092
  return projectRoot !== void 0 && !path5.isAbsolute(file) ? path5.resolve(projectRoot, file) : file;
6074
6093
  }
6094
+ function literalHeadingExists(filePath, heading) {
6095
+ const ordinalMatch = /^([^#\r\n]+)#(\d+)$/.exec(heading);
6096
+ const base = (ordinalMatch?.[1] ?? heading).trim().toLowerCase();
6097
+ if (base.length === 0) return false;
6098
+ return listSections(filePath, readFileText).some((h) => h.trim().toLowerCase() === base);
6099
+ }
6075
6100
  function runSection(opts) {
6076
6101
  const crossFilePairs = parseCrossFileMultiSpec(opts.spec);
6077
6102
  if (crossFilePairs !== null) return runSectionCrossFile(crossFilePairs, opts);
@@ -6082,7 +6107,7 @@ function runSection(opts) {
6082
6107
  const specFilePath = opts.spec.slice(0, colonIdx);
6083
6108
  const filePath = resolveAgainstProjectRoot(specFilePath, opts.projectRoot);
6084
6109
  const heading = opts.spec.slice(colonIdx + 2);
6085
- if (heading.includes(",")) {
6110
+ if (heading.includes(",") && !literalHeadingExists(filePath, heading)) {
6086
6111
  const multiHeadings = heading.split(",").map((h) => h.trim()).filter((h) => h.length > 0);
6087
6112
  if (multiHeadings.length > 1) return runSectionMulti(specFilePath, filePath, multiHeadings, opts);
6088
6113
  }
@@ -6516,14 +6541,10 @@ function formatStatsSuffix(refCounts, sym) {
6516
6541
  return refCounts !== void 0 ? ` [${countNoun(refCounts.get(sym.name) ?? 0, "ref")}, ${hasRealDocstring(sym.docstring) ? "documented" : "undocumented"}]` : "";
6517
6542
  }
6518
6543
  function filteredToEmptyNotice(preFilterCount, minLines, grep) {
6519
- const plural = preFilterCount === 1 ? "symbol" : "symbols";
6520
6544
  const parts = [];
6521
6545
  if (minLines !== void 0) parts.push(`--min-lines ${minLines}`);
6522
6546
  if (grep !== void 0) parts.push(`--grep ${grep}`);
6523
- const cause = parts.length === 0 ? "the active filter" : parts.join(" + ");
6524
- const knob = parts.length > 1 ? "filters" : "filter";
6525
- const verb = preFilterCount === 1 ? "was" : "were";
6526
- return ` (all ${preFilterCount} indexed ${plural} ${verb} filtered out by ${cause}; the file is indexed -- widen or drop the ${knob} to see them)`;
6547
+ return filtersFilteredToEmptyNotice(preFilterCount, parts, "indexed symbol", "indexed symbols", "the file is indexed");
6527
6548
  }
6528
6549
  function prepareSymbolListing(file, opts) {
6529
6550
  const resolved = resolveIndexPath(file, opts.projectRoot ?? process.cwd());
@@ -6632,6 +6653,12 @@ function runSkeleton(opts) {
6632
6653
  recordReadStat("stub_view", fullSourceBytes, text, opts.file);
6633
6654
  return { text, code: 0 };
6634
6655
  }
6656
+ var DOC_SUMMARY_MAX_CHARS = 140;
6657
+ function clipDocSummary(firstLine) {
6658
+ if (firstLine.length <= DOC_SUMMARY_MAX_CHARS) return firstLine;
6659
+ const cut = firstLine.lastIndexOf(" ", DOC_SUMMARY_MAX_CHARS);
6660
+ return `${firstLine.slice(0, cut > 40 ? cut : DOC_SUMMARY_MAX_CHARS).trimEnd()}\u2026`;
6661
+ }
6635
6662
  function runOutline(opts) {
6636
6663
  const multiFiles = parseMultiFileSpec(opts.file);
6637
6664
  if (multiFiles !== null) return runPerFileListing(multiFiles, (file) => runOutline({ ...opts, file, includeFilePath: true }), opts.json === true);
@@ -6669,7 +6696,7 @@ function runOutline(opts) {
6669
6696
  const rangeStr = `${sym.lineStart.toString().padStart(4)}-${sym.lineEnd.toString().padEnd(6)}`;
6670
6697
  const kindStr = sym.kind.padEnd(14);
6671
6698
  const bodyLen = sym.lineEnd - sym.lineStart + 1;
6672
- const docFirst = hasRealDocstring(sym.docstring) ? ` # ${sym.docstring.split("\n")[0] ?? ""}` : "";
6699
+ const docFirst = hasRealDocstring(sym.docstring) ? ` # ${clipDocSummary(sym.docstring.split("\n")[0] ?? "")}` : "";
6673
6700
  const statsStr = formatStatsSuffix(refCounts, sym);
6674
6701
  lines.push(` ${rangeStr} ${kindStr} ${sym.name} (${bodyLen}\u2113)${docFirst}${statsStr}`);
6675
6702
  }
@@ -6709,11 +6736,11 @@ function runCsvQuery(opts) {
6709
6736
  const rowsJson = result.rows.map((r) => Object.fromEntries(result.header.map((h, i) => [h, r[i]])));
6710
6737
  const headTruncated = result.rows.length < result.totalRows;
6711
6738
  const capped = guardJsonRows(rowsJson);
6712
- const jsonText = JSON.stringify({ items: capped.items, truncated: capped.truncated || headTruncated, totalCount: result.totalRows });
6739
+ const jsonText = JSON.stringify({ items: capped.items, truncated: capped.truncated || headTruncated, totalCount: result.totalRows, ...result.totalRows === 0 && result.preFilterRows > 0 ? { filteredFromRows: result.preFilterRows } : {} });
6713
6740
  emit(jsonText);
6714
6741
  recordReadStat("csv_query", fullSourceBytes, jsonText, opts.file);
6715
6742
  } else {
6716
- const tableText = formatCsvTable(result);
6743
+ const tableText = formatCsvTable(result, (opts.where ?? []).map((w) => `--where ${w}`));
6717
6744
  emit(tableText);
6718
6745
  recordReadStat("csv_query", fullSourceBytes, tableText, opts.file);
6719
6746
  }
@@ -6856,6 +6883,7 @@ function resolveYamlMergeKeys(node) {
6856
6883
  }
6857
6884
  function parseYamlDocument(text) {
6858
6885
  const docs = loadAll(text).map(resolveYamlMergeKeys);
6886
+ if (docs.length === 0) return null;
6859
6887
  return docs.length === 1 ? docs[0] : docs;
6860
6888
  }
6861
6889
  function runYamlOutline(opts) {
@@ -7439,6 +7467,9 @@ async function runImageText(file) {
7439
7467
  const data = fs5.readFileSync(file);
7440
7468
  const ocr = await ocrImage(data);
7441
7469
  if (ocr === null) {
7470
+ if (ocrIntegrityFailed()) {
7471
+ throw new Error(`${file} was not OCRed: the cached language model failed its checksum and was discarded, so the next run re-downloads it`);
7472
+ }
7442
7473
  if (isOcrEngineAvailable()) {
7443
7474
  throw new Error(`${file} could not be processed by OCR (unreadable image, timeout, or offline model fetch)`);
7444
7475
  }
@@ -7489,15 +7520,14 @@ ${emptyIndexMessage(rootDir2)}`, code: 1 };
7489
7520
  const match = resolution.entry;
7490
7521
  const rootDir = resolveProjectRoot({ project: opts.projectRoot ?? process.cwd() });
7491
7522
  const excludeTests = opts.excludeTests === true;
7492
- const unboundedQuery = excludeTests || opts.grep !== void 0;
7493
- const allCallers = resolveCallers(match.name, void 0, match.filePath, rootDir, unboundedQuery);
7523
+ const allCallers = resolveCallers(match.name, void 0, match.filePath, rootDir, true);
7494
7524
  const testFiltered = excludeTests ? allCallers.filter((c) => !isTestFile(c.file)) : allCallers;
7495
7525
  const hiddenByExcludeTests = excludeTests ? allCallers.length - testFiltered.length : 0;
7496
7526
  const preGrepCount = testFiltered.length;
7497
7527
  const matchesGrep = opts.grep !== void 0 ? compileGrepMatcher(opts.grep) : void 0;
7498
7528
  const callers = matchesGrep !== void 0 ? testFiltered.filter((c) => matchesGrep(c.caller)) : testFiltered;
7499
7529
  const hiddenByGrep = matchesGrep !== void 0 ? preGrepCount - callers.length : 0;
7500
- const totalCallers = unboundedQuery ? callers.length : queryRefCounts([match.name], globalDbPath(), rootDir).get(match.name) ?? callers.length;
7530
+ const totalCallers = callers.length;
7501
7531
  const section = findContainingSection(match.filePath, match.lineStart, match.lineEnd, readFileText);
7502
7532
  const limit = opts.limit ?? 20;
7503
7533
  const shown = callers.slice(0, limit);
@@ -9146,10 +9176,14 @@ function bfsCallChains(start, callersOf, maxDepth) {
9146
9176
  const tip = chain[chain.length - 1];
9147
9177
  if (tip === void 0) continue;
9148
9178
  const callers = callersOf(tip);
9149
- if (callers.length === 0 || chain.length > maxDepth) {
9179
+ if (callers.length === 0) {
9150
9180
  complete.push(chain);
9151
9181
  continue;
9152
9182
  }
9183
+ if (chain.length > maxDepth) {
9184
+ complete.push([...chain, "(depth-limit)"]);
9185
+ continue;
9186
+ }
9153
9187
  let expanded = false;
9154
9188
  for (const caller of callers) {
9155
9189
  if (chain.includes(caller)) {
@@ -9235,8 +9269,11 @@ function runCallers(opts) {
9235
9269
  emitErr2(`Symbol '${name}' not found in '${file}'`);
9236
9270
  return 1;
9237
9271
  }
9238
- const unbounded = opts.excludeTests === true || opts.grep !== void 0;
9239
- const resolved = resolveCallers(name, opts.limit, fileHint, rootDir, unbounded);
9272
+ const unbounded = opts.excludeTests === true || opts.grep !== void 0 || fileHint !== void 0;
9273
+ const requestedLimit = opts.limit ?? DEFAULT_REF_QUERY_LIMIT;
9274
+ const probed = resolveCallers(name, unbounded ? opts.limit : requestedLimit + 1, fileHint, rootDir, unbounded);
9275
+ const sqlTruncated = !unbounded && probed.length > requestedLimit;
9276
+ const resolved = sqlTruncated ? probed.slice(0, requestedLimit) : probed;
9240
9277
  const suppressed = opts.excludeTests === true ? resolved.filter((e) => isTestFile(e.file)).length : 0;
9241
9278
  let filtered = opts.excludeTests === true ? resolved.filter((e) => !isTestFile(e.file)) : resolved;
9242
9279
  const preGrepCount = filtered.length;
@@ -9277,7 +9314,7 @@ function runCallers(opts) {
9277
9314
  return { ...e, file: displayPath, filePath: displayPath };
9278
9315
  });
9279
9316
  const capped = guardJsonRows(rows);
9280
- const limitTruncated = entries.length < filtered.length;
9317
+ const limitTruncated = entries.length < filtered.length || sqlTruncated;
9281
9318
  const hiddenByGrep = preGrepCount - filtered.length;
9282
9319
  emit2(JSON.stringify({ items: capped.items, truncated: capped.truncated || limitTruncated, totalCount: filtered.length, ...hiddenByGrep > 0 ? { hiddenByGrep } : {}, ...opts.excludeTests === true && suppressed > 0 ? { hiddenByExcludeTests: suppressed } : {} }, null, 2));
9283
9320
  return 0;
@@ -9316,7 +9353,7 @@ function runCallChain(opts) {
9316
9353
  const getSyms = buildFileSymCache();
9317
9354
  let suppressedCount = 0;
9318
9355
  const callersOf = (n) => {
9319
- const refs = queryRefs({ name: n, limit: DEFAULT_REF_QUERY_LIMIT, rootDir });
9356
+ const refs = queryRefs({ name: n, limit: UNBOUNDED_REF_LIMIT, rootDir });
9320
9357
  if (refs.length === 0) return [];
9321
9358
  const scoped = fileHint !== void 0 && n === name ? filterRefsForSymbol(refs, n, fileHint, getSyms) : refs;
9322
9359
  const names = /* @__PURE__ */ new Set();
@@ -9379,12 +9416,16 @@ function runImpact(opts) {
9379
9416
  const hops = /* @__PURE__ */ new Map([[rootName, 0]]);
9380
9417
  const queue = [[rootName, 0]];
9381
9418
  let suppressedCount = 0;
9419
+ let depthCapped = 0;
9382
9420
  while (queue.length > 0) {
9383
9421
  const item = queue.shift();
9384
9422
  if (item === void 0) break;
9385
9423
  const [name, depth] = item;
9386
- if (depth >= DEPTH_CAP) continue;
9387
- const refs = queryRefs({ name, limit: DEFAULT_REF_QUERY_LIMIT, rootDir });
9424
+ if (depth >= DEPTH_CAP) {
9425
+ depthCapped += 1;
9426
+ continue;
9427
+ }
9428
+ const refs = queryRefs({ name, limit: UNBOUNDED_REF_LIMIT, rootDir });
9388
9429
  const scoped = fileHint !== void 0 && name === rootName ? filterRefsForSymbol(refs, name, fileHint, getSyms) : refs;
9389
9430
  for (const ref of scoped) {
9390
9431
  if (opts.excludeTests === true && isTestFile(ref.filePath)) {
@@ -9412,6 +9453,12 @@ function runImpact(opts) {
9412
9453
  const matchesGrep = opts.grep !== void 0 ? compileGrepMatcher(opts.grep) : void 0;
9413
9454
  const grepped = matchesGrep !== void 0 ? allSorted.filter(([symbol]) => matchesGrep(symbol)) : allSorted;
9414
9455
  const sorted = grepped.slice(0, top);
9456
+ if (depthCapped > 0) {
9457
+ emitErr2(`Impact truncated at the ${DEPTH_CAP}-hop depth limit: ${depthCapped} symbol${depthCapped === 1 ? "" : "s"} at that depth ${depthCapped === 1 ? "was" : "were"} not expanded, so callers reachable only beyond ${DEPTH_CAP} hops are missing.`);
9458
+ }
9459
+ if (grepped.length > sorted.length) {
9460
+ emitErr2(`Showing top ${sorted.length} of ${grepped.length} impacted symbols (raise --top to see the rest).`);
9461
+ }
9415
9462
  if (matchesGrep !== void 0 && sorted.length === 0 && allSorted.length > 0) {
9416
9463
  if (opts.json === true) {
9417
9464
  emitErr2(grepFilteredToEmptyNotice(allSorted.length, opts.grep, "impacted symbol", "impacted symbols"));
@@ -9541,14 +9588,17 @@ function runDead(opts) {
9541
9588
  }
9542
9589
  return 1;
9543
9590
  }
9544
- const syms = kinds.flatMap((k) => querySymbols({ kind: k, limit: 5e3, rootDir }));
9591
+ const syms = kinds.flatMap((k) => querySymbols({ kind: k, limit: UNBOUNDED_REF_LIMIT, rootDir }));
9545
9592
  const getSyms = buildFileSymCache();
9546
9593
  const results = [];
9547
9594
  let suppressed = 0;
9548
9595
  for (const sym of syms) {
9549
9596
  if (opts.includePrivate !== true && sym.name.startsWith("_")) continue;
9550
9597
  const refs = queryRefs({ name: sym.name, limit: DEFAULT_REF_QUERY_LIMIT, rootDir });
9551
- const scoped = filterRefsForSymbol(refs, sym.name, sym.filePath, getSyms);
9598
+ let scoped = filterRefsForSymbol(refs, sym.name, sym.filePath, getSyms);
9599
+ if (scoped.length === 0 && refs.length >= DEFAULT_REF_QUERY_LIMIT) {
9600
+ scoped = filterRefsForSymbol(queryRefs({ name: sym.name, limit: UNBOUNDED_REF_LIMIT, rootDir }), sym.name, sym.filePath, getSyms);
9601
+ }
9552
9602
  if (!isDeadSymbol(sym.name, scoped.length)) continue;
9553
9603
  if (sym.kind === "method") {
9554
9604
  const ownScope = enclosingNamedScope(getSyms(sym.filePath), sym.lineStart);
@@ -9883,7 +9933,7 @@ function runScope(opts) {
9883
9933
  }
9884
9934
  const filePath = resolveIndexPath(file);
9885
9935
  const syms = querySymbols({ filePath, limit: ALL_SYMBOLS_IN_FILE_LIMIT });
9886
- const enclosing = syms.filter((s) => s.lineStart <= line && line <= s.lineEnd).sort((a, b) => b.lineStart - a.lineStart);
9936
+ const enclosing = syms.filter((s) => s.lineStart <= line && line <= s.lineEnd).sort((a, b) => b.lineStart - a.lineStart || a.lineEnd - b.lineEnd || a.name.localeCompare(b.name, "en"));
9887
9937
  if (syms.length === 0) {
9888
9938
  if (!fs6.existsSync(filePath)) {
9889
9939
  emitErr2(`Could not read: ${file}`);
@@ -9967,17 +10017,18 @@ function canonicalCycleKey(cyclePath) {
9967
10017
  }
9968
10018
  return [...nodes.slice(minIdx), ...nodes.slice(0, minIdx)].join(" ");
9969
10019
  }
9970
- function findCycles(graph) {
10020
+ function findCyclesCapped(graph) {
9971
10021
  const cycles = [];
9972
10022
  const seen = /* @__PURE__ */ new Set();
9973
10023
  const sccs = tarjanSCCs(graph);
10024
+ const probeLimit = MAX_CYCLES + 1;
9974
10025
  for (const component of sccs) {
9975
10026
  let dfs2 = function(start, node, pathSoFar) {
9976
- if (cycles.length >= MAX_CYCLES) return;
10027
+ if (cycles.length >= probeLimit) return;
9977
10028
  stack.add(node);
9978
10029
  for (const nb of graph.get(node) ?? []) {
9979
10030
  if (!sccSet.has(nb)) continue;
9980
- if (cycles.length >= MAX_CYCLES) break;
10031
+ if (cycles.length >= probeLimit) break;
9981
10032
  if (nb === start) {
9982
10033
  const cyclePath = [...pathSoFar, node, start];
9983
10034
  const key = canonicalCycleKey(cyclePath);
@@ -9992,6 +10043,7 @@ function findCycles(graph) {
9992
10043
  stack.delete(node);
9993
10044
  };
9994
10045
  var dfs = dfs2;
10046
+ if (cycles.length >= probeLimit) break;
9995
10047
  if (component.length === 1) {
9996
10048
  const [only] = component;
9997
10049
  if (only !== void 0 && (graph.get(only) ?? []).includes(only)) cycles.push([only, only]);
@@ -10000,11 +10052,12 @@ function findCycles(graph) {
10000
10052
  const sccSet = new Set(component);
10001
10053
  const stack = /* @__PURE__ */ new Set();
10002
10054
  for (const start of component) {
10003
- if (cycles.length >= MAX_CYCLES) break;
10055
+ if (cycles.length >= probeLimit) break;
10004
10056
  dfs2(start, start, []);
10005
10057
  }
10006
10058
  }
10007
- return cycles;
10059
+ if (cycles.length > MAX_CYCLES) return { cycles: cycles.slice(0, MAX_CYCLES), truncated: true };
10060
+ return { cycles, truncated: false };
10008
10061
  }
10009
10062
  function runSimilar(opts) {
10010
10063
  if (opts.top !== void 0 && opts.top <= 0) {
@@ -10108,8 +10161,8 @@ function runCoverageGaps(opts) {
10108
10161
  }
10109
10162
  const top = opts.top ?? 50;
10110
10163
  const rootDir = resolveProjectRoot({ project: process.cwd() });
10111
- const allFns = querySymbols({ kind: "function", limit: 2e3, rootDir });
10112
- const allMethods = querySymbols({ kind: "method", limit: 2e3, rootDir });
10164
+ const allFns = querySymbols({ kind: "function", limit: UNBOUNDED_REF_LIMIT, rootDir });
10165
+ const allMethods = querySymbols({ kind: "method", limit: UNBOUNDED_REF_LIMIT, rootDir });
10113
10166
  const candidates = [...allFns, ...allMethods];
10114
10167
  const gaps = [];
10115
10168
  for (const sym of candidates) {
@@ -10120,6 +10173,9 @@ function runCoverageGaps(opts) {
10120
10173
  if (!hasTestRef) gaps.push({ name: sym.name, kind: sym.kind, file: sym.filePath, line: sym.lineStart });
10121
10174
  }
10122
10175
  const sliced = gaps.slice(0, top);
10176
+ if (gaps.length > sliced.length) {
10177
+ emitErr2(`Showing top ${sliced.length} of ${gaps.length} coverage gaps (raise --top to see the rest).`);
10178
+ }
10123
10179
  if (opts.json === true) {
10124
10180
  emit2(JSON.stringify(sliced, null, 2));
10125
10181
  return 0;
@@ -10188,16 +10244,16 @@ function runArch(opts) {
10188
10244
  }
10189
10245
  const hubs = [...importedBy.entries()].sort((a, b) => b[1].size - a[1].size).slice(0, top).map(([f, importers]) => ({ file: f, importedBy: importers.size }));
10190
10246
  const entryPoints = files.filter((f) => !importedBy.has(f) && (graph.get(f) ?? []).length > 0).slice(0, top).map((f) => ({ file: f }));
10191
- const cycles = findCycles(graph);
10247
+ const { cycles, truncated: cyclesTruncated } = findCyclesCapped(graph);
10192
10248
  if (opts.json === true) {
10193
- emit2(JSON.stringify({ hubs, entryPoints, cycles }, null, 2));
10249
+ emit2(JSON.stringify({ hubs, entryPoints, cycles, ...cyclesTruncated ? { cyclesTruncated: true } : {} }, null, 2));
10194
10250
  return 0;
10195
10251
  }
10196
10252
  emit2(`hubs (top ${top} most-imported):`);
10197
10253
  for (const h of hubs) emit2(` ${h.importedBy} importers ${toDisplayPath(getDisplayRoot(opts.cwd), h.file)}`);
10198
10254
  emit2(`entry points (imported by nobody, top ${top}):`);
10199
10255
  for (const e of entryPoints) emit2(` ${toDisplayPath(getDisplayRoot(opts.cwd), e.file)}`);
10200
- emit2(`cycles (${cycles.length} found):`);
10256
+ emit2(cyclesTruncated ? `cycles (first ${cycles.length}, truncated at the ${MAX_CYCLES}-cycle enumeration limit: more cycles exist):` : `cycles (${cycles.length} found):`);
10201
10257
  for (const c of cycles) emit2(` ${c.map((f) => toDisplayPath(getDisplayRoot(opts.cwd), f)).join(" -> ")}`);
10202
10258
  return 0;
10203
10259
  }
@@ -10249,17 +10305,21 @@ function runAsk(opts) {
10249
10305
  const BACKEND_ENV = "TOKEN_GOAT_ASK_BACKEND";
10250
10306
  const backendLabel = process.env[BACKEND_ENV] ?? "";
10251
10307
  const entries = hits.map((h) => ({ file: h.filePath, symbol: h.name, kind: h.kind, line: h.lineStart, readCmd: `token-goat read "${h.filePath}::${h.name}@${h.lineStart}"` }));
10252
- const degrade = () => {
10308
+ const degrade = (reason, extraNote) => {
10253
10309
  if (opts.json === true) {
10254
- emit2(JSON.stringify({ degraded: true, note: `Set ${BACKEND_ENV}=claude|codex for LLM synthesis`, context: entries }, null, 2));
10310
+ emit2(JSON.stringify({ degraded: true, note: reason, ...extraNote !== void 0 ? { hint: extraNote } : {}, context: entries }, null, 2));
10255
10311
  return 0;
10256
10312
  }
10257
- emit2(`[degraded mode - set ${BACKEND_ENV}=claude|codex for LLM synthesis]`);
10313
+ emit2(`[degraded mode - ${reason}]`);
10314
+ if (extraNote !== void 0) emit2(extraNote);
10258
10315
  for (const e of entries) emit2(`token-goat read "${toDisplayPath(rootDir, e.file)}::${e.symbol}@${e.line}"`);
10259
10316
  return 0;
10260
10317
  };
10261
- if (!backendLabel) return degrade();
10262
- if (hits.length === 0) return degrade();
10318
+ if (!backendLabel) return degrade(`set ${BACKEND_ENV}=claude|codex for LLM synthesis`);
10319
+ if (hits.length === 0) {
10320
+ if (isIndexEmptyForProject(globalDbPath(), rootDir)) return degrade(`${BACKEND_ENV}=${backendLabel} is set, but nothing is indexed for this project yet, so there is no context to answer from`, emptyIndexMessage(rootDir));
10321
+ return degrade(`${BACKEND_ENV}=${backendLabel} is set, but no indexed symbol matched this question, so there is no context to answer from -- try different wording or token-goat semantic`);
10322
+ }
10263
10323
  const isWin = process.platform === "win32";
10264
10324
  let backendPath = null;
10265
10325
  try {
@@ -10268,7 +10328,7 @@ function runAsk(opts) {
10268
10328
  if (found) backendPath = found;
10269
10329
  } catch {
10270
10330
  }
10271
- if (!backendPath) return degrade();
10331
+ if (!backendPath) return degrade(`${BACKEND_ENV}=${backendLabel} is set, but '${backendLabel}' was not found on PATH`);
10272
10332
  const context = hits.map((h, i) => `[${i + 1}] ${h.filePath}
10273
10333
  ${h.body ?? ""}`).join("\n\n");
10274
10334
  const prompt = `Answer the QUESTION using only the CODE SNIPPETS below.
@@ -10319,7 +10379,7 @@ ANSWER:`;
10319
10379
  }
10320
10380
  }
10321
10381
  }
10322
- return degrade();
10382
+ return degrade(`${BACKEND_ENV}=${backendLabel} ran but returned no answer`);
10323
10383
  }
10324
10384
 
10325
10385
  // src/content_store.ts