token-goat 2.8.4 → 2.8.6

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.
@@ -23,9 +23,11 @@ import {
23
23
  extractPdfMeta,
24
24
  extractPdfOutline,
25
25
  extractPdfText,
26
+ fenceUntrusted,
26
27
  fenceUntrustedContent,
27
28
  formatCsvProfile,
28
29
  formatCsvTable,
30
+ getEmbeddingCoverage,
29
31
  getFileEntry,
30
32
  indexFileSync,
31
33
  isAvailable,
@@ -49,7 +51,7 @@ import {
49
51
  queryRefs,
50
52
  queryRefsByContext,
51
53
  querySymbols,
52
- scanForInjectionPatterns,
54
+ scanAndRecord,
53
55
  searchEvidenceSemantically,
54
56
  searchSemantic,
55
57
  searchSymbolsFts,
@@ -64,7 +66,7 @@ import {
64
66
  walkProject,
65
67
  yamlLineClosesQuote,
66
68
  yamlOpenQuoteAfter
67
- } from "./token-goat-chunk-4OTIB7SB.mjs";
69
+ } from "./token-goat-chunk-DK4VLLYB.mjs";
68
70
  import {
69
71
  Database,
70
72
  PER_FILE_COUNTERFACTUAL_CEILING,
@@ -118,7 +120,7 @@ import {
118
120
  unsupportedLanguageName,
119
121
  windowsCmdQuoteArg,
120
122
  withExtension
121
- } from "./token-goat-chunk-E76UNTVK.mjs";
123
+ } from "./token-goat-chunk-2JZ66BBE.mjs";
122
124
  import {
123
125
  registerReset
124
126
  } from "./token-goat-chunk-AO2QD2AG.mjs";
@@ -5353,15 +5355,12 @@ function guardText(text, command) {
5353
5355
  const cfg = loadConfig();
5354
5356
  return cfg.overflow_guard.enabled ? trimToBudget(text, cfg.overflow_guard.max_tokens, command) : text;
5355
5357
  }
5356
- function fenceGithubTextIfMatched(text) {
5357
- let matches = [];
5358
- try {
5359
- if (loadConfig().injection.enabled) matches = scanForInjectionPatterns(text);
5360
- } catch {
5361
- matches = [];
5362
- }
5358
+ function fenceGithubText(text) {
5359
+ return fenceUntrusted(text, UNTRUSTED_GITHUB_TAG);
5360
+ }
5361
+ function fenceGithubFieldIfMatched(text) {
5362
+ const matches = scanAndRecord(text);
5363
5363
  if (matches.length === 0) return text;
5364
- recordStat("injection_detected", 0, 0, void 0, matches.join(","));
5365
5364
  return fenceUntrustedContent(text, matches, UNTRUSTED_GITHUB_TAG);
5366
5365
  }
5367
5366
  function guardJsonRows(items) {
@@ -7217,11 +7216,11 @@ function runPrSlice(opts) {
7217
7216
  }
7218
7217
  const fullSourceBytes = Buffer.byteLength(diffText, "utf8");
7219
7218
  if (opts.json === true) {
7220
- const jsonText = JSON.stringify({ path: parsed.path, diff: fenceGithubTextIfMatched(fileDiff) });
7219
+ const jsonText = JSON.stringify({ path: parsed.path, diff: fenceGithubFieldIfMatched(fileDiff) });
7221
7220
  emit(jsonText);
7222
7221
  recordReadStat("pr_slice", fullSourceBytes, jsonText, `${repo}#${opts.pr} diff:${parsed.path}`);
7223
7222
  } else {
7224
- emitGuarded(fenceGithubTextIfMatched(fileDiff), "pr-slice");
7223
+ emitGuarded(fenceGithubText(fileDiff), "pr-slice");
7225
7224
  recordReadStat("pr_slice", fullSourceBytes, fileDiff, `${repo}#${opts.pr} diff:${parsed.path}`);
7226
7225
  }
7227
7226
  return 0;
@@ -7230,14 +7229,14 @@ function runPrSlice(opts) {
7230
7229
  const comments = fetchPrComments(opts.pr, repo);
7231
7230
  const fullSourceBytes = Buffer.byteLength(JSON.stringify(comments), "utf8");
7232
7231
  if (opts.json === true) {
7233
- const fencedComments = comments.map((c) => ({ ...c, body: fenceGithubTextIfMatched(c.body) }));
7232
+ const fencedComments = comments.map((c) => ({ ...c, body: fenceGithubFieldIfMatched(c.body) }));
7234
7233
  const capped = guardJsonRows(fencedComments);
7235
7234
  const jsonText = JSON.stringify({ items: capped.items, truncated: capped.truncated, totalCount: capped.totalCount });
7236
7235
  emit(jsonText);
7237
7236
  recordReadStat("pr_slice", fullSourceBytes, jsonText, `${repo}#${opts.pr} comments`);
7238
7237
  } else {
7239
7238
  const text = formatCommentsSlice(comments);
7240
- emitGuarded(fenceGithubTextIfMatched(text), "pr-slice");
7239
+ emitGuarded(fenceGithubText(text), "pr-slice");
7241
7240
  recordReadStat("pr_slice", fullSourceBytes, text, `${repo}#${opts.pr} comments`);
7242
7241
  }
7243
7242
  return 0;
@@ -7248,15 +7247,15 @@ function runPrSlice(opts) {
7248
7247
  if (opts.json === true) {
7249
7248
  const fencedDesc = {
7250
7249
  ...desc,
7251
- title: fenceGithubTextIfMatched(desc.title),
7252
- body: desc.body !== null ? fenceGithubTextIfMatched(desc.body) : null
7250
+ title: fenceGithubFieldIfMatched(desc.title),
7251
+ body: desc.body !== null ? fenceGithubFieldIfMatched(desc.body) : null
7253
7252
  };
7254
7253
  const jsonText = JSON.stringify(fencedDesc);
7255
7254
  emit(jsonText);
7256
7255
  recordReadStat("pr_slice", fullSourceBytes, jsonText, `${repo}#${opts.pr} description`);
7257
7256
  } else {
7258
7257
  const text = formatDescriptionSlice(desc);
7259
- emitGuarded(fenceGithubTextIfMatched(text), "pr-slice");
7258
+ emitGuarded(fenceGithubText(text), "pr-slice");
7260
7259
  recordReadStat("pr_slice", fullSourceBytes, text, `${repo}#${opts.pr} description`);
7261
7260
  }
7262
7261
  return 0;
@@ -7673,7 +7672,7 @@ function runFind(opts) {
7673
7672
  }
7674
7673
  if (opts.json === true) {
7675
7674
  const fuzzyPayload = fuzzyNames.length > 0 ? { fuzzy: true, matchedNames: fuzzyNames } : {};
7676
- emit(JSON.stringify({ files, truncated, ...fuzzyPayload }, null, 2));
7675
+ emit(JSON.stringify({ files, truncated, totalCount: allFiles.length, ...fuzzyPayload }, null, 2));
7677
7676
  return 0;
7678
7677
  }
7679
7678
  if (fuzzyNames.length > 0) {
@@ -8943,6 +8942,18 @@ async function runSemantic(query, opts) {
8943
8942
  `Matching on meaning is off (${extractErrorMessage(e)}); these results come from keyword search alone.`
8944
8943
  );
8945
8944
  }
8945
+ if (rawHits.length === 0 && isAvailable()) {
8946
+ try {
8947
+ const enabled = loadConfig().indexing.embeddings_enabled;
8948
+ const { indexedFiles, embeddedFiles } = enabled ? getEmbeddingCoverage(globalDbPath(), rootDir) : { indexedFiles: 0, embeddedFiles: 0 };
8949
+ if (indexedFiles > 0 && embeddedFiles < indexedFiles) {
8950
+ console.warn(
8951
+ `Matching on meaning found nothing, and only ${embeddedFiles} of ${indexedFiles} indexed file(s) in this project have embeddings \u2014 these results come from keyword search alone. Run 'token-goat doctor' for why.`
8952
+ );
8953
+ }
8954
+ } catch {
8955
+ }
8956
+ }
8946
8957
  const mergedHits = mergeNearbyHits(rawHits);
8947
8958
  const overFetchFts = Math.min(MAX_OVER_FETCH, n * OVER_FETCH_FACTOR);
8948
8959
  const ftsRows = searchSymbolsFts(query, overFetchFts, void 0, rootDir);
@@ -8989,7 +9000,9 @@ async function runSemantic(query, opts) {
8989
9000
  const anyFilter = matchesGrep !== void 0 || opts.excludeTests === true;
8990
9001
  const filteredHits = anyFilter ? fusedHits.filter(keepHit) : fusedHits;
8991
9002
  const suppressedTotal = opts.excludeTests === true ? fusedHits.filter((h) => (matchesGrep === void 0 || matchesGrep(toDisplayPath(rootDir, h.filePath))) && isTestFile(h.filePath)).length : 0;
9003
+ const eligibleCount = filteredHits.length;
8992
9004
  const hits = filteredHits.slice(0, n);
9005
+ const candidatesClipped = rawHits.length >= overFetchForMerge || ftsRows.length >= overFetchFts;
8993
9006
  const hadDense = mergedHits.length > 0;
8994
9007
  const hadFts = ftsRows.length > 0;
8995
9008
  const source = hadDense && hadFts ? "hybrid" : hadDense ? "embeddings" : "fts";
@@ -9005,7 +9018,8 @@ async function runSemantic(query, opts) {
9005
9018
  preview: previewLines(h.previewText, 3)
9006
9019
  }));
9007
9020
  const capped = guardJsonRows(items);
9008
- const text3 = JSON.stringify({ source, ...capped }, null, 2);
9021
+ const limitTruncated = hits.length < eligibleCount;
9022
+ const text3 = JSON.stringify({ source, ...capped, truncated: capped.truncated || limitTruncated, totalCount: eligibleCount, ...candidatesClipped ? { totalCountIsFloor: true } : {} }, null, 2);
9009
9023
  recordReadStat("semantic_search", sumFileSizes(hits.map((h) => h.filePath)), text3, query);
9010
9024
  return { text: text3, code: 0 };
9011
9025
  }
@@ -9019,6 +9033,9 @@ ${previewLines(h.previewText, 3)}`;
9019
9033
  ${previewLines(h.previewText, 3)}`;
9020
9034
  });
9021
9035
  const text2 = guardText(blocks.join("\n\n"), "semantic");
9036
+ if (hits.length < eligibleCount) {
9037
+ emitErr(`Showing ${hits.length} of ${candidatesClipped ? "at least " : ""}${countNoun(eligibleCount, "match", "matches")} (raise --limit to see the rest).`);
9038
+ }
9022
9039
  recordReadStat("semantic_search", sumFileSizes(hits.map((h) => h.filePath)), text2, query);
9023
9040
  return { text: text2, code: 0 };
9024
9041
  }
@@ -9819,7 +9836,7 @@ var TYPE_KINDS = [
9819
9836
  var CORE_SYMBOL_KINDS = [
9820
9837
  .../* @__PURE__ */ new Set([...GENERIC_SYMBOL_KINDS, ...TYPE_KINDS, ...OTHER_ADAPTER_SYMBOL_KINDS])
9821
9838
  ];
9822
- var TYPES_EXCLUDE_TESTS_SCAN_LIMIT = 5e3;
9839
+ var TYPES_SCAN_LIMIT = 5e3;
9823
9840
  function runTypes(opts) {
9824
9841
  if (opts.limit !== void 0 && opts.limit <= 0) {
9825
9842
  emitErr2(`--limit must be a positive number, got: ${opts.limit}`);
@@ -9832,34 +9849,25 @@ function runTypes(opts) {
9832
9849
  const rootDir = resolveProjectRoot({ project: process.cwd() });
9833
9850
  const results = [];
9834
9851
  let suppressed = 0;
9835
- const queryLimit = excludeTests ? TYPES_EXCLUDE_TESTS_SCAN_LIMIT : limit;
9836
9852
  for (const k of TYPE_KINDS) {
9837
- const syms = querySymbols({ kind: k, ...fpOpt, limit: queryLimit, rootDir });
9838
- if (!excludeTests) {
9839
- results.push(...syms);
9840
- continue;
9841
- }
9842
- const kept = [];
9853
+ const syms = querySymbols({ kind: k, ...fpOpt, limit: TYPES_SCAN_LIMIT, rootDir });
9843
9854
  for (const sym of syms) {
9844
- if (isTestFile(sym.filePath)) {
9855
+ if (excludeTests && isTestFile(sym.filePath)) {
9845
9856
  suppressed += 1;
9846
9857
  continue;
9847
9858
  }
9848
- kept.push(sym);
9859
+ results.push(sym);
9849
9860
  }
9850
- results.push(...kept.slice(0, limit));
9851
9861
  }
9852
- const classes = querySymbols({ kind: "class", ...fpOpt, limit: queryLimit, rootDir });
9853
- const typeClasses = [];
9862
+ const classes = querySymbols({ kind: "class", ...fpOpt, limit: TYPES_SCAN_LIMIT, rootDir });
9854
9863
  for (const cls of classes) {
9855
9864
  if (!looksLikeTypeClass(cls.body)) continue;
9856
9865
  if (excludeTests && isTestFile(cls.filePath)) {
9857
9866
  suppressed += 1;
9858
9867
  continue;
9859
9868
  }
9860
- typeClasses.push(cls);
9869
+ results.push(cls);
9861
9870
  }
9862
- results.push(...excludeTests ? typeClasses.slice(0, limit) : typeClasses);
9863
9871
  results.sort(
9864
9872
  // Pin the locale: an unlocaled localeCompare() sorts differently across
9865
9873
  // Node's small-icu vs full-icu builds and different system default
@@ -9886,7 +9894,16 @@ function runTypes(opts) {
9886
9894
  }
9887
9895
  const preFilterCount = results.length;
9888
9896
  const matchesGrep = opts.grep !== void 0 ? compileGrepMatcher(opts.grep) : void 0;
9889
- const filtered = matchesGrep !== void 0 ? results.filter((r) => matchesGrep(r.name)) : results;
9897
+ const matched = matchesGrep !== void 0 ? results.filter((r) => matchesGrep(r.name)) : results;
9898
+ const eligibleCount = matched.length;
9899
+ const perKindShown = /* @__PURE__ */ new Map();
9900
+ const filtered = matched.filter((r) => {
9901
+ const seen = perKindShown.get(r.kind) ?? 0;
9902
+ if (seen >= limit) return false;
9903
+ perKindShown.set(r.kind, seen + 1);
9904
+ return true;
9905
+ });
9906
+ const cappedOut = eligibleCount - filtered.length;
9890
9907
  if (filtered.length === 0) {
9891
9908
  if (opts.json === true) {
9892
9909
  emit2(JSON.stringify({ items: [], truncated: false, totalCount: 0, hiddenByGrep: preFilterCount }, null, 2));
@@ -9897,8 +9914,8 @@ function runTypes(opts) {
9897
9914
  }
9898
9915
  if (opts.json === true) {
9899
9916
  const capped = guardJsonRows(filtered.map((r) => ({ ...r, filePath: toDisplayPath(rootDir, r.filePath) })));
9900
- const hiddenByGrep = preFilterCount - filtered.length;
9901
- emit2(JSON.stringify({ items: capped.items, truncated: capped.truncated, totalCount: capped.totalCount, ...hiddenByGrep > 0 ? { hiddenByGrep } : {}, ...excludeTests && suppressed > 0 ? { hiddenByExcludeTests: suppressed } : {} }, null, 2));
9917
+ const hiddenByGrep = preFilterCount - eligibleCount;
9918
+ emit2(JSON.stringify({ items: capped.items, truncated: capped.truncated || cappedOut > 0, totalCount: eligibleCount, ...hiddenByGrep > 0 ? { hiddenByGrep } : {}, ...excludeTests && suppressed > 0 ? { hiddenByExcludeTests: suppressed } : {} }, null, 2));
9902
9919
  return 0;
9903
9920
  }
9904
9921
  if (excludeTests && suppressed > 0) {
@@ -9907,6 +9924,9 @@ function runTypes(opts) {
9907
9924
  for (const r of filtered) {
9908
9925
  emit2(`${r.name} ${r.kind} ${toDisplayPath(rootDir, r.filePath)}:${r.lineStart}`);
9909
9926
  }
9927
+ if (cappedOut > 0) {
9928
+ emitErr2(`Showing ${filtered.length} of ${eligibleCount} type declarations (--limit is per kind; raise it to see the rest).`);
9929
+ }
9910
9930
  return 0;
9911
9931
  }
9912
9932
  function runScope(opts) {
@@ -9922,7 +9942,7 @@ function runScope(opts) {
9922
9942
  return 1;
9923
9943
  }
9924
9944
  const line = Number.parseInt(lineStr, 10);
9925
- if (!Number.isFinite(line) || line < 1) {
9945
+ if (!Number.isSafeInteger(line) || line < 1) {
9926
9946
  emitErr2(`Invalid line number: ${lineStr}`);
9927
9947
  return 1;
9928
9948
  }
@@ -9932,25 +9952,32 @@ function runScope(opts) {
9932
9952
  return 1;
9933
9953
  }
9934
9954
  const filePath = resolveIndexPath(file);
9935
- const syms = querySymbols({ filePath, limit: ALL_SYMBOLS_IN_FILE_LIMIT });
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"));
9937
- if (syms.length === 0) {
9938
- if (!fs6.existsSync(filePath)) {
9939
- emitErr2(`Could not read: ${file}`);
9955
+ const enclosing = querySymbols({ filePath, enclosingLine: line, limit: ALL_SYMBOLS_IN_FILE_LIMIT }).sort((a, b) => b.lineStart - a.lineStart || a.lineEnd - b.lineEnd || a.name.localeCompare(b.name, "en"));
9956
+ if (enclosing.length === 0) {
9957
+ if (querySymbols({ filePath, limit: 1 }).length === 0) {
9958
+ if (!fs6.existsSync(filePath)) {
9959
+ emitErr2(`Could not read: ${file}`);
9960
+ return 1;
9961
+ }
9962
+ emitErr2(
9963
+ `No indexed symbols in '${file}' \u2014 the file exists but nothing is indexed for it, so every line looks empty`
9964
+ );
9940
9965
  return 1;
9941
9966
  }
9942
- emitErr2(`No indexed symbols in '${file}' \u2014 the file exists but nothing is indexed for it, so every line looks empty`);
9943
- return 1;
9944
- }
9945
- if (enclosing.length === 0) {
9946
9967
  emitErr2(`No symbols enclosing line ${line} in '${file}'`);
9947
9968
  return 1;
9948
9969
  }
9970
+ const scopeDisplayRoot = getDisplayRoot();
9949
9971
  if (opts.json === true) {
9950
- emit2(JSON.stringify(enclosing, null, 2));
9972
+ emit2(
9973
+ JSON.stringify(
9974
+ enclosing.map((s) => ({ ...s, filePath: toDisplayPath(scopeDisplayRoot, s.filePath) })),
9975
+ null,
9976
+ 2
9977
+ )
9978
+ );
9951
9979
  return 0;
9952
9980
  }
9953
- const scopeDisplayRoot = getDisplayRoot();
9954
9981
  for (const s of enclosing) {
9955
9982
  emit2(`${s.name} ${s.kind} ${toDisplayPath(scopeDisplayRoot, s.filePath)}:${s.lineStart}-${s.lineEnd}`);
9956
9983
  }
@@ -10075,8 +10102,12 @@ function runSimilar(opts) {
10075
10102
  const words = [anchor.name, ...(anchor.docstring ?? "").split(/\s+/).filter((w) => w.length > 4)];
10076
10103
  const query = words.slice(0, 8).join(" ");
10077
10104
  const rootDir = resolveProjectRoot({ project: process.cwd() });
10078
- const hits = searchSymbolsFts(query, top + 1, void 0, rootDir);
10079
- const results = hits.filter((h) => !(h.filePath === anchor.filePath && h.name === anchor.name)).slice(0, top);
10105
+ const hits = searchSymbolsFts(query, UNBOUNDED_REF_LIMIT, void 0, rootDir);
10106
+ const matches = hits.filter((h) => !(h.filePath === anchor.filePath && h.name === anchor.name));
10107
+ const results = matches.slice(0, top);
10108
+ if (matches.length > results.length) {
10109
+ emitErr2(`Showing top ${results.length} of ${matches.length} similar symbols (raise --top to see the rest).`);
10110
+ }
10080
10111
  if (opts.json === true) {
10081
10112
  emit2(JSON.stringify(results.map((h) => ({ name: h.name, kind: h.kind, file: h.filePath, line: h.lineStart })), null, 2));
10082
10113
  return 0;
@@ -10092,7 +10123,9 @@ function runContextFor(opts) {
10092
10123
  const top = opts.top ?? 12;
10093
10124
  const budget = opts.budget;
10094
10125
  const rootDir = resolveProjectRoot({ project: process.cwd() });
10095
- const hits = searchSymbolsFts(opts.task, top, void 0, rootDir);
10126
+ const fetched = searchSymbolsFts(opts.task, top + 1, void 0, rootDir);
10127
+ const moreBeyondTop = fetched.length > top;
10128
+ const hits = fetched.slice(0, top);
10096
10129
  if (hits.length === 0) {
10097
10130
  emitErr2(`No matches found for '${opts.task}'`);
10098
10131
  return 1;
@@ -10105,6 +10138,15 @@ function runContextFor(opts) {
10105
10138
  tokensSoFar += bodyTokens;
10106
10139
  entries.push({ file: h.filePath, symbol: h.name, kind: h.kind, line: h.lineStart, readCmd: `token-goat read "${h.filePath}::${h.name}@${h.lineStart}"` });
10107
10140
  }
10141
+ const skippedByBudget = hits.length - entries.length;
10142
+ const notices = [];
10143
+ if (skippedByBudget > 0) {
10144
+ notices.push(
10145
+ entries.length === 0 ? `All ${countNoun(skippedByBudget, "matching symbol")} were larger than --budget ${String(opts.budget)} tokens, so none are shown. Raise --budget.` : `Showing ${entries.length} of ${countNoun(hits.length, "matching symbol")}; ${skippedByBudget} did not fit --budget ${String(opts.budget)} tokens.`
10146
+ );
10147
+ }
10148
+ if (moreBeyondTop) notices.push(`More matches exist beyond --top ${top} (raise it to see them).`);
10149
+ for (const n of notices) emitErr2(n);
10108
10150
  if (opts.json === true) {
10109
10151
  emit2(JSON.stringify(entries, null, 2));
10110
10152
  return 0;
@@ -10242,16 +10284,20 @@ function runArch(opts) {
10242
10284
  }
10243
10285
  graph.set(file, internal);
10244
10286
  }
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 }));
10246
- const entryPoints = files.filter((f) => !importedBy.has(f) && (graph.get(f) ?? []).length > 0).slice(0, top).map((f) => ({ file: f }));
10287
+ const allHubs = [...importedBy.entries()].sort((a, b) => b[1].size - a[1].size).map(([f, importers]) => ({ file: f, importedBy: importers.size }));
10288
+ const hubsTotal = allHubs.length;
10289
+ const hubs = allHubs.slice(0, top);
10290
+ const allEntryPoints = files.filter((f) => !importedBy.has(f) && (graph.get(f) ?? []).length > 0).map((f) => ({ file: f }));
10291
+ const entryPointsTotal = allEntryPoints.length;
10292
+ const entryPoints = allEntryPoints.slice(0, top);
10247
10293
  const { cycles, truncated: cyclesTruncated } = findCyclesCapped(graph);
10248
10294
  if (opts.json === true) {
10249
- emit2(JSON.stringify({ hubs, entryPoints, cycles, ...cyclesTruncated ? { cyclesTruncated: true } : {} }, null, 2));
10295
+ emit2(JSON.stringify({ hubs, hubsTotal, hubsTruncated: hubs.length < hubsTotal, entryPoints, entryPointsTotal, entryPointsTruncated: entryPoints.length < entryPointsTotal, cycles, ...cyclesTruncated ? { cyclesTruncated: true } : {} }, null, 2));
10250
10296
  return 0;
10251
10297
  }
10252
- emit2(`hubs (top ${top} most-imported):`);
10298
+ emit2(hubs.length < hubsTotal ? `hubs (top ${hubs.length} of ${hubsTotal} most-imported):` : `hubs (${hubsTotal} most-imported):`);
10253
10299
  for (const h of hubs) emit2(` ${h.importedBy} importers ${toDisplayPath(getDisplayRoot(opts.cwd), h.file)}`);
10254
- emit2(`entry points (imported by nobody, top ${top}):`);
10300
+ emit2(entryPoints.length < entryPointsTotal ? `entry points (imported by nobody, top ${entryPoints.length} of ${entryPointsTotal}):` : `entry points (imported by nobody, ${entryPointsTotal} found):`);
10255
10301
  for (const e of entryPoints) emit2(` ${toDisplayPath(getDisplayRoot(opts.cwd), e.file)}`);
10256
10302
  emit2(cyclesTruncated ? `cycles (first ${cycles.length}, truncated at the ${MAX_CYCLES}-cycle enumeration limit: more cycles exist):` : `cycles (${cycles.length} found):`);
10257
10303
  for (const c of cycles) emit2(` ${c.map((f) => toDisplayPath(getDisplayRoot(opts.cwd), f)).join(" -> ")}`);
@@ -10301,7 +10347,12 @@ function runAsk(opts) {
10301
10347
  }
10302
10348
  const top = opts.top ?? 8;
10303
10349
  const rootDir = resolveProjectRoot({ project: process.cwd() });
10304
- const hits = searchSymbolsFts(opts.question, top, void 0, rootDir);
10350
+ const fetched = searchSymbolsFts(opts.question, top + 1, void 0, rootDir);
10351
+ const moreBeyondTop = fetched.length > top;
10352
+ const hits = fetched.slice(0, top);
10353
+ if (moreBeyondTop) {
10354
+ emitErr2(`Answer is grounded in the top ${countNoun(top, "match", "matches")}; more matched (raise --top to widen the evidence).`);
10355
+ }
10305
10356
  const BACKEND_ENV = "TOKEN_GOAT_ASK_BACKEND";
10306
10357
  const backendLabel = process.env[BACKEND_ENV] ?? "";
10307
10358
  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}"` }));
@@ -25,7 +25,7 @@ import {
25
25
  runSkeleton,
26
26
  runSymbol,
27
27
  withPinnedReads
28
- } from "./token-goat-chunk-RE7S7H26.mjs";
28
+ } from "./token-goat-chunk-XAWIELXH.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-4OTIB7SB.mjs";
37
+ } from "./token-goat-chunk-DK4VLLYB.mjs";
38
38
  import {
39
39
  VERSION,
40
40
  dataDir,
@@ -46,7 +46,7 @@ import {
46
46
  recordStat,
47
47
  resolveProjectRoot,
48
48
  savedTokensFromBytes
49
- } from "./token-goat-chunk-E76UNTVK.mjs";
49
+ } from "./token-goat-chunk-2JZ66BBE.mjs";
50
50
  import "./token-goat-chunk-AO2QD2AG.mjs";
51
51
  import "./token-goat-chunk-AEX54RUZ.mjs";
52
52
 
@@ -2,11 +2,11 @@ import { createRequire as __cjsRequire } from 'node:module';
2
2
  const require = __cjsRequire(import.meta.url);
3
3
  import {
4
4
  relayInProcess
5
- } from "./token-goat-chunk-ZQ3PUOP3.mjs";
6
- import "./token-goat-chunk-IZRXU64B.mjs";
7
- import "./token-goat-chunk-4OTIB7SB.mjs";
8
- import "./token-goat-chunk-CZALRRGN.mjs";
9
- import "./token-goat-chunk-E76UNTVK.mjs";
5
+ } from "./token-goat-chunk-MA5237JN.mjs";
6
+ import "./token-goat-chunk-TELKICYU.mjs";
7
+ import "./token-goat-chunk-DK4VLLYB.mjs";
8
+ import "./token-goat-chunk-GUNYAGOZ.mjs";
9
+ import "./token-goat-chunk-2JZ66BBE.mjs";
10
10
  import "./token-goat-chunk-AO2QD2AG.mjs";
11
11
  import "./token-goat-chunk-AEX54RUZ.mjs";
12
12
  export {
@@ -2,13 +2,13 @@ import { createRequire as __cjsRequire } from 'node:module';
2
2
  const require = __cjsRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./token-goat-chunk-U4UI5F3S.mjs";
6
- import "./token-goat-chunk-RE7S7H26.mjs";
7
- import "./token-goat-chunk-IZRXU64B.mjs";
8
- import "./token-goat-chunk-4OTIB7SB.mjs";
5
+ } from "./token-goat-chunk-U7M2LTGP.mjs";
6
+ import "./token-goat-chunk-XAWIELXH.mjs";
7
+ import "./token-goat-chunk-TELKICYU.mjs";
8
+ import "./token-goat-chunk-DK4VLLYB.mjs";
9
9
  import {
10
10
  installEpipeGuard
11
- } from "./token-goat-chunk-E76UNTVK.mjs";
11
+ } from "./token-goat-chunk-2JZ66BBE.mjs";
12
12
  import "./token-goat-chunk-AO2QD2AG.mjs";
13
13
  import "./token-goat-chunk-AEX54RUZ.mjs";
14
14
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "token-goat",
3
- "version": "2.8.4",
3
+ "version": "2.8.6",
4
4
  "description": "Surgical token-reduction companion for Claude Code and other AI coding agents",
5
5
  "type": "module",
6
6
  "main": "./dist/token-goat.mjs",