token-goat 2.9.4 → 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.
@@ -12,7 +12,7 @@ init_define_import_meta_env();
12
12
  import { createRequire } from "node:module";
13
13
  function resolveVersion() {
14
14
  if (true) {
15
- return "2.9.4";
15
+ return "2.9.5";
16
16
  }
17
17
  const require2 = createRequire(import.meta.url);
18
18
  const pkg = require2("../package.json");
@@ -2273,8 +2273,16 @@ var CONFIG_DEFAULTS = {
2273
2273
  // existing users -- see the reread_deny/reread_deny_min_bytes fix's commit message.
2274
2274
  reread_deny_min_bytes: 51200,
2275
2275
  stable_doc_compacts: true,
2276
- // Off until measured. This one rewrites what the model reads on a FIRST look at a source file, where -- unlike every re-read mechanism beside it -- the reader has no prior copy to notice an omission against. Its restore rate and edit-error delta cannot be observed until it has run, so the honest default is the one that changes nothing.
2277
- fold_code_bodies: false,
2276
+ // On. It has now run. The gate that finds the spans answered only from the index, and the index carried the shipping parser stamp on 46 of 17,952 files, so the lever was very nearly dead in practice: a disk parse of the delivered file is now the fallback, worth +3.0 points of withheld bytes on shell reads with no index at all. The cost side is the one this comment used to call unobservable, and it is observable: joining folds to later reads of the folded symbol scores 62.3% recovery, but the same window measured backwards scores 55.8% and a shuffled pairing scores 24.3%, so the excess attributable to the fold is 6.6 points rather than 62. Unlike every re-read mechanism beside it this rewrites a FIRST look, where the reader has no prior copy to notice an omission against, which is why the notice names the symbol and the command that returns it verbatim.
2277
+ fold_code_bodies: true,
2278
+ // On, and separate from the body fold above because the two do not carry the same risk. A body fold needs symbol spans from the index, so it can cut at the wrong line when the index is stale, and it hides the implementation an agent came to read. A comment fold reads the block boundaries off the delivered text itself, so it cannot be stale and works on the first read of a file the indexer has never seen, which is exactly the surface nothing else here reaches. It keeps the opening two lines of a block of 12 or more, so the summary sentence a reader navigates by survives and only the elaboration is replaced, and it alters the text of no line it keeps, its notice naming the absolute range removed so the recall is exact. Measured across this project's own 259 source files it removes 8.07% of the delivered bytes over 420 folds in 170 files. The nearest published measurement is stronger and cruder: removing docstrings outright cost 3 points of resolution rate on SWE-bench Verified for 22% of the tokens (arXiv:2606.01326), and keeping the opening summary is the gentler trade on that curve.
2279
+ fold_comment_blocks: true,
2280
+ // On. It has now run: measured over 2,032 real document reads it removes 43.4% of the pool and 57.4% of the reads it touches, keeping every heading, table, block quote and fenced line, and replacing only the tail of a paragraph whose opening sentence is already a complete one. The recall it needs is a ranged Read of the single line named in the notice, which costs one call and is printed at the point of the cut rather than left for the reader to work out. The project-config lock below stays regardless of this default: a repository still cannot set this key, so the choice to fold is the reader's environment and never the code being read.
2281
+ fold_prose_paragraphs: true,
2282
+ // On. Fires on an untargeted (no offset/limit) Read of a markdown document at least 8,000 bytes with at least 6 headings, replacing the delivered body with a heading tree plus the document preamble when that replacement is meaningfully smaller. Built from the delivered text itself, never the index, so it works on a document the indexer has never seen. Measured over 5,104 real session transcripts (13,870 Read deliveries, 130,249,204 bytes): untargeted markdown reads with >=6 headings at this 8,000-byte floor withhold 41.03% of all Read bytes, within 1.8 points of the best floor tried (2,000 B) while firing far less often on small documents where the interruption is least worth it.
2283
+ outline_large_documents: true,
2284
+ // On. The source-code sibling of outline_large_documents directly above, and gated the same way: an untargeted (no offset/limit) Read of a tree-sitter language at least 12,000 bytes with at least 8 symbols is replaced by its structural skeleton, the preamble plus one declaration line per symbol, with each withheld run named and pointed at the command that returns it. Symbols come from tree-sitter over the delivered text, never the index and never the regex extractors, so a partial symbol list turns the fold off rather than shipping a skeleton missing declarations nothing signals. Measured over 5,104 real session transcripts (130,325,670 delivered Read bytes): 558 reads clear this floor, and the fold withholds 11,241,796 B, 8.63% of all Read bytes.
2285
+ skeleton_large_sources: true,
2278
2286
  truncated_read_min_lines: 200,
2279
2287
  protect_recent_reads: 4,
2280
2288
  warn_unbalanced_shell_quoting: true,
@@ -2611,6 +2619,14 @@ var PROJECT_LOCKED_SECTIONS = [
2611
2619
  var PROJECT_LOCKED_KEYS = [
2612
2620
  // A repository must not be able to decide how much of its own source an agent gets to see. Turning this on folds function bodies out of every Read of this project's files, so a checked-in `.token-goat.toml` setting it true would shrink what a reviewing agent is shown of the very code it came to review -- and the fold is silent about intent, so it reads as normal output. The user's own global config and TOKEN_GOAT_FOLD_CODE_BODIES still set it freely; only the project-supplied layer is refused.
2613
2621
  "hints.fold_code_bodies",
2622
+ // Same reasoning as the body fold above, on the comments rather than the code: a checked-in project file must not be able to fold a repository's own explanatory comments out of what a reviewing agent is shown, which is precisely where an intent that disagrees with the code would be written down. The user's global config and TOKEN_GOAT_FOLD_COMMENT_BLOCKS still set it freely.
2623
+ "hints.fold_comment_blocks",
2624
+ // Same reasoning one document over: a repository must not be able to fold its own README or changelog out of what a reviewing agent is shown. The user's global config and TOKEN_GOAT_FOLD_PROSE_PARAGRAPHS still set it freely.
2625
+ "hints.fold_prose_paragraphs",
2626
+ // Same reasoning again: a repository must not be able to hide its own documentation's structure from a reviewing agent by disabling the heading-tree replacement, nor -- more to the point here -- by leaving it on to shrink what a reviewing agent sees of a doc the repo itself ships. The user's global config and TOKEN_GOAT_OUTLINE_LARGE_DOCUMENTS still set it freely.
2627
+ "hints.outline_large_documents",
2628
+ // Same reasoning one file type over: a repository must not be able to decide, from its own checked-in config, how much of its source a reviewing agent is shown -- neither by turning the skeleton off to bury a declaration in a wall of bodies, nor by leaving it on to withhold the bodies themselves. The user's global config and TOKEN_GOAT_SKELETON_LARGE_SOURCES still set it freely.
2629
+ "hints.skeleton_large_sources",
2614
2630
  "image_shrink.max_image_pixels",
2615
2631
  "indexing.cross_project_symbols",
2616
2632
  "worker.blocked_roots"
@@ -3024,6 +3040,10 @@ function _buildConfig(raw, projectRaw = {}) {
3024
3040
  hi.reread_deny_min_bytes = validatedIntWithLegacySentinel(hi_raw["reread_deny_min_bytes"], hi.reread_deny_min_bytes, 2048, ...boundsOf("hints.reread_deny_min_bytes"));
3025
3041
  hi.stable_doc_compacts = validatedBool(hi_raw["stable_doc_compacts"], hi.stable_doc_compacts);
3026
3042
  hi.fold_code_bodies = validatedBool(hi_raw["fold_code_bodies"], hi.fold_code_bodies);
3043
+ hi.fold_comment_blocks = validatedBool(hi_raw["fold_comment_blocks"], hi.fold_comment_blocks);
3044
+ hi.fold_prose_paragraphs = validatedBool(hi_raw["fold_prose_paragraphs"], hi.fold_prose_paragraphs);
3045
+ hi.outline_large_documents = validatedBool(hi_raw["outline_large_documents"], hi.outline_large_documents);
3046
+ hi.skeleton_large_sources = validatedBool(hi_raw["skeleton_large_sources"], hi.skeleton_large_sources);
3027
3047
  hi.truncated_read_min_lines = validatedInt(hi_raw["truncated_read_min_lines"], hi.truncated_read_min_lines, ...boundsOf("hints.truncated_read_min_lines"));
3028
3048
  hi.protect_recent_reads = validatedInt(hi_raw["protect_recent_reads"], hi.protect_recent_reads, ...boundsOf("hints.protect_recent_reads"));
3029
3049
  hi.warn_unbalanced_shell_quoting = validatedBool(hi_raw["warn_unbalanced_shell_quoting"], hi.warn_unbalanced_shell_quoting);
@@ -3053,6 +3073,10 @@ function _buildConfig(raw, projectRaw = {}) {
3053
3073
  hi.git_hint_max_ms = envInt("TOKEN_GOAT_GIT_HINT_MAX_MS", hi.git_hint_max_ms, ...boundsOf("hints.git_hint_max_ms"));
3054
3074
  hi.stable_doc_compacts = envBool("TOKEN_GOAT_STABLE_DOC_COMPACTS", hi.stable_doc_compacts);
3055
3075
  hi.fold_code_bodies = envBool("TOKEN_GOAT_FOLD_CODE_BODIES", hi.fold_code_bodies);
3076
+ hi.fold_comment_blocks = envBool("TOKEN_GOAT_FOLD_COMMENT_BLOCKS", hi.fold_comment_blocks);
3077
+ hi.fold_prose_paragraphs = envBool("TOKEN_GOAT_FOLD_PROSE_PARAGRAPHS", hi.fold_prose_paragraphs);
3078
+ hi.outline_large_documents = envBool("TOKEN_GOAT_OUTLINE_LARGE_DOCUMENTS", hi.outline_large_documents);
3079
+ hi.skeleton_large_sources = envBool("TOKEN_GOAT_SKELETON_LARGE_SOURCES", hi.skeleton_large_sources);
3056
3080
  hi.context_threshold_advisory = envBool("TOKEN_GOAT_CONTEXT_THRESHOLD_ADVISORY", hi.context_threshold_advisory);
3057
3081
  hi.pre_skill_advisory = envBool("TOKEN_GOAT_PRE_SKILL_ADVISORY", hi.pre_skill_advisory);
3058
3082
  hi.quiet_hours = envStr("TOKEN_GOAT_QUIET_HOURS", hi.quiet_hours);
@@ -3221,6 +3245,10 @@ var CONFIG_KEY_ENV_OVERRIDES = {
3221
3245
  "hints.git_hint_max_ms": ["TOKEN_GOAT_GIT_HINT_MAX_MS"],
3222
3246
  "hints.stable_doc_compacts": ["TOKEN_GOAT_STABLE_DOC_COMPACTS"],
3223
3247
  "hints.fold_code_bodies": ["TOKEN_GOAT_FOLD_CODE_BODIES"],
3248
+ "hints.fold_comment_blocks": ["TOKEN_GOAT_FOLD_COMMENT_BLOCKS"],
3249
+ "hints.fold_prose_paragraphs": ["TOKEN_GOAT_FOLD_PROSE_PARAGRAPHS"],
3250
+ "hints.outline_large_documents": ["TOKEN_GOAT_OUTLINE_LARGE_DOCUMENTS"],
3251
+ "hints.skeleton_large_sources": ["TOKEN_GOAT_SKELETON_LARGE_SOURCES"],
3224
3252
  "hints.context_threshold_advisory": ["TOKEN_GOAT_CONTEXT_THRESHOLD_ADVISORY"],
3225
3253
  "hints.pre_skill_advisory": ["TOKEN_GOAT_PRE_SKILL_ADVISORY"],
3226
3254
  "hints.quiet_hours": ["TOKEN_GOAT_QUIET_HOURS"],
@@ -3360,6 +3388,10 @@ function saveConfig(config) {
3360
3388
  reread_deny_min_bytes: config.hints.reread_deny_min_bytes,
3361
3389
  stable_doc_compacts: config.hints.stable_doc_compacts,
3362
3390
  fold_code_bodies: config.hints.fold_code_bodies,
3391
+ fold_comment_blocks: config.hints.fold_comment_blocks,
3392
+ fold_prose_paragraphs: config.hints.fold_prose_paragraphs,
3393
+ outline_large_documents: config.hints.outline_large_documents,
3394
+ skeleton_large_sources: config.hints.skeleton_large_sources,
3363
3395
  truncated_read_min_lines: config.hints.truncated_read_min_lines,
3364
3396
  protect_recent_reads: config.hints.protect_recent_reads,
3365
3397
  prompt_triggers: config.hints.prompt_triggers,
@@ -4293,7 +4325,7 @@ function makeLineSymbol(filePath, name, kind, line, sig, parent, lines, style) {
4293
4325
  parent: parent ?? ""
4294
4326
  };
4295
4327
  }
4296
- function makeSymbolEmitter(symbols, sections, seen, filePath, maxSymbols = 500, maxHeadingLen = 120) {
4328
+ function makeSymbolEmitter(symbols, sections, seen, filePath, maxSymbols = 1e4, maxHeadingLen = 120) {
4297
4329
  return function emit(name, kind, line) {
4298
4330
  if (!name || name.length > maxHeadingLen) return;
4299
4331
  if (symbols.length >= maxSymbols) return;
@@ -5996,7 +6028,8 @@ var _KIND_GROUPS = [
5996
6028
  members: /* @__PURE__ */ new Set([
5997
6029
  "skill_load",
5998
6030
  "skill_oversized_first_load",
5999
- "skill_compact_inlined"
6031
+ "skill_compact_inlined",
6032
+ "skill_heading_tree_inlined"
6000
6033
  ])
6001
6034
  },
6002
6035
  // SOURCE_CONTENT: real rewrites of tool output that remove real bytes (agent report compaction, Grep fold, browser tab dedup, bash/content compression and the handoff pair). The by-source table has shown a 'content' row since the source was added, but the by-kind table had no member set for it, so every one of these kinds printed under 'Other'. The taskoutput: prefix branch in _kindGroupLabel routes here too.
@@ -6011,6 +6044,8 @@ var _KIND_GROUPS = [
6011
6044
  "grep:fold",
6012
6045
  "read:served_elide",
6013
6046
  "read:body_fold",
6047
+ "read:markdown_outline",
6048
+ "read:source_skeleton",
6014
6049
  "handoff_create",
6015
6050
  "handoff_resolve",
6016
6051
  "plan_echo_collapse"
@@ -6326,6 +6361,10 @@ function renderStats(stats, opts) {
6326
6361
 
6327
6362
  // src/stats.ts
6328
6363
  var HARNESS_UNRECORDED = "unrecorded (pre-2.8.1)";
6364
+ var PRICING_VERSION_UNRECORDED = "unrecorded (pre-tg_version column)";
6365
+ function hasMixedPricingEras(summary) {
6366
+ return Object.keys(summary.by_pricing_version).length > 1;
6367
+ }
6329
6368
  var SOURCE_IMAGE = "image";
6330
6369
  var SOURCE_HINT = "hint";
6331
6370
  var SOURCE_READ = "read";
@@ -6431,6 +6470,8 @@ var KIND_TO_SOURCE = {
6431
6470
  skill_oversized_first_load: SOURCE_SKILL,
6432
6471
  // Cold first load of an oversized skill where preSkillHandler inlined the compact slice in its reply instead of pointing at `skill-body --compact`. Unlike its skill_oversized_first_load sibling (event-only, 0 bytes -- the pointer deny saves nothing by itself, the follow-up command does) this one records real savings: the full body never landed, the slice did, so bytesSaved is body minus slice.
6433
6472
  skill_compact_inlined: SOURCE_SKILL,
6473
+ // Cold first load of an oversized skill with no compact marker at all, where preSkillHandler inlined a heading tree in its reply instead of letting the whole body fall through. Same shape as skill_compact_inlined: real savings, bytesSaved is body minus the rendered tree.
6474
+ skill_heading_tree_inlined: SOURCE_SKILL,
6434
6475
  secret_redacted: SOURCE_OTHER,
6435
6476
  // Fail-soft diagnostic counters from hooks_edit.ts: they record that a side task threw, never a byte saving, so "other" is the right home. Listed explicitly rather than left to kindToSource()'s fallback so the registration guard can tell a deliberate placement from an unregistered kind.
6436
6477
  dirty_queue_append_failed: SOURCE_OTHER,
@@ -6458,6 +6499,10 @@ var KIND_TO_SOURCE = {
6458
6499
  "read:served_elide": SOURCE_CONTENT,
6459
6500
  // Same bucket and same reasoning as read:served_elide directly above: a rewrite of a Read that did happen, with real bytes removed, not an advisory about whether to read at all.
6460
6501
  "read:body_fold": SOURCE_CONTENT,
6502
+ // Same bucket and same reasoning as read:body_fold directly above: a coarser sibling rewrite of a large untargeted markdown Read (hooks_read.ts foldMarkdownOutline) that replaces the body with a heading tree plus preamble, with real bytes removed, not an advisory about whether to read at all.
6503
+ "read:markdown_outline": SOURCE_CONTENT,
6504
+ // Same bucket and same reasoning as read:markdown_outline directly above, on source instead of prose: the structural-skeleton replacement of a large untargeted source Read (hooks_read.ts foldSourceSkeleton), with real bytes removed, not an advisory about whether to read at all.
6505
+ "read:source_skeleton": SOURCE_CONTENT,
6461
6506
  content_retrieve: SOURCE_CONTENT,
6462
6507
  handoff_create: SOURCE_CONTENT,
6463
6508
  handoff_resolve: SOURCE_CONTENT
@@ -6741,12 +6786,21 @@ function summarize(windowDays = 30, testDb, homeDir) {
6741
6786
  const byKind = {};
6742
6787
  const byDay = {};
6743
6788
  const byHarness = {};
6789
+ const byPricingVersion = {};
6744
6790
  let totalEvents = 0;
6745
6791
  let totalBytes = 0;
6746
6792
  let totalTokens = 0;
6747
6793
  const db = testDb ?? getGlobalDb(homeDir);
6748
6794
  const hasHarness = statsHasHarnessColumn(db);
6749
- const cols = hasHarness ? "ts, kind, bytes_saved, tokens_saved, harness" : "ts, kind, bytes_saved, tokens_saved";
6795
+ const hasVersion = statsHasVersionColumn(db);
6796
+ const cols = [
6797
+ "ts",
6798
+ "kind",
6799
+ "bytes_saved",
6800
+ "tokens_saved",
6801
+ ...hasHarness ? ["harness"] : [],
6802
+ ...hasVersion ? ["tg_version"] : []
6803
+ ].join(", ");
6750
6804
  const query = sinceTs !== null ? `SELECT ${cols} FROM stats WHERE ts >= ? ORDER BY ts DESC` : `SELECT ${cols} FROM stats ORDER BY ts DESC`;
6751
6805
  const stmt = db.prepare(query);
6752
6806
  const rows = sinceTs !== null ? stmt.all(sinceTs) : stmt.all();
@@ -6780,6 +6834,11 @@ function summarize(windowDays = 30, testDb, homeDir) {
6780
6834
  byHarness[harness] = zeroBucket();
6781
6835
  }
6782
6836
  incBucket(byHarness[harness], bytesSaved, tokensSaved);
6837
+ const pricingVersion = row.tg_version || PRICING_VERSION_UNRECORDED;
6838
+ if (!byPricingVersion[pricingVersion]) {
6839
+ byPricingVersion[pricingVersion] = zeroBucket();
6840
+ }
6841
+ incBucket(byPricingVersion[pricingVersion], bytesSaved, tokensSaved);
6783
6842
  }
6784
6843
  const bySourceDict = {};
6785
6844
  for (const [kind, bucket] of Object.entries(byKind)) {
@@ -6817,6 +6876,7 @@ function summarize(windowDays = 30, testDb, homeDir) {
6817
6876
  by_project: byProjectList,
6818
6877
  by_source: bySourceDict,
6819
6878
  by_harness: byHarness,
6879
+ by_pricing_version: byPricingVersion,
6820
6880
  counts,
6821
6881
  by_command: Object.entries(byCommandDict).map(([command, bucket]) => ({ ...bucket, command })).filter((r) => r.events > 0),
6822
6882
  window_days: windowDays
@@ -6828,6 +6888,15 @@ function _totalsLines(summary) {
6828
6888
  `Total events: ${summary.total_events}`,
6829
6889
  `Bytes saved: ${fmtBytes(summary.total_bytes_saved)}`,
6830
6890
  `Tokens saved: ${summary.total_tokens_saved}`,
6891
+ // Disclosure, not a correction: `total_tokens_saved` above sums rows written under whichever
6892
+ // pricing formula was live when each was recorded, and `tg_version` cannot be read back into
6893
+ // "which formula" for a row from before this disclosure existed (PRICING_VERSION_UNRECORDED is
6894
+ // the overwhelming majority of all-time rows). Excluding those rows from the headline would
6895
+ // discard nearly the whole figure rather than fix it, so the honest move is to keep the sum and
6896
+ // say plainly that it spans more than one era, not to quietly present a mixed total as single-formula.
6897
+ ...hasMixedPricingEras(summary) ? [
6898
+ `Pricing note: totals mix ${countNoun(Object.keys(summary.by_pricing_version).length, "tg_version era")} (${countNoun(summary.by_pricing_version[PRICING_VERSION_UNRECORDED]?.events ?? 0, "row")} unrecorded); see 'token-goat stats --json' -> by_pricing_version for the breakdown`
6899
+ ] : [],
6831
6900
  // Printed on its own line, below the token total and never inside it, because it counts
6832
6901
  // placeholders rather than tokens. Omitted entirely when nothing was redacted, so the line is
6833
6902
  // information rather than a permanent zero. See COUNT_ONLY_KINDS.
@@ -7268,7 +7337,7 @@ function emitRewrite(updatedOutput, detail, savings, redaction = "count-here") {
7268
7337
  }
7269
7338
  if (savings !== void 0) {
7270
7339
  const bytesSaved = savings.originalBytes - Buffer.byteLength(updatedOutput, "utf-8");
7271
- if (bytesSaved > 0) recordStat(savings.kind, bytesSaved, savedTokensFromBytes(bytesSaved));
7340
+ if (bytesSaved > 0) recordStat(savings.kind, bytesSaved, savedTokensFromBytes(bytesSaved), void 0, savings.detail);
7272
7341
  }
7273
7342
  return { hookType: "rewriteOutput", updatedOutput };
7274
7343
  }
@@ -9769,11 +9838,46 @@ var DEFAULT_MAX_BYTES = 64 * 1024;
9769
9838
  var MAX_INSPECT_BYTES = 2 * 1024 * 1024;
9770
9839
  var DEFAULT_MAX_INPUT_BYTES = 500 * 1024;
9771
9840
  var FALLBACK_MAX_LINE_CHARS = 400;
9841
+ var LONG_LINE_MAX_CHARS = 1e3;
9842
+ var ELIDED_MARKER_RE = /… \[\d+ chars elided\]/;
9772
9843
  function getMaxInputBytes() {
9773
9844
  const raw = process.env["TOKEN_GOAT_FILTER_MAX_BYTES"];
9774
9845
  const v = raw ? Number.parseInt(raw, 10) : 0;
9775
9846
  return Number.isFinite(v) && v > 0 ? v : DEFAULT_MAX_INPUT_BYTES;
9776
9847
  }
9848
+ function utf8SafeEnd(buf, n) {
9849
+ if (n >= buf.length) return buf.length;
9850
+ let end = n;
9851
+ while (end > 0 && (buf[end] & 192) === 128) end--;
9852
+ return end;
9853
+ }
9854
+ function clampKeepingEnds(text, maxBytes) {
9855
+ const buf = Buffer.from(text, "utf8");
9856
+ if (buf.length <= maxBytes) return null;
9857
+ const lines = text.split("\n");
9858
+ const budget = maxBytes - Buffer.byteLength(`... [${lines.length} more lines elided by token-goat]
9859
+ `, "utf8");
9860
+ const half = Math.floor(budget / 2);
9861
+ let headEnd = 0;
9862
+ for (let used = 0; headEnd < lines.length; headEnd++) {
9863
+ const n = Buffer.byteLength(lines[headEnd], "utf8") + 1;
9864
+ if (used + n > half) break;
9865
+ used += n;
9866
+ }
9867
+ let tailStart = lines.length;
9868
+ for (let used = 0; tailStart > headEnd; tailStart--) {
9869
+ const n = Buffer.byteLength(lines[tailStart - 1], "utf8") + 1;
9870
+ if (used + n > half) break;
9871
+ used += n;
9872
+ }
9873
+ if (headEnd === 0 && tailStart === lines.length) return buf.subarray(0, utf8SafeEnd(buf, maxBytes)).toString("utf8");
9874
+ const elided = tailStart - headEnd;
9875
+ return [
9876
+ ...lines.slice(0, headEnd),
9877
+ `... [${elided} more line${elided === 1 ? "" : "s"} elided by token-goat]`,
9878
+ ...lines.slice(tailStart)
9879
+ ].join("\n");
9880
+ }
9777
9881
  function compressionMarker(filter, pct) {
9778
9882
  return `
9779
9883
  [token-goat: ${filter} filter -${Math.round(pct)}%; disable via TOKEN_GOAT_BASH_COMPRESS]`;
@@ -9785,6 +9889,7 @@ ${stderr.replace(/\s+$/, "")}`;
9785
9889
  return stdout.trim() ? stdout.replace(/\s+$/, "") : stderr.replace(/\s+$/, "");
9786
9890
  }
9787
9891
  var ERROR_SIGNAL_RE = /error:|Error:|ERROR|FAILED|failed|fatal:|Traceback|exception:|Exception:|AssertionError|assert |panic:/i;
9892
+ var TABLE_ROW_ANOMALY_RE = /\b(?:CrashLoopBackOff|ImagePullBackOff|ErrImagePull|CreateContainerError|CreateContainerConfigError|InvalidImageName|RunContainerError|OOMKilled|Evicted|Terminating|ContainerCreating|PodInitializing|NotReady|SchedulingDisabled|Unschedulable|Pending|Failed|Error|Unknown|Unhealthy|DEGRADED|UNAVAILABLE|STOPPED|STOPPING|TERMINATED|TERMINATING|FAILED|ROLLBACK_COMPLETE|ROLLBACK_FAILED|CREATE_FAILED|UPDATE_FAILED|DELETE_FAILED)\b/;
9788
9893
  var TIMESTAMP_PREFIX_RE = /^\[?\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?\]?\s*|^\d{2}:\d{2}:\d{2}(?:\.\d+)?\s+/;
9789
9894
  var REDIRECT_TOKEN_RE = /^(\d*)(>>?|<<?).*$|^&>$|^>&.*$/;
9790
9895
  function maskQuotedSpans(cmd) {
@@ -9947,18 +10052,28 @@ function truncateMiddleSmart(lines, maxLines, opts = {}) {
9947
10052
  const total = lines.length;
9948
10053
  const effHead = Math.min(headKeep, Math.floor(total / 4));
9949
10054
  const effTail = Math.min(tailKeep, Math.floor(total / 4));
10055
+ const chosenErrors = errorIndices.length <= maxErrorLines ? errorIndices : [
10056
+ ...errorIndices.slice(0, Math.ceil(maxErrorLines / 2)),
10057
+ ...errorIndices.slice(errorIndices.length - Math.floor(maxErrorLines / 2))
10058
+ ];
10059
+ const budgetForMiddle = Math.max(0, maxLines - effHead - effTail);
10060
+ const half = Math.ceil(chosenErrors.length / 2);
10061
+ const front = chosenErrors.slice(0, half);
10062
+ const back = chosenErrors.slice(half).reverse();
10063
+ const visitOrder = [];
10064
+ for (let k = 0; k < Math.max(front.length, back.length); k++) {
10065
+ if (k < front.length) visitOrder.push(front[k]);
10066
+ if (k < back.length) visitOrder.push(back[k]);
10067
+ }
9950
10068
  const middle = /* @__PURE__ */ new Set();
9951
- for (let k = 0; k < errorIndices.length && k < maxErrorLines; k++) {
9952
- const ei = errorIndices[k];
10069
+ outer: for (const ei of visitOrder) {
9953
10070
  for (let ci = Math.max(0, ei - errorContext); ci < Math.min(total, ei + errorContext + 1); ci++) {
10071
+ if (ci < effHead || ci >= total - effTail) continue;
10072
+ if (middle.size >= budgetForMiddle) break outer;
9954
10073
  middle.add(ci);
9955
10074
  }
9956
10075
  }
9957
- for (let i = 0; i < effHead; i++) middle.delete(i);
9958
- for (let i = total - effTail; i < total; i++) middle.delete(i);
9959
- const budgetForMiddle = Math.max(0, maxLines - effHead - effTail);
9960
- let sortedMiddle = Array.from(middle).sort((a, b) => a - b);
9961
- if (sortedMiddle.length > budgetForMiddle) sortedMiddle = sortedMiddle.slice(0, budgetForMiddle);
10076
+ const sortedMiddle = Array.from(middle).sort((a, b) => a - b);
9962
10077
  const result = [];
9963
10078
  const appendSection = (indices) => {
9964
10079
  for (let pos = 0; pos < indices.length; pos++) {
@@ -9989,23 +10104,19 @@ function truncateMiddleSmart(lines, maxLines, opts = {}) {
9989
10104
  function capBytes(text, maxBytes) {
9990
10105
  const encoded = Buffer.from(text, "utf8");
9991
10106
  if (encoded.length <= maxBytes) return text;
9992
- const marker = `
9993
- ... [${encoded.length - maxBytes} bytes elided by token-goat]`;
9994
- const budget = maxBytes - Buffer.byteLength(marker, "utf8");
9995
- if (budget <= 0) return marker.trim();
9996
- let slice = encoded.subarray(0, budget);
9997
- const nl = slice.lastIndexOf(10);
9998
- if (nl > budget / 2) slice = slice.subarray(0, nl);
9999
- while (slice.length > 0 && (encoded[slice.length] & 192) === 128) {
10000
- slice = slice.subarray(0, slice.length - 1);
10001
- }
10002
- return slice.toString("utf8") + marker;
10107
+ const widestMarker = `
10108
+ ... [${encoded.length} bytes elided by token-goat]`;
10109
+ const budget = maxBytes - Buffer.byteLength(widestMarker, "utf8");
10110
+ if (budget <= 0) return widestMarker.trim();
10111
+ const kept = clampKeepingEnds(text, budget) ?? text;
10112
+ return `${kept}
10113
+ ... [${encoded.length - Buffer.byteLength(kept, "utf8")} bytes elided by token-goat]`;
10003
10114
  }
10004
10115
  function capTokens(text, maxTokens) {
10005
10116
  const clean = stripAnsiCodes(text);
10006
10117
  if (clean.length / 3.5 <= maxTokens) return text;
10007
10118
  const maxBytes = Math.floor(maxTokens * 3.5);
10008
- let truncated = capBytes(clean, maxBytes);
10119
+ let truncated = clampKeepingEnds(clean, maxBytes) ?? clean;
10009
10120
  if (!truncated.includes("[token-goat: output capped at")) {
10010
10121
  truncated = truncated.replace(BYTES_ELIDED_MARKER_RE, "");
10011
10122
  truncated += `
@@ -10025,9 +10136,19 @@ function truncateTableRows(text, maxRows, hint) {
10025
10136
  const lines = text.split("\n");
10026
10137
  const nonEmpty = lines.filter((l) => l.trim());
10027
10138
  if (nonEmpty.length <= maxRows + 1) return text;
10028
- const elided = nonEmpty.length - maxRows - 1;
10029
- return `${nonEmpty.slice(0, maxRows + 1).join("\n")}
10030
- [token-goat: ${elided} more rows; ${hint}]`;
10139
+ const header = nonEmpty[0];
10140
+ const rows = nonEmpty.slice(1);
10141
+ const wanted = /* @__PURE__ */ new Set();
10142
+ for (let i = 0; i < rows.length && wanted.size < maxRows; i++) {
10143
+ if (TABLE_ROW_ANOMALY_RE.test(rows[i])) wanted.add(i);
10144
+ }
10145
+ const anomalies = wanted.size;
10146
+ for (let i = 0; i < rows.length && wanted.size < maxRows; i++) wanted.add(i);
10147
+ const kept = [...wanted].sort((a, b) => a - b);
10148
+ const elided = rows.length - kept.length;
10149
+ const note = anomalies ? `[token-goat: ${elided} more rows; ${anomalies} row(s) kept for a not-ready status, the rest from the top; ${hint}]` : `[token-goat: ${elided} more rows; ${hint}]`;
10150
+ return `${[header, ...kept.map((i) => rows[i])].join("\n")}
10151
+ ${note}`;
10031
10152
  }
10032
10153
  function trimRepeatedPrefix(lines, pattern, keep) {
10033
10154
  const out = [];
@@ -10315,6 +10436,7 @@ function shlexSplit(cmd) {
10315
10436
  function capLongLines(lines, maxChars = FALLBACK_MAX_LINE_CHARS) {
10316
10437
  return lines.map((line) => {
10317
10438
  if (line.length <= maxChars) return line;
10439
+ if (ELIDED_MARKER_RE.test(line)) return line;
10318
10440
  let cut = maxChars;
10319
10441
  const high = line.charCodeAt(cut - 1);
10320
10442
  const low = line.charCodeAt(cut);
@@ -10511,7 +10633,7 @@ function isRewriteWorthwhile({
10511
10633
  return bytesSaved - noticeBytes >= minNetSavingsBytes;
10512
10634
  }
10513
10635
  function compressedTokensSaved(bytesSaved) {
10514
- return bytesSaved <= 0 ? 0 : Math.max(1, Math.floor(bytesSaved / 3) + 1);
10636
+ return bytesSaved <= 0 ? 0 : Math.max(1, savedTokensFromBytes(bytesSaved));
10515
10637
  }
10516
10638
  var CompressedOutput = class {
10517
10639
  constructor(text, originalBytes, compressedBytes, filterName, exitCode = 0, notes = []) {
@@ -10532,7 +10654,7 @@ var CompressedOutput = class {
10532
10654
  get bytesSaved() {
10533
10655
  return Math.max(0, this.originalBytes - this.compressedBytes);
10534
10656
  }
10535
- /** Estimated token savings (`n // 3 + 1`, matching `estimateTokens`). */
10657
+ /** Estimated token savings, matching `compressedTokensSaved` (bytes/4, the codebase-wide pricing constant). */
10536
10658
  get tokensSaved() {
10537
10659
  return compressedTokensSaved(this.bytesSaved);
10538
10660
  }
@@ -10643,19 +10765,19 @@ var ToolFilter = class {
10643
10765
  * Filters that handle errors structurally (pytest, cargo) override this
10644
10766
  * directly and leave `errorPassthrough` false.
10645
10767
  */
10646
- compress(stdout, stderr, exitCode, argv) {
10768
+ compress(stdout, stderr, exitCode, argv, ctx = {}) {
10647
10769
  if (this.errorPassthrough) {
10648
10770
  const err = preserveStderrOnError(stdout, stderr, exitCode);
10649
10771
  if (err !== null) return err;
10650
10772
  }
10651
- return this.compressBody(stdout, stderr, exitCode, argv);
10773
+ return this.compressBody(stdout, stderr, exitCode, argv, ctx);
10652
10774
  }
10653
10775
  /**
10654
10776
  * Inner compression logic, called after the error-passthrough guard.
10655
10777
  * Default is a passthrough that joins the two streams — useful when the only
10656
10778
  * compression is the ANSI / progress strip `apply` already performed.
10657
10779
  */
10658
- compressBody(stdout, stderr, _exitCode, _argv) {
10780
+ compressBody(stdout, stderr, _exitCode, _argv, _ctx = {}) {
10659
10781
  if (stderr && stdout) return `${stdout.replace(/\s+$/, "")}
10660
10782
  ---
10661
10783
  ${stderr.replace(/\s+$/, "")}`;
@@ -10677,14 +10799,16 @@ ${stderr.replace(/\s+$/, "")}`;
10677
10799
  const notes = [];
10678
10800
  const soBytes = Buffer.from(so, "utf8");
10679
10801
  const seBytes = Buffer.from(se, "utf8");
10680
- if (soBytes.length > maxInput) {
10681
- so = soBytes.subarray(0, maxInput).toString("utf8");
10682
- notes.push(`input truncated at ${Math.floor(maxInput / 1024)}KB (TOKEN_GOAT_FILTER_MAX_BYTES)`);
10802
+ const soClamped = clampKeepingEnds(so, maxInput);
10803
+ const seClamped = clampKeepingEnds(se, maxInput);
10804
+ if (soClamped !== null) {
10805
+ so = soClamped;
10806
+ notes.push(`input over ${Math.floor(maxInput / 1024)}KB: kept both ends (TOKEN_GOAT_FILTER_MAX_BYTES)`);
10683
10807
  }
10684
- if (seBytes.length > maxInput) {
10685
- se = seBytes.subarray(0, maxInput).toString("utf8");
10686
- if (!notes.some((n) => n.includes("input truncated"))) {
10687
- notes.push(`stderr truncated at ${Math.floor(maxInput / 1024)}KB (TOKEN_GOAT_FILTER_MAX_BYTES)`);
10808
+ if (seClamped !== null) {
10809
+ se = seClamped;
10810
+ if (!notes.some((n) => n.includes("kept both ends"))) {
10811
+ notes.push(`stderr over ${Math.floor(maxInput / 1024)}KB: kept both ends (TOKEN_GOAT_FILTER_MAX_BYTES)`);
10688
10812
  }
10689
10813
  }
10690
10814
  const originalBytes = soBytes.length + seBytes.length;
@@ -10706,7 +10830,7 @@ ${stderr.replace(/\s+$/, "")}`;
10706
10830
  notes.push(`input exceeded inspect budget (${Math.floor(MAX_INSPECT_BYTES / 1024)} KiB); fell back to truncation`);
10707
10831
  body = fallbackTruncate(normOut, normErr, maxLines);
10708
10832
  } else {
10709
- body = this.compress(normOut, normErr, exitCode, argv);
10833
+ body = this.compress(normOut, normErr, exitCode, argv, { inputTruncated: soClamped !== null || seClamped !== null });
10710
10834
  }
10711
10835
  } catch (exc) {
10712
10836
  const kind = exc instanceof Error ? exc.constructor.name : "Error";
@@ -10715,6 +10839,7 @@ ${stderr.replace(/\s+$/, "")}`;
10715
10839
  const fbErr = this.postNormalise(normalise(se, { skipProgress }));
10716
10840
  body = fallbackTruncate(fbOut, fbErr, maxLines);
10717
10841
  }
10842
+ body = capLongLines(body.split("\n"), LONG_LINE_MAX_CHARS).join("\n");
10718
10843
  const lines = body.split("\n");
10719
10844
  if (lines.length > maxLines) body = truncateMiddleSmart(lines, maxLines).join("\n");
10720
10845
  body = capBytes(body, maxBytes);
@@ -15395,7 +15520,7 @@ var GrepFilter = class extends ToolFilter {
15395
15520
  }
15396
15521
  return false;
15397
15522
  }
15398
- compress(stdout, stderr, _exitCode, argv) {
15523
+ compress(stdout, stderr, _exitCode, argv, ctx = {}) {
15399
15524
  const text = this.combineOutput(stdout, stderr);
15400
15525
  const lines = text.split("\n");
15401
15526
  const nonEmpty = lines.filter((l) => l.trim());
@@ -15422,7 +15547,7 @@ var GrepFilter = class extends ToolFilter {
15422
15547
  }
15423
15548
  const totalMatches = [...fileCounts.values()].reduce((a, b) => a + b, 0) + unattributed;
15424
15549
  const numFiles = fileCounts.size;
15425
- const outLines = [`grep: ${totalMatches} matches across ${numFiles} file(s)`];
15550
+ const outLines = ctx.inputTruncated ? [`grep: at least ${totalMatches} matches across ${numFiles} file(s) (counted over a truncated input; per-file counts below are lower bounds)`] : [`grep: ${totalMatches} matches across ${numFiles} file(s)`];
15426
15551
  const sorted = [...fileCounts.entries()].sort((a, b) => b[1] - a[1]);
15427
15552
  const shown = sorted.slice(0, _GREP_MAX_FILE_LINES);
15428
15553
  for (const [fname, count] of shown) {
@@ -15505,8 +15630,11 @@ var RgFilter = class _RgFilter extends ToolFilter {
15505
15630
  const kept = groups.filter((_, i) => topIdx.has(i));
15506
15631
  const suppressed = groups.length - kept.length;
15507
15632
  const joined = kept.join("\n" + _RgFilter._SEP + "\n");
15633
+ const lastKept = scored[Math.min(_RG_TOP_GROUPS, scored.length) - 1]?.score;
15634
+ const firstDropped = scored[_RG_TOP_GROUPS]?.score;
15635
+ const tied = firstDropped !== void 0 && firstDropped === lastKept;
15508
15636
  return joined + `
15509
- [token-goat: ${suppressed} more match groups suppressed \u2014 rerun with -l for filenames only]`;
15637
+ [token-goat: ${suppressed} more match groups suppressed${tied ? ", tied on match count with the ones kept and separated only by filename order" : ", each with fewer matches than those kept"}: rerun with -l for filenames only]`;
15510
15638
  }
15511
15639
  // Same per-line clip GrepFilter applies: every branch below can return match lines verbatim, so the cap is applied once here rather than at each of the five return sites.
15512
15640
  compress(stdout, stderr, exitCode, argv) {
@@ -16017,6 +16145,12 @@ var RsyncFilter = class extends ToolFilter {
16017
16145
  var _DIFF_FILE_HEADER_RE = /^(?:diff\s|---\s)/;
16018
16146
  var _DIFF_HUNK_RE = /^@@ /;
16019
16147
  var _DIFF_MAX_FULL_FILES = 20;
16148
+ var _DIFF_MAX_STAT_EXTRA_LINES = 40;
16149
+ function _isDiffBodyLine(line) {
16150
+ if (line === "") return true;
16151
+ const c = line[0] ?? "";
16152
+ return c === " " || c === "+" || c === "-" || c === "@" || c === "\\";
16153
+ }
16020
16154
  function _isDiffAdd(line) {
16021
16155
  return line.startsWith("+") && !line.startsWith("+++");
16022
16156
  }
@@ -16126,12 +16260,33 @@ var DiffFilter = class extends ToolFilter {
16126
16260
  const statLines = [
16127
16261
  `[token-goat: large diff (${realFiles.length} files); stat-only view]`
16128
16262
  ];
16129
- for (const blockStr of realFiles) {
16263
+ let extrasKept = 0;
16264
+ let extrasDropped = 0;
16265
+ const emitExtras = (candidates) => {
16266
+ const foreign = candidates.filter((l) => l.trim() !== "" && !_isDiffBodyLine(l));
16267
+ for (const line of capLongLines(foreign)) {
16268
+ if (extrasKept >= _DIFF_MAX_STAT_EXTRA_LINES) {
16269
+ extrasDropped++;
16270
+ continue;
16271
+ }
16272
+ extrasKept++;
16273
+ statLines.push(line);
16274
+ }
16275
+ };
16276
+ for (const blockStr of rawBlocks) {
16130
16277
  const blockLines = blockStr.split("\n");
16131
- const header = blockLines[0];
16278
+ const header = blockLines[0] ?? "";
16279
+ if (!_DIFF_FILE_HEADER_RE.test(header)) {
16280
+ emitExtras(blockLines);
16281
+ continue;
16282
+ }
16132
16283
  const adds = blockLines.filter(_isDiffAdd).length;
16133
16284
  const dels = blockLines.filter(_isDiffRemove).length;
16134
16285
  statLines.push(`${header} +${adds} -${dels}`);
16286
+ emitExtras(blockLines.slice(1));
16287
+ }
16288
+ if (extrasDropped > 0) {
16289
+ statLines.push(`[token-goat: ${extrasDropped} more non-diff line${extrasDropped === 1 ? "" : "s"} omitted]`);
16135
16290
  }
16136
16291
  return statLines.join("\n");
16137
16292
  }
@@ -18148,7 +18303,7 @@ var Sqlite3Filter = class _Sqlite3Filter extends ToolFilter {
18148
18303
  binaries = /* @__PURE__ */ new Set(["sqlite3"]);
18149
18304
  static ROW_THRESHOLD = 20;
18150
18305
  static KEEP_ROWS = 5;
18151
- compress(stdout, stderr, _exitCode, _argv) {
18306
+ compress(stdout, stderr, _exitCode, _argv, ctx = {}) {
18152
18307
  const merged = this.combineOutput(stdout, stderr);
18153
18308
  const lines = merged.split("\n");
18154
18309
  const nonEmpty = lines.filter((ln) => ln.trim());
@@ -18167,7 +18322,9 @@ var Sqlite3Filter = class _Sqlite3Filter extends ToolFilter {
18167
18322
  const nonEmptyBody = bodyLines.filter((ln) => ln.trim());
18168
18323
  if (nonEmptyBody.length > _Sqlite3Filter.ROW_THRESHOLD) {
18169
18324
  kept.push(...nonEmptyBody.slice(0, _Sqlite3Filter.KEEP_ROWS));
18170
- kept.push(`[token-goat: ${nonEmptyBody.length} rows (showing first ${_Sqlite3Filter.KEEP_ROWS})]`);
18325
+ kept.push(
18326
+ ctx.inputTruncated === true ? `[token-goat: at least ${nonEmptyBody.length} rows (counted over a truncated input; showing first ${_Sqlite3Filter.KEEP_ROWS})]` : `[token-goat: ${nonEmptyBody.length} rows (showing first ${_Sqlite3Filter.KEEP_ROWS})]`
18327
+ );
18171
18328
  } else {
18172
18329
  kept.push(...bodyLines);
18173
18330
  }
@@ -19509,7 +19666,7 @@ var KubectlFilter = class extends ToolFilter {
19509
19666
  } else if (subcommand === "diff") {
19510
19667
  const diffLines = text.split("\n");
19511
19668
  if (diffLines.length > 50) {
19512
- text = headTailCompress(diffLines, 50, 0, "diff lines");
19669
+ text = headTailCompress(diffLines, 35, 15, "diff lines");
19513
19670
  }
19514
19671
  }
19515
19672
  if (stderr.trim()) {
@@ -19925,8 +20082,14 @@ function _capPatchLinesInBlock(block, maxLines) {
19925
20082
  const headerLines = lines.slice(0, diffStart);
19926
20083
  let diffLines = lines.slice(diffStart);
19927
20084
  if (diffLines.length > maxLines) {
20085
+ const tailKeep = Math.min(10, Math.floor(maxLines / 3));
20086
+ const headKeep = maxLines - tailKeep;
19928
20087
  const elided = diffLines.length - maxLines;
19929
- diffLines = [...diffLines.slice(0, maxLines), `--- patch: ${elided} lines omitted by token-goat ---`];
20088
+ diffLines = [
20089
+ ...diffLines.slice(0, headKeep),
20090
+ `--- patch: ${elided} lines omitted by token-goat ---`,
20091
+ ...diffLines.slice(diffLines.length - tailKeep)
20092
+ ];
19930
20093
  }
19931
20094
  return [...headerLines, ...diffLines].join("\n");
19932
20095
  }
@@ -19971,7 +20134,7 @@ function _compressGitLogStat(stdout, stderr) {
19971
20134
  const MAX_STAT_FILES = 20;
19972
20135
  return _compressGitLogCapped(stdout, stderr, (block) => _capStatLinesInBlock(block, MAX_STAT_FILES));
19973
20136
  }
19974
- function _compressGitLogEnhanced(stdout, stderr, argv) {
20137
+ function _compressGitLogEnhanced(stdout, stderr, argv, inputTruncated = false) {
19975
20138
  const flags = new Set(argv);
19976
20139
  let isOneline = flags.has("--oneline") || flags.has("--format=oneline") || flags.has("--pretty=oneline") || argv.some((a) => a.startsWith("--format=%h") || a.startsWith("--pretty=%h"));
19977
20140
  if (!isOneline) {
@@ -19995,7 +20158,8 @@ function _compressGitLogEnhanced(stdout, stderr, argv) {
19995
20158
  let keptLines;
19996
20159
  if (blocks.length > ONELINE_CAP) {
19997
20160
  const elided = blocks.length - ONELINE_CAP;
19998
- keptLines = [...blocks.slice(0, ONELINE_CAP), `[token-goat: +${elided} more commits]`];
20161
+ const elidedNote = inputTruncated ? `[token-goat: at least ${elided} more commits (counted over a truncated input)]` : `[token-goat: +${elided} more commits]`;
20162
+ keptLines = [...blocks.slice(0, ONELINE_CAP), elidedNote];
19999
20163
  } else {
20000
20164
  keptLines = blocks;
20001
20165
  }
@@ -20010,8 +20174,8 @@ function _compressGitLogEnhanced(stdout, stderr, argv) {
20010
20174
  var GitLogFilter = class extends GitBaseFilter {
20011
20175
  name = "git-log";
20012
20176
  subcommands = /* @__PURE__ */ new Set(["log"]);
20013
- compress(stdout, stderr, _exitCode, argv) {
20014
- return _compressGitLogEnhanced(stdout, stderr, argv);
20177
+ compress(stdout, stderr, _exitCode, argv, ctx = {}) {
20178
+ return _compressGitLogEnhanced(stdout, stderr, argv, ctx.inputTruncated === true);
20015
20179
  }
20016
20180
  };
20017
20181
  var _GIT_DIFF_BINARY_RE = /^Binary files?(?: .+)? differ$/;
@@ -20252,43 +20416,6 @@ function _compressGitDiffBody(stdout, stderr, maxHunksPerFile = 10) {
20252
20416
  if (stderr.trim()) text += "\n---\n" + stderr.replace(/\s+$/, "");
20253
20417
  return text;
20254
20418
  }
20255
- function _compressGitDiffSimple(stdout, stderr, maxHunksPerFile = 3) {
20256
- const fileBlocks = splitBlocks(stdout, _GIT_DIFF_FILE_RE);
20257
- if (!fileBlocks.length) return stdout;
20258
- const realFiles = fileBlocks.filter((b) => _GIT_DIFF_FILE_RE.test(b));
20259
- if (realFiles.length > 200) {
20260
- const statLines = realFiles.map((b) => {
20261
- const header = b.split("\n", 1)[0] ?? "";
20262
- const lines = b.split("\n");
20263
- const adds = lines.filter(_isDiffAdd2).length;
20264
- const dels = lines.filter(_isDiffRemove2).length;
20265
- return `${header} +${adds} -${dels}`;
20266
- });
20267
- return `[token-goat: large diff (${realFiles.length} files); showing stat-only view]
20268
- ` + statLines.join("\n");
20269
- }
20270
- const outBlocks = [];
20271
- for (const block of fileBlocks) {
20272
- if (!_GIT_DIFF_FILE_RE.test(block)) {
20273
- outBlocks.push(block);
20274
- continue;
20275
- }
20276
- const hunks = splitBlocks(block, _GIT_DIFF_HUNK_RE);
20277
- if (hunks.length <= maxHunksPerFile + 1) {
20278
- outBlocks.push(block);
20279
- continue;
20280
- }
20281
- const head = hunks.slice(0, maxHunksPerFile + 1);
20282
- const elided = hunks.slice(maxHunksPerFile + 1);
20283
- outBlocks.push(
20284
- head.join("\n") + `
20285
- [token-goat: +${elided.length} more hunks in this file elided]`
20286
- );
20287
- }
20288
- let text = outBlocks.join("\n");
20289
- if (stderr.trim()) text += "\n---\n" + stderr.replace(/\s+$/, "");
20290
- return text;
20291
- }
20292
20419
  function _compressGitDiffEnhanced(stdout, stderr, argv) {
20293
20420
  const flags = new Set(argv);
20294
20421
  const isStat = flags.has("--stat") || flags.has("--shortstat") || flags.has("--name-only");
@@ -20805,13 +20932,7 @@ var GitFilter = class extends GitBaseFilter {
20805
20932
  const positionals = gitPositionalArgs(argv.slice(1));
20806
20933
  const subcommand = positionals[0] ?? "";
20807
20934
  if (subcommand === "diff" || subcommand === "show") {
20808
- let maxHunksPerFile;
20809
- try {
20810
- maxHunksPerFile = loadConfig().bash_diff.max_hunks_per_file;
20811
- } catch {
20812
- maxHunksPerFile = void 0;
20813
- }
20814
- return maxHunksPerFile === void 0 ? _compressGitDiffSimple(stdout, stderr) : _compressGitDiffSimple(stdout, stderr, maxHunksPerFile);
20935
+ return _compressGitDiffEnhanced(stdout, stderr, argv);
20815
20936
  }
20816
20937
  if (subcommand === "ls-files" || subcommand === "ls-tree")
20817
20938
  return _truncateListing(stdout, stderr, 100);