token-goat 2.6.33 → 2.6.34

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.
Files changed (24) hide show
  1. package/README.md +4 -0
  2. package/dist/{token-goat-chunk-WFRR2DBG.mjs → token-goat-chunk-7FWBSPCT.mjs} +446 -339
  3. package/dist/{token-goat-chunk-HPWPUBAK.mjs → token-goat-chunk-CKRATWCJ.mjs} +5 -5
  4. package/dist/{token-goat-chunk-TDYTBCT4.mjs → token-goat-chunk-CN7GUPX6.mjs} +328 -86
  5. package/dist/{token-goat-chunk-L6FVTDPD.mjs → token-goat-chunk-FRMEOIRU.mjs} +361 -23
  6. package/dist/{token-goat-chunk-RPOXYJWK.mjs → token-goat-chunk-GXBPEQR2.mjs} +70 -25
  7. package/dist/{token-goat-chunk-77CX3MCN.mjs → token-goat-chunk-I6EQH27G.mjs} +2 -2
  8. package/dist/{token-goat-chunk-FN6WLFIX.mjs → token-goat-chunk-IYYQOPY4.mjs} +801 -51
  9. package/dist/{token-goat-chunk-72R7M5H7.mjs → token-goat-chunk-L7QLEVO7.mjs} +3 -3
  10. package/dist/{token-goat-chunk-OHQ7RBLL.mjs → token-goat-chunk-OA3GFKP2.mjs} +2 -2
  11. package/dist/{token-goat-hook-chunk-HXHFLKEL.mjs → token-goat-chunk-WA2ER6TZ.mjs} +7 -2
  12. package/dist/{token-goat-chunk-RQQ4SY2P.mjs → token-goat-hook-chunk-2ISZ4AZR.mjs} +7 -2
  13. package/dist/{token-goat-hook-chunk-ZT47M3IA.mjs → token-goat-hook-chunk-6C5RN3CM.mjs} +2 -2
  14. package/dist/{token-goat-hook-chunk-UWV4MTYI.mjs → token-goat-hook-chunk-6QTWMSRO.mjs} +328 -86
  15. package/dist/{token-goat-hook-chunk-XBOWDNJB.mjs → token-goat-hook-chunk-ATEGFQAU.mjs} +5 -5
  16. package/dist/{token-goat-hook-chunk-QD2DX2U7.mjs → token-goat-hook-chunk-BU5OFDGQ.mjs} +361 -23
  17. package/dist/{token-goat-hook-chunk-E6AIPKNF.mjs → token-goat-hook-chunk-CNLMPHXT.mjs} +442 -335
  18. package/dist/{token-goat-hook-chunk-WBBHV6TZ.mjs → token-goat-hook-chunk-I5VM5PO5.mjs} +70 -25
  19. package/dist/{token-goat-hook-chunk-VDXQXYJK.mjs → token-goat-hook-chunk-KGLMNOGD.mjs} +801 -51
  20. package/dist/{token-goat-hook-chunk-EKGDLI4D.mjs → token-goat-hook-chunk-OQML5EMA.mjs} +3 -3
  21. package/dist/{token-goat-hook-chunk-26DLRODR.mjs → token-goat-hook-chunk-QTZ6YUMQ.mjs} +2 -2
  22. package/dist/token-goat-hook.mjs +5 -5
  23. package/dist/token-goat.core.mjs +5 -5
  24. package/package.json +8 -8
@@ -8,7 +8,7 @@ import {
8
8
  isRewriteWorthwhile,
9
9
  resolveMinNetSavingsBytes,
10
10
  shlexSplit
11
- } from "./token-goat-chunk-RQQ4SY2P.mjs";
11
+ } from "./token-goat-chunk-WA2ER6TZ.mjs";
12
12
  import {
13
13
  BASH_OUTPUT_SUBDIR,
14
14
  HOOK_EVENTS,
@@ -27,7 +27,7 @@ import {
27
27
  storeBashOutput,
28
28
  storeWebOutput,
29
29
  summarizeOutputDelta
30
- } from "./token-goat-chunk-OHQ7RBLL.mjs";
30
+ } from "./token-goat-chunk-OA3GFKP2.mjs";
31
31
  import {
32
32
  BODY_FIRST_TOOL_RESPONSE_KEYS,
33
33
  OUTPUT_FIRST_TOOL_RESPONSE_KEYS,
@@ -117,11 +117,12 @@ import {
117
117
  wasCliReadThisSession,
118
118
  wasFileReadThisSession,
119
119
  wasHintShown
120
- } from "./token-goat-chunk-TDYTBCT4.mjs";
120
+ } from "./token-goat-chunk-CN7GUPX6.mjs";
121
121
  import {
122
122
  VERSION,
123
123
  detectHarness,
124
124
  detectLanguage,
125
+ displaySafePath,
125
126
  extractErrorMessage,
126
127
  foldPath,
127
128
  getDb,
@@ -135,7 +136,7 @@ import {
135
136
  runGit,
136
137
  shortFingerprint,
137
138
  toKB
138
- } from "./token-goat-chunk-WFRR2DBG.mjs";
139
+ } from "./token-goat-chunk-7FWBSPCT.mjs";
139
140
  import "./token-goat-chunk-AO2QD2AG.mjs";
140
141
  import "./token-goat-chunk-AEX54RUZ.mjs";
141
142
 
@@ -314,9 +315,9 @@ function postEditHandlerInner(event) {
314
315
  } catch {
315
316
  }
316
317
  if (meetsSavingsFloor(editedSize)) {
317
- const escapedPath = normalized.replace(/`/g, "\\`").replace(/"/g, '\\"');
318
+ const escapedPath = displaySafePath(normalized).replace(/`/g, "\\`").replace(/"/g, '\\"');
318
319
  return contextOutput(
319
- editedBasename + ' was edited. Use `token-goat section "' + escapedPath + '::HeadingName"` to re-read a specific section rather than the full file.'
320
+ displaySafePath(editedBasename) + ' was edited. Use `token-goat section "' + escapedPath + '::HeadingName"` to re-read a specific section rather than the full file.'
320
321
  );
321
322
  }
322
323
  }
@@ -575,7 +576,7 @@ import crypto from "node:crypto";
575
576
  var TRACKED_SKILL = "token-goat";
576
577
  var MAX_COMMANDS_SHOWN = 8;
577
578
  async function currentCommandNames() {
578
- const { buildProgram } = await import("./token-goat-chunk-HPWPUBAK.mjs");
579
+ const { buildProgram } = await import("./token-goat-chunk-CKRATWCJ.mjs");
579
580
  return flattenCommandNames(buildCommandManifest(buildProgram()));
580
581
  }
581
582
  async function recordSkillVersionSnapshot(sessionId, skillName) {
@@ -7054,6 +7055,10 @@ function stripCdPrefix(cmd) {
7054
7055
  const stripped = cmd.replace(/^(?:cd\s+(?:"[^"]*"|'[^']*'|\S+)\s*&&\s*)+/, "");
7055
7056
  return stripped.trim() || cmd;
7056
7057
  }
7058
+ function stripTrailingStderrRedirect(cmd) {
7059
+ const stripped = cmd.replace(/\s*2>&1\s*$/, "");
7060
+ return stripped.trim() || cmd;
7061
+ }
7057
7062
  function hasTestRunScopeOrBudget(cmd) {
7058
7063
  const hasTimeout = /(?:^|\s)(?:timeout(?:\.exe)?\s+\S+|--(?:test)?timeout(?:=|\s)\S+)/i.test(cmd);
7059
7064
  const hasSelector = /(?:\s::\S+|\s(?:-k|-t|-run|--(?:testNamePattern|last-failed|test|project))(?:=|\s)|\b(?:tests?|spec)\S*\.(?:[cm]?[jt]sx?|py|go|rs)\b|(?:^|\s)\.\/\S+)/i.test(cmd);
@@ -7145,6 +7150,182 @@ function isHeadMovingGitCommand(cmd) {
7145
7150
  }
7146
7151
  var ORIG_HEAD_ELIGIBLE_GIT_RE = /^\s*git\s+(?:pull|merge|rebase)\b/i;
7147
7152
  var ORIG_HEAD_REFLOG_MSG_RE = /^(merge\s|rebase\s\(|pull\s)/i;
7153
+ var GIT_RESTORE_RE = /^\s*git\s+restore\b/i;
7154
+ var GIT_RESTORE_STAGED_ONLY_RE = /(?:^|\s)(?:--staged|-S)(?:\s|$)/;
7155
+ var GIT_STASH_APPLY_RE = /^\s*git\s+stash\s+(?:pop|apply)\b/i;
7156
+ var PATCH_APPLY_SEGMENT_RE = /^\s*(?:\S*[/\\])?(?:git\s+apply|patch)\b/i;
7157
+ var PATCH_APPLY_INSPECT_ONLY_RE = /(?:^|\s)--(?:check|stat|numstat|summary|dry-run)(?:\s|=|$)/i;
7158
+ function isFormatterWriteSegment(segment) {
7159
+ return /(?:^|\s|[/\\])(?:prettier|eslint)\b/i.test(segment) && /(?:^|\s)--(?:write|fix)(?:\s|$)/i.test(segment);
7160
+ }
7161
+ function splitShellSegments(cmd) {
7162
+ const segments = [];
7163
+ let cur = "";
7164
+ let quote = null;
7165
+ for (let i = 0; i < cmd.length; i++) {
7166
+ const ch = cmd[i];
7167
+ if (quote !== null) {
7168
+ cur += ch;
7169
+ if (ch === quote) quote = null;
7170
+ continue;
7171
+ }
7172
+ if (ch === '"' || ch === "'") {
7173
+ quote = ch;
7174
+ cur += ch;
7175
+ continue;
7176
+ }
7177
+ if (ch === "\\" && i + 1 < cmd.length) {
7178
+ cur += ch + cmd[i + 1];
7179
+ i++;
7180
+ continue;
7181
+ }
7182
+ if (ch === "|" || ch === ";" || ch === "\n" || ch === "&") {
7183
+ segments.push(cur);
7184
+ cur = "";
7185
+ if ((ch === "|" || ch === "&") && cmd[i + 1] === ch) i++;
7186
+ continue;
7187
+ }
7188
+ cur += ch;
7189
+ }
7190
+ segments.push(cur);
7191
+ return segments.map((s) => s.trim()).filter((s) => s.length > 0);
7192
+ }
7193
+ function safeShlexSplit(segment) {
7194
+ try {
7195
+ return shlexSplit(segment);
7196
+ } catch {
7197
+ return null;
7198
+ }
7199
+ }
7200
+ function segmentCommandIs(segment, name2) {
7201
+ return new RegExp("^\\s*(?:\\S*[/\\\\])?" + name2 + "(?:\\.exe)?\\b", "i").test(segment);
7202
+ }
7203
+ function extractSedInPlaceFiles(segment) {
7204
+ if (!segmentCommandIs(segment, "sed")) return [];
7205
+ const tokens = safeShlexSplit(segment);
7206
+ if (tokens === null) return [];
7207
+ const rest = tokens.slice(1);
7208
+ const inPlaceIdx = rest.findIndex((t) => t === "--in-place" || t.startsWith("--in-place=") || /^-[a-zA-Z]*i/.test(t));
7209
+ if (inPlaceIdx === -1) return [];
7210
+ const files = [];
7211
+ let scriptTaken = rest.some((t) => t === "-e" || t === "-f" || t.startsWith("--expression") || t.startsWith("--file"));
7212
+ for (let i = 0; i < rest.length; i++) {
7213
+ const t = rest[i];
7214
+ if (i === inPlaceIdx) {
7215
+ if (t === "-i" && rest[i + 1] === "") i++;
7216
+ continue;
7217
+ }
7218
+ if (t === "-e" || t === "-f" || t === "--expression" || t === "--file") {
7219
+ i++;
7220
+ continue;
7221
+ }
7222
+ if (t.length > 1 && t.startsWith("-")) continue;
7223
+ if (!scriptTaken) {
7224
+ scriptTaken = true;
7225
+ continue;
7226
+ }
7227
+ files.push(t);
7228
+ }
7229
+ return files;
7230
+ }
7231
+ function extractTeeFiles(segment) {
7232
+ if (!segmentCommandIs(segment, "tee")) return [];
7233
+ const tokens = safeShlexSplit(segment);
7234
+ if (tokens === null) return [];
7235
+ return tokens.slice(1).filter((t) => t.length > 0 && !t.startsWith("-"));
7236
+ }
7237
+ function extractRedirectTargets(segment) {
7238
+ const targets = [];
7239
+ const re = /(?:^|\s)[0-9]?>>?\s*("[^"]*"|'[^']*'|[^\s|&<>]+)/g;
7240
+ let m;
7241
+ while ((m = re.exec(segment)) !== null) {
7242
+ const raw = m[1];
7243
+ targets.push(raw.length >= 2 && (raw[0] === '"' || raw[0] === "'") ? raw.slice(1, -1) : raw);
7244
+ }
7245
+ return targets;
7246
+ }
7247
+ function extractGitRestorePathspecs(cmd) {
7248
+ const m = /^\s*git\s+restore\b(.*)$/i.exec(cmd);
7249
+ if (m === null) return [];
7250
+ const tokens = safeShlexSplit(m[1] ?? "");
7251
+ if (tokens === null) return [];
7252
+ const dashDash = tokens.indexOf("--");
7253
+ if (dashDash !== -1) return tokens.slice(dashDash + 1);
7254
+ const specs = [];
7255
+ let skipNext = false;
7256
+ for (const t of tokens) {
7257
+ if (skipNext) {
7258
+ skipNext = false;
7259
+ continue;
7260
+ }
7261
+ if (t === "-s" || t === "--source") {
7262
+ skipNext = true;
7263
+ continue;
7264
+ }
7265
+ if (t.startsWith("-")) continue;
7266
+ specs.push(t);
7267
+ }
7268
+ return specs;
7269
+ }
7270
+ function gitRepoRoot(gitDir) {
7271
+ const toplevel = runGit(["rev-parse", "--show-toplevel"], { cwd: gitDir, timeoutMs: 5e3 });
7272
+ return toplevel.exitCode === 0 && toplevel.stdout.trim() !== "" ? toplevel.stdout.trim() : gitDir;
7273
+ }
7274
+ function expandGitPathspecs(specs, gitDir) {
7275
+ if (specs.length === 0) return [];
7276
+ const listed = runGit(["ls-files", "--full-name", "--", ...specs], { cwd: gitDir, timeoutMs: 5e3 });
7277
+ if (listed.exitCode !== 0) return [];
7278
+ const repoRoot = gitRepoRoot(gitDir);
7279
+ return listed.stdout.split("\n").map((l) => l.trim()).filter((l) => l.length > 0).map((rel) => resolveIndexPath(rel, repoRoot));
7280
+ }
7281
+ function workingTreeStatusPaths(gitDir) {
7282
+ const status = runGit(["status", "--porcelain"], { cwd: gitDir, timeoutMs: 5e3 });
7283
+ if (status.exitCode !== 0) return [];
7284
+ const repoRoot = gitRepoRoot(gitDir);
7285
+ const out = [];
7286
+ for (const line of status.stdout.split("\n")) {
7287
+ if (line.length < 4) continue;
7288
+ let rel = line.slice(3).trim();
7289
+ if (rel.length === 0) continue;
7290
+ const arrow = rel.lastIndexOf(" -> ");
7291
+ if (arrow !== -1) rel = rel.slice(arrow + 4);
7292
+ if (rel.startsWith('"')) {
7293
+ try {
7294
+ rel = JSON.parse(rel);
7295
+ } catch {
7296
+ continue;
7297
+ }
7298
+ }
7299
+ out.push(resolveIndexPath(rel, repoRoot));
7300
+ }
7301
+ return out;
7302
+ }
7303
+ function enqueueRewrittenPath(absPath) {
7304
+ if (/(?:^|[/\\])(?:NUL|nul)$/.test(absPath) || absPath.replace(/\\/g, "/").endsWith("/dev/null")) return;
7305
+ try {
7306
+ if (!statSync3(absPath).isFile()) return;
7307
+ } catch {
7308
+ return;
7309
+ }
7310
+ enqueueDirtyPathSafe(absPath, { alreadyResolved: true });
7311
+ }
7312
+ function enqueueNonHeadMovingRewrites(cmd, rawCmd, cwd) {
7313
+ const atCwd = (f) => resolveIndexPath(resolveCdHintPath(rawCmd, f, cwd), cwd);
7314
+ const paths = [];
7315
+ let needsStatusSweep = GIT_STASH_APPLY_RE.test(cmd);
7316
+ if (GIT_RESTORE_RE.test(cmd) && !GIT_RESTORE_STAGED_ONLY_RE.test(cmd)) {
7317
+ paths.push(...expandGitPathspecs(extractGitRestorePathspecs(cmd), cwd));
7318
+ }
7319
+ for (const segment of splitShellSegments(cmd)) {
7320
+ if (PATCH_APPLY_SEGMENT_RE.test(segment) && !PATCH_APPLY_INSPECT_ONLY_RE.test(segment)) needsStatusSweep = true;
7321
+ if (isFormatterWriteSegment(segment)) needsStatusSweep = true;
7322
+ for (const f of extractSedInPlaceFiles(segment)) paths.push(atCwd(f));
7323
+ for (const f of extractTeeFiles(segment)) paths.push(atCwd(f));
7324
+ for (const f of extractRedirectTargets(segment)) paths.push(atCwd(f));
7325
+ }
7326
+ if (needsStatusSweep) paths.push(...workingTreeStatusPaths(cwd));
7327
+ for (const p of paths) enqueueRewrittenPath(p);
7328
+ }
7148
7329
  function isTempPath(fp) {
7149
7330
  const norm = fp.replace(/\\/g, "/");
7150
7331
  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/") || isUnderSystemTemp(fp);
@@ -7259,9 +7440,126 @@ function extractWslCatFile(cmd) {
7259
7440
  if (flags === null) return null;
7260
7441
  return { filePath, ...flags };
7261
7442
  }
7443
+ function pythonScanText(text) {
7444
+ const dashC = /^python3?\s+-c\s*(['"])([\s\S]*)\1\s*$/.exec(text);
7445
+ if (dashC) {
7446
+ const source = dashC[2] ?? "";
7447
+ return { source, masked: maskPythonStrings(source) };
7448
+ }
7449
+ if (/^python3?\b/.test(text)) return { source: text, masked: text };
7450
+ return { source: text, masked: maskPythonStrings(text) };
7451
+ }
7452
+ function maskPythonStrings(text) {
7453
+ const out = text.split("");
7454
+ let i = 0;
7455
+ while (i < text.length) {
7456
+ const ch = text[i];
7457
+ if (ch !== "'" && ch !== '"') {
7458
+ i += 1;
7459
+ continue;
7460
+ }
7461
+ const triple = text.slice(i, i + 3);
7462
+ const delim = triple === ch + ch + ch ? triple : ch;
7463
+ let j = i + delim.length;
7464
+ while (j < text.length) {
7465
+ if (text[j] === "\\") {
7466
+ j += 2;
7467
+ continue;
7468
+ }
7469
+ if (text.slice(j, j + delim.length) === delim) break;
7470
+ out[j] = " ";
7471
+ j += 1;
7472
+ }
7473
+ i = j + delim.length;
7474
+ }
7475
+ return out.join("");
7476
+ }
7477
+ function pythonOpenCalls(text) {
7478
+ const { source, masked } = pythonScanText(text);
7479
+ const calls = [];
7480
+ for (const opener of masked.matchAll(/\bopen\s*\(/g)) {
7481
+ const start = (opener.index ?? 0) + opener[0].length;
7482
+ const args = [];
7483
+ let current = "";
7484
+ let depth = 0;
7485
+ let quote = "";
7486
+ let closed = false;
7487
+ for (let i = start; i < source.length; i++) {
7488
+ const ch = source[i] ?? "";
7489
+ if (quote !== "") {
7490
+ if (ch === "\\") {
7491
+ current += ch + (source[i + 1] ?? "");
7492
+ i += 1;
7493
+ continue;
7494
+ }
7495
+ if (ch === quote) quote = "";
7496
+ current += ch;
7497
+ continue;
7498
+ }
7499
+ if (ch === "'" || ch === '"') {
7500
+ quote = ch;
7501
+ current += ch;
7502
+ continue;
7503
+ }
7504
+ if (ch === "(" || ch === "[" || ch === "{") {
7505
+ depth += 1;
7506
+ current += ch;
7507
+ continue;
7508
+ }
7509
+ if (ch === ")" && depth === 0) {
7510
+ args.push(current);
7511
+ closed = true;
7512
+ break;
7513
+ }
7514
+ if (ch === ")" || ch === "]" || ch === "}") {
7515
+ depth -= 1;
7516
+ current += ch;
7517
+ continue;
7518
+ }
7519
+ if (ch === "," && depth === 0) {
7520
+ args.push(current);
7521
+ current = "";
7522
+ continue;
7523
+ }
7524
+ current += ch;
7525
+ }
7526
+ if (!closed) continue;
7527
+ const literalOf = (arg) => {
7528
+ const m = /^\s*r?(['"])([^'"]*)\1\s*$/.exec(arg);
7529
+ return m?.[2] ?? null;
7530
+ };
7531
+ const keyword = args.find((a) => /^\s*mode\s*=/.test(a));
7532
+ const positional = args[1] !== void 0 && /^\s*[A-Za-z_]\w*\s*=[^=]/.test(args[1]) ? void 0 : args[1];
7533
+ const modeArg = keyword !== void 0 ? keyword.replace(/^\s*mode\s*=/, "") : positional;
7534
+ calls.push({
7535
+ pathLiteral: args.length > 0 ? literalOf(args[0] ?? "") : null,
7536
+ hasMode: modeArg !== void 0,
7537
+ modeLiteral: modeArg === void 0 ? null : literalOf(modeArg)
7538
+ });
7539
+ }
7540
+ return calls;
7541
+ }
7542
+ function pythonOpenWritesAFile(text) {
7543
+ return pythonOpenCalls(text).some(
7544
+ (call) => call.hasMode && (call.modeLiteral === null || /[wax+]/.test(call.modeLiteral))
7545
+ );
7546
+ }
7547
+ function pythonWritesThroughFileObject(text) {
7548
+ const { masked } = pythonScanText(text);
7549
+ for (const call of masked.matchAll(/\.write(?:lines)?\s*\(/g)) {
7550
+ const receiver = masked.slice(0, call.index ?? 0);
7551
+ if (/(?:^|[^\w.])(?:sys\s*\.\s*)?(?:stdout|stderr|stdin)(?:\s*\.\s*buffer)?$/.test(receiver)) continue;
7552
+ return true;
7553
+ }
7554
+ return false;
7555
+ }
7556
+ function pythonOpenPathsAreAllLiteral(text) {
7557
+ const calls = pythonOpenCalls(text);
7558
+ return calls.length > 0 && calls.every((call) => call.pathLiteral !== null);
7559
+ }
7262
7560
  function extractPythonFileRead(cmd) {
7263
7561
  if (!/^python3?\b/.test(cmd)) return null;
7264
- if (/open\s*\([^)]*,\s*['"][wa]/i.test(cmd) || /\.write\s*\(/.test(cmd)) return null;
7562
+ if (pythonOpenWritesAFile(cmd) || pythonWritesThroughFileObject(cmd)) return null;
7265
7563
  const outputOpen = /open\s*\(\s*r?['"]([^'"]+\.output)['"]/i.exec(cmd);
7266
7564
  if (outputOpen?.[1]) {
7267
7565
  const filePath = outputOpen[1];
@@ -7272,7 +7570,7 @@ function extractPythonFileRead(cmd) {
7272
7570
  const heredocMatch = /^python3?\s+-\s+<<\s*'?(\w+)'?\s*\n([\s\S]*?)\n\1\s*$/.exec(cmd);
7273
7571
  if (heredocMatch) {
7274
7572
  const body = heredocMatch[2] ?? "";
7275
- if (/open\s*\([^)]*,\s*['"][wa]/i.test(body) || /\.write\s*\(/.test(body) || /\.writelines\s*\(/.test(body)) return null;
7573
+ if (pythonOpenWritesAFile(body) || pythonWritesThroughFileObject(body)) return null;
7276
7574
  const heredocOpen = /open\s*\(\s*r?['"]([^'"]+\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties))['"]/i.exec(body);
7277
7575
  if (heredocOpen?.[1]) {
7278
7576
  const filePath = heredocOpen[1];
@@ -7280,7 +7578,7 @@ function extractPythonFileRead(cmd) {
7280
7578
  const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
7281
7579
  return { filePath, isDoc, isTranscript: false };
7282
7580
  }
7283
- if (/open\s*\(/.test(body)) {
7581
+ if (/open\s*\(/.test(body) && !pythonOpenPathsAreAllLiteral(body)) {
7284
7582
  const literal2 = /['"]([^'"]+\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties))['"]/i.exec(body);
7285
7583
  if (literal2?.[1]) {
7286
7584
  const filePath = literal2[1];
@@ -7301,7 +7599,7 @@ function extractPythonFileRead(cmd) {
7301
7599
  const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
7302
7600
  return { filePath, isDoc, isTranscript: false };
7303
7601
  }
7304
- if (/open\s*\(/.test(cmd)) {
7602
+ if (/open\s*\(/.test(cmd) && !pythonOpenPathsAreAllLiteral(cmd)) {
7305
7603
  const literal2 = /['"]([^'"]+\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties))['"]/i.exec(cmd);
7306
7604
  if (literal2) {
7307
7605
  const filePath = literal2[1] ?? "";
@@ -7348,6 +7646,30 @@ function extractSedRange(cmd) {
7348
7646
  if (isTempPath(filePath)) return null;
7349
7647
  return { filePath, ranges };
7350
7648
  }
7649
+ function extractAwkRange(cmd) {
7650
+ const m = /^awk\s+(?:'([^']+)'|"([^"]+)")\s+(?:"([^"]+)"|'([^']+)'|(\S+))(?:\s+2>\/dev\/null)?\s*$/.exec(cmd);
7651
+ if (!m) return null;
7652
+ const program = m[1] ?? m[2];
7653
+ if (program === void 0) return null;
7654
+ const cmp = /^\s*NR\s*>=\s*(\d+)\s*&&\s*NR\s*<=\s*(\d+)\s*$/.exec(program);
7655
+ const rng = /^\s*NR\s*==\s*(\d+)\s*,\s*NR\s*==\s*(\d+)\s*$/.exec(program);
7656
+ const hit = cmp ?? rng;
7657
+ if (!hit) return null;
7658
+ const start = parseInt(hit[1], 10);
7659
+ const end = parseInt(hit[2], 10);
7660
+ if (start < 1 || end < start) return null;
7661
+ const filePath = m[3] ?? m[4] ?? m[5];
7662
+ if (filePath === void 0) return null;
7663
+ if (isTempPath(filePath)) return null;
7664
+ return { filePath, ranges: [[start, end]] };
7665
+ }
7666
+ function extractLineRangeRead(cmd) {
7667
+ const sed = extractSedRange(cmd);
7668
+ if (sed !== null) return { ...sed, tool: "sed" };
7669
+ const awk = extractAwkRange(cmd);
7670
+ if (awk !== null) return { ...awk, tool: "awk" };
7671
+ return null;
7672
+ }
7351
7673
  var SYMBOL_BEARING_LANGUAGES = /* @__PURE__ */ new Set([
7352
7674
  "python",
7353
7675
  "typescript",
@@ -7378,11 +7700,11 @@ var SYMBOL_BEARING_LANGUAGES = /* @__PURE__ */ new Set([
7378
7700
  "salesforce_metadata",
7379
7701
  "salesforce_markup"
7380
7702
  ]);
7381
- function sedRangeHint(filePath, ranges) {
7703
+ function sedRangeHint(filePath, ranges, tool) {
7382
7704
  const lang = detectLanguage(filePath);
7383
7705
  const rangeReads = ranges.map(([s, e]) => '`token-goat read "' + filePath + "@" + s + "-" + e + '"`');
7384
7706
  const allReads = rangeReads.length === 2 ? rangeReads.join(" and ") : rangeReads.length >= 3 ? rangeReads.slice(0, -1).join(", ") + ", and " + rangeReads[rangeReads.length - 1] : rangeReads[0];
7385
- const prefix = "`sed -n` line-range reads bypass read hooks. ";
7707
+ const prefix = "`" + (tool === "awk" ? "awk" : "sed -n") + "` line-range reads bypass read hooks. ";
7386
7708
  if (lang === "markdown") {
7387
7709
  return prefix + 'For Markdown, `token-goat section "' + filePath + '::<heading>"` extracts a whole section by name (robust to line shifts); or ' + allReads + " for exactly those lines.";
7388
7710
  }
@@ -7407,7 +7729,7 @@ function findRangeOverlap(prior, start, end) {
7407
7729
  return best;
7408
7730
  }
7409
7731
  function sedOverlapHint(filePath, prior, start, end) {
7410
- const base = "You already read lines " + prior[0] + "-" + prior[1] + " of " + filePath + " via an earlier `sed` this session; this read (" + start + "-" + end + ") overlaps. ";
7732
+ const base = "You already read lines " + prior[0] + "-" + prior[1] + " of " + filePath + " via an earlier line-range read this session; this read (" + start + "-" + end + ") overlaps. ";
7411
7733
  const segments = [];
7412
7734
  if (start < prior[0]) segments.push([start, Math.min(end, prior[0] - 1)]);
7413
7735
  if (end > prior[1]) segments.push([Math.max(start, prior[1] + 1), end]);
@@ -7708,11 +8030,12 @@ function maybeCompressRewrite(event, rawCmd, cmd) {
7708
8030
  }
7709
8031
  if (!cfg.enabled) return null;
7710
8032
  if (!canRunWrappedShell()) return null;
7711
- const detected = detectFromCommand(cmd, getCwd(event));
8033
+ const gateCmd = stripTrailingStderrRedirect(cmd);
8034
+ const detected = detectFromCommand(gateCmd, getCwd(event));
7712
8035
  let filterName;
7713
8036
  if (detected !== null) {
7714
8037
  filterName = detected.filter.name;
7715
- } else if (isCompressibleSingleCommand(cmd)) {
8038
+ } else if (isCompressibleSingleCommand(gateCmd)) {
7716
8039
  filterName = "generic";
7717
8040
  } else {
7718
8041
  return null;
@@ -7929,9 +8252,9 @@ function preBashHandlerInner(event) {
7929
8252
  "Use `token-goat outline <file>` to see symbol names and line counts without loading files."
7930
8253
  );
7931
8254
  }
7932
- const sedRange = extractSedRange(cmd);
8255
+ const sedRange = extractLineRangeRead(cmd);
7933
8256
  if (sedRange !== null) {
7934
- const { filePath, ranges } = sedRange;
8257
+ const { filePath, ranges, tool } = sedRange;
7935
8258
  const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
7936
8259
  recordStat("session_hint", 0, 0);
7937
8260
  const sedDedupKey = resolveIndexPath(hintPath, preHookCwd ?? process.cwd());
@@ -7947,7 +8270,7 @@ function preBashHandlerInner(event) {
7947
8270
  }
7948
8271
  }
7949
8272
  const hints = [...overlapHints];
7950
- if (freshRanges.length > 0) hints.push(sedRangeHint(hintPath, freshRanges));
8273
+ if (freshRanges.length > 0) hints.push(sedRangeHint(hintPath, freshRanges, tool));
7951
8274
  return contextOutput(hints.join(" "));
7952
8275
  }
7953
8276
  const catJsonPipe = extractCatJsonPipe(cmd);
@@ -8200,7 +8523,7 @@ function preBashHandlerInner(event) {
8200
8523
  );
8201
8524
  }
8202
8525
  }
8203
- if (!isBuildCommand(cmd) && detectFromCommand(cmd, preHookCwd ?? void 0) === null) return passOutput();
8526
+ if (!isBuildCommand(cmd) && detectFromCommand(stripTrailingStderrRedirect(cmd), preHookCwd ?? void 0) === null) return passOutput();
8204
8527
  const cmdHash = shortFingerprint(stripOutputPipeline(cmd));
8205
8528
  const outputId = getBashOutputId(cmdHash);
8206
8529
  const entryRaw = outputId !== null ? getBashOutput(outputId) : null;
@@ -8315,8 +8638,7 @@ async function postBashHandler(event) {
8315
8638
  }
8316
8639
  const mutationDiff = runGit(["diff", "--name-only", diffBase, "HEAD"], { cwd: gitDir, timeoutMs: 5e3 });
8317
8640
  if (mutationDiff.exitCode === 0) {
8318
- const toplevel = runGit(["rev-parse", "--show-toplevel"], { cwd: gitDir, timeoutMs: 5e3 });
8319
- const repoRoot = toplevel.exitCode === 0 && toplevel.stdout.trim() !== "" ? toplevel.stdout.trim() : gitDir;
8641
+ const repoRoot = gitRepoRoot(gitDir);
8320
8642
  for (const rel of mutationDiff.stdout.split("\n")) {
8321
8643
  const trimmed = rel.trim();
8322
8644
  if (trimmed.length === 0) continue;
@@ -8324,6 +8646,9 @@ async function postBashHandler(event) {
8324
8646
  }
8325
8647
  }
8326
8648
  }
8649
+ if (exitCode === null || exitCode === 0) {
8650
+ enqueueNonHeadMovingRewrites(cmd, rawCmd, cwd ?? process.cwd());
8651
+ }
8327
8652
  const curlDl = extractCurlDownload(cmd);
8328
8653
  if (curlDl !== null && (exitCode === null || exitCode === 0)) {
8329
8654
  const resolvedOutputPath = resolveIndexPath(curlDl.outputPath, cwd ?? process.cwd());
@@ -8971,8 +9296,21 @@ function compressGWorkspaceMcpResult(toolName, resultText) {
8971
9296
  const summarized = summarizeAttachments(trimmed);
8972
9297
  return finishWithGenericPass(resultText, summarized);
8973
9298
  }
9299
+ var DATA_URL_BASE64_RE = /data:([a-zA-Z0-9.+-]+\/[a-zA-Z0-9.+-]+);base64,([A-Za-z0-9+/=]+)/g;
9300
+ var MIN_BASE64_PAYLOAD_LEN = 100;
9301
+ function stripBase64DataUrls(resultText) {
9302
+ let changed = false;
9303
+ const out = resultText.replace(DATA_URL_BASE64_RE, (match, mime, payload) => {
9304
+ if (payload.length < MIN_BASE64_PAYLOAD_LEN) return match;
9305
+ changed = true;
9306
+ return `data:${mime};base64,<stripped ${payload.length} bytes>`;
9307
+ });
9308
+ return changed ? out : null;
9309
+ }
8974
9310
  function compressMcpResultWithPacks(toolName, resultText) {
8975
- return compressGithubMcpResult(toolName, resultText) ?? compressBrowserMcpResult(toolName, resultText) ?? compressGWorkspaceMcpResult(toolName, resultText);
9311
+ const base64Stripped = stripBase64DataUrls(resultText);
9312
+ const text = base64Stripped ?? resultText;
9313
+ return compressGithubMcpResult(toolName, text) ?? compressBrowserMcpResult(toolName, text) ?? compressGWorkspaceMcpResult(toolName, text) ?? base64Stripped;
8976
9314
  }
8977
9315
 
8978
9316
  // src/hooks_mcp.ts