token-goat 2.9.3 → 2.9.5

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.
@@ -86,7 +86,7 @@ import {
86
86
  runZipRead,
87
87
  symbolNamesInFile,
88
88
  upsertNote
89
- } from "./token-goat-chunk-7A7SBE6R.mjs";
89
+ } from "./token-goat-chunk-4P6GTCMM.mjs";
90
90
  import {
91
91
  DEFAULT_RECONCILE_BUDGET_MS,
92
92
  GEMINI_TOOL_NAME_MAP,
@@ -108,7 +108,7 @@ import {
108
108
  runReconcile,
109
109
  storeWebOutput,
110
110
  summarizeResidentContext
111
- } from "./token-goat-chunk-A4VBNWSL.mjs";
111
+ } from "./token-goat-chunk-JO5JX72D.mjs";
112
112
  import {
113
113
  AGENT_SALT_MARKER,
114
114
  BASH_OUTPUT_SUBDIR,
@@ -175,6 +175,7 @@ import {
175
175
  getFileEntry,
176
176
  getHintStatsSummary,
177
177
  getHintStatsTotals,
178
+ getParserFreshness,
178
179
  getProjectIndexCounts,
179
180
  getSessionFiles,
180
181
  getSessionId,
@@ -193,6 +194,7 @@ import {
193
194
  isHintCategory,
194
195
  isParseSkipEligible,
195
196
  isRecallCacheType,
197
+ isSuppressionCategory,
196
198
  isWorkerRunning,
197
199
  listBlobs,
198
200
  listOutputs,
@@ -238,7 +240,7 @@ import {
238
240
  visionTokensSavedByText,
239
241
  walkProject,
240
242
  writeCompact
241
- } from "./token-goat-chunk-3XVGPLDS.mjs";
243
+ } from "./token-goat-chunk-U4FTM2SB.mjs";
242
244
  import {
243
245
  C,
244
246
  CONFIG_KEY_ENV_OVERRIDES,
@@ -364,7 +366,7 @@ import {
364
366
  withFileLock,
365
367
  withRetryOnLock,
366
368
  writeJsonSettings
367
- } from "./token-goat-chunk-KKHKXUVS.mjs";
369
+ } from "./token-goat-chunk-ZOKNDG6V.mjs";
368
370
  import {
369
371
  __export,
370
372
  init_define_import_meta_env
@@ -5950,6 +5952,7 @@ function statsJsonPayload(summary) {
5950
5952
  by_command: summary.by_command,
5951
5953
  by_source: summary.by_source,
5952
5954
  by_harness: summary.by_harness,
5955
+ by_pricing_version: summary.by_pricing_version,
5953
5956
  counts: summary.counts,
5954
5957
  window_days: summary.window_days
5955
5958
  };
@@ -6219,6 +6222,38 @@ function checkEmbeddingCoverage(dbPath, rootDir) {
6219
6222
  };
6220
6223
  }
6221
6224
  }
6225
+ var PARSER_FRESHNESS_WARN_FRACTION = 0.25;
6226
+ function checkParserFreshness(dbPath, rootDir) {
6227
+ if (!fs12.existsSync(dbPath)) {
6228
+ return { name: "Parser freshness", status: "ok", message: "no database yet" };
6229
+ }
6230
+ try {
6231
+ const { indexedFiles, currentFiles } = getParserFreshness(dbPath, PARSER_FINGERPRINT, rootDir);
6232
+ if (indexedFiles === 0) {
6233
+ return { name: "Parser freshness", status: "ok", message: "no indexed files yet" };
6234
+ }
6235
+ const stale = indexedFiles - currentFiles;
6236
+ const pct3 = Math.round(stale / indexedFiles * 100);
6237
+ if (currentFiles / indexedFiles < 1 - PARSER_FRESHNESS_WARN_FRACTION) {
6238
+ return {
6239
+ name: "Parser freshness",
6240
+ status: "warn",
6241
+ message: `${stale} of ${indexedFiles} indexed file(s) (${pct3}%) were parsed by a different build of the extraction logic, so their symbols are whatever that build extracted \u2014 'symbol', 'read', 'outline' and 'skeleton' answer from those rows, and the read-hook body fold declines on them. Run 'token-goat index' in this project to reparse them; --force is not needed, a parser mismatch reindexes on its own`
6242
+ };
6243
+ }
6244
+ return {
6245
+ name: "Parser freshness",
6246
+ status: "ok",
6247
+ message: `${currentFiles} of ${indexedFiles} indexed file(s) match the running parser`
6248
+ };
6249
+ } catch (err2) {
6250
+ return {
6251
+ name: "Parser freshness",
6252
+ status: "warn",
6253
+ message: `could not query parser freshness: ${extractErrorMessage(err2)}`
6254
+ };
6255
+ }
6256
+ }
6222
6257
  var DIRTY_QUEUE_BACKLOG_WARN_THRESHOLD = 500;
6223
6258
  function checkDirtyQueueHealth(dataDir2) {
6224
6259
  let pendingCount = 0;
@@ -6454,6 +6489,9 @@ var LOCKED_BOOLEAN_SAFE_VALUE = {
6454
6489
  "screenshot.block_private_targets": true,
6455
6490
  "network.offline": true,
6456
6491
  "indexing.cross_project_symbols": false,
6492
+ // `hints.fold_code_bodies` is deliberately absent for the same reason as `hints.fold_prose_paragraphs` below: it now ships on. Its cost was the thing keeping it off, and that cost is now measured rather than assumed -- 6.6 points of recovery reads beyond what the same symbols draw anyway, against a fold that withholds a whole function body. It remains project-locked, so the environment deciding it is still reported by the value-replacement arm.
6493
+ // `hints.fold_prose_paragraphs` is deliberately absent: it now ships on, so neither direction is a weakening. Setting it true restates the shipped default, and setting it false shows the reader MORE of a document, so warning on either would break this file's own rule that a default install stays quiet. It remains project-locked, so the environment deciding it is still worth reporting, which is what the value-replacement arm below does.
6494
+ // The three remaining hints keys, fold_comment_blocks and outline_large_documents and skeleton_large_sources, are absent for exactly the same reason and just as deliberately: each ships on, so neither direction of an override is a weakening, and a false entry here would make a default install warn about itself. All three stay project-locked, so the environment deciding one is still reported by the value-replacement arm below.
6457
6495
  "webfetch.compress_bodies": true
6458
6496
  };
6459
6497
  function lockedEnvOverridableKeys() {
@@ -6520,9 +6558,15 @@ function checkSecurityPosture(cfg, dataDirPath) {
6520
6558
  cfg.mcp.confine_reads_to_project_root ? {
6521
6559
  name: "Security mcp roots",
6522
6560
  status: "ok",
6523
- message: extraRoots === 0 ? "reads are confined to the project root" : `reads are confined to the project root plus ${extraRoots} configured root${extraRoots === 1 ? "" : "s"}`
6561
+ // Two separate controls, and reporting only the first read as a guarantee about the second. `confine_reads_to_project_root` keeps a read inside the root the caller named; `mcp.allowed_roots` decides which roots may be named at all, and empty means any of them (assertRootAllowed returns early). Saying "confined to the project root" while the caller picks that root overstates the confinement, so the empty case now names itself.
6562
+ message: extraRoots === 0 ? "reads are confined to the project root the caller names, but mcp.allowed_roots is empty, so an MCP caller may name any root on this machine" : `reads are confined to the project root, and callers may name only the ${extraRoots} root${extraRoots === 1 ? "" : "s"} in mcp.allowed_roots`
6524
6563
  } : { name: "Security mcp roots", status: "warn", message: "confinement is off (mcp.confine_reads_to_project_root): a read can leave the project" }
6525
6564
  );
6565
+ results.push({
6566
+ name: "Security symbol scope",
6567
+ status: "ok",
6568
+ message: cfg.indexing.cross_project_symbols ? "symbol lookups can resolve into other projects in the machine-wide index (indexing.cross_project_symbols = false confines them to this project)" : "symbol lookups are confined to this project"
6569
+ });
6526
6570
  const overridden = envOverriddenSecuritySettings();
6527
6571
  const weakened = overridden.filter((o) => o.kind === "weakened");
6528
6572
  const replaced = overridden.filter((o) => o.kind === "replaced");
@@ -6656,6 +6700,7 @@ function runDoctor(dataDir2, configPath2, rootDir, processes) {
6656
6700
  results.push(checkConfigValid(actualConfigPath));
6657
6701
  results.push(checkEmbeddings(loadConfig(rootDir)));
6658
6702
  results.push(checkEmbeddingCoverage(path12.join(actualDataDir, "global.db"), rootDir));
6703
+ results.push(checkParserFreshness(path12.join(actualDataDir, "global.db"), rootDir));
6659
6704
  for (const result of checkSecurityPosture(loadConfig(rootDir), actualDataDir)) results.push(result);
6660
6705
  results.push(checkDiskSpace(actualDataDir));
6661
6706
  const copilotResult = checkCopilotCli(copilotCliConfigPath(), copilotCliScriptPath());
@@ -14057,15 +14102,15 @@ var READ_DIVERT_MARKER_RE = /(?:was already read this session|Already read |You'
14057
14102
  var READ_DIVERT_MAX_BYTES = 2500;
14058
14103
  var READ_FULL_SERVE_MIN_BYTES = 10240;
14059
14104
  var DENY_TEMPLATES = [
14060
- { kind: "node_modules_deny", re: /node_modules is typically noise/ },
14061
- { kind: "lock_file_deny", re: /Lock files are rarely useful to read in full/ },
14062
- { kind: "tsbuildinfo_deny", re: /TypeScript incremental build cache file/ },
14063
- { kind: "generated_build_deny", re: /Generated\/build artifact — read the source file instead\./ },
14064
- { kind: "compact_sidecar_served", re: /Serving the extractive compact sidecar in place of the full file/ },
14065
- { kind: "notebook_sidecar_served", re: /Serving the output-stripped notebook in place of the full file/ },
14105
+ { kind: "node_modules_deny", re: /node_modules is typically noise/, tool: "Read" },
14106
+ { kind: "lock_file_deny", re: /Lock files are rarely useful to read in full/, tool: "Read" },
14107
+ { kind: "tsbuildinfo_deny", re: /TypeScript incremental build cache file/, tool: "Read" },
14108
+ { kind: "generated_build_deny", re: /Generated\/build artifact — read the source file instead\./, tool: "Read" },
14109
+ { kind: "compact_sidecar_served", re: /Serving the extractive compact sidecar in place of the full file/, tool: "Read" },
14110
+ { kind: "notebook_sidecar_served", re: /Serving the output-stripped notebook in place of the full file/, tool: "Read" },
14066
14111
  // Must stay ABOVE markdown_heading_tree_deny: this deny's message embeds the same "Large markdown file (N headings)" guidance block, so the generic alternative would swallow it and the new intervention would be uncountable. DENY_TEMPLATES is scanned with `.find`, so the more specific wording has to come first.
14067
- { kind: "subagent_markdown_first_read_deny", re: /Subagent first read of a large markdown file/ },
14068
- { kind: "markdown_heading_tree_deny", re: /Large markdown file \(\d+ headings\)/ },
14112
+ { kind: "subagent_markdown_first_read_deny", re: /Subagent first read of a large markdown file/, tool: "Read" },
14113
+ { kind: "markdown_heading_tree_deny", re: /Large markdown file \(\d+ headings\)/, tool: "Read" },
14069
14114
  // The first alternative is a wording hooks_read.ts no longer emits (it claimed a compact-manifest
14070
14115
  // section that never existed, reworded in 3d044feb). It stays because this table classifies a
14071
14116
  // historical corpus, not just today's output: transcripts written before the rewording still
@@ -14073,24 +14118,32 @@ var DENY_TEMPLATES = [
14073
14118
  // 30 -- 41% of its history -- with a green suite. A superseded alternative is only removable
14074
14119
  // once no transcript contains it, which source code cannot tell you. See the superseded-wording
14075
14120
  // test in tests/deny_outcomes.test.ts.
14076
- { kind: "memory_md_reread_deny", re: /MEMORY\.md was read this session\. Its content is in the compact manifest|already read this session\. Memory files rarely change mid-session/ },
14077
- { kind: "improve_state_reread_deny", re: /Orchestrator state already read this session/ },
14078
- { kind: "env_reread_deny", re: /Environment files rarely change mid-session/ },
14079
- { kind: "session_artifact_truncated_deny", re: /File was truncated on last read\. Use `token-goat bash-output/ },
14080
- { kind: "session_artifact_unchanged_deny", re: /is unchanged since last read\. Use `token-goat bash-output/ },
14081
- { kind: "session_artifact_diff_deny", re: /Content changed since last read of [\s\S]*?bash-output --file/ },
14082
- { kind: "session_artifact_large_deny", re: /(?:Session transcript|Tool-result file) is large \(/ },
14083
- { kind: "session_artifact_generic_reread_deny", re: /already read this session\. Use `token-goat bash-output --file/ },
14084
- { kind: "truncated_read_deny", re: /File was truncated on last read \(>33K tokens\)/ },
14085
- { kind: "doc_unchanged_deny", re: /is unchanged since last read\. Use `token-goat (?:section|read)/ },
14086
- { kind: "doc_diff_deny", re: /Content changed since last read of [\s\S]*?Use `token-goat (?:section|read)/ },
14087
- { kind: "read_served_deny", re: /was already served in this session, byte for byte/ },
14088
- { kind: "markdown_already_read_deny", re: /Markdown file already read this session\. Use `token-goat section/ },
14089
- { kind: "read_count_deny", re: /Read this file \d+ times already/ },
14090
- { kind: "generic_reread_deny", re: /was already read this session \(\d+ read/ },
14091
- { kind: "large_file_deny", re: /is very large \(\d+(?:\.\d+)?KB\)\./ },
14092
- { kind: "file_type_handler_deny", re: /too large to preview \(exceeds the in-hook scan cap\)|cannot be read as text\.|Read cannot return spreadsheet content|Read cannot return slide content|Read cannot return document content|Use Read with offset and limit parameters to read specific line ranges/ }
14121
+ { kind: "memory_md_reread_deny", re: /MEMORY\.md was read this session\. Its content is in the compact manifest|already read this session\. Memory files rarely change mid-session/, tool: "Read" },
14122
+ { kind: "improve_state_reread_deny", re: /Orchestrator state already read this session/, tool: "Read" },
14123
+ { kind: "env_reread_deny", re: /Environment files rarely change mid-session/, tool: "Read" },
14124
+ { kind: "session_artifact_truncated_deny", re: /File was truncated on last read\. Use `token-goat bash-output/, tool: "Read" },
14125
+ { kind: "session_artifact_unchanged_deny", re: /is unchanged since last read\. Use `token-goat bash-output/, tool: "Read" },
14126
+ { kind: "session_artifact_diff_deny", re: /Content changed since last read of [\s\S]*?bash-output --file/, tool: "Read" },
14127
+ { kind: "session_artifact_large_deny", re: /(?:Session transcript|Tool-result file) is large \(/, tool: "Read" },
14128
+ { kind: "session_artifact_generic_reread_deny", re: /already read this session\. Use `token-goat bash-output --file/, tool: "Read" },
14129
+ { kind: "truncated_read_deny", re: /File was truncated on last read \(>33K tokens\)/, tool: "Read" },
14130
+ { kind: "doc_unchanged_deny", re: /is unchanged since last read\. Use `token-goat (?:section|read)/, tool: "Read" },
14131
+ { kind: "doc_diff_deny", re: /Content changed since last read of [\s\S]*?Use `token-goat (?:section|read)/, tool: "Read" },
14132
+ { kind: "read_served_deny", re: /was already served in this session, byte for byte/, tool: "Read" },
14133
+ { kind: "markdown_already_read_deny", re: /Markdown file already read this session\. Use `token-goat section/, tool: "Read" },
14134
+ { kind: "read_count_deny", re: /Read this file \d+ times already/, tool: "Read" },
14135
+ { kind: "generic_reread_deny", re: /was already read this session \(\d+ read/, tool: "Read" },
14136
+ { kind: "large_file_deny", re: /is very large \(\d+(?:\.\d+)?KB\)\./, tool: "Read" },
14137
+ { kind: "file_type_handler_deny", re: /too large to preview \(exceeds the in-hook scan cap\)|cannot be read as text\.|Read cannot return spreadsheet content|Read cannot return slide content|Read cannot return document content|Use Read with offset and limit parameters to read specific line ranges/, tool: "Read" },
14138
+ // Skill-tool denies from src/hooks_skill.ts's preSkillHandler -- kind names prefixed skill_ so they read as a family, distinct from the Read-deny kinds above. The two heading-tree wordings share the prose around them ("...its heading tree...instead of the full body. Use `token-goat skill-section NAME '<heading>'`...") but each anchors on an infix the other never emits -- truncated always says "shows N of M headings below" (no "inlined"), complete always says "(N headings) is inlined below" (no "shows") -- so neither regex can match the other's text; skill_heading_tree_truncated_deny is still listed first on the same specific-before-generic principle as markdown_heading_tree_deny above, in case a future rewording narrows the gap.
14139
+ { kind: "skill_already_loaded_deny", re: /was already loaded this session and is cached/, tool: "Skill" },
14140
+ { kind: "skill_compact_slice_deny", re: /has a compact slice available/, tool: "Skill" },
14141
+ // The compact slice has two outcomes and they are separate kinds because they cost different things: the pointer above withholds the slice behind a command, this one inlines the slice and withholds only the rest of the body. Its "(N bytes) is inlined below" cannot collide with the heading-tree wording below, which requires "heading tree (N headings) is inlined below".
14142
+ { kind: "skill_compact_slice_inlined_deny", re: /compact slice \(\d+ bytes\) is inlined below instead of the full body/, tool: "Skill" },
14143
+ { kind: "skill_heading_tree_truncated_deny", re: /heading tree shows \d+ of \d+ headings below instead of the full body/, tool: "Skill" },
14144
+ { kind: "skill_heading_tree_complete_deny", re: /heading tree \(\d+ headings\) is inlined below instead of the full body/, tool: "Skill" }
14093
14145
  ];
14146
+ var DENY_TEMPLATE_KINDS = DENY_TEMPLATES.map((t) => t.kind);
14094
14147
  var DENY_KINDS_WITH_SIZE = /* @__PURE__ */ new Set(["large_file_deny", "session_artifact_large_deny", "file_type_handler_deny"]);
14095
14148
  var DENY_SIZE_RE = /(\d+(?:\.\d+)?)\s*(B|KB|MB|GB|TB)\b/;
14096
14149
  function parseWithheldBytes(kind, text) {
@@ -14360,9 +14413,8 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
14360
14413
  s.editErrorBaseline.totalErrors += 1;
14361
14414
  editErrorById.set(id, true);
14362
14415
  }
14363
- if (name === "Read") {
14364
- s.readInterception.readResults += 1;
14365
- const denyTemplate = DENY_TEMPLATES.find((t) => t.re.test(text));
14416
+ if (name === "Read" || name === "Skill") {
14417
+ const denyTemplate = DENY_TEMPLATES.find((t) => t.tool === name && t.re.test(text));
14366
14418
  if (denyTemplate !== void 0) {
14367
14419
  const path31 = readPathById.get(id) ?? "";
14368
14420
  const lastSlash = path31.lastIndexOf("/");
@@ -14377,6 +14429,9 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
14377
14429
  toolCalls: []
14378
14430
  });
14379
14431
  }
14432
+ }
14433
+ if (name === "Read") {
14434
+ s.readInterception.readResults += 1;
14380
14435
  if (bytes < READ_DIVERT_MAX_BYTES && READ_DIVERT_MARKER_RE.test(text)) {
14381
14436
  s.readInterception.divertedByMarker += 1;
14382
14437
  s.readInterception.divertedBytes += bytes;
@@ -14906,7 +14961,7 @@ function analyzeMcpCache() {
14906
14961
  const metrics = serverMetrics.get(toolName);
14907
14962
  metrics.callCount += 1;
14908
14963
  metrics.totalBytes += sizeBytes;
14909
- metrics.perCallEstimate = Math.floor(metrics.totalBytes / metrics.callCount / 3) + 1;
14964
+ metrics.perCallEstimate = estimateTokensFromLength(metrics.totalBytes / metrics.callCount);
14910
14965
  }
14911
14966
  return serverMetrics;
14912
14967
  }
@@ -15286,15 +15341,18 @@ function suppressedCell(row) {
15286
15341
  if (!row.suppressed) return "no";
15287
15342
  return row.suppressionPermanent ? "yes (permanent)" : "yes";
15288
15343
  }
15344
+ function efficacyCell(row) {
15345
+ const pct3 = row.efficacyPct === null ? "n/a" : `${row.efficacyPct}%`;
15346
+ return isSuppressionCategory(row.category) ? `${pct3} *` : pct3;
15347
+ }
15289
15348
  function printSummary(rows) {
15290
15349
  const w = (text) => {
15291
15350
  process.stdout.write(text);
15292
15351
  };
15293
- w(pad("category", 22) + pad("emitted", 9) + pad("acted-on", 10) + pad("efficacy", 10) + pad("suppressed", 17) + pad("manual+", 9) + pad("manual-", 9) + "spent\n");
15352
+ w(pad("category", 22) + pad("emitted", 9) + pad("acted-on", 10) + pad("efficacy", 12) + pad("suppressed", 17) + pad("manual+", 9) + pad("manual-", 9) + "spent\n");
15294
15353
  for (const row of rows) {
15295
- const pct3 = row.efficacyPct === null ? "n/a" : `${row.efficacyPct}%`;
15296
15354
  w(
15297
- pad(row.category, 22) + pad(String(row.emitted), 9) + pad(String(row.actedOn), 10) + pad(pct3, 10) + pad(suppressedCell(row), 17) + pad(String(row.manualEffective), 9) + pad(String(row.manualIneffective), 9) + formatSpentCell(row) + "\n"
15355
+ pad(row.category, 22) + pad(String(row.emitted), 9) + pad(String(row.actedOn), 10) + pad(efficacyCell(row), 12) + pad(suppressedCell(row), 17) + pad(String(row.manualEffective), 9) + pad(String(row.manualIneffective), 9) + formatSpentCell(row) + "\n"
15298
15356
  );
15299
15357
  }
15300
15358
  }
@@ -15335,6 +15393,11 @@ function runHintStatsCommand(opts = {}) {
15335
15393
  process.stdout.write("No hint emissions recorded yet \u2014 the zeros below are absence of data, not measured ineffectiveness.\n");
15336
15394
  }
15337
15395
  printSummary(rows);
15396
+ if (rows.some((r) => isSuppressionCategory(r.category) && r.emitted > 0)) {
15397
+ process.stdout.write(
15398
+ '\n* Scored on an absence: these hints ask the agent NOT to do something, so a window that expires with no re-read counts as compliance. Their percentage is not comparable with the unmarked rows, which only count when the agent runs the command the hint named. A high starred figure means "the warned-against read was not seen", not "this hint persuaded anyone".\n'
15399
+ );
15400
+ }
15338
15401
  const permanent = rows.filter((r) => r.suppressionPermanent);
15339
15402
  if (permanent.length > 0) {
15340
15403
  const names = permanent.map((r) => r.category).join(", ");
@@ -15667,7 +15730,7 @@ async function cmdMcpServe() {
15667
15730
  let StdioServerTransport;
15668
15731
  try {
15669
15732
  ;
15670
- ({ createMcpServer } = await import("./token-goat-chunk-3UBORD6Z.mjs"));
15733
+ ({ createMcpServer } = await import("./token-goat-chunk-DRL5USXI.mjs"));
15671
15734
  ({ StdioServerTransport } = await import("./token-goat-chunk-EVC4TOLE.mjs"));
15672
15735
  } catch (err2) {
15673
15736
  process.stderr.write(
@@ -15692,7 +15755,7 @@ async function cmdHook(event, opts) {
15692
15755
  if (typeof opts.harness === "string" && opts.harness.length > 0) {
15693
15756
  process.env[ENV_KEYS.HARNESS_OVERRIDE] = opts.harness;
15694
15757
  }
15695
- const { relay } = await import("./token-goat-chunk-HC5NMGPD.mjs");
15758
+ const { relay } = await import("./token-goat-chunk-GFBXHGFY.mjs");
15696
15759
  await relay(event);
15697
15760
  }
15698
15761
  async function cmdInstall(opts) {
@@ -16234,23 +16297,22 @@ async function cmdPdfLocate(file, pattern, opts) {
16234
16297
  if (opts.maxMatches !== void 0) locateOpts.maxMatches = requirePositiveInt("--max-matches", opts.maxMatches);
16235
16298
  if (opts.context !== void 0) locateOpts.context = requirePositiveInt("--context", opts.context);
16236
16299
  if (opts.pages !== void 0) locateOpts.pages = opts.pages;
16237
- const matches2 = await runPdfLocate(file, pattern, locateOpts);
16300
+ const { matches: matches2, truncated } = await runPdfLocate(file, pattern, locateOpts);
16238
16301
  const pages = matches2.map((m) => m.page);
16239
16302
  let printed;
16240
16303
  if (opts.json === true) {
16241
16304
  const fencedMatches = matches2.map((m) => ({ ...m, snippet: fenceFileFieldIfMatched(m.snippet) }));
16242
- printed = JSON.stringify({ file, pattern, matchCount: fencedMatches.length, pages, matches: fencedMatches }, null, 2);
16305
+ printed = JSON.stringify({ file, pattern, matchCount: fencedMatches.length, truncated, pages, matches: fencedMatches }, null, 2);
16243
16306
  out(printed);
16244
16307
  } else if (matches2.length === 0) {
16245
16308
  printed = "(no matches)";
16246
16309
  out(printed);
16247
16310
  } else {
16248
16311
  const lines = matches2.map((m) => `p${m.page}: ${m.snippet}`);
16249
- printed = fenceFileText(
16250
- `${lines.join("\n")}
16312
+ const summary = truncated ? `at least ${countNoun(matches2.length, "match", "matches")} across at least ${countNoun(pages.length, "page")}; scan stopped at --max-matches, raise it for more` : `${countNoun(matches2.length, "match", "matches")} across ${countNoun(pages.length, "page")}`;
16313
+ printed = fenceFileText(`${lines.join("\n")}
16251
16314
 
16252
- ${countNoun(matches2.length, "match", "matches")} across ${countNoun(pages.length, "page")}`
16253
- );
16315
+ ${summary}`);
16254
16316
  out(printed);
16255
16317
  }
16256
16318
  const fullSourceBytes = fileSizeOrZero(file);
@@ -16626,7 +16688,7 @@ function emitExtraFileArgsNote(command, first, extras, opts = {}) {
16626
16688
  }
16627
16689
  async function cmdCompress(opts) {
16628
16690
  try {
16629
- const bashRunner = await import("./token-goat-chunk-LLNA42NI.mjs");
16691
+ const bashRunner = await import("./token-goat-chunk-KFFAZ5DJ.mjs");
16630
16692
  if (opts.compress === false) {
16631
16693
  process.exitCode = bashRunner.runRaw(opts.cmd, parseTimeout(opts.timeout, bashRunner.DEFAULT_TIMEOUT_SECONDS));
16632
16694
  return;
@@ -25,7 +25,7 @@ import {
25
25
  runSkeleton,
26
26
  runSymbol,
27
27
  withPinnedReads
28
- } from "./token-goat-chunk-7A7SBE6R.mjs";
28
+ } from "./token-goat-chunk-4P6GTCMM.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-3XVGPLDS.mjs";
37
+ } from "./token-goat-chunk-U4FTM2SB.mjs";
38
38
  import {
39
39
  ENV_KEYS,
40
40
  VERSION,
@@ -48,7 +48,7 @@ import {
48
48
  recordStat,
49
49
  resolveProjectRoot,
50
50
  savedTokensFromBytes
51
- } from "./token-goat-chunk-KKHKXUVS.mjs";
51
+ } from "./token-goat-chunk-ZOKNDG6V.mjs";
52
52
  import "./token-goat-chunk-EEIDFMEM.mjs";
53
53
  import {
54
54
  init_define_import_meta_env
@@ -4,14 +4,14 @@ import {
4
4
  buildEvent,
5
5
  relay,
6
6
  relayInProcess
7
- } from "./token-goat-chunk-2VVXTAGD.mjs";
7
+ } from "./token-goat-chunk-ZLP6TCGN.mjs";
8
8
  import {
9
9
  MAX_STDIN_BYTES,
10
10
  readStdinJson
11
- } from "./token-goat-chunk-A4VBNWSL.mjs";
12
- import "./token-goat-chunk-3XVGPLDS.mjs";
13
- import "./token-goat-chunk-OKJX2JWW.mjs";
14
- import "./token-goat-chunk-KKHKXUVS.mjs";
11
+ } from "./token-goat-chunk-JO5JX72D.mjs";
12
+ import "./token-goat-chunk-U4FTM2SB.mjs";
13
+ import "./token-goat-chunk-HTJP6FHK.mjs";
14
+ import "./token-goat-chunk-ZOKNDG6V.mjs";
15
15
  import "./token-goat-chunk-EEIDFMEM.mjs";
16
16
  import "./token-goat-chunk-A37V4PBF.mjs";
17
17
  export {
@@ -2,7 +2,7 @@ import { createRequire as __cjsRequire } from 'node:module';
2
2
  const require = __cjsRequire(import.meta.url);
3
3
  import {
4
4
  detectHarness
5
- } from "./token-goat-chunk-KKHKXUVS.mjs";
5
+ } from "./token-goat-chunk-ZOKNDG6V.mjs";
6
6
  import {
7
7
  init_define_import_meta_env
8
8
  } from "./token-goat-chunk-A37V4PBF.mjs";
@@ -78,6 +78,7 @@ function canRunWrappedShell() {
78
78
  }
79
79
 
80
80
  export {
81
+ bashOutputCapBytes,
81
82
  deliveredOutputBytes,
82
83
  wrappedShell,
83
84
  canRunWrappedShell
@@ -9,7 +9,7 @@ import {
9
9
  isBlobStale,
10
10
  loadBlob,
11
11
  storeBlob
12
- } from "./token-goat-chunk-3XVGPLDS.mjs";
12
+ } from "./token-goat-chunk-U4FTM2SB.mjs";
13
13
  import {
14
14
  SYMBOL_BODY_CHAR_CAP,
15
15
  copilotCliMcpToolsDir,
@@ -24,7 +24,7 @@ import {
24
24
  resolveIndexPath,
25
25
  shortFingerprint,
26
26
  toDisplayPath
27
- } from "./token-goat-chunk-KKHKXUVS.mjs";
27
+ } from "./token-goat-chunk-ZOKNDG6V.mjs";
28
28
  import {
29
29
  registerReset
30
30
  } from "./token-goat-chunk-EEIDFMEM.mjs";
@@ -3,7 +3,7 @@ const require = __cjsRequire(import.meta.url);
3
3
  import {
4
4
  deliveredOutputBytes,
5
5
  wrappedShell
6
- } from "./token-goat-chunk-OKJX2JWW.mjs";
6
+ } from "./token-goat-chunk-HTJP6FHK.mjs";
7
7
  import {
8
8
  ToolFilter,
9
9
  capTokens,
@@ -15,7 +15,7 @@ import {
15
15
  recordStat,
16
16
  selectFilter,
17
17
  shlexSplit
18
- } from "./token-goat-chunk-KKHKXUVS.mjs";
18
+ } from "./token-goat-chunk-ZOKNDG6V.mjs";
19
19
  import "./token-goat-chunk-EEIDFMEM.mjs";
20
20
  import {
21
21
  init_define_import_meta_env