token-goat 2.6.16 → 2.6.17

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.
@@ -9,7 +9,7 @@ import * as path from "node:path";
9
9
  import { createRequire } from "node:module";
10
10
  function resolveVersion() {
11
11
  if (true) {
12
- return "2.6.16";
12
+ return "2.6.17";
13
13
  }
14
14
  const require2 = createRequire(import.meta.url);
15
15
  const pkg = require2("../package.json");
@@ -126,6 +126,7 @@ var KNOWN_HARNESS_NAMES = /* @__PURE__ */ new Set([
126
126
  "pi",
127
127
  "copilot_cli",
128
128
  "grok",
129
+ "qwen",
129
130
  "generic"
130
131
  ]);
131
132
  function detectHarness() {
@@ -1728,10 +1729,8 @@ var CONFIG_DEFAULTS = {
1728
1729
  record_zero_savings: false
1729
1730
  },
1730
1731
  hints: {
1731
- suppress_after_ignored: 5,
1732
1732
  quiet_hours: "",
1733
1733
  json_sidecar: false,
1734
- verbose_until_seen_count: 2,
1735
1734
  min_file_lines_for_hint: 0,
1736
1735
  bash_dedup_min_bytes: 200,
1737
1736
  web_dedup_min_bytes: 200,
@@ -1874,8 +1873,6 @@ var NUMERIC_FIELD_BOUNDS = {
1874
1873
  "image_shrink.ocr_min_confidence": { min: 0, max: 100 },
1875
1874
  "repomap.compact_file_threshold": { min: 0, max: 1e5 },
1876
1875
  "overflow_guard.max_tokens": { min: 1e3, max: 1e6 },
1877
- "hints.suppress_after_ignored": { min: 0, max: 1e3 },
1878
- "hints.verbose_until_seen_count": { min: 0, max: 1e4 },
1879
1876
  "hints.min_file_lines_for_hint": { min: 0, max: 1e6 },
1880
1877
  "hints.bash_dedup_min_bytes": { min: 0, max: 1e5 },
1881
1878
  "hints.web_dedup_min_bytes": { min: 0, max: 1e5 },
@@ -1957,9 +1954,12 @@ var ENV_KEYS2 = [
1957
1954
  "TOKEN_GOAT_WRITE_REWRITE_MIN_LINES",
1958
1955
  "TOKEN_GOAT_WRITE_REWRITE_UNCHANGED_PCT"
1959
1956
  ];
1957
+ function allEnvKeys() {
1958
+ return [.../* @__PURE__ */ new Set([...ENV_KEYS2, ...Object.values(CONFIG_KEY_ENV_OVERRIDES).flat()])];
1959
+ }
1960
1960
  function configEnvFingerprint() {
1961
1961
  const snap = {};
1962
- for (const k of ENV_KEYS2) {
1962
+ for (const k of allEnvKeys()) {
1963
1963
  snap[k] = process.env[k];
1964
1964
  }
1965
1965
  return JSON.stringify(snap);
@@ -2007,6 +2007,25 @@ function resolveConfigProjectRoot() {
2007
2007
  _projectRootCache = { cwd, root };
2008
2008
  return root;
2009
2009
  }
2010
+ function isAutoTriggerMultiplierExplicit() {
2011
+ const setsMultiplier = (text) => {
2012
+ const raw = parse(text);
2013
+ const ca_raw = raw["compact_assist"];
2014
+ if (ca_raw === null || typeof ca_raw !== "object" || Array.isArray(ca_raw)) {
2015
+ return false;
2016
+ }
2017
+ return ca_raw["auto_trigger_multiplier"] !== void 0;
2018
+ };
2019
+ try {
2020
+ if (setsMultiplier(fs3.readFileSync(configPath(), "utf8"))) return true;
2021
+ } catch {
2022
+ }
2023
+ try {
2024
+ return setsMultiplier(fs3.readFileSync(projectConfigPath(resolveConfigProjectRoot()), "utf8"));
2025
+ } catch {
2026
+ return false;
2027
+ }
2028
+ }
2010
2029
  function loadConfig(projectRoot) {
2011
2030
  const p = configPath();
2012
2031
  const root = projectRoot ?? resolveConfigProjectRoot();
@@ -2143,10 +2162,8 @@ function _buildConfig(raw, projectRaw = {}) {
2143
2162
  st.record_zero_savings = validatedBool(st_raw["record_zero_savings"], st.record_zero_savings);
2144
2163
  const hi_raw = section(raw, "hints");
2145
2164
  const hi = getDefaultConfig("hints");
2146
- hi.suppress_after_ignored = validatedInt(hi_raw["suppress_after_ignored"], hi.suppress_after_ignored, ...boundsOf("hints.suppress_after_ignored"));
2147
2165
  hi.quiet_hours = validatedStr(hi_raw["quiet_hours"], hi.quiet_hours);
2148
2166
  hi.json_sidecar = validatedBool(hi_raw["json_sidecar"], hi.json_sidecar);
2149
- hi.verbose_until_seen_count = validatedInt(hi_raw["verbose_until_seen_count"], hi.verbose_until_seen_count, ...boundsOf("hints.verbose_until_seen_count"));
2150
2167
  hi.min_file_lines_for_hint = validatedInt(hi_raw["min_file_lines_for_hint"], hi.min_file_lines_for_hint, ...boundsOf("hints.min_file_lines_for_hint"));
2151
2168
  hi.bash_dedup_min_bytes = validatedInt(hi_raw["bash_dedup_min_bytes"], hi.bash_dedup_min_bytes, ...boundsOf("hints.bash_dedup_min_bytes"));
2152
2169
  hi.bash_dedup_min_bytes = envInt("TOKEN_GOAT_BASH_DEDUP_MIN_BYTES", hi.bash_dedup_min_bytes, ...boundsOf("hints.bash_dedup_min_bytes"));
@@ -2262,6 +2279,49 @@ function _buildConfig(raw, projectRaw = {}) {
2262
2279
  hint_stats: hs
2263
2280
  };
2264
2281
  }
2282
+ var CONFIG_KEY_ENV_OVERRIDES = {
2283
+ "compact_assist.enabled": ["TOKEN_GOAT_COMPACT_ASSIST", "TOKENWISE_COMPACT_ASSIST"],
2284
+ "bash_compress.enabled": ["TOKEN_GOAT_BASH_COMPRESS"],
2285
+ "bash_compress.cache_min_bytes": ["TOKEN_GOAT_BASH_CACHE_MIN_BYTES"],
2286
+ "bash_compress.cache_max_file_count": ["TOKEN_GOAT_BASH_CACHE_MAX_FILES"],
2287
+ "bash_compress.cache_max_bytes": ["TOKEN_GOAT_BASH_CACHE_MAX_BYTES"],
2288
+ "bash_compress.cache_max_bytes_per_output": ["TOKEN_GOAT_BASH_CACHE_MAX_BYTES_PER_OUTPUT"],
2289
+ "session_brief.enabled": ["TOKEN_GOAT_SESSION_BRIEF"],
2290
+ "skill_preservation.enabled": ["TOKEN_GOAT_SKILL_PRESERVATION"],
2291
+ "skill_preservation.compress_bodies": ["TOKEN_GOAT_SKILL_COMPRESS"],
2292
+ "skill_preservation.pre_skill_enabled": ["TOKEN_GOAT_PRE_SKILL"],
2293
+ "skill_preservation.orphan_sweep_enabled": ["TOKEN_GOAT_ORPHAN_SWEEP"],
2294
+ "image_shrink.max_image_pixels": ["TOKEN_GOAT_MAX_IMAGE_PIXELS"],
2295
+ "image_shrink.ocr_enabled": ["TOKEN_GOAT_OCR_ENABLED"],
2296
+ "repomap.compact_file_threshold": ["TOKEN_GOAT_REPOMAP_COMPACT_THRESHOLD"],
2297
+ "repomap.exclude_tests": ["TOKEN_GOAT_REPOMAP_EXCLUDE_TESTS"],
2298
+ "overflow_guard.enabled": ["TOKEN_GOAT_OVERFLOW_GUARD"],
2299
+ "overflow_guard.max_tokens": ["TOKEN_GOAT_OVERFLOW_MAX_TOKENS"],
2300
+ "hints.json_sidecar": ["TOKEN_GOAT_HINT_JSON_SIDECAR"],
2301
+ "hints.bash_dedup_min_bytes": ["TOKEN_GOAT_BASH_DEDUP_MIN_BYTES"],
2302
+ "hints.web_dedup_min_bytes": ["TOKEN_GOAT_WEB_DEDUP_MIN_BYTES"],
2303
+ "hints.grep_dedup_min_matches": ["TOKEN_GOAT_GREP_DEDUP_MIN_MATCHES"],
2304
+ "hints.glob_dedup_min_matches": ["TOKEN_GOAT_GLOB_DEDUP_MIN_MATCHES"],
2305
+ "hints.write_rewrite_min_lines": ["TOKEN_GOAT_WRITE_REWRITE_MIN_LINES"],
2306
+ "hints.write_rewrite_unchanged_pct": ["TOKEN_GOAT_WRITE_REWRITE_UNCHANGED_PCT"],
2307
+ "hints.large_read_redirect_bytes": ["TOKEN_GOAT_LARGE_READ_BYTES"],
2308
+ "hints.warn_unbalanced_shell_quoting": ["TOKEN_GOAT_WARN_UNBALANCED_SHELL_QUOTING"],
2309
+ "hints.serve_diff_on_reread": ["TOKEN_GOAT_SERVE_DIFF_ON_REREAD"],
2310
+ "hints.log_large_file_hint_outcomes": ["TOKEN_GOAT_LOG_LARGE_FILE_HINT_OUTCOMES"],
2311
+ "hints.cross_session_read_dedup": ["TOKEN_GOAT_CROSS_SESSION_READ_DEDUP"],
2312
+ "hints.cross_session_read_dedup_ttl_secs": ["TOKEN_GOAT_CROSS_SESSION_READ_DEDUP_TTL_SECS"],
2313
+ "hints.mcp_dedup_ttl_secs": ["TOKEN_GOAT_MCP_DEDUP_TTL_SECS"],
2314
+ "hints.min_session_hint_savings_bytes": ["TOKEN_GOAT_SESSION_HINT_MIN_BYTES"],
2315
+ "hooks.watchdog_ms": ["TOKEN_GOAT_HOOK_WATCHDOG_MS"],
2316
+ "webfetch.max_file_count": ["TOKEN_GOAT_WEB_CACHE_MAX_FILES"],
2317
+ "webfetch.max_bytes": ["TOKEN_GOAT_WEB_CACHE_MAX_BYTES"],
2318
+ "webfetch.compress_bodies": ["TOKEN_GOAT_WEB_COMPRESS"],
2319
+ "worker.max_pool_workers": ["TOKEN_GOAT_WORKER_MAX_POOL"],
2320
+ "compression.profile": ["TOKEN_GOAT_COMPRESS_PROFILE"],
2321
+ "context.model_window_tokens": ["TOKEN_GOAT_MODEL_WINDOW_TOKENS"],
2322
+ "injection.enabled": ["TOKEN_GOAT_INJECTION_ENABLED"],
2323
+ "indexing.embeddings_enabled": ["TOKEN_GOAT_EMBEDDINGS_ENABLED"]
2324
+ };
2265
2325
 
2266
2326
  // src/secret_redact.ts
2267
2327
  var SECRET_PATTERNS = [
@@ -2269,8 +2329,17 @@ var SECRET_PATTERNS = [
2269
2329
  // ("sk-ant-"), so they're matched first — the generic OpenAI pattern's
2270
2330
  // negative lookahead below is defense in depth, not the sole guard.
2271
2331
  ["anthropic_api_key", /sk-ant-[A-Za-z0-9_-]{20,}/g],
2272
- ["openai_api_key", /sk-(?!ant-)[A-Za-z0-9]{20,}/g],
2332
+ // Modern (post-2024, now the default) OpenAI project keys: "sk-proj-" followed by a long
2333
+ // base64url-ish body that legitimately contains '-' and '_' -- the generic pattern below
2334
+ // deliberately excludes those characters (hyphenated prose would false-fire), so this needs
2335
+ // its own entry with the more specific prefix matched first, like sk-ant- above.
2336
+ ["openai_project_key", /sk-proj-[A-Za-z0-9_-]{20,}/g],
2337
+ ["openai_api_key", /sk-(?!ant-|proj-)[A-Za-z0-9]{20,}/g],
2273
2338
  ["aws_access_key", /AKIA[0-9A-Z]{16}/g],
2339
+ // Fine-grained PATs ("github_pat_...") are matched before the classic gh[oprsu]_ pattern so
2340
+ // the full token is always consumed as one match -- a fine-grained token's own body can
2341
+ // contain '_' and could otherwise partially match the classic pattern.
2342
+ ["github_token", /github_pat_[A-Za-z0-9_]{22,}/g],
2274
2343
  ["github_token", /gh[oprsu]_[A-Za-z0-9]{36,}/g],
2275
2344
  ["slack_token", /xox[baprs]-[A-Za-z0-9-]+/g],
2276
2345
  // Matches the full block (BEGIN marker through its matching END marker), not just the
@@ -2425,6 +2494,23 @@ CREATE TABLE IF NOT EXISTS hint_manual_marks (
2425
2494
  effective_count INTEGER NOT NULL DEFAULT 0,
2426
2495
  ineffective_count INTEGER NOT NULL DEFAULT 0
2427
2496
  );
2497
+
2498
+ -- Durable counter backing hint_stats.ts's backoff-threshold probe-recovery schedule: how many
2499
+ -- CONSECUTIVE suppressed occasions have elapsed for (category, harness) since a hint in this
2500
+ -- category was last actually shown (either organically, because shouldSuppress no longer holds,
2501
+ -- or via a prior probe). shouldSuppress itself stays a pure function of hint_emissions -- this
2502
+ -- table exists only because a suppressed occasion is deliberately never written to
2503
+ -- hint_emissions (see that table's own comment), so without a separate durable counter here
2504
+ -- there would be no way to know "how many suppressed occasions have we seen" across the
2505
+ -- short-lived hook CLI processes that call applyHintTracking. Keyed by (category, harness), not
2506
+ -- category alone, to match shouldSuppress/categoryStats' own per-harness scoping -- unlike
2507
+ -- hint_manual_marks (a human-entered vote, deliberately not harness-split).
2508
+ CREATE TABLE IF NOT EXISTS hint_suppression_probes (
2509
+ category TEXT NOT NULL,
2510
+ harness TEXT NOT NULL,
2511
+ streak INTEGER NOT NULL DEFAULT 0,
2512
+ PRIMARY KEY (category, harness)
2513
+ );
2428
2514
  `;
2429
2515
  var FTS_SQL = `
2430
2516
  CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(
@@ -2475,7 +2561,7 @@ CREATE TRIGGER IF NOT EXISTS cache_recall_au AFTER UPDATE ON cache_recall BEGIN
2475
2561
  VALUES (new.row_id, new.label, new.content);
2476
2562
  END;
2477
2563
  `;
2478
- var SCHEMA_VERSION = 5;
2564
+ var SCHEMA_VERSION = 6;
2479
2565
  function alterTableIdempotent(conn, sql) {
2480
2566
  try {
2481
2567
  conn.exec(sql);
@@ -3521,6 +3607,42 @@ function denyOutput(message) {
3521
3607
  function contextOutput(context) {
3522
3608
  return { hookType: "context", context };
3523
3609
  }
3610
+ function countNonEmptyLines(text) {
3611
+ return text.split(/\r\n|\r|\n/).filter((line) => line.length > 0).length;
3612
+ }
3613
+ function makeDedupHintHandlers(opts) {
3614
+ const post = (event) => {
3615
+ try {
3616
+ if (getToolName(event) !== opts.toolName) return passOutput();
3617
+ const signature = opts.buildSignature(getToolInput(event));
3618
+ if (signature === null) return passOutput();
3619
+ const text = extractToolResponseField(event.raw, OUTPUT_FIRST_TOOL_RESPONSE_KEYS);
3620
+ opts.recordQuery(signature, countNonEmptyLines(text));
3621
+ return passOutput();
3622
+ } catch {
3623
+ return passOutput();
3624
+ }
3625
+ };
3626
+ const pre = (event) => {
3627
+ try {
3628
+ if (getToolName(event) !== opts.toolName) return passOutput();
3629
+ const toolInput = getToolInput(event);
3630
+ const signature = opts.buildSignature(toolInput);
3631
+ if (signature === null) return passOutput();
3632
+ const priorCount = opts.getMatchCount(signature);
3633
+ if (priorCount === null) return passOutput();
3634
+ if (priorCount < loadConfig().hints[opts.minMatchesConfigKey]) return passOutput();
3635
+ recordStat(opts.statName, 0, 0);
3636
+ const pattern = typeof toolInput["pattern"] === "string" ? toolInput["pattern"] : "";
3637
+ return contextOutput(
3638
+ "Note: an identical " + opts.toolName + ' for "' + pattern + '" already ran this session and returned ' + priorCount + (priorCount === 1 ? " match" : " matches") + ". If that result already answers this, you can skip re-running it."
3639
+ );
3640
+ } catch {
3641
+ return passOutput();
3642
+ }
3643
+ };
3644
+ return { post, pre };
3645
+ }
3524
3646
 
3525
3647
  // src/hint_stats.ts
3526
3648
  var HINT_CATEGORIES = [
@@ -3555,6 +3677,10 @@ function extractPathCorrelator(text) {
3555
3677
  return cleaned;
3556
3678
  }
3557
3679
  function classifyBashHint(text) {
3680
+ const fileMatch = /token-goat bash-output --file "([^"]+)"/.exec(text);
3681
+ if (fileMatch?.[1] !== void 0) {
3682
+ return { category: "bash_recall", correlator: fileMatch[1] };
3683
+ }
3558
3684
  const idMatch = /token-goat bash-output ([A-Za-z0-9_.-]+)/.exec(text);
3559
3685
  if (idMatch?.[1] !== void 0) {
3560
3686
  return { category: "bash_recall", correlator: idMatch[1] };
@@ -3568,13 +3694,49 @@ function classifyReadHint(text) {
3568
3694
  function classifyEditHint(text) {
3569
3695
  return { category: "edit_reread_suggest", correlator: extractPathCorrelator(text) };
3570
3696
  }
3697
+ function isProbeOccasion(occasion, thresholds) {
3698
+ const sorted = [...new Set(thresholds.filter((t) => t > 0))].sort((a, b) => a - b);
3699
+ if (sorted.length === 0) return false;
3700
+ if (sorted.includes(occasion)) return true;
3701
+ const last = sorted[sorted.length - 1];
3702
+ return occasion > last && occasion % last === 0;
3703
+ }
3704
+ function bumpSuppressionStreak(category) {
3705
+ try {
3706
+ const db = getDb(globalDbPath());
3707
+ const harness = getHarnessName();
3708
+ db.prepare(
3709
+ `INSERT INTO hint_suppression_probes (category, harness, streak) VALUES (@category, @harness, 1)
3710
+ ON CONFLICT(category, harness) DO UPDATE SET streak = streak + 1`
3711
+ ).run({ category, harness });
3712
+ const row = db.prepare(`SELECT streak FROM hint_suppression_probes WHERE category = ? AND harness = ?`).get(category, harness);
3713
+ return row?.streak ?? 0;
3714
+ } catch {
3715
+ return 0;
3716
+ }
3717
+ }
3718
+ function resetSuppressionStreak(category) {
3719
+ try {
3720
+ const db = getDb(globalDbPath());
3721
+ db.prepare(`UPDATE hint_suppression_probes SET streak = 0 WHERE category = ? AND harness = ?`).run(category, getHarnessName());
3722
+ } catch {
3723
+ }
3724
+ }
3571
3725
  function applyHintTracking(event, output, classify) {
3572
3726
  if (output.hookType !== "context") return output;
3573
3727
  const { category, correlator } = classify(output.context);
3728
+ const compensateSelfResolve = event.eventName === "pre_tool_use";
3574
3729
  if (shouldSuppress(category, event.sessionId)) {
3575
- return passOutput();
3730
+ const occasion = bumpSuppressionStreak(category);
3731
+ const thresholds = loadConfig().hints.backoff_thresholds;
3732
+ if (!isProbeOccasion(occasion, thresholds)) {
3733
+ return passOutput();
3734
+ }
3735
+ logHintEmission(category, event.sessionId, correlator, compensateSelfResolve);
3736
+ return output;
3576
3737
  }
3577
- logHintEmission(category, event.sessionId, correlator, event.eventName === "pre_tool_use");
3738
+ resetSuppressionStreak(category);
3739
+ logHintEmission(category, event.sessionId, correlator, compensateSelfResolve);
3578
3740
  return output;
3579
3741
  }
3580
3742
  function logHintEmission(category, sessionId, correlator, compensateSelfResolve = false) {
@@ -3598,12 +3760,21 @@ function logHintEmission(category, sessionId, correlator, compensateSelfResolve
3598
3760
  }
3599
3761
  }
3600
3762
  var TOKEN_GOAT_INVOCATION_RE = /(?:^|[\s;&|])token-goat(?=[\s]|$)/;
3763
+ function commandMentionsCorrelator(command, correlator) {
3764
+ let idx = command.indexOf(correlator);
3765
+ while (idx !== -1) {
3766
+ const after = command[idx + correlator.length];
3767
+ if (after === void 0 || !/[A-Za-z0-9_.-]/.test(after)) return true;
3768
+ idx = command.indexOf(correlator, idx + 1);
3769
+ }
3770
+ return false;
3771
+ }
3601
3772
  function isActedOn(category, correlator, command) {
3602
3773
  if (!TOKEN_GOAT_INVOCATION_RE.test(command)) return false;
3603
3774
  if (category === "bash_recall") {
3604
- return command.includes("bash-output") && command.includes(correlator);
3775
+ return command.includes("bash-output") && commandMentionsCorrelator(command, correlator);
3605
3776
  }
3606
- return command.includes(correlator);
3777
+ return commandMentionsCorrelator(command, correlator);
3607
3778
  }
3608
3779
  function resolvePendingHintsForEvent(event) {
3609
3780
  try {
@@ -3660,6 +3831,18 @@ function meetsSavingsFloor(bytesSaved) {
3660
3831
  // src/compact.ts
3661
3832
  import * as fs8 from "node:fs";
3662
3833
  import * as path9 from "node:path";
3834
+
3835
+ // src/bash_compress.ts
3836
+ var ANSI_ESCAPE_RE = (
3837
+ // eslint-disable-next-line no-control-regex -- intentionally matches ESC (\x1B) and BEL (\x07) control bytes
3838
+ /\x1B\[[0-?]*[ -/]*[@-~]|\x1B\].*?(?:\x07|\x1B\\|$)|\x1B[PX^_].*?\x1B\\|\x1B[@-_]/gs
3839
+ );
3840
+ function stripAnsiCodes(text) {
3841
+ if (!text.includes("\x1B")) return text;
3842
+ return text.replace(ANSI_ESCAPE_RE, "");
3843
+ }
3844
+
3845
+ // src/compact.ts
3663
3846
  var CONTEXT_AUTOCOMPACT_TOKENS = 66e4;
3664
3847
  var CATALOG_TOKENS = 10800;
3665
3848
  var CONTEXT_TIER_WARM = 0.5;
@@ -3676,7 +3859,8 @@ var HARNESS_MULTIPLIER_DEFAULTS = {
3676
3859
  generic: 1
3677
3860
  };
3678
3861
  function estimateTokens(text) {
3679
- return Math.max(1, Math.floor(text.length / 3) + 1);
3862
+ const stripped = stripAnsiCodes(text);
3863
+ return Math.max(1, Math.floor(stripped.length / 3) + 1);
3680
3864
  }
3681
3865
  function tierForFraction(fill) {
3682
3866
  if (fill >= CONTEXT_TIER_CRITICAL) return "critical";
@@ -3704,19 +3888,6 @@ function getEffectiveAutoTriggerWindow() {
3704
3888
  );
3705
3889
  return CONTEXT_AUTOCOMPACT_TOKENS * multiplier;
3706
3890
  }
3707
- function isAutoTriggerMultiplierExplicit() {
3708
- try {
3709
- const text = fs8.readFileSync(configPath(), "utf8");
3710
- const raw = parse(text);
3711
- const ca_raw = raw["compact_assist"];
3712
- if (ca_raw === null || typeof ca_raw !== "object" || Array.isArray(ca_raw)) {
3713
- return false;
3714
- }
3715
- return ca_raw["auto_trigger_multiplier"] !== void 0;
3716
- } catch {
3717
- return false;
3718
- }
3719
- }
3720
3891
  function getContextPressure(cache) {
3721
3892
  try {
3722
3893
  if (!cache) {
@@ -3984,11 +4155,11 @@ function buildPackageManifestHint(options) {
3984
4155
  return null;
3985
4156
  }
3986
4157
  }
3987
- function _sanitizeHintPath(path23) {
3988
- if (typeof path23 !== "string") {
4158
+ function _sanitizeHintPath(path24) {
4159
+ if (typeof path24 !== "string") {
3989
4160
  return "???";
3990
4161
  }
3991
- return path23.replace(/[\x00]/g, "").slice(0, 200);
4162
+ return path24.replace(/[\x00]/g, "").slice(0, 200);
3992
4163
  }
3993
4164
 
3994
4165
  // src/hints/lang_patterns.ts
@@ -6435,11 +6606,9 @@ async function findCrossSessionEntry(skillName, contentSha) {
6435
6606
  }
6436
6607
  const dir = skillOutputsDir();
6437
6608
  const bodyPath = resolve5(dir, `${meta.outputId}.txt`);
6438
- const gzPath = resolve5(dir, `${meta.outputId}.gz`);
6439
6609
  try {
6440
6610
  const bodyExists = await fs10.access(bodyPath).then(() => true).catch(() => false);
6441
- const gzExists = await fs10.access(gzPath).then(() => true).catch(() => false);
6442
- if (bodyExists || gzExists) {
6611
+ if (bodyExists) {
6443
6612
  return meta;
6444
6613
  }
6445
6614
  } catch {
@@ -6684,7 +6853,7 @@ function pruneSkillOutputs(maxCount = DEFAULT_MAX_COUNT, maxAgeMs = DEFAULT_MAX_
6684
6853
  entries.push({ outputId, ts });
6685
6854
  }
6686
6855
  const removeEntry = (outputId) => {
6687
- for (const ext of [".meta", ".txt", ".gz"]) {
6856
+ for (const ext of [".meta", ".txt"]) {
6688
6857
  try {
6689
6858
  unlinkSync5(resolve5(dir, `${outputId}${ext}`));
6690
6859
  } catch {
@@ -6716,6 +6885,22 @@ function pruneSkillOutputs(maxCount = DEFAULT_MAX_COUNT, maxAgeMs = DEFAULT_MAX_
6716
6885
  import * as fs11 from "node:fs";
6717
6886
  import * as path12 from "node:path";
6718
6887
 
6888
+ // src/lazy_module.ts
6889
+ function createLazyModuleLoader(load2, errorLabel) {
6890
+ let cache;
6891
+ return async () => {
6892
+ if (cache !== void 0) return cache;
6893
+ try {
6894
+ cache = await load2();
6895
+ } catch (err) {
6896
+ process.stderr.write(`token-goat: ${errorLabel}: ${String(err)}
6897
+ `);
6898
+ cache = null;
6899
+ }
6900
+ return cache;
6901
+ };
6902
+ }
6903
+
6719
6904
  // src/image_ocr.ts
6720
6905
  import { spawn as spawn2 } from "node:child_process";
6721
6906
  import { createRequire as createRequire3 } from "node:module";
@@ -6838,19 +7023,10 @@ function formatShrinkSummary(result, subject) {
6838
7023
  const dataUrl = `data:image/${result.format};base64,${result.data.toString("base64")}`;
6839
7024
  return { summary, dataUrl };
6840
7025
  }
6841
- var _sharpCache;
6842
- async function loadSharp() {
6843
- if (_sharpCache !== void 0) return _sharpCache;
6844
- try {
6845
- const mod = await import("sharp");
6846
- _sharpCache = mod.default;
6847
- } catch (err) {
6848
- process.stderr.write(`token-goat: image shrink disabled (sharp unavailable): ${String(err)}
6849
- `);
6850
- _sharpCache = null;
6851
- }
6852
- return _sharpCache;
6853
- }
7026
+ var loadSharp = createLazyModuleLoader(async () => {
7027
+ const mod = await import("sharp");
7028
+ return mod.default;
7029
+ }, "image shrink disabled (sharp unavailable)");
6854
7030
  function isImagePath(p) {
6855
7031
  return IMAGE_EXTENSIONS.has(path12.extname(p).toLowerCase());
6856
7032
  }
@@ -6897,14 +7073,36 @@ async function preReadImageHandler(event) {
6897
7073
  if (filePath === void 0) return passOutput();
6898
7074
  if (!isImagePath(filePath)) return passOutput();
6899
7075
  const size = statSize(filePath);
6900
- if (size === null || size < DEFAULT_SIZE_THRESHOLD_BYTES) return passOutput();
6901
- let input;
6902
- try {
6903
- input = fs11.readFileSync(filePath);
6904
- } catch {
6905
- return passOutput();
7076
+ if (size === null) return passOutput();
7077
+ let input = null;
7078
+ let qualifies = size >= DEFAULT_SIZE_THRESHOLD_BYTES;
7079
+ if (!qualifies) {
7080
+ try {
7081
+ input = fs11.readFileSync(filePath);
7082
+ } catch {
7083
+ return passOutput();
7084
+ }
7085
+ const sharp = await loadSharp();
7086
+ if (sharp === null) return passOutput();
7087
+ try {
7088
+ const cfg = loadConfig().image_shrink;
7089
+ const limitInputPixels = cfg.max_image_pixels > 0 ? cfg.max_image_pixels : false;
7090
+ const meta = await sharp(input, { limitInputPixels }).metadata();
7091
+ const longestEdge = Math.max(meta.width ?? 0, meta.height ?? 0);
7092
+ qualifies = longestEdge > DEFAULT_MAX_DIMENSION;
7093
+ } catch {
7094
+ return passOutput();
7095
+ }
6906
7096
  }
6907
- const result = await shrinkImage(input);
7097
+ if (!qualifies) return passOutput();
7098
+ if (input === null) {
7099
+ try {
7100
+ input = fs11.readFileSync(filePath);
7101
+ } catch {
7102
+ return passOutput();
7103
+ }
7104
+ }
7105
+ const result = await shrinkImage(input, { sizeThresholdBytes: 0 });
6908
7106
  if (result === null) return passOutput();
6909
7107
  const basename9 = path12.basename(filePath);
6910
7108
  if (loadConfig().image_shrink.ocr_enabled) {
@@ -7160,7 +7358,11 @@ var EXTENSION_LANGUAGE = /* @__PURE__ */ new Map([
7160
7358
  [".tokens", "salesforce_markup"],
7161
7359
  [".page", "salesforce_markup"],
7162
7360
  [".component", "salesforce_markup"],
7163
- [".email", "salesforce_markup"]
7361
+ [".email", "salesforce_markup"],
7362
+ [".vue", "vue"],
7363
+ [".svelte", "svelte"],
7364
+ [".astro", "astro"],
7365
+ [".ipynb", "ipynb"]
7164
7366
  ]);
7165
7367
  var FILENAME_LANGUAGE = /* @__PURE__ */ new Map([
7166
7368
  ["dockerfile", "dockerfile"],
@@ -7878,12 +8080,6 @@ function postReadHandler(event) {
7878
8080
  registerHook("post_tool_use", postReadHandler, { toolName: "Read" });
7879
8081
 
7880
8082
  // src/hooks_grep.ts
7881
- function extractToolResponse(raw) {
7882
- return extractToolResponseField(raw, OUTPUT_FIRST_TOOL_RESPONSE_KEYS);
7883
- }
7884
- function countNonEmptyLines(text) {
7885
- return text.split(/\r\n|\r|\n/).filter((line) => line.length > 0).length;
7886
- }
7887
8083
  function grepIntInput(toolInput, key) {
7888
8084
  const value = toolInput[key];
7889
8085
  if (typeof value === "number" && Number.isFinite(value)) return value;
@@ -7896,7 +8092,7 @@ function grepIntInput(toolInput, key) {
7896
8092
  function grepSignature(toolInput) {
7897
8093
  const pattern = toolInput["pattern"];
7898
8094
  if (typeof pattern !== "string" || pattern === "") return null;
7899
- const path23 = typeof toolInput["path"] === "string" ? toolInput["path"] : "";
8095
+ const path24 = typeof toolInput["path"] === "string" ? toolInput["path"] : "";
7900
8096
  const outputMode = typeof toolInput["output_mode"] === "string" ? toolInput["output_mode"] : "files_with_matches";
7901
8097
  const glob = typeof toolInput["glob"] === "string" ? toolInput["glob"] : "";
7902
8098
  const type = typeof toolInput["type"] === "string" ? toolInput["type"] : "";
@@ -7911,7 +8107,7 @@ function grepSignature(toolInput) {
7911
8107
  const offset = grepIntInput(toolInput, "offset");
7912
8108
  return JSON.stringify([
7913
8109
  pattern,
7914
- path23,
8110
+ path24,
7915
8111
  outputMode,
7916
8112
  glob,
7917
8113
  type,
@@ -7926,95 +8122,47 @@ function grepSignature(toolInput) {
7926
8122
  offset
7927
8123
  ]);
7928
8124
  }
7929
- function postGrepHandler(event) {
7930
- try {
7931
- if (getToolName(event) !== "Grep") return passOutput();
7932
- const signature = grepSignature(getToolInput(event));
7933
- if (signature === null) return passOutput();
7934
- recordGrepQuery(signature, countNonEmptyLines(extractToolResponse(event.raw)));
7935
- return passOutput();
7936
- } catch {
7937
- return passOutput();
7938
- }
7939
- }
7940
- function preGrepDedupHandler(event) {
7941
- try {
7942
- if (getToolName(event) !== "Grep") return passOutput();
7943
- const toolInput = getToolInput(event);
7944
- const signature = grepSignature(toolInput);
7945
- if (signature === null) return passOutput();
7946
- const priorCount = getGrepMatchCount(signature);
7947
- if (priorCount === null) return passOutput();
7948
- if (priorCount < loadConfig().hints.grep_dedup_min_matches) return passOutput();
7949
- recordStat("grep_dedup_hint", 0, 0);
7950
- const pattern = typeof toolInput["pattern"] === "string" ? toolInput["pattern"] : "";
7951
- return contextOutput(
7952
- 'Note: an identical Grep for "' + pattern + '" already ran this session and returned ' + priorCount + (priorCount === 1 ? " match" : " matches") + ". If that result already answers this, you can skip re-running it."
7953
- );
7954
- } catch {
7955
- return passOutput();
7956
- }
7957
- }
8125
+ var { post: postGrepHandler, pre: preGrepDedupHandler } = makeDedupHintHandlers({
8126
+ toolName: "Grep",
8127
+ buildSignature: grepSignature,
8128
+ recordQuery: recordGrepQuery,
8129
+ getMatchCount: getGrepMatchCount,
8130
+ minMatchesConfigKey: "grep_dedup_min_matches",
8131
+ statName: "grep_dedup_hint"
8132
+ });
7958
8133
  registerHook("pre_tool_use", preGrepDedupHandler, { toolName: "Grep" });
7959
8134
  registerHook("post_tool_use", postGrepHandler, { toolName: "Grep" });
7960
8135
 
7961
8136
  // src/hooks_glob.ts
7962
- function extractToolResponse2(raw) {
7963
- return extractToolResponseField(raw, OUTPUT_FIRST_TOOL_RESPONSE_KEYS);
7964
- }
7965
- function countNonEmptyLines2(text) {
7966
- return text.split(/\r\n|\r|\n/).filter((line) => line.length > 0).length;
7967
- }
7968
8137
  function globSignature(toolInput) {
7969
8138
  const pattern = toolInput["pattern"];
7970
8139
  if (typeof pattern !== "string" || pattern === "") return null;
7971
- const path23 = typeof toolInput["path"] === "string" ? toolInput["path"] : "";
7972
- return JSON.stringify([pattern, path23]);
7973
- }
7974
- function postGlobHandler(event) {
7975
- try {
7976
- if (getToolName(event) !== "Glob") return passOutput();
7977
- const signature = globSignature(getToolInput(event));
7978
- if (signature === null) return passOutput();
7979
- recordGlobQuery(signature, countNonEmptyLines2(extractToolResponse2(event.raw)));
7980
- return passOutput();
7981
- } catch {
7982
- return passOutput();
7983
- }
7984
- }
7985
- function preGlobDedupHandler(event) {
7986
- try {
7987
- if (getToolName(event) !== "Glob") return passOutput();
7988
- const toolInput = getToolInput(event);
7989
- const signature = globSignature(toolInput);
7990
- if (signature === null) return passOutput();
7991
- const priorCount = getGlobMatchCount(signature);
7992
- if (priorCount === null) return passOutput();
7993
- if (priorCount < loadConfig().hints.glob_dedup_min_matches) return passOutput();
7994
- recordStat("glob_dedup_hint", 0, 0);
7995
- const pattern = typeof toolInput["pattern"] === "string" ? toolInput["pattern"] : "";
7996
- return contextOutput(
7997
- 'Note: an identical Glob for "' + pattern + '" already ran this session and returned ' + priorCount + (priorCount === 1 ? " match" : " matches") + ". If that result already answers this, you can skip re-running it."
7998
- );
7999
- } catch {
8000
- return passOutput();
8001
- }
8002
- }
8140
+ const path24 = typeof toolInput["path"] === "string" ? toolInput["path"] : "";
8141
+ return JSON.stringify([pattern, path24]);
8142
+ }
8143
+ var { post: postGlobHandler, pre: preGlobDedupHandler } = makeDedupHintHandlers({
8144
+ toolName: "Glob",
8145
+ buildSignature: globSignature,
8146
+ recordQuery: recordGlobQuery,
8147
+ getMatchCount: getGlobMatchCount,
8148
+ minMatchesConfigKey: "glob_dedup_min_matches",
8149
+ statName: "glob_dedup_hint"
8150
+ });
8003
8151
  registerHook("pre_tool_use", preGlobDedupHandler, { toolName: "Glob" });
8004
8152
  registerHook("post_tool_use", postGlobHandler, { toolName: "Glob" });
8005
8153
 
8006
8154
  // src/hooks_edit.ts
8007
- import * as path20 from "node:path";
8155
+ import * as path21 from "node:path";
8008
8156
  import { statSync as statSync11 } from "node:fs";
8009
8157
 
8010
8158
  // src/hooks_index.ts
8011
8159
  import * as fs17 from "node:fs";
8012
- import * as path19 from "node:path";
8160
+ import * as path20 from "node:path";
8013
8161
 
8014
8162
  // src/worker.ts
8015
8163
  import { spawn as spawn3 } from "node:child_process";
8016
8164
  import * as fs16 from "node:fs";
8017
- import * as path18 from "node:path";
8165
+ import * as path19 from "node:path";
8018
8166
  import { fileURLToPath } from "node:url";
8019
8167
 
8020
8168
  // src/parser.ts
@@ -8052,6 +8200,44 @@ registerReset(() => {
8052
8200
  PIPELINE_RETRY_DELAY_MS = DEFAULT_PIPELINE_RETRY_DELAY_MS;
8053
8201
  });
8054
8202
 
8203
+ // src/doc_embed_extract.ts
8204
+ import * as path17 from "node:path";
8205
+
8206
+ // src/pdf_extract.ts
8207
+ var loadPdfjs = createLazyModuleLoader(async () => {
8208
+ const mod = await import("pdfjs-dist/legacy/build/pdf.mjs");
8209
+ if (typeof import.meta.resolve === "function") {
8210
+ try {
8211
+ mod.GlobalWorkerOptions.workerSrc = await import.meta.resolve("pdfjs-dist/legacy/build/pdf.worker.mjs");
8212
+ } catch {
8213
+ }
8214
+ }
8215
+ return mod;
8216
+ }, "pdf-extract disabled (pdfjs-dist unavailable)");
8217
+
8218
+ // src/ooxml_extract.ts
8219
+ var loadFflate = createLazyModuleLoader(
8220
+ async () => await import("fflate"),
8221
+ "office-file reading disabled (fflate unavailable)"
8222
+ );
8223
+ var loadXmlParser = createLazyModuleLoader(
8224
+ async () => await import("fast-xml-parser"),
8225
+ "office-file reading disabled (fast-xml-parser unavailable)"
8226
+ );
8227
+ var MAX_OOXML_INPUT_BYTES = 50 * 1024 * 1024;
8228
+
8229
+ // src/xlsx_extract.ts
8230
+ var loadExcelJs = createLazyModuleLoader(async () => {
8231
+ const mod = await import("exceljs");
8232
+ return mod.default ?? mod;
8233
+ }, "xlsx reading disabled (exceljs package unavailable)");
8234
+
8235
+ // src/doc_embed_extract.ts
8236
+ var EMBEDDABLE_DOCUMENT_EXTENSIONS = /* @__PURE__ */ new Set([".pdf", ".docx", ".pptx", ".xlsx"]);
8237
+ function isEmbeddableDocument(filePath) {
8238
+ return EMBEDDABLE_DOCUMENT_EXTENSIONS.has(path17.extname(filePath).toLowerCase());
8239
+ }
8240
+
8055
8241
  // src/languages/csharp.ts
8056
8242
  var DELEGATE_RE = new RegExp(
8057
8243
  "^\\s*(?:public|protected|private|internal)?\\s*delegate\\s+(?:[A-Za-z_][A-Za-z0-9_<>?,\\[\\]\\s]*?)\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*[<(]"
@@ -8084,20 +8270,21 @@ var PROP_RE = new RegExp(
8084
8270
  );
8085
8271
 
8086
8272
  // src/languages/kotlin.ts
8273
+ var RECEIVER_RE = "(?:[A-Za-z_][A-Za-z0-9_<>?.,\\s]*\\.)?";
8087
8274
  var FUN_RE = new RegExp(
8088
- "^\\s*(?:(?:public|internal|protected|private|open|override|abstract|suspend|inline|infix|operator|external|actual|expect|final|sealed|tailrec)\\s+)*fun\\s+(?:<[^>]*>\\s*)?([A-Za-z_][A-Za-z0-9_]*)\\s*[(<]"
8275
+ "^\\s*(?:(?:public|internal|protected|private|open|override|abstract|suspend|inline|infix|operator|external|actual|expect|final|sealed|tailrec)\\s+)*fun\\s+(?:<[^>]*>\\s*)?" + RECEIVER_RE + "([A-Za-z_][A-Za-z0-9_]*)\\s*[(<]"
8089
8276
  );
8090
8277
  var CONST_RE = new RegExp(
8091
8278
  "^\\s*(?:(?:public|internal|protected|private|open|override|abstract|final|actual|expect|const|lateinit|companion)\\s+)*(?:const\\s+)?val\\s+([A-Z_][A-Z0-9_]*)\\s*(?::|=)"
8092
8279
  );
8093
8280
  var CLASS_HEADER_RE2 = new RegExp(
8094
- "^(?:(?:public|internal|protected|private|open|abstract|sealed|data|inner|expect|actual|value|annotation|fun)\\s+)*(?:class|interface|object|enum\\s+class)\\s+([A-Za-z_][A-Za-z0-9_]*)"
8281
+ "^(?:(?:public|internal|protected|private|open|abstract|sealed|data|inner|expect|actual|value|annotation|fun)\\s+)*(class|interface|object|enum\\s+class)\\s+([A-Za-z_][A-Za-z0-9_]*)"
8095
8282
  );
8096
8283
  var COMPANION_RE = new RegExp(
8097
8284
  "^(?:(?:public|internal|protected|private)\\s+)*companion\\s+object(?:\\s+([A-Za-z_][A-Za-z0-9_]*))?\\b"
8098
8285
  );
8099
8286
  var TOP_FUN_RE = new RegExp(
8100
- "^(?:(?:public|internal|private|suspend|inline|infix|operator|external|actual|expect|tailrec)\\s+)*fun\\s+(?:<[^>]*>\\s*)?([A-Za-z_][A-Za-z0-9_]*)\\s*[(<]"
8287
+ "^(?:(?:public|internal|private|suspend|inline|infix|operator|external|actual|expect|tailrec)\\s+)*fun\\s+(?:<[^>]*>\\s*)?" + RECEIVER_RE + "([A-Za-z_][A-Za-z0-9_]*)\\s*[(<]"
8101
8288
  );
8102
8289
 
8103
8290
  // src/languages/sql_idx.ts
@@ -8141,6 +8328,7 @@ var METHOD_RE3 = new RegExp(
8141
8328
  "gm"
8142
8329
  );
8143
8330
  var PURE_ANNOTATION_LINE_RE = new RegExp(`^(?:@${IDENT}(?:\\([^)]*\\))?[ \\t]*)+$`);
8331
+ var ANNOTATION_OPENER_RE = new RegExp(`^@${IDENT}\\(`);
8144
8332
 
8145
8333
  // src/parser.ts
8146
8334
  var _require4 = createRequire5(import.meta.url);
@@ -8305,14 +8493,14 @@ var JS_NOISE = REF_NOISE_BY_LANG.get("typescript") ?? /* @__PURE__ */ new Set();
8305
8493
 
8306
8494
  // src/index_prune.ts
8307
8495
  import * as fs15 from "node:fs";
8308
- import * as path17 from "node:path";
8496
+ import * as path18 from "node:path";
8309
8497
  function isTooShallowToPrune(rootPrefix) {
8310
8498
  const segments = rootPrefix.split("/").filter((s) => s.length > 0 && !/^[a-z]:$/i.test(s));
8311
8499
  if (segments.length === 2 && segments[0]?.toLowerCase() === "mnt" && /^[a-z]$/i.test(segments[1] ?? "")) return true;
8312
8500
  return segments.length === 0;
8313
8501
  }
8314
8502
  function recordKnownRoot(filePath, dbPath = globalDbPath()) {
8315
- const project = findProject(path17.dirname(filePath));
8503
+ const project = findProject(path18.dirname(filePath));
8316
8504
  if (project === null || isTooShallowToPrune(project.root)) return;
8317
8505
  const db = getDb(dbPath);
8318
8506
  db.prepare(
@@ -8323,7 +8511,7 @@ function recordKnownRoot(filePath, dbPath = globalDbPath()) {
8323
8511
  var KNOWN_ROOT_MISSING_GRACE_MS = 7 * 24 * 60 * 60 * 1e3;
8324
8512
  var KNOWN_ROOT_RECORD_MIN_INTERVAL_MS = 60 * 60 * 1e3;
8325
8513
  function knownRootRecordMarkerPath(dir) {
8326
- return path17.join(dir, "known-root-record.marker");
8514
+ return path18.join(dir, "known-root-record.marker");
8327
8515
  }
8328
8516
  function recordKnownRootThrottled(filePath, dir = dataDir(), dbPath = globalDbPath()) {
8329
8517
  const markerPath = knownRootRecordMarkerPath(dir);
@@ -8358,10 +8546,10 @@ function parseDirtyQueueLines(raw) {
8358
8546
  return out;
8359
8547
  }
8360
8548
  function workerPidPath(dir = dataDir()) {
8361
- return path18.join(dir, "worker.pid");
8549
+ return path19.join(dir, "worker.pid");
8362
8550
  }
8363
8551
  function workerErrorLogPath(dir) {
8364
- return path18.join(dir, "worker-errors.log");
8552
+ return path19.join(dir, "worker-errors.log");
8365
8553
  }
8366
8554
  var WORKER_ERROR_LOG_MAX_BYTES = 5 * 1024 * 1024;
8367
8555
  var CORRUPT_QUARANTINE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
@@ -8401,7 +8589,7 @@ function isWorkerRunning(dir = dataDir()) {
8401
8589
  return pidAlive(pid);
8402
8590
  }
8403
8591
  function workerHealthCheckMarkerPath(dir) {
8404
- return path18.join(dir, "worker-healthcheck.marker");
8592
+ return path19.join(dir, "worker-healthcheck.marker");
8405
8593
  }
8406
8594
  var WORKER_HEALTHCHECK_MIN_INTERVAL_MS = 5 * 60 * 1e3;
8407
8595
  function ensureWorkerAlive(dir = dataDir()) {
@@ -8504,11 +8692,11 @@ var KNOWN_ROOTS_SWEEP_INTERVAL_MS = 24 * 60 * 60 * 1e3;
8504
8692
 
8505
8693
  // src/hooks_index.ts
8506
8694
  function dirtyQueuePath() {
8507
- return path19.join(dataDir(), "queue", "dirty.txt");
8695
+ return path20.join(dataDir(), "queue", "dirty.txt");
8508
8696
  }
8509
8697
  function appendDirtyPath(normalizedPath) {
8510
8698
  const queuePath = dirtyQueuePath();
8511
- const dir = path19.dirname(queuePath);
8699
+ const dir = path20.dirname(queuePath);
8512
8700
  try {
8513
8701
  fs17.mkdirSync(dir, { recursive: true });
8514
8702
  } catch (e) {
@@ -8523,6 +8711,12 @@ function appendDirtyPath(normalizedPath) {
8523
8711
  fs17.appendFileSync(queuePath, `${leadingNewline}${normalizedPath}
8524
8712
  `);
8525
8713
  }
8714
+ function enqueueDirtyPathSafe(filePath, opts) {
8715
+ try {
8716
+ appendDirtyPath(opts?.alreadyResolved === true ? filePath : resolveIndexPath(filePath));
8717
+ } catch {
8718
+ }
8719
+ }
8526
8720
  function getDirtyPaths() {
8527
8721
  const queuePath = dirtyQueuePath();
8528
8722
  let raw;
@@ -8536,9 +8730,9 @@ function getDirtyPaths() {
8536
8730
  function preCompactIndexHandler(_event) {
8537
8731
  const paths = getDirtyPaths();
8538
8732
  if (paths.length > 0) {
8539
- const sidecar = path19.join(dataDir(), "queue", "pending.txt");
8733
+ const sidecar = path20.join(dataDir(), "queue", "pending.txt");
8540
8734
  try {
8541
- fs17.mkdirSync(path19.dirname(sidecar), { recursive: true });
8735
+ fs17.mkdirSync(path20.dirname(sidecar), { recursive: true });
8542
8736
  atomicWriteBytes(sidecar, Buffer.from(`${paths.join("\n")}
8543
8737
  `, "utf8"));
8544
8738
  } catch {
@@ -8575,7 +8769,7 @@ function postEditHandlerInner(event) {
8575
8769
  if (loadConfig().hints.stable_doc_compacts) {
8576
8770
  markCompactStale(compactPathFor(normalized));
8577
8771
  }
8578
- const editedBasename = path20.basename(normalized);
8772
+ const editedBasename = path21.basename(normalized);
8579
8773
  if (/\.(md|mdx|markdown|rst)$/i.test(editedBasename)) {
8580
8774
  let editedSize = Infinity;
8581
8775
  try {
@@ -8602,6 +8796,7 @@ registerHook("post_tool_use", postEditHandler, { toolName: "NotebookEdit" });
8602
8796
  // src/hooks_write.ts
8603
8797
  import { readFileSync as readFileSync13, statSync as statSync12 } from "node:fs";
8604
8798
  var MAX_LINES_FOR_DIFF = 4e3;
8799
+ var MAX_OLD_FILE_BYTES_FOR_DIFF = 4 * 1024 * 1024;
8605
8800
  function splitLines(text) {
8606
8801
  return text.split(/\r\n|\r|\n/);
8607
8802
  }
@@ -8634,6 +8829,7 @@ function preWriteRewriteHandler(event) {
8634
8829
  return passOutput();
8635
8830
  }
8636
8831
  if (!stat2.isFile()) return passOutput();
8832
+ if (stat2.size > MAX_OLD_FILE_BYTES_FOR_DIFF) return passOutput();
8637
8833
  let oldContent;
8638
8834
  try {
8639
8835
  oldContent = readFileSync13(filePath, "utf-8");
@@ -8751,11 +8947,11 @@ function gitStateFingerprintSync(cwd) {
8751
8947
  }
8752
8948
  }
8753
8949
  var DIR_FINGERPRINT_LISTING_CAP_ENTRIES = 1e4;
8754
- function dirStateFingerprintSync(path23) {
8950
+ function dirStateFingerprintSync(path24) {
8755
8951
  try {
8756
- const stat2 = statSync13(path23);
8952
+ const stat2 = statSync13(path24);
8757
8953
  if (!stat2.isDirectory()) return null;
8758
- const entries = readdirSync10(path23);
8954
+ const entries = readdirSync10(path24);
8759
8955
  if (entries.length <= DIR_FINGERPRINT_LISTING_CAP_ENTRIES) {
8760
8956
  return shortFingerprint(entries.slice().sort().join("\0"));
8761
8957
  }
@@ -8765,12 +8961,12 @@ function dirStateFingerprintSync(path23) {
8765
8961
  }
8766
8962
  }
8767
8963
  var FILE_FINGERPRINT_CONTENT_CAP_BYTES = 2 * 1024 * 1024;
8768
- function fileStateFingerprintSync(path23) {
8964
+ function fileStateFingerprintSync(path24) {
8769
8965
  try {
8770
- const stat2 = statSync13(path23);
8966
+ const stat2 = statSync13(path24);
8771
8967
  if (!stat2.isFile()) return null;
8772
8968
  if (stat2.size <= FILE_FINGERPRINT_CONTENT_CAP_BYTES) {
8773
- return shortFingerprint(readFileSync14(path23));
8969
+ return shortFingerprint(readFileSync14(path24));
8774
8970
  }
8775
8971
  return shortFingerprint(`${stat2.mtimeMs}\0${stat2.size}`);
8776
8972
  } catch {
@@ -8964,19 +9160,20 @@ function summarizeOutputDelta(oldOutput, newOutput) {
8964
9160
  async function storeBashOutput(command, output, exitCode, cwd = null) {
8965
9161
  const id = await commandHash(command, cwd);
8966
9162
  const fingerprints = computeBashFingerprints(command, cwd);
9163
+ const redactedOutput = redactSecrets(output).text;
8967
9164
  const entry = {
8968
9165
  id,
8969
9166
  command,
8970
- output,
9167
+ output: redactedOutput,
8971
9168
  exitCode,
8972
9169
  storedAt: Date.now(),
8973
- sizeBytes: Buffer.byteLength(output, "utf-8"),
9170
+ sizeBytes: Buffer.byteLength(redactedOutput, "utf-8"),
8974
9171
  ...fingerprints ? { fingerprints } : {}
8975
9172
  };
8976
9173
  _byId.set(id, entry);
8977
9174
  storeBlob(BASH_OUTPUT_SUBDIR, id, entry);
8978
9175
  indexRecallEntry("bash", id, command, `${command}
8979
- ${output}`, entry.storedAt);
9176
+ ${redactedOutput}`, entry.storedAt);
8980
9177
  return id;
8981
9178
  }
8982
9179
  function coerceBashEntry(raw) {
@@ -9087,7 +9284,14 @@ function buildManifest(sessionId) {
9087
9284
  );
9088
9285
  lines.push(...buildSafeToDiscardSection(files));
9089
9286
  lines.push(...buildMemEpochSection());
9090
- return lines.join("\n");
9287
+ return capManifestChars(lines.join("\n"));
9288
+ }
9289
+ function capManifestChars(manifest) {
9290
+ const cap = loadConfig().compact_assist.max_manifest_chars;
9291
+ if (cap <= 0 || manifest.length <= cap) return manifest;
9292
+ const omitted = manifest.length - cap;
9293
+ return manifest.slice(0, cap) + `
9294
+ ...(manifest truncated at ${cap} chars; ${omitted} chars omitted)`;
9091
9295
  }
9092
9296
  function buildSafeToDiscardSection(files) {
9093
9297
  const rerunHashes = getSessionBashReruns();
@@ -9148,6 +9352,7 @@ function buildMemEpochSection() {
9148
9352
  ];
9149
9353
  }
9150
9354
  function preCompactHandler(event) {
9355
+ if (!loadConfig().compact_assist.enabled) return passOutput();
9151
9356
  return contextOutput(buildManifest(event.sessionId));
9152
9357
  }
9153
9358
  registerHook("pre_compact", preCompactHandler);
@@ -9231,11 +9436,12 @@ function cacheIdForUrl(url) {
9231
9436
  }
9232
9437
  function storeWebOutput(url, content, dedupKey = url) {
9233
9438
  const cacheId = cacheIdForUrl(dedupKey);
9234
- _byId2.set(cacheId, content);
9439
+ const redactedContent = redactSecrets(content).text;
9440
+ _byId2.set(cacheId, redactedContent);
9235
9441
  _urlIndex.set(url, cacheId);
9236
- storeBlob(WEB_OUTPUT_SUBDIR, cacheId, { url, content });
9442
+ storeBlob(WEB_OUTPUT_SUBDIR, cacheId, { url, content: redactedContent });
9237
9443
  indexRecallEntry("web", cacheId, url, `${url}
9238
- ${content}`, Date.now());
9444
+ ${redactedContent}`, Date.now());
9239
9445
  return cacheId;
9240
9446
  }
9241
9447
  function coerceWebBlob(raw) {
@@ -13546,8 +13752,8 @@ function trimCharacterEnd(str, char) {
13546
13752
  function unicodeEscape(str) {
13547
13753
  return str.replace(/[\s\S]/g, (c) => "\\u" + c.charCodeAt().toString(16).padStart(4, "0"));
13548
13754
  }
13549
- function get(obj, path23) {
13550
- for (const key of path23) {
13755
+ function get(obj, path24) {
13756
+ for (const key of path24) {
13551
13757
  if (!obj) {
13552
13758
  return void 0;
13553
13759
  }
@@ -14748,8 +14954,8 @@ function withBrackets(str, brackets) {
14748
14954
  const rbr = typeof brackets[1] === "string" ? brackets[1] : "]";
14749
14955
  return lbr + str + rbr;
14750
14956
  }
14751
- function pathRewrite(path23, rewriter, baseUrl, metadata, elem) {
14752
- const modifiedPath = typeof rewriter === "function" ? rewriter(path23, metadata, elem) : path23;
14957
+ function pathRewrite(path24, rewriter, baseUrl, metadata, elem) {
14958
+ const modifiedPath = typeof rewriter === "function" ? rewriter(path24, metadata, elem) : path24;
14753
14959
  return modifiedPath[0] === "/" && baseUrl ? trimCharacterEnd(baseUrl, "/") + modifiedPath : modifiedPath;
14754
14960
  }
14755
14961
  function formatImage(elem, walk, builder, formatOptions) {
@@ -15073,9 +15279,9 @@ function handleDeprecatedOptions(options) {
15073
15279
  options.selectors.push(...tagDefinitions);
15074
15280
  options.selectors = mergeDuplicatesPreferLast(options.selectors, (s) => s.selector);
15075
15281
  }
15076
- function set(obj, path23, value) {
15077
- const valueKey = path23.pop();
15078
- for (const key of path23) {
15282
+ function set(obj, path24, value) {
15283
+ const valueKey = path24.pop();
15284
+ for (const key of path24) {
15079
15285
  let nested = obj[key];
15080
15286
  if (!nested) {
15081
15287
  nested = {};
@@ -15151,7 +15357,7 @@ ${safeText}
15151
15357
  }
15152
15358
 
15153
15359
  // src/hooks_fetch.ts
15154
- function extractToolResponse3(raw) {
15360
+ function extractToolResponse(raw) {
15155
15361
  return extractToolResponseField(raw, BODY_FIRST_TOOL_RESPONSE_KEYS);
15156
15362
  }
15157
15363
  function resolveWebFetchContext(event) {
@@ -15201,7 +15407,7 @@ function postFetchHandler(event) {
15201
15407
  }
15202
15408
  const { toolInput, url } = ctx;
15203
15409
  recordStat("web_fetch");
15204
- const body = extractToolResponse3(event.raw);
15410
+ const body = extractToolResponse(event.raw);
15205
15411
  const injCfg = loadConfig().injection;
15206
15412
  const injectionMatches = injCfg.enabled && body ? scanForInjectionPatterns(body) : [];
15207
15413
  if (injectionMatches.length > 0) {
@@ -15326,16 +15532,6 @@ async function postSkillHandler(event) {
15326
15532
  registerHook("pre_tool_use", preSkillHandler, { toolName: "Skill" });
15327
15533
  registerHook("post_tool_use", postSkillHandler, { toolName: "Skill" });
15328
15534
 
15329
- // src/bash_compress.ts
15330
- var ANSI_ESCAPE_RE = (
15331
- // eslint-disable-next-line no-control-regex -- intentionally matches ESC (\x1B) and BEL (\x07) control bytes
15332
- /\x1B\[[0-?]*[ -/]*[@-~]|\x1B\].*?(?:\x07|\x1B\\)|\x1B[PX^_].*?\x1B\\|\x1B[@-Z\\-_]/gs
15333
- );
15334
- function stripAnsiCodes(text) {
15335
- if (!text.includes("\x1B")) return text;
15336
- return text.replace(ANSI_ESCAPE_RE, "");
15337
- }
15338
-
15339
15535
  // src/tool_filters/helpers.ts
15340
15536
  var DEFAULT_MAX_LINES = 1e3;
15341
15537
  var DEFAULT_MAX_BYTES = 64 * 1024;
@@ -15407,7 +15603,10 @@ function sanitizeControlChars(text) {
15407
15603
  }
15408
15604
  function stripProgress(text) {
15409
15605
  if (!text.includes("\r")) return text;
15410
- return text.split("\n").map((line) => line.includes("\r") ? line.slice(line.lastIndexOf("\r") + 1) : line).join("\n");
15606
+ return text.split("\n").map((line) => {
15607
+ const trimmed = line.endsWith("\r") ? line.slice(0, -1) : line;
15608
+ return trimmed.includes("\r") ? trimmed.slice(trimmed.lastIndexOf("\r") + 1) : trimmed;
15609
+ }).join("\n");
15411
15610
  }
15412
15611
  function normalise(text, opts = {}) {
15413
15612
  if (!text) return "";
@@ -16317,7 +16516,7 @@ function makeLanguageFilter(cfg) {
16317
16516
  let deduped = false;
16318
16517
  for (let i = 0; i < dRules.length; i++) {
16319
16518
  if (dRules[i].re.test(line)) {
16320
- const key = line.slice(0, dRules[i].keyLen ?? 40);
16519
+ const key = dRules[i].keyLen !== void 0 ? line.slice(0, dRules[i].keyLen) : line;
16321
16520
  const ds = dState[i];
16322
16521
  const seen = (ds.seen.get(key) ?? 0) + 1;
16323
16522
  ds.seen.set(key, seen);
@@ -17742,61 +17941,42 @@ function compressNpmAuditJson(text) {
17742
17941
  const result = { ...data, vulnerabilities: keep };
17743
17942
  return JSON.stringify(result, null, 2);
17744
17943
  }
17944
+ var _NPM_AUDIT_SEVERITY_LINE_RE = /^Severity:\s*(critical|high|moderate|low)/i;
17945
+ var _NPM_AUDIT_LEGACY_HDR_RE = /^(critical|high|moderate|low)\s+\S/i;
17745
17946
  function compressNpmAuditHuman(text) {
17746
- const lines = text.split("\n");
17947
+ const paragraphs = [[]];
17948
+ for (const line of text.split("\n")) {
17949
+ if (line.trim() === "") paragraphs.push([]);
17950
+ else paragraphs[paragraphs.length - 1].push(line);
17951
+ }
17952
+ const blockSeverity = (para) => {
17953
+ for (const l of para) {
17954
+ const modern = _NPM_AUDIT_SEVERITY_LINE_RE.exec(l);
17955
+ if (modern) return modern[1].toLowerCase();
17956
+ if (_NPM_AUDIT_LEGACY_HDR_RE.test(l)) return l.trim().split(/\s+/)[0].toLowerCase();
17957
+ }
17958
+ return null;
17959
+ };
17747
17960
  const kept = [];
17748
17961
  const severityCounts = {};
17749
17962
  let blockCount = 0;
17750
- let inBlock = false;
17751
- let blockLines = [];
17752
- const SEVERITY_HDR_RE = /^(critical|high|moderate|low)\s+\S/i;
17753
- const FOUND_SUMMARY_RE = /^found \d+ vulnerabilit/i;
17754
- for (const line of lines) {
17755
- if (FOUND_SUMMARY_RE.test(line)) {
17756
- kept.push(line);
17757
- continue;
17758
- }
17759
- if (SEVERITY_HDR_RE.test(line)) {
17760
- if (inBlock && blockLines.length) {
17761
- if (blockCount < 10) kept.push(...blockLines);
17762
- else {
17763
- const sev = (blockLines[0] ?? "").split(/\s+/)[0].toLowerCase();
17764
- severityCounts[sev] = (severityCounts[sev] ?? 0) + 1;
17765
- }
17766
- blockCount += 1;
17767
- }
17768
- inBlock = true;
17769
- blockLines = [line];
17770
- continue;
17771
- }
17772
- if (inBlock) {
17773
- if (line.trim() === "") {
17774
- blockLines.push(line);
17775
- if (blockCount < 10) kept.push(...blockLines);
17776
- else {
17777
- const sev = (blockLines[0] ?? "").split(/\s+/)[0].toLowerCase();
17778
- severityCounts[sev] = (severityCounts[sev] ?? 0) + 1;
17779
- }
17780
- blockLines = [];
17781
- blockCount += 1;
17782
- inBlock = false;
17783
- } else {
17784
- blockLines.push(line);
17785
- }
17963
+ for (const para of paragraphs) {
17964
+ if (!para.length) continue;
17965
+ const severity = blockSeverity(para);
17966
+ if (severity === null || blockCount < 10) {
17967
+ if (kept.length) kept.push("");
17968
+ kept.push(...para);
17969
+ if (severity !== null) blockCount += 1;
17786
17970
  } else {
17787
- kept.push(line);
17788
- }
17789
- }
17790
- if (inBlock && blockLines.length) {
17791
- if (blockCount < 10) kept.push(...blockLines);
17792
- else {
17793
- const sev = (blockLines[0] ?? "").split(/\s+/)[0].toLowerCase();
17794
- severityCounts[sev] = (severityCounts[sev] ?? 0) + 1;
17971
+ severityCounts[severity] = (severityCounts[severity] ?? 0) + 1;
17972
+ blockCount += 1;
17795
17973
  }
17796
17974
  }
17797
17975
  if (Object.keys(severityCounts).length) {
17798
- const parts = Object.entries(severityCounts).map(([s, n]) => `${n} ${s}`);
17799
- kept.push(`[token-goat: collapsed ${Object.values(severityCounts).reduce((a, b) => a + b, 0)} additional advisory blocks (${parts.join(", ")})]`);
17976
+ kept.push(
17977
+ "",
17978
+ `[token-goat: collapsed ${Object.values(severityCounts).reduce((a, b) => a + b, 0)} additional advisory blocks (${Object.entries(severityCounts).map(([s, n]) => `${n} ${s}`).join(", ")})]`
17979
+ );
17800
17980
  }
17801
17981
  return squeezeBlankLines(kept.join("\n"));
17802
17982
  }
@@ -17872,6 +18052,7 @@ function _compressEslintStanza(text) {
17872
18052
  if (!currentFile.length) return;
17873
18053
  const header = currentFile[0];
17874
18054
  const body = currentFile.slice(1);
18055
+ out.push(header);
17875
18056
  let perRule = /* @__PURE__ */ new Map();
17876
18057
  for (const line of body) {
17877
18058
  if (!_ESLINT_LOC_RE.test(line)) {
@@ -17887,7 +18068,6 @@ function _compressEslintStanza(text) {
17887
18068
  bucket.push(line);
17888
18069
  perRule.set(rule, bucket);
17889
18070
  }
17890
- out.push(header);
17891
18071
  out.push(..._emitEslintRules(perRule));
17892
18072
  currentFile = [];
17893
18073
  }
@@ -19554,7 +19734,28 @@ function _hunkWhitespaceEolOnlyPairCount(hunkLines) {
19554
19734
  }
19555
19735
  return removed.length;
19556
19736
  }
19557
- function _compressGitDiffBody(stdout, stderr) {
19737
+ function _hunkDensity(hunkText) {
19738
+ const content = hunkText.split("\n").slice(1);
19739
+ if (content.length === 0) return 0;
19740
+ const changed = content.filter((l) => l.startsWith("+") || l.startsWith("-")).length;
19741
+ return changed / content.length;
19742
+ }
19743
+ function _capHunksByDensity(hunks, maxHunksPerFile) {
19744
+ const real = hunks.slice(1);
19745
+ if (maxHunksPerFile <= 0 || real.length <= maxHunksPerFile) return { hunks, note: null };
19746
+ const scored = real.map((h, i) => ({ i, h, d: _hunkDensity(h) }));
19747
+ const keepSet = new Set(
19748
+ scored.slice().sort((a, b) => b.d - a.d).slice(0, maxHunksPerFile).map((x) => x.i)
19749
+ );
19750
+ const dropped = scored.filter((x) => !keepSet.has(x.i));
19751
+ const avg = dropped.length ? dropped.reduce((a, x) => a + x.d, 0) / dropped.length : 0;
19752
+ const kept = scored.filter((x) => keepSet.has(x.i)).map((x) => x.h);
19753
+ return {
19754
+ hunks: [hunks[0] ?? "", ...kept],
19755
+ note: `[token-goat: ${dropped.length} more hunks, avg density ${avg.toFixed(2)} \u2014 likely whitespace/formatting]`
19756
+ };
19757
+ }
19758
+ function _compressGitDiffBody(stdout, stderr, maxHunksPerFile = 10) {
19558
19759
  const MAX_HUNK_CHANGED = 50;
19559
19760
  const HUNK_HEAD_KEEP = 20;
19560
19761
  const HUNK_TAIL_KEEP = 5;
@@ -19572,11 +19773,13 @@ function _compressGitDiffBody(stdout, stderr) {
19572
19773
  outBlocks.push((blockLines[0] ?? "") + "\n" + binaryLine);
19573
19774
  continue;
19574
19775
  }
19575
- const hunks = splitBlocks(block, _GIT_DIFF_HUNK_RE);
19776
+ let hunks = splitBlocks(block, _GIT_DIFF_HUNK_RE);
19576
19777
  if (hunks.length <= 1) {
19577
19778
  outBlocks.push(block);
19578
19779
  continue;
19579
19780
  }
19781
+ const { hunks: cappedHunks, note: hunkCapNote } = _capHunksByDensity(hunks, maxHunksPerFile);
19782
+ hunks = cappedHunks;
19580
19783
  const compressedHunks = [];
19581
19784
  for (const hunk of hunks) {
19582
19785
  const hunkLines = hunk.split("\n");
@@ -19623,6 +19826,7 @@ function _compressGitDiffBody(stdout, stderr) {
19623
19826
  }
19624
19827
  }
19625
19828
  }
19829
+ if (hunkCapNote) compressedHunks.push(hunkCapNote);
19626
19830
  outBlocks.push(compressedHunks.join("\n"));
19627
19831
  }
19628
19832
  let text = outBlocks.join("\n");
@@ -19670,7 +19874,13 @@ function _compressGitDiffEnhanced(stdout, stderr, argv) {
19670
19874
  const flags = new Set(argv);
19671
19875
  const isStat = flags.has("--stat") || flags.has("--shortstat") || flags.has("--name-only");
19672
19876
  if (isStat) return _compressGitDiffStat(stdout, stderr, argv);
19673
- return _compressGitDiffBody(stdout, stderr);
19877
+ let maxHunksPerFile;
19878
+ try {
19879
+ maxHunksPerFile = loadConfig().bash_diff.max_hunks_per_file;
19880
+ } catch {
19881
+ maxHunksPerFile = void 0;
19882
+ }
19883
+ return maxHunksPerFile === void 0 ? _compressGitDiffBody(stdout, stderr) : _compressGitDiffBody(stdout, stderr, maxHunksPerFile);
19674
19884
  }
19675
19885
  var GitDiffFilter = class extends GitBaseFilter {
19676
19886
  name = "git-diff";
@@ -20820,7 +21030,7 @@ var MesonFilter = class extends ToolFilter {
20820
21030
  }
20821
21031
  };
20822
21032
  var MSBUILD_ERROR_RE = /.*\(\d+(?:,\d+)?\)\s*:\s*error\s+/;
20823
- var MSBUILD_WARNING_RE = /(.*?)\((\d+)(?:,\d+)?\)\s*:\s*warning\s+(\w+)/;
21033
+ var MSBUILD_WARNING_RE = /(.*?)\((\d+)(?:,(\d+))?\)\s*:\s*warning\s+(\w+)/;
20824
21034
  var MSBUILD_BUILD_STARTED_RE = /^Build started/;
20825
21035
  var MSBUILD_PROJECT_BUILDING_RE = /^------ Build started: Project:/;
20826
21036
  var MSBUILD_COPY_RE = /^\s+(?:Copy|CopyFilesToOutputDirectory|CopyToOutputDirectory)\b/;
@@ -20857,8 +21067,9 @@ var MSBuildFilter = class extends ToolFilter {
20857
21067
  if (warnMatch) {
20858
21068
  const filePath = (warnMatch[1] ?? "").trim();
20859
21069
  const lineNum = warnMatch[2] ?? "";
20860
- const code = warnMatch[3] ?? "";
20861
- const dedupKey = `${filePath}|${lineNum}|${code}`;
21070
+ const colNum = warnMatch[3] ?? "";
21071
+ const code = warnMatch[4] ?? "";
21072
+ const dedupKey = `${filePath}|${lineNum}|${colNum}|${code}`;
20862
21073
  if (!seenWarningCodes.has(dedupKey)) {
20863
21074
  seenWarningCodes.add(dedupKey);
20864
21075
  kept.push(line);
@@ -21316,7 +21527,7 @@ ${stdout.replace(/\s+$/, "")}` : stderr : stdout;
21316
21527
  for (const line of benchLines) {
21317
21528
  if (CARGO_TEST_RUNNING_RE.test(line)) {
21318
21529
  runnerHeaderCount++;
21319
- if (runnerHeaderCount > 1) kept.push(line);
21530
+ if (runnerHeaderCount === 1) kept.push(line);
21320
21531
  continue;
21321
21532
  }
21322
21533
  kept.push(line);
@@ -22519,7 +22730,8 @@ ${stderr.replace(/\s+$/, "")}` : stderr;
22519
22730
  maybeNote(notes, providerCollapsed, `collapsed ${providerCollapsed} provider install/find lines`);
22520
22731
  if (nonEmpty.length > 12) {
22521
22732
  let compressed = headTailCompress(nonEmpty, 5, 5, "lines");
22522
- if (notes.length) compressed = compressed.replace(/\s+$/, "") + "\n" + notes.join("\n");
22733
+ if (notes.length) compressed = `${compressed.replace(/\s+$/, "")}
22734
+ [token-goat: ${notes.join("; ")}]`;
22523
22735
  return compressed;
22524
22736
  }
22525
22737
  this.emitNotes(kept, notes);
@@ -22660,7 +22872,11 @@ var AwsCliFilter = class extends ToolFilter {
22660
22872
  this._JSON_ARRAY_THRESHOLD,
22661
22873
  this._JSON_ARRAY_KEEP
22662
22874
  );
22663
- if (compressed !== null) text = compressed;
22875
+ if (compressed !== null) {
22876
+ text = compressed;
22877
+ } else if (text.includes("\n") && text.includes("|")) {
22878
+ text = _compressTable(text, 25);
22879
+ }
22664
22880
  }
22665
22881
  if (stderr.trim()) {
22666
22882
  text = text.trim() ? `${text.replace(/\s+$/, "")}
@@ -24157,14 +24373,13 @@ function compressGhRunView(text) {
24157
24373
  }
24158
24374
  function compressGhList(text, subcommand) {
24159
24375
  const lines = text.split("\n");
24160
- let headerIdx = 0;
24376
+ let dataStart = 0;
24161
24377
  for (let i = 0; i < lines.length; i++) {
24162
24378
  if (lines[i].trim()) {
24163
- headerIdx = i;
24379
+ dataStart = i;
24164
24380
  break;
24165
24381
  }
24166
24382
  }
24167
- const dataStart = headerIdx + 1;
24168
24383
  let dataEnd = lines.length;
24169
24384
  for (let i = dataStart; i < lines.length; i++) {
24170
24385
  if (!lines[i].trim()) {
@@ -24519,7 +24734,6 @@ var BanditFilter = class extends ToolFilter {
24519
24734
  inIssue = false;
24520
24735
  issueBuf = [];
24521
24736
  currentSeverity = "";
24522
- if (line.trim()) kept.push(line);
24523
24737
  } else {
24524
24738
  const sevM = /Severity:\s*(\w+)/i.exec(line);
24525
24739
  if (sevM) currentSeverity = sevM[1] ?? "";
@@ -25049,7 +25263,7 @@ var LsFilter = class _LsFilter extends ToolFilter {
25049
25263
  const entrySet = new Set(entryIdx);
25050
25264
  const keptEntryIdx = new Set(entryIdx.slice(0, _LS_MAX_ENTRIES));
25051
25265
  const hiddenCount = entryIdx.length - keptEntryIdx.size;
25052
- const fileEntries = entryIdx.map((i) => lines[i]).filter((l) => !_DIR_EXE_DIR_ENTRY_RE.test(l));
25266
+ const fileEntries = entryIdx.slice(_LS_MAX_ENTRIES).map((i) => lines[i]).filter((l) => !_DIR_EXE_DIR_ENTRY_RE.test(l));
25053
25267
  const extPart = _lsExtSummary(fileEntries);
25054
25268
  const hiddenMarker = extPart ? _LS_HIDDEN_MARKER_EXT.replace("{n}", String(hiddenCount)).replace("{ext_summary}", () => extPart) : _LS_HIDDEN_MARKER.replace("{n}", String(hiddenCount));
25055
25269
  const out = [];
@@ -25088,7 +25302,7 @@ var LsFilter = class _LsFilter extends ToolFilter {
25088
25302
  }
25089
25303
  out.push(...entries.slice(0, _LS_MAX_ENTRIES));
25090
25304
  const hidden = entries.length - _LS_MAX_ENTRIES;
25091
- const extPart = _lsExtSummary(entries);
25305
+ const extPart = _lsExtSummary(entries.slice(_LS_MAX_ENTRIES));
25092
25306
  if (extPart) {
25093
25307
  out.push(_LS_HIDDEN_MARKER_EXT.replace("{n}", String(hidden)).replace("{ext_summary}", () => extPart));
25094
25308
  } else {
@@ -26970,7 +27184,6 @@ var haskellFilter = makeLanguageFilter({
26970
27184
  {
26971
27185
  re: HASKELL_WARNING_RE,
26972
27186
  maxPerKey: 3,
26973
- keyLen: 40,
26974
27187
  note: (n) => `deduplicated ${n} repeated warning(s)`
26975
27188
  }
26976
27189
  ]
@@ -27572,13 +27785,20 @@ var Sqlite3Filter = class _Sqlite3Filter extends ToolFilter {
27572
27785
  if (nonEmpty.length && schemaLines.length / nonEmpty.length >= 0.5) return merged;
27573
27786
  const errors = lines.filter((ln) => SQLITE3_ERROR_RE.test(ln));
27574
27787
  const dataLines = lines.filter((ln) => !SQLITE3_ERROR_RE.test(ln));
27575
- const nonEmptyData = dataLines.filter((ln) => ln.trim());
27576
27788
  const kept = [...errors];
27577
- if (nonEmptyData.length > _Sqlite3Filter.ROW_THRESHOLD) {
27578
- kept.push(...nonEmptyData.slice(0, _Sqlite3Filter.KEEP_ROWS));
27579
- kept.push(`[token-goat: ${nonEmptyData.length} rows (showing first ${_Sqlite3Filter.KEEP_ROWS})]`);
27789
+ let headerRows = [];
27790
+ let bodyLines = dataLines;
27791
+ if (dataLines.length >= 2 && dataLines[0].trim() && /^[\s\-+]+$/.test(dataLines[1]) && dataLines[1].trim()) {
27792
+ headerRows = dataLines.slice(0, 2);
27793
+ bodyLines = dataLines.slice(2);
27794
+ }
27795
+ kept.push(...headerRows);
27796
+ const nonEmptyBody = bodyLines.filter((ln) => ln.trim());
27797
+ if (nonEmptyBody.length > _Sqlite3Filter.ROW_THRESHOLD) {
27798
+ kept.push(...nonEmptyBody.slice(0, _Sqlite3Filter.KEEP_ROWS));
27799
+ kept.push(`[token-goat: ${nonEmptyBody.length} rows (showing first ${_Sqlite3Filter.KEEP_ROWS})]`);
27580
27800
  } else {
27581
- kept.push(...dataLines);
27801
+ kept.push(...bodyLines);
27582
27802
  }
27583
27803
  return this.finalize(kept);
27584
27804
  }
@@ -27786,6 +28006,26 @@ var SysPackageFilter = class extends ToolFilter {
27786
28006
  }
27787
28007
  };
27788
28008
  var sysPackageFilter = new SysPackageFilter();
28009
+ var WmicFilter = class extends ToolFilter {
28010
+ name = "wmic";
28011
+ binaries = /* @__PURE__ */ new Set(["wmic"]);
28012
+ compressBody(stdout, stderr, _exitCode, _argv) {
28013
+ const merged = this.combineOutput(stdout, stderr);
28014
+ const lines = merged.split("\n");
28015
+ let padCount = 0;
28016
+ const kept = lines.map(
28017
+ (line) => line.replace(/[ \t]{2,}/g, () => {
28018
+ padCount++;
28019
+ return " ";
28020
+ }).replace(/[ \t]+$/, "")
28021
+ );
28022
+ const notes = [];
28023
+ maybeNote(notes, padCount, `collapsed ${padCount} runs of fixed-width column padding`);
28024
+ this.emitNotes(kept, notes);
28025
+ return this.finalize(kept);
28026
+ }
28027
+ };
28028
+ var wmicFilter = new WmicFilter();
27789
28029
  var PROTOC_INFO_RE = /^\[libprotobuf INFO /;
27790
28030
  var PROTOC_LIB_WARN_RE = /^\[libprotobuf (?:WARNING|ERROR) /;
27791
28031
  var PROTOC_DIAG_RE = /^[^\s:][^:]*\.proto:\d+:\d+: (?:warning|error):/i;
@@ -28482,6 +28722,7 @@ var MISC_FILTERS = [
28482
28722
  sqlite3Filter,
28483
28723
  redisCLIFilter,
28484
28724
  sysPackageFilter,
28725
+ wmicFilter,
28485
28726
  protocFilter,
28486
28727
  sassFilter,
28487
28728
  toxFilter,
@@ -28566,7 +28807,7 @@ function detectFromCommand(command) {
28566
28807
 
28567
28808
  // src/shell.ts
28568
28809
  import * as fs18 from "node:fs";
28569
- import * as path21 from "node:path";
28810
+ import * as path22 from "node:path";
28570
28811
  var WSL_LAUNCHER_SEGMENTS = /* @__PURE__ */ new Set(["system32", "syswow64", "windowsapps"]);
28571
28812
  function isExecutable(p) {
28572
28813
  try {
@@ -28579,15 +28820,15 @@ function isWslLauncherDir(dir) {
28579
28820
  return dir.toLowerCase().split(/[\\/]+/).some((seg) => WSL_LAUNCHER_SEGMENTS.has(seg));
28580
28821
  }
28581
28822
  function locateBashOnPath(pathValue, isExe = isExecutable) {
28582
- for (const dir of (pathValue ?? "").split(path21.delimiter)) {
28823
+ for (const dir of (pathValue ?? "").split(path22.delimiter)) {
28583
28824
  if (!dir || isWslLauncherDir(dir)) continue;
28584
- const cand = path21.join(dir, "bash.exe");
28825
+ const cand = path22.join(dir, "bash.exe");
28585
28826
  if (isExe(cand)) return cand;
28586
28827
  }
28587
28828
  return null;
28588
28829
  }
28589
28830
  function knownGitBashPaths() {
28590
- const localPrograms = process.env["LOCALAPPDATA"] ? path21.join(process.env["LOCALAPPDATA"], "Programs") : void 0;
28831
+ const localPrograms = process.env["LOCALAPPDATA"] ? path22.join(process.env["LOCALAPPDATA"], "Programs") : void 0;
28591
28832
  const bases = [
28592
28833
  process.env["ProgramFiles"],
28593
28834
  process.env["ProgramFiles(x86)"],
@@ -28597,8 +28838,8 @@ function knownGitBashPaths() {
28597
28838
  const out = [];
28598
28839
  for (const base of bases) {
28599
28840
  if (!base) continue;
28600
- out.push(path21.join(base, "Git", "bin", "bash.exe"));
28601
- out.push(path21.join(base, "Git", "usr", "bin", "bash.exe"));
28841
+ out.push(path22.join(base, "Git", "bin", "bash.exe"));
28842
+ out.push(path22.join(base, "Git", "usr", "bin", "bash.exe"));
28602
28843
  }
28603
28844
  return out;
28604
28845
  }
@@ -28647,8 +28888,8 @@ function resolveCdHintPath(rawCmd, filePath, cwd) {
28647
28888
  function surgicalHintFor(hintPath, isEnv, isConfig, isDoc) {
28648
28889
  return isEnv ? 'Use `token-goat config-get "' + hintPath + '" KEY_NAME` to read a specific variable.' : isConfig ? 'Use `token-goat config-get "' + hintPath + '" KEY_NAME` or `token-goat section "' + hintPath + '::sectionName"` to read a specific value.' : isDoc ? 'Use `token-goat section "' + hintPath + '::SectionHeading"` to read one section.' : 'Use `token-goat read "' + hintPath + '::SymbolName"` to read one function or class.';
28649
28890
  }
28650
- function surgicalHintForConfigDoc(filePath, isConfig, isDoc) {
28651
- return isConfig ? 'Use `token-goat config-get "' + filePath + '" KEY_NAME` or `token-goat section "' + filePath + '::sectionName"` to read a specific value.' : isDoc ? 'Use `token-goat section "' + filePath + '::SectionHeading"` to read one section.' : 'Use `token-goat read "' + filePath + '::SymbolName"` or `token-goat skeleton "' + filePath + '"` to see the file structure.';
28891
+ function surgicalHintForConfigDoc(filePath, isConfig, isDoc, isSql) {
28892
+ return isConfig ? 'Use `token-goat config-get "' + filePath + '" KEY_NAME` or `token-goat section "' + filePath + '::sectionName"` to read a specific value.' : isSql ? 'Use `token-goat section "' + filePath + '::table_name"` to pull one CREATE TABLE / CREATE TYPE block.' : isDoc ? 'Use `token-goat section "' + filePath + '::SectionHeading"` to read one section.' : 'Use `token-goat read "' + filePath + '::SymbolName"` or `token-goat skeleton "' + filePath + '"` to see the file structure.';
28652
28893
  }
28653
28894
  function stripOutputPipeline(cmd) {
28654
28895
  let inSingle = false;
@@ -28697,6 +28938,15 @@ function extractCommand(event) {
28697
28938
  const cmd = event.toolInput["command"];
28698
28939
  return typeof cmd === "string" && cmd.trim() !== "" ? cmd.trim() : void 0;
28699
28940
  }
28941
+ var HEAD_MOVING_GIT_RE = /^\s*git\s+(?:checkout|switch|pull|merge|rebase|reset|cherry-pick)\b/i;
28942
+ var PATH_SCOPED_CHECKOUT_RE = /^\s*git\s+checkout\s+(?:.*\s)?--(?:\s|$)/i;
28943
+ function isHeadMovingGitCommand(cmd) {
28944
+ if (!HEAD_MOVING_GIT_RE.test(cmd)) return false;
28945
+ if (PATH_SCOPED_CHECKOUT_RE.test(cmd)) return false;
28946
+ return true;
28947
+ }
28948
+ var ORIG_HEAD_ELIGIBLE_GIT_RE = /^\s*git\s+(?:pull|merge|rebase)\b/i;
28949
+ var ORIG_HEAD_REFLOG_MSG_RE = /^(merge\s|rebase\s\(|pull\s)/i;
28700
28950
  function isTempPath(fp) {
28701
28951
  const norm = fp.replace(/\\/g, "/");
28702
28952
  return /^\/tmp\//i.test(norm) || /\/var\/folders\//i.test(norm) || /AppData\/Local\/Temp\//i.test(norm) || norm.startsWith("/c/Users/") && norm.includes("/AppData/Local/Temp/");
@@ -28720,6 +28970,12 @@ function classifyFileExtensions(filePath) {
28720
28970
  const isConfig = /\.(?:json|yaml|yml|toml|conf|cfg|ini|properties)$/i.test(filePath);
28721
28971
  return { isDoc, isEnv, isConfig, isSql };
28722
28972
  }
28973
+ function classifyDocConfig(filePath) {
28974
+ const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
28975
+ const isConfig = /\.(?:json|yaml|yml|toml|conf|cfg|ini|properties)$/i.test(filePath);
28976
+ const isSql = /\.sql$/i.test(filePath);
28977
+ return { isDoc, isConfig, isSql };
28978
+ }
28723
28979
  function classifyCatPath(filePath, cmd0) {
28724
28980
  if (isTempPath(filePath)) return null;
28725
28981
  const flags = classifyFileExtensions(filePath);
@@ -28871,9 +29127,8 @@ function extractHeadFile(cmd) {
28871
29127
  if (filePath === void 0) return null;
28872
29128
  if (isTempPath(filePath)) return null;
28873
29129
  if (!/\.(?:ts|tsx|js|jsx|py|go|java|rs|rb|cs|md|mdx|rst|txt|json|yaml|yml|toml|sql|sh)$/i.test(filePath)) return null;
28874
- const isDoc = /\.(?:md|mdx|rst|txt|sql)$/i.test(filePath);
28875
- const isConfig = /\.(?:json|yaml|yml|toml|conf|cfg|ini|properties)$/i.test(filePath);
28876
- return { filePath, isDoc, isConfig };
29130
+ const { isDoc, isConfig, isSql } = classifyDocConfig(filePath);
29131
+ return { filePath, isDoc, isConfig, isSql };
28877
29132
  }
28878
29133
  function extractSedRange(cmd) {
28879
29134
  const m = /^sed\s+-n\s+['"](?:\d+,\d+p)(?:;\d+,\d+p)*['"]\s+(?:"([^"]+)"|'([^']+)'|(\S+))(?:\s+2>\/dev\/null)?\s*$/.exec(cmd);
@@ -28964,9 +29219,8 @@ function extractNodeFileRead(cmd) {
28964
29219
  const filePath = readSync2[1];
28965
29220
  if (isOrchestratorStateFile(filePath)) return null;
28966
29221
  if (isTempPath(filePath)) return null;
28967
- const isDoc = /\.(?:md|mdx|rst|txt|sql)$/i.test(filePath);
28968
- const isConfig = /\.(?:json|yaml|yml|toml|conf|cfg|ini|properties)$/i.test(filePath);
28969
- return { filePath, isDoc, isConfig };
29222
+ const { isDoc, isConfig, isSql } = classifyDocConfig(filePath);
29223
+ return { filePath, isDoc, isConfig, isSql };
28970
29224
  }
28971
29225
  const requireM = /require\(['"]([^'"]+\.json)['"]\)/i.exec(cmd);
28972
29226
  if (requireM?.[1]) {
@@ -28974,7 +29228,7 @@ function extractNodeFileRead(cmd) {
28974
29228
  if (filePath.includes("node_modules")) return null;
28975
29229
  if (isOrchestratorStateFile(filePath)) return null;
28976
29230
  if (isTempPath(filePath)) return null;
28977
- return { filePath, isDoc: false, isConfig: true };
29231
+ return { filePath, isDoc: false, isConfig: true, isSql: false };
28978
29232
  }
28979
29233
  return null;
28980
29234
  }
@@ -28990,9 +29244,8 @@ function extractTailFile(cmd) {
28990
29244
  if (!filePath) return null;
28991
29245
  if (isTempPath(filePath)) return null;
28992
29246
  if (!/\.(?:ts|tsx|js|jsx|py|go|java|rs|rb|cs|md|mdx|rst|txt|json|yaml|yml|toml|sql|sh)$/i.test(filePath)) return null;
28993
- const isDoc = /\.(?:md|mdx|rst|txt|sql)$/i.test(filePath);
28994
- const isConfig = /\.(?:json|yaml|yml|toml|conf|cfg|ini|properties)$/i.test(filePath);
28995
- return { filePath, isDoc, isConfig };
29247
+ const { isDoc, isConfig, isSql } = classifyDocConfig(filePath);
29248
+ return { filePath, isDoc, isConfig, isSql };
28996
29249
  }
28997
29250
  function extractGetContentTail(cmd) {
28998
29251
  const tailMatch = /-Tail\s+(\d+)/i.exec(cmd);
@@ -29004,26 +29257,24 @@ function extractGetContentTail(cmd) {
29004
29257
  const afterCmd = cmd.slice(getnMatch[0].length);
29005
29258
  const beforeTail = afterCmd.split(/-Tail/i)[0]?.trim() ?? "";
29006
29259
  const afterTail = afterCmd.split(/-Tail\s+\d+/i)[1]?.trim() ?? "";
29007
- const filePath = beforeTail || afterTail;
29260
+ const filePath = (beforeTail || afterTail).replace(/^["']|["']$/g, "");
29008
29261
  if (!filePath) return null;
29009
29262
  if (isTempPath(filePath)) return null;
29010
29263
  if (!/\.(?:ts|tsx|js|jsx|py|go|java|rs|rb|cs|md|mdx|rst|txt|json|yaml|yml|toml|sql|sh|ps1|psm1)$/i.test(filePath)) return null;
29011
- const isDoc = /\.(?:md|mdx|rst|txt|sql)$/i.test(filePath);
29012
- const isConfig = /\.(?:json|yaml|yml|toml|conf|cfg|ini|properties)$/i.test(filePath);
29013
- return { filePath, isDoc, isConfig };
29264
+ const { isDoc, isConfig, isSql } = classifyDocConfig(filePath);
29265
+ return { filePath, isDoc, isConfig, isSql };
29014
29266
  }
29015
29267
  function extractGetContentSelectFirst(cmd) {
29016
29268
  const m = /^(Get-Content|gc)\s+([^|]+)\s*\|\s*(Select-Object|select)\s+(-First\s+(\d+))/i.exec(cmd);
29017
29269
  if (!m) return null;
29018
- const filePath = m[2]?.trim() ?? "";
29270
+ const filePath = (m[2]?.trim() ?? "").replace(/^["']|["']$/g, "");
29019
29271
  const n = parseInt(m[5] ?? "0", 10);
29020
29272
  if (n <= 10) return null;
29021
29273
  if (!filePath) return null;
29022
29274
  if (isTempPath(filePath)) return null;
29023
29275
  if (!/\.(?:ts|tsx|js|jsx|py|go|java|rs|rb|cs|md|mdx|rst|txt|json|yaml|yml|toml|sql|sh|ps1|psm1)$/i.test(filePath)) return null;
29024
- const isDoc = /\.(?:md|mdx|rst|txt|sql)$/i.test(filePath);
29025
- const isConfig = /\.(?:json|yaml|yml|toml|conf|cfg|ini|properties)$/i.test(filePath);
29026
- return { filePath, isDoc, isConfig };
29276
+ const { isDoc, isConfig, isSql } = classifyDocConfig(filePath);
29277
+ return { filePath, isDoc, isConfig, isSql };
29027
29278
  }
29028
29279
  function extractTasksOutput(cmd) {
29029
29280
  const taskOutputRe = /[/\\]tasks[/\\]([a-z0-9]+)\.output$/;
@@ -29137,10 +29388,10 @@ function extractTgSurgicalRead(cmd, cwd) {
29137
29388
  }
29138
29389
  function curlHasUnsafeFlags(cmd) {
29139
29390
  if (/-X\s+(?:POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/i.test(cmd)) return true;
29140
- if (/--request\s+(?:POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/i.test(cmd)) return true;
29391
+ if (/--request(?:\s+|=)(?:POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/i.test(cmd)) return true;
29141
29392
  if (/(?:^|\s)(?:-d|--data(?:-raw|-binary|-urlencode)?|-F|--form)\b/.test(cmd)) return true;
29142
29393
  if (/(?:^|\s)(?:-u|--user)\b/.test(cmd)) return true;
29143
- if (/-H\s+['"]?Authorization/i.test(cmd)) return true;
29394
+ if (/(?:-H|--header)(?:\s+|=)['"]?Authorization/i.test(cmd)) return true;
29144
29395
  return false;
29145
29396
  }
29146
29397
  function isCurlGetCommand(cmd) {
@@ -29150,9 +29401,9 @@ function isCurlGetCommand(cmd) {
29150
29401
  function isReadOnlyGhApi(cmd) {
29151
29402
  if (!/^gh\s+api\b/.test(cmd)) return false;
29152
29403
  if (/\bgraphql\b/.test(cmd)) return false;
29153
- if (/-H\s+['"]?Authorization/i.test(cmd)) return false;
29154
- if (/(?:-X|--method)\s+GET\b/i.test(cmd)) return true;
29155
- if (/(?:-X|--method)\s+(?:POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\b/i.test(cmd)) return false;
29404
+ if (/(?:-H|--header)(?:\s+|=)['"]?Authorization/i.test(cmd)) return false;
29405
+ if (/(?:-X|--method)(?:\s+|=)GET\b/i.test(cmd)) return true;
29406
+ if (/(?:-X|--method)(?:\s+|=)(?:POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\b/i.test(cmd)) return false;
29156
29407
  if (/\s(?:-f|-F|--field|--raw-field|--input)\b/.test(cmd)) return false;
29157
29408
  return true;
29158
29409
  }
@@ -29430,29 +29681,31 @@ function preBashHandlerInner(event) {
29430
29681
  const sedRange = extractSedRange(cmd);
29431
29682
  if (sedRange !== null) {
29432
29683
  const { filePath, ranges } = sedRange;
29684
+ const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
29433
29685
  recordStat("session_hint", 0, 0);
29434
- const sedDedupKey = resolveIndexPath(filePath, preHookCwd ?? process.cwd());
29686
+ const sedDedupKey = resolveIndexPath(hintPath, preHookCwd ?? process.cwd());
29435
29687
  const overlapHints = [];
29436
29688
  const freshRanges = [];
29437
29689
  for (const [start, end] of ranges) {
29438
29690
  const priorOverlap = findRangeOverlap(getFileLineRanges(sedDedupKey), start, end);
29439
29691
  recordFileLineRange(sedDedupKey, start, end);
29440
29692
  if (priorOverlap !== null) {
29441
- overlapHints.push(sedOverlapHint(filePath, priorOverlap, start, end));
29693
+ overlapHints.push(sedOverlapHint(hintPath, priorOverlap, start, end));
29442
29694
  } else {
29443
29695
  freshRanges.push([start, end]);
29444
29696
  }
29445
29697
  }
29446
29698
  const hints = [...overlapHints];
29447
- if (freshRanges.length > 0) hints.push(sedRangeHint(filePath, freshRanges));
29699
+ if (freshRanges.length > 0) hints.push(sedRangeHint(hintPath, freshRanges));
29448
29700
  return contextOutput(hints.join(" "));
29449
29701
  }
29450
29702
  const catJsonPipe = extractCatJsonPipe(cmd);
29451
29703
  if (catJsonPipe !== null) {
29452
29704
  const { filePath } = catJsonPipe;
29705
+ const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
29453
29706
  recordStat("session_hint", 0, 0);
29454
29707
  return contextOutput(
29455
- '`cat | jq` loads the whole file. Use `token-goat config-get "' + filePath + '" KEY_NAME` or `token-goat section "' + filePath + '::sectionName"` to slice one value.'
29708
+ '`cat | jq` loads the whole file. Use `token-goat config-get "' + hintPath + '" KEY_NAME` or `token-goat section "' + hintPath + '::sectionName"` to slice one value.'
29456
29709
  );
29457
29710
  }
29458
29711
  const catResult = extractCatFile(cmd);
@@ -29487,7 +29740,7 @@ function preBashHandlerInner(event) {
29487
29740
  recordStat("session_hint", 0, 0);
29488
29741
  const lead = "`Get-Content` via a `powershell -Command` wrapper bypasses read hooks and loads the entire file into context. ";
29489
29742
  if (isSql) {
29490
- return cdStripped ? contextOutput(lead + 'Use `token-goat section "' + hintPath + '::table_name"` to pull one CREATE TABLE / CREATE TYPE block.') : denyOutput(lead + 'Use `token-goat section "' + hintPath + '::table_name"` to pull one CREATE TABLE / CREATE TYPE block.');
29743
+ return contextOutput(lead + 'Use `token-goat section "' + hintPath + '::table_name"` to pull one CREATE TABLE / CREATE TYPE block.');
29491
29744
  }
29492
29745
  const hint = surgicalHintFor(hintPath, isEnv, isConfig, isDoc);
29493
29746
  return cdStripped ? contextOutput(lead + hint) : denyOutput(lead + hint);
@@ -29519,33 +29772,37 @@ function preBashHandlerInner(event) {
29519
29772
  }
29520
29773
  const tailResult = extractTailFile(cmd);
29521
29774
  if (tailResult !== null) {
29522
- const { filePath, isDoc, isConfig } = tailResult;
29775
+ const { filePath, isDoc, isConfig, isSql } = tailResult;
29776
+ const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
29523
29777
  recordStat("session_hint", 0, 0);
29524
- return contextOutput("`tail` bypasses read hooks. " + surgicalHintForConfigDoc(filePath, isConfig, isDoc));
29778
+ return contextOutput("`tail` bypasses read hooks. " + surgicalHintForConfigDoc(hintPath, isConfig, isDoc, isSql));
29525
29779
  }
29526
29780
  const headResult = extractHeadFile(cmd);
29527
29781
  if (headResult !== null) {
29528
- const { filePath, isDoc, isConfig } = headResult;
29782
+ const { filePath, isDoc, isConfig, isSql } = headResult;
29783
+ const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
29529
29784
  recordStat("session_hint", 0, 0);
29530
- return contextOutput("`head` bypasses read hooks. " + surgicalHintForConfigDoc(filePath, isConfig, isDoc));
29785
+ return contextOutput("`head` bypasses read hooks. " + surgicalHintForConfigDoc(hintPath, isConfig, isDoc, isSql));
29531
29786
  }
29532
29787
  const gcTailResult = extractGetContentTail(cmd);
29533
29788
  if (gcTailResult !== null) {
29534
- const { filePath, isDoc, isConfig } = gcTailResult;
29789
+ const { filePath, isDoc, isConfig, isSql } = gcTailResult;
29790
+ const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
29535
29791
  recordStat("session_hint", 0, 0);
29536
- return contextOutput("`Get-Content -Tail` bypasses read hooks. " + surgicalHintForConfigDoc(filePath, isConfig, isDoc));
29792
+ return contextOutput("`Get-Content -Tail` bypasses read hooks. " + surgicalHintForConfigDoc(hintPath, isConfig, isDoc, isSql));
29537
29793
  }
29538
29794
  const gcSelectResult = extractGetContentSelectFirst(cmd);
29539
29795
  if (gcSelectResult !== null) {
29540
- const { filePath, isDoc, isConfig } = gcSelectResult;
29796
+ const { filePath, isDoc, isConfig, isSql } = gcSelectResult;
29797
+ const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
29541
29798
  recordStat("session_hint", 0, 0);
29542
- return contextOutput("`Select-Object -First` bypasses read hooks. " + surgicalHintForConfigDoc(filePath, isConfig, isDoc));
29799
+ return contextOutput("`Select-Object -First` bypasses read hooks. " + surgicalHintForConfigDoc(hintPath, isConfig, isDoc, isSql));
29543
29800
  }
29544
29801
  const nodeRead = extractNodeFileRead(cmd);
29545
29802
  if (nodeRead !== null) {
29546
- const { filePath, isDoc, isConfig } = nodeRead;
29803
+ const { filePath, isDoc, isConfig, isSql } = nodeRead;
29547
29804
  const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
29548
- const hint = isDoc ? 'Use `token-goat section "' + hintPath + '::SectionHeading"` to read one section.' : isConfig ? 'Use `token-goat config-get "' + hintPath + '" KEY_NAME` or `token-goat section "' + hintPath + '::sectionName"` to read a specific value.' : 'Use `token-goat read "' + hintPath + '::SymbolName"` to extract a specific symbol.';
29805
+ const hint = isSql ? 'Use `token-goat section "' + hintPath + '::table_name"` to pull one CREATE TABLE / CREATE TYPE block.' : isDoc ? 'Use `token-goat section "' + hintPath + '::SectionHeading"` to read one section.' : isConfig ? 'Use `token-goat config-get "' + hintPath + '" KEY_NAME` or `token-goat section "' + hintPath + '::sectionName"` to read a specific value.' : 'Use `token-goat read "' + hintPath + '::SymbolName"` to extract a specific symbol.';
29549
29806
  recordStat("session_hint", 0, 0);
29550
29807
  return cdStripped ? contextOutput("Node.js `fs.readFileSync()` bypasses read hooks. " + hint) : denyOutput("Node.js `fs.readFileSync()` bypasses read hooks. " + hint);
29551
29808
  }
@@ -29558,9 +29815,10 @@ function preBashHandlerInner(event) {
29558
29815
  const mdHeadingGrep = extractMarkdownHeadingGrep(cmd);
29559
29816
  if (mdHeadingGrep !== null) {
29560
29817
  const { filePath } = mdHeadingGrep;
29818
+ const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
29561
29819
  recordStat("session_hint", 0, 0);
29562
29820
  return contextOutput(
29563
- 'Use `token-goat outline "' + filePath + '"` to get all headings with line ranges \u2014 then `token-goat section "' + filePath + '::Heading"` to read one section.'
29821
+ 'Use `token-goat outline "' + hintPath + '"` to get all headings with line ranges \u2014 then `token-goat section "' + hintPath + '::Heading"` to read one section.'
29564
29822
  );
29565
29823
  }
29566
29824
  const rgSymbol = extractRgSymbolSearch(cmd);
@@ -29574,9 +29832,10 @@ function preBashHandlerInner(event) {
29574
29832
  const rgStructural = extractRgStructuralSearch(cmd);
29575
29833
  if (rgStructural !== null) {
29576
29834
  const { filePath } = rgStructural;
29835
+ const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
29577
29836
  recordStat("session_hint", 0, 0);
29578
29837
  return contextOutput(
29579
- 'Searching for code definitions with `rg`/`grep` is slower than surgical reads. Use `token-goat skeleton "' + filePath + '"` to see all symbols with line numbers, or `token-goat outline "' + filePath + '"` for symbols with docstrings and line ranges.'
29838
+ 'Searching for code definitions with `rg`/`grep` is slower than surgical reads. Use `token-goat skeleton "' + hintPath + '"` to see all symbols with line numbers, or `token-goat outline "' + hintPath + '"` for symbols with docstrings and line ranges.'
29580
29839
  );
29581
29840
  }
29582
29841
  const monitoringHint = getMonitoringRecallHint(cmd);
@@ -29738,6 +29997,27 @@ async function postBashHandler(event) {
29738
29997
  const exitCode = extractExitCode(event.raw);
29739
29998
  const cwd = getCwd(event) ?? null;
29740
29999
  const cacheMinBytes = loadConfig().bash_compress.cache_min_bytes;
30000
+ if (isHeadMovingGitCommand(cmd) && (exitCode === null || exitCode === 0)) {
30001
+ const gitDir = cwd ?? process.cwd();
30002
+ let diffBase = "HEAD@{1}";
30003
+ if (ORIG_HEAD_ELIGIBLE_GIT_RE.test(cmd)) {
30004
+ const reflogTop = runGit(["reflog", "-1", "--format=%gs", "HEAD"], { cwd: gitDir, timeoutMs: 5e3 });
30005
+ if (reflogTop.exitCode === 0 && ORIG_HEAD_REFLOG_MSG_RE.test(reflogTop.stdout.trim())) {
30006
+ const origHead = runGit(["rev-parse", "--verify", "-q", "ORIG_HEAD"], { cwd: gitDir, timeoutMs: 5e3 });
30007
+ if (origHead.exitCode === 0 && origHead.stdout.trim() !== "") diffBase = "ORIG_HEAD";
30008
+ }
30009
+ }
30010
+ const mutationDiff = runGit(["diff", "--name-only", diffBase, "HEAD"], { cwd: gitDir, timeoutMs: 5e3 });
30011
+ if (mutationDiff.exitCode === 0) {
30012
+ const toplevel = runGit(["rev-parse", "--show-toplevel"], { cwd: gitDir, timeoutMs: 5e3 });
30013
+ const repoRoot = toplevel.exitCode === 0 && toplevel.stdout.trim() !== "" ? toplevel.stdout.trim() : gitDir;
30014
+ for (const rel of mutationDiff.stdout.split("\n")) {
30015
+ const trimmed = rel.trim();
30016
+ if (trimmed.length === 0) continue;
30017
+ enqueueDirtyPathSafe(resolveIndexPath(trimmed, repoRoot), { alreadyResolved: true });
30018
+ }
30019
+ }
30020
+ }
29741
30021
  const curlDl = extractCurlDownload(cmd);
29742
30022
  if (curlDl !== null && (exitCode === null || exitCode === 0)) {
29743
30023
  const resolvedOutputPath = resolveIndexPath(curlDl.outputPath, cwd ?? process.cwd());
@@ -29832,7 +30112,9 @@ function postBashOutputHandler(event) {
29832
30112
  if (getToolName(event) !== "BashOutput" || !event.sessionId) return passOutput();
29833
30113
  const bashId = getBashId(getToolInput(event));
29834
30114
  if (bashId === void 0) return passOutput();
29835
- const output = extractToolResultText(event.raw);
30115
+ const rawOutput = extractToolResultText(event.raw);
30116
+ if (!rawOutput) return passOutput();
30117
+ const output = redactSecrets(rawOutput).text;
29836
30118
  if (!output) return passOutput();
29837
30119
  const prior = getBashOutput(pollCacheId(event.sessionId, bashId));
29838
30120
  const minBytes = loadConfig().bash_compress.cache_min_bytes;
@@ -29866,10 +30148,109 @@ ${delta}`
29866
30148
  }
29867
30149
  registerHook("post_tool_use", postBashOutputHandler, { toolName: "BashOutput" });
29868
30150
 
30151
+ // src/hooks_taskoutput.ts
30152
+ function pollCacheId2(sessionId, taskId) {
30153
+ return `taskpoll_${shortFingerprint(`${sessionId}\0taskoutput\0${taskId}`)}`;
30154
+ }
30155
+ function storePollSnapshot2(sessionId, taskId, output) {
30156
+ const id = pollCacheId2(sessionId, taskId);
30157
+ const entry = {
30158
+ id,
30159
+ command: `taskoutput:${taskId}`,
30160
+ output,
30161
+ exitCode: 0,
30162
+ storedAt: Date.now(),
30163
+ sizeBytes: Buffer.byteLength(output, "utf-8")
30164
+ };
30165
+ storeBlob(BASH_OUTPUT_SUBDIR, id, entry);
30166
+ }
30167
+ function collapseRepeatedLines(text) {
30168
+ return dedupeConsecutive(text.split("\n")).join("\n");
30169
+ }
30170
+ function getTaskId(toolInput) {
30171
+ const value = toolInput["task_id"];
30172
+ return typeof value === "string" && value !== "" ? value : void 0;
30173
+ }
30174
+ function postTaskOutputHandler(event) {
30175
+ try {
30176
+ if (getToolName(event) !== "TaskOutput" || !event.sessionId) return passOutput();
30177
+ const taskId = getTaskId(getToolInput(event));
30178
+ if (taskId === void 0) return passOutput();
30179
+ const rawOutput = extractToolResultText(event.raw);
30180
+ if (!rawOutput) return passOutput();
30181
+ const output = redactSecrets(rawOutput).text;
30182
+ if (!output) return passOutput();
30183
+ const prior = getBashOutput(pollCacheId2(event.sessionId, taskId));
30184
+ const minBytes = loadConfig().bash_compress.cache_min_bytes;
30185
+ if (prior === null) {
30186
+ storePollSnapshot2(event.sessionId, taskId, output);
30187
+ const collapsed = collapseRepeatedLines(output);
30188
+ if (collapsed === output) return passOutput();
30189
+ const savings = Buffer.byteLength(output, "utf-8") - Buffer.byteLength(collapsed, "utf-8");
30190
+ if (savings < minBytes) return passOutput();
30191
+ return {
30192
+ hookType: "rewriteOutput",
30193
+ updatedOutput: collapsed
30194
+ };
30195
+ }
30196
+ if (output === prior.output) {
30197
+ storePollSnapshot2(event.sessionId, taskId, output);
30198
+ if (Buffer.byteLength(output, "utf-8") < minBytes) return passOutput();
30199
+ return {
30200
+ hookType: "rewriteOutput",
30201
+ updatedOutput: `[token-goat: task_id ${taskId} unchanged since last poll -- no new output]`
30202
+ };
30203
+ }
30204
+ if (!output.startsWith(prior.output)) {
30205
+ storePollSnapshot2(event.sessionId, taskId, output);
30206
+ return passOutput();
30207
+ }
30208
+ const delta = output.slice(prior.output.length);
30209
+ storePollSnapshot2(event.sessionId, taskId, output);
30210
+ if (Buffer.byteLength(delta, "utf-8") < minBytes) return passOutput();
30211
+ return {
30212
+ hookType: "rewriteOutput",
30213
+ updatedOutput: `[token-goat: task_id ${taskId} delta since last poll]
30214
+ ${collapseRepeatedLines(delta)}`
30215
+ };
30216
+ } catch {
30217
+ return passOutput();
30218
+ }
30219
+ }
30220
+ registerHook("post_tool_use", postTaskOutputHandler, { toolName: "TaskOutput" });
30221
+
30222
+ // src/hooks_exitplanmode.ts
30223
+ var PLAN_OMIT_POINTER = "[token-goat: plan body omitted -- identical to this call's own tool_input, already saved to the plans file]";
30224
+ var APPROVED_PLAN_MARKER = "## Approved Plan:";
30225
+ function postExitPlanModeHandler(event) {
30226
+ try {
30227
+ if (getToolName(event) !== "ExitPlanMode") return passOutput();
30228
+ const output = extractToolResultText(event.raw);
30229
+ if (!output) return passOutput();
30230
+ const markerIndex = output.indexOf(APPROVED_PLAN_MARKER);
30231
+ if (markerIndex === -1) return passOutput();
30232
+ const planValue = getToolInput(event)["plan"];
30233
+ if (typeof planValue !== "string" || planValue.trim() === "") return passOutput();
30234
+ const plan = planValue.trim();
30235
+ const suffix = output.slice(markerIndex + APPROVED_PLAN_MARKER.length).trim();
30236
+ if (suffix !== plan && !suffix.includes(plan) && !plan.includes(suffix)) return passOutput();
30237
+ const prefix = output.slice(0, markerIndex + APPROVED_PLAN_MARKER.length);
30238
+ const rewritten = `${prefix}
30239
+ ${PLAN_OMIT_POINTER}`;
30240
+ return {
30241
+ hookType: "rewriteOutput",
30242
+ updatedOutput: rewritten
30243
+ };
30244
+ } catch {
30245
+ return passOutput();
30246
+ }
30247
+ }
30248
+ registerHook("post_tool_use", postExitPlanModeHandler, { toolName: "ExitPlanMode" });
30249
+
29869
30250
  // src/mcp_cache.ts
29870
30251
  var MCP_MAX_CACHE_BYTES = 2 * 1024 * 1024;
29871
30252
  var READ_VERBS_RE = /(?:^|_)(?:get|list|search|read|view|fetch|describe|export|download|find|show|query|resolve|context)(?=_|$)/i;
29872
- var MUTATING_VERBS_RE = /^request(?=_|$)|(?:^|_)(?:create|update|delete|send|write|push|post|remove|label|unlabel|merge|modify|draft|fork|reply|move|rename|set|add|run|execute|close|copy|upload|insert|revoke|reset|archive|restore|annotate|register|unregister|star|unstar|like|unlike|vote|block|unblock|invite|kick|ban|click|fill|press|type|navigate|evaluate|drag|hover|handle|snapshot|wait|emulate|new|select|resize|audit|apply|commit|grant|deploy|toggle|pin|trigger|finalize|approve|cancel|sync)(?=_|$)/i;
30253
+ var MUTATING_VERBS_RE = /^(?:request|run)(?=_|$)|(?:^|_)(?:create|update|delete|send|write|push|post|remove|label|unlabel|merge|modify|draft|fork|reply|move|rename|set|add|execute|close|copy|upload|insert|revoke|reset|archive|restore|annotate|register|unregister|star|unstar|like|unlike|vote|block|unblock|invite|kick|ban|click|fill|press|type|navigate|evaluate|drag|hover|handle|snapshot|wait|emulate|new|select|resize|audit|apply|commit|grant|deploy|toggle|pin|trigger|finalize|approve|cancel|sync)(?=_|$)/i;
29873
30254
  var STATE_CHANGING_INPUT_KEYS = ["createIfEmpty", "clear", "save_to_disk"];
29874
30255
  function isMcpReadOnly(toolName, toolInput) {
29875
30256
  if (!toolName.startsWith("mcp__")) {
@@ -29902,21 +30283,28 @@ function mcpInputPreview(toolInput) {
29902
30283
  }
29903
30284
  function storeMcpOutput(sessionId, toolName, toolInput, resultText) {
29904
30285
  if (!sessionId || !resultText) return null;
29905
- const sizeBytes = Buffer.byteLength(resultText, "utf-8");
29906
- if (sizeBytes > MCP_MAX_CACHE_BYTES) return null;
30286
+ const rawSizeBytes = Buffer.byteLength(resultText, "utf-8");
30287
+ if (rawSizeBytes > MCP_MAX_CACHE_BYTES) return null;
29907
30288
  const id = mcpOutputId(sessionId, mcpHash(toolName, toolInput));
29908
30289
  const label = `mcp:${toolName} ${mcpInputPreview(toolInput)}`.trim();
30290
+ const redactedOutput = redactSecrets(resultText).text;
29909
30291
  const entry = {
29910
30292
  id,
29911
30293
  command: label,
29912
- output: resultText,
30294
+ output: redactedOutput,
29913
30295
  exitCode: 0,
29914
30296
  storedAt: Date.now(),
29915
- sizeBytes
30297
+ // Sized off the redacted output, not the raw pre-redaction resultText (rawSizeBytes is only
30298
+ // the cache-eligibility gate above) -- mirrors bash_output_cache.ts's storeBashOutput, whose
30299
+ // sizeBytes is likewise computed from redactedOutput. Sizing off the raw text here left
30300
+ // mcp-audit's per-call token estimate and `mcp-history`'s byte column reporting a stale byte
30301
+ // count for any entry a secret was actually stripped from, out of sync with the stored/served
30302
+ // (redacted, shorter) output.
30303
+ sizeBytes: Buffer.byteLength(redactedOutput, "utf-8")
29916
30304
  };
29917
30305
  if (!storeBlob(BASH_OUTPUT_SUBDIR, id, entry)) return null;
29918
30306
  indexRecallEntry("mcp", id, label, `${label}
29919
- ${resultText}`, entry.storedAt);
30307
+ ${redactedOutput}`, entry.storedAt);
29920
30308
  return id;
29921
30309
  }
29922
30310
  function getMcpOutput(sessionId, toolName, toolInput, ttlMs = Number.POSITIVE_INFINITY) {
@@ -30041,9 +30429,34 @@ var BROWSER_NETWORK_STRIP_KEYS = /* @__PURE__ */ new Set([
30041
30429
  "responseCookies",
30042
30430
  "cookies"
30043
30431
  ]);
30432
+ var BROWSER_SNAPSHOT_METHOD_RE = /(?:^|_)(?:take_snapshot|read_page|page_snapshot|snapshot)$/i;
30433
+ var SNAPSHOT_NAME_MAX_CHARS = 200;
30434
+ var SNAPSHOT_NODE_LINE_RE = /^( *)(uid=\S+) (\S+) "((?:[^"\\]|\\.)*)"(.*)$/;
30435
+ function truncateSnapshotLine(line) {
30436
+ const m = SNAPSHOT_NODE_LINE_RE.exec(line);
30437
+ if (!m) return line;
30438
+ const [, indent, uid, role, name2, rest] = m;
30439
+ if (name2 === void 0) return line;
30440
+ if (name2.length <= SNAPSHOT_NAME_MAX_CHARS) return line;
30441
+ const elided = name2.length - SNAPSHOT_NAME_MAX_CHARS;
30442
+ return `${indent}${uid} ${role} "${name2.slice(0, SNAPSHOT_NAME_MAX_CHARS)} \u2026 [${elided} chars elided]"${rest}`;
30443
+ }
30444
+ function compressSnapshotText(resultText) {
30445
+ const compressed = resultText.split("\n").map(truncateSnapshotLine).join("\n");
30446
+ if (compressed.length >= resultText.length) return null;
30447
+ if (compressed.length > resultText.length * (1 - PACK_MIN_SAVINGS_RATIO)) return null;
30448
+ return compressed;
30449
+ }
30044
30450
  function compressBrowserMcpResult(toolName, resultText) {
30045
30451
  if (!BROWSER_TOOL_RE.test(toolName)) return null;
30046
30452
  const method = toolName.split("__").pop() || "";
30453
+ if (BROWSER_SNAPSHOT_METHOD_RE.test(method)) {
30454
+ try {
30455
+ return compressSnapshotText(resultText);
30456
+ } catch {
30457
+ return null;
30458
+ }
30459
+ }
30047
30460
  let shouldStrip = null;
30048
30461
  if (BROWSER_CONSOLE_METHOD_RE.test(method)) shouldStrip = (key) => BROWSER_CONSOLE_STRIP_KEYS.has(key);
30049
30462
  else if (BROWSER_NETWORK_METHOD_RE.test(method)) shouldStrip = (key) => BROWSER_NETWORK_STRIP_KEYS.has(key);
@@ -30057,8 +30470,81 @@ function compressBrowserMcpResult(toolName, resultText) {
30057
30470
  const stripped = deepStrip(parsed, shouldStrip);
30058
30471
  return finishWithGenericPass(resultText, stripped);
30059
30472
  }
30473
+ var GWORKSPACE_TOOL_RE = /^mcp__.*(?:gmail|drive).*__/i;
30474
+ var GWORKSPACE_STRIP_EXACT_KEYS = /* @__PURE__ */ new Set(["htmlBody", "html_body", "raw", "payload"]);
30475
+ var GWORKSPACE_BODY_KEYS = /* @__PURE__ */ new Set(["plaintextBody", "plaintext_body", "body"]);
30476
+ var GWORKSPACE_ATTACHMENT_KEEP_KEYS = /* @__PURE__ */ new Set(["id", "filename", "name", "mimeType", "mime_type", "size", "inline"]);
30477
+ var QUOTED_REPLY_LINE_RE = /^(?:>|On .{0,120} wrote:$|-{2,}\s*Original Message\s*-{2,}$)/i;
30478
+ var FORWARD_HEADER_FROM_RE = /^From:\s/i;
30479
+ var FORWARD_HEADER_CLUSTER_RE = /^(?:Sent|Date|To|Subject):\s/i;
30480
+ var FORWARD_HEADER_CLUSTER_LOOKAHEAD = 3;
30481
+ var FORWARD_HEADER_CLUSTER_MIN_MATCHES = 2;
30482
+ function findQuotedReplyOffset(text) {
30483
+ const lines = text.split("\n");
30484
+ let offset = 0;
30485
+ for (let i = 0; i < lines.length; i++) {
30486
+ const line = lines[i] ?? "";
30487
+ if (QUOTED_REPLY_LINE_RE.test(line)) return offset;
30488
+ if (FORWARD_HEADER_FROM_RE.test(line)) {
30489
+ let siblingMatches = 0;
30490
+ for (let j = i + 1; j < lines.length && j <= i + FORWARD_HEADER_CLUSTER_LOOKAHEAD; j++) {
30491
+ if (FORWARD_HEADER_CLUSTER_RE.test(lines[j] ?? "")) siblingMatches++;
30492
+ }
30493
+ if (siblingMatches >= FORWARD_HEADER_CLUSTER_MIN_MATCHES) return offset;
30494
+ }
30495
+ offset += line.length + 1;
30496
+ }
30497
+ return -1;
30498
+ }
30499
+ function trimQuotedReply(text) {
30500
+ const offset = findQuotedReplyOffset(text);
30501
+ if (offset < 0) return text;
30502
+ return text.slice(0, offset).trimEnd();
30503
+ }
30504
+ function trimGmailBodies(value) {
30505
+ if (Array.isArray(value)) return value.map(trimGmailBodies);
30506
+ if (value === null || typeof value !== "object") return value;
30507
+ const out = {};
30508
+ for (const [key, val] of Object.entries(value)) {
30509
+ out[key] = GWORKSPACE_BODY_KEYS.has(key) && typeof val === "string" ? trimQuotedReply(val) : trimGmailBodies(val);
30510
+ }
30511
+ return out;
30512
+ }
30513
+ function summarizeAttachments(value) {
30514
+ if (Array.isArray(value)) return value.map(summarizeAttachments);
30515
+ if (value === null || typeof value !== "object") return value;
30516
+ const out = {};
30517
+ for (const [key, val] of Object.entries(value)) {
30518
+ if (key === "attachments" && Array.isArray(val)) {
30519
+ out[key] = val.map((att) => {
30520
+ if (att === null || typeof att !== "object") return att;
30521
+ const kept = {};
30522
+ for (const [k, v] of Object.entries(att)) {
30523
+ if (GWORKSPACE_ATTACHMENT_KEEP_KEYS.has(k)) kept[k] = v;
30524
+ }
30525
+ return kept;
30526
+ });
30527
+ } else {
30528
+ out[key] = summarizeAttachments(val);
30529
+ }
30530
+ }
30531
+ return out;
30532
+ }
30533
+ function compressGWorkspaceMcpResult(toolName, resultText) {
30534
+ if (!GWORKSPACE_TOOL_RE.test(toolName)) return null;
30535
+ let parsed;
30536
+ try {
30537
+ parsed = JSON.parse(resultText);
30538
+ } catch {
30539
+ return null;
30540
+ }
30541
+ const stripped = deepStrip(parsed, (key) => GWORKSPACE_STRIP_EXACT_KEYS.has(key));
30542
+ const trimmed = trimGmailBodies(stripped);
30543
+ const summarized = summarizeAttachments(trimmed);
30544
+ return finishWithGenericPass(resultText, summarized);
30545
+ }
30060
30546
  function compressMcpResultWithPacks(toolName, resultText) {
30061
- return compressGithubMcpResult(toolName, resultText) ?? compressBrowserMcpResult(toolName, resultText);
30547
+ return compressGithubMcpResult(toolName, resultText) ?? compressBrowserMcpResult(toolName, resultText) ?? compressGWorkspaceMcpResult(toolName, resultText);
30062
30548
  }
30063
30549
 
30064
30550
  // src/hooks_mcp.ts
@@ -30081,23 +30567,29 @@ function preMcpHandler(event) {
30081
30567
  }
30082
30568
  function postMcpHandler(event) {
30083
30569
  const toolName = getToolName(event);
30084
- if (!toolName || !event.sessionId) return passOutput();
30570
+ if (!toolName || !event.sessionId || !toolName.startsWith("mcp__")) return passOutput();
30085
30571
  const toolInput = getToolInput(event);
30086
- if (!isMcpReadOnly(toolName, toolInput)) return passOutput();
30087
30572
  if (isMcpErrorResponse(event.raw)) return passOutput();
30088
- const ttlMs = loadConfig().hints.mcp_dedup_ttl_secs * 1e3;
30089
- if (getMcpOutput(event.sessionId, toolName, toolInput, ttlMs)) return passOutput();
30573
+ const readOnly = isMcpReadOnly(toolName, toolInput);
30090
30574
  const resultText = extractToolResultText(event.raw);
30091
30575
  if (!resultText) return passOutput();
30092
- const id = storeMcpOutput(event.sessionId, toolName, toolInput, resultText);
30093
- if (id !== null && resultText.length >= MCP_COMPRESS_MIN_BYTES && process.env["TOKEN_GOAT_MCP_COMPRESS"] !== "0") {
30576
+ let id = null;
30577
+ if (readOnly) {
30578
+ const ttlMs = loadConfig().hints.mcp_dedup_ttl_secs * 1e3;
30579
+ if (getMcpOutput(event.sessionId, toolName, toolInput, ttlMs)) return passOutput();
30580
+ id = storeMcpOutput(event.sessionId, toolName, toolInput, resultText);
30581
+ }
30582
+ if (resultText.length >= MCP_COMPRESS_MIN_BYTES && process.env["TOKEN_GOAT_MCP_COMPRESS"] !== "0") {
30094
30583
  const compressed = compressMcpResultWithPacks(toolName, resultText) ?? compressMcpResult(resultText);
30095
30584
  if (compressed !== null) {
30096
- return {
30097
- hookType: "rewriteOutput",
30098
- updatedOutput: `[token-goat: compressed, full via mcp-output ${id}]
30585
+ if (id === null) id = storeMcpOutput(event.sessionId, toolName, toolInput, resultText);
30586
+ if (id !== null) {
30587
+ return {
30588
+ hookType: "rewriteOutput",
30589
+ updatedOutput: `[token-goat: compressed, full via mcp-output ${id}]
30099
30590
  ${compressed}`
30100
- };
30591
+ };
30592
+ }
30101
30593
  }
30102
30594
  }
30103
30595
  return passOutput();
@@ -30252,7 +30744,7 @@ registerHook("post_tool_use", postBrowserImageHandler);
30252
30744
 
30253
30745
  // src/baseline.ts
30254
30746
  import * as fs19 from "node:fs";
30255
- import * as path22 from "node:path";
30747
+ import * as path23 from "node:path";
30256
30748
  var SKIP_DIRS = /* @__PURE__ */ new Set([
30257
30749
  "node_modules",
30258
30750
  ".git",
@@ -30282,6 +30774,7 @@ function walkProject(rootDir, opts = {}) {
30282
30774
  const languages = {};
30283
30775
  const stack = [rootDir];
30284
30776
  const excludeTests = opts.excludeTests === true;
30777
+ const includeEmbeddableDocuments = opts.includeEmbeddableDocuments === true;
30285
30778
  const extraSkipDirs = loadConfig().indexing.skip_dirs;
30286
30779
  while (stack.length > 0 && files.length < MAX_FILES_SCANNED) {
30287
30780
  const dir = stack.pop();
@@ -30293,7 +30786,7 @@ function walkProject(rootDir, opts = {}) {
30293
30786
  continue;
30294
30787
  }
30295
30788
  for (const entry of entries) {
30296
- const full = path22.join(dir, entry.name);
30789
+ const full = path23.join(dir, entry.name);
30297
30790
  if (entry.isDirectory()) {
30298
30791
  if (SKIP_DIRS.has(entry.name) || extraSkipDirs.includes(entry.name)) continue;
30299
30792
  if (entry.name.startsWith(".") && entry.name !== ".") {
@@ -30302,7 +30795,7 @@ function walkProject(rootDir, opts = {}) {
30302
30795
  stack.push(full);
30303
30796
  } else if (entry.isFile()) {
30304
30797
  const lang = detectLanguage(full);
30305
- if (lang === "unknown") continue;
30798
+ if (lang === "unknown" && !(includeEmbeddableDocuments && isEmbeddableDocument(full))) continue;
30306
30799
  if (excludeTests && isTestFile(full)) continue;
30307
30800
  files.push(full);
30308
30801
  languages[lang] = (languages[lang] ?? 0) + 1;
@@ -30338,9 +30831,11 @@ function fetchTopSymbols(limit, dbPath, rootDir) {
30338
30831
  }
30339
30832
  }
30340
30833
  function buildProjectMap(rootDir = process.cwd(), opts = {}) {
30341
- const root = path22.resolve(rootDir);
30342
- const { files, languages } = walkProject(root, { excludeTests: loadConfig().repomap.exclude_tests });
30343
- const symbolLimit = opts.compact ? 10 : 30;
30834
+ const root = path23.resolve(rootDir);
30835
+ const config = loadConfig();
30836
+ const { files, languages } = walkProject(root, { excludeTests: config.repomap.exclude_tests });
30837
+ const compact = opts.compact === true || files.length > config.repomap.compact_file_threshold;
30838
+ const symbolLimit = compact ? 10 : 30;
30344
30839
  const topSymbols = fetchTopSymbols(symbolLimit, globalDbPath(), root);
30345
30840
  const recentFiles = files.map((f) => {
30346
30841
  let mtime;
@@ -30350,18 +30845,19 @@ function buildProjectMap(rootDir = process.cwd(), opts = {}) {
30350
30845
  mtime = 0;
30351
30846
  }
30352
30847
  return { f, mtime };
30353
- }).sort((a, b) => b.mtime - a.mtime).slice(0, opts.compact ? 5 : 15).map((x) => path22.relative(root, x.f));
30848
+ }).sort((a, b) => b.mtime - a.mtime).slice(0, compact ? 5 : 15).map((x) => path23.relative(root, x.f));
30354
30849
  return {
30355
30850
  rootDir: root,
30356
30851
  fileCount: files.length,
30357
30852
  languages,
30358
30853
  topSymbols,
30359
- recentFiles
30854
+ recentFiles,
30855
+ compact
30360
30856
  };
30361
30857
  }
30362
30858
  function formatProjectMap(map2, compact = false) {
30363
30859
  const lines = [];
30364
- const rel = path22.basename(map2.rootDir);
30860
+ const rel = path23.basename(map2.rootDir);
30365
30861
  lines.push(`# Project map: ${rel}`);
30366
30862
  lines.push(`Files: ${map2.fileCount}`);
30367
30863
  const langPairs = Object.entries(map2.languages).sort((a, b) => b[1] - a[1]);
@@ -30374,7 +30870,7 @@ function formatProjectMap(map2, compact = false) {
30374
30870
  if (compact) {
30375
30871
  lines.push(`- ${s.name} (${s.kind})`);
30376
30872
  } else {
30377
- const loc = `${path22.basename(s.filePath)}:${s.lineStart}-${s.lineEnd}`;
30873
+ const loc = `${path23.basename(s.filePath)}:${s.lineStart}-${s.lineEnd}`;
30378
30874
  lines.push(`- ${s.name} (${s.kind}) \u2014 ${loc}`);
30379
30875
  }
30380
30876
  }
@@ -30398,7 +30894,7 @@ function buildSubagentBriefing() {
30398
30894
  lines.push("## Session briefing (for context)");
30399
30895
  try {
30400
30896
  const map2 = buildProjectMap(process.cwd(), { compact: true });
30401
- const mapText = formatProjectMap(map2, true);
30897
+ const mapText = formatProjectMap(map2, map2.compact);
30402
30898
  lines.push(mapText);
30403
30899
  } catch {
30404
30900
  }