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
@@ -4,14 +4,14 @@ import {
4
4
  buildEvent,
5
5
  relay,
6
6
  relayInProcess
7
- } from "./token-goat-hook-chunk-QD2DX2U7.mjs";
8
- import "./token-goat-hook-chunk-HXHFLKEL.mjs";
7
+ } from "./token-goat-hook-chunk-BU5OFDGQ.mjs";
8
+ import "./token-goat-hook-chunk-2ISZ4AZR.mjs";
9
9
  import {
10
10
  MAX_STDIN_BYTES,
11
11
  readStdinJson
12
- } from "./token-goat-hook-chunk-ZT47M3IA.mjs";
13
- import "./token-goat-hook-chunk-UWV4MTYI.mjs";
14
- import "./token-goat-hook-chunk-E6AIPKNF.mjs";
12
+ } from "./token-goat-hook-chunk-6C5RN3CM.mjs";
13
+ import "./token-goat-hook-chunk-6QTWMSRO.mjs";
14
+ import "./token-goat-hook-chunk-CNLMPHXT.mjs";
15
15
  import "./token-goat-hook-chunk-BUOCULAM.mjs";
16
16
  import "./token-goat-hook-chunk-RFRLWOQH.mjs";
17
17
  export {
@@ -8,7 +8,7 @@ import {
8
8
  isRewriteWorthwhile,
9
9
  resolveMinNetSavingsBytes,
10
10
  shlexSplit
11
- } from "./token-goat-hook-chunk-HXHFLKEL.mjs";
11
+ } from "./token-goat-hook-chunk-2ISZ4AZR.mjs";
12
12
  import {
13
13
  BASH_OUTPUT_SUBDIR,
14
14
  HOOK_EVENTS,
@@ -26,7 +26,7 @@ import {
26
26
  storeBashOutput,
27
27
  storeWebOutput,
28
28
  summarizeOutputDelta
29
- } from "./token-goat-hook-chunk-ZT47M3IA.mjs";
29
+ } from "./token-goat-hook-chunk-6C5RN3CM.mjs";
30
30
  import {
31
31
  BODY_FIRST_TOOL_RESPONSE_KEYS,
32
32
  OUTPUT_FIRST_TOOL_RESPONSE_KEYS,
@@ -116,11 +116,12 @@ import {
116
116
  wasCliReadThisSession,
117
117
  wasFileReadThisSession,
118
118
  wasHintShown
119
- } from "./token-goat-hook-chunk-UWV4MTYI.mjs";
119
+ } from "./token-goat-hook-chunk-6QTWMSRO.mjs";
120
120
  import {
121
121
  VERSION,
122
122
  detectHarness,
123
123
  detectLanguage,
124
+ displaySafePath,
124
125
  extractErrorMessage,
125
126
  foldPath,
126
127
  getDb,
@@ -134,7 +135,7 @@ import {
134
135
  runGit,
135
136
  shortFingerprint,
136
137
  toKB
137
- } from "./token-goat-hook-chunk-E6AIPKNF.mjs";
138
+ } from "./token-goat-hook-chunk-CNLMPHXT.mjs";
138
139
 
139
140
  // src/hooks_grep.ts
140
141
  function grepIntInput(toolInput, key) {
@@ -311,9 +312,9 @@ function postEditHandlerInner(event) {
311
312
  } catch {
312
313
  }
313
314
  if (meetsSavingsFloor(editedSize)) {
314
- const escapedPath = normalized.replace(/`/g, "\\`").replace(/"/g, '\\"');
315
+ const escapedPath = displaySafePath(normalized).replace(/`/g, "\\`").replace(/"/g, '\\"');
315
316
  return contextOutput(
316
- editedBasename + ' was edited. Use `token-goat section "' + escapedPath + '::HeadingName"` to re-read a specific section rather than the full file.'
317
+ displaySafePath(editedBasename) + ' was edited. Use `token-goat section "' + escapedPath + '::HeadingName"` to re-read a specific section rather than the full file.'
317
318
  );
318
319
  }
319
320
  }
@@ -572,7 +573,7 @@ import crypto from "node:crypto";
572
573
  var TRACKED_SKILL = "token-goat";
573
574
  var MAX_COMMANDS_SHOWN = 8;
574
575
  async function currentCommandNames() {
575
- const { buildProgram } = await import("./token-goat-hook-chunk-WBBHV6TZ.mjs");
576
+ const { buildProgram } = await import("./token-goat-hook-chunk-I5VM5PO5.mjs");
576
577
  return flattenCommandNames(buildCommandManifest(buildProgram()));
577
578
  }
578
579
  async function recordSkillVersionSnapshot(sessionId, skillName) {
@@ -7051,6 +7052,10 @@ function stripCdPrefix(cmd) {
7051
7052
  const stripped = cmd.replace(/^(?:cd\s+(?:"[^"]*"|'[^']*'|\S+)\s*&&\s*)+/, "");
7052
7053
  return stripped.trim() || cmd;
7053
7054
  }
7055
+ function stripTrailingStderrRedirect(cmd) {
7056
+ const stripped = cmd.replace(/\s*2>&1\s*$/, "");
7057
+ return stripped.trim() || cmd;
7058
+ }
7054
7059
  function hasTestRunScopeOrBudget(cmd) {
7055
7060
  const hasTimeout = /(?:^|\s)(?:timeout(?:\.exe)?\s+\S+|--(?:test)?timeout(?:=|\s)\S+)/i.test(cmd);
7056
7061
  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);
@@ -7142,6 +7147,182 @@ function isHeadMovingGitCommand(cmd) {
7142
7147
  }
7143
7148
  var ORIG_HEAD_ELIGIBLE_GIT_RE = /^\s*git\s+(?:pull|merge|rebase)\b/i;
7144
7149
  var ORIG_HEAD_REFLOG_MSG_RE = /^(merge\s|rebase\s\(|pull\s)/i;
7150
+ var GIT_RESTORE_RE = /^\s*git\s+restore\b/i;
7151
+ var GIT_RESTORE_STAGED_ONLY_RE = /(?:^|\s)(?:--staged|-S)(?:\s|$)/;
7152
+ var GIT_STASH_APPLY_RE = /^\s*git\s+stash\s+(?:pop|apply)\b/i;
7153
+ var PATCH_APPLY_SEGMENT_RE = /^\s*(?:\S*[/\\])?(?:git\s+apply|patch)\b/i;
7154
+ var PATCH_APPLY_INSPECT_ONLY_RE = /(?:^|\s)--(?:check|stat|numstat|summary|dry-run)(?:\s|=|$)/i;
7155
+ function isFormatterWriteSegment(segment) {
7156
+ return /(?:^|\s|[/\\])(?:prettier|eslint)\b/i.test(segment) && /(?:^|\s)--(?:write|fix)(?:\s|$)/i.test(segment);
7157
+ }
7158
+ function splitShellSegments(cmd) {
7159
+ const segments = [];
7160
+ let cur = "";
7161
+ let quote = null;
7162
+ for (let i = 0; i < cmd.length; i++) {
7163
+ const ch = cmd[i];
7164
+ if (quote !== null) {
7165
+ cur += ch;
7166
+ if (ch === quote) quote = null;
7167
+ continue;
7168
+ }
7169
+ if (ch === '"' || ch === "'") {
7170
+ quote = ch;
7171
+ cur += ch;
7172
+ continue;
7173
+ }
7174
+ if (ch === "\\" && i + 1 < cmd.length) {
7175
+ cur += ch + cmd[i + 1];
7176
+ i++;
7177
+ continue;
7178
+ }
7179
+ if (ch === "|" || ch === ";" || ch === "\n" || ch === "&") {
7180
+ segments.push(cur);
7181
+ cur = "";
7182
+ if ((ch === "|" || ch === "&") && cmd[i + 1] === ch) i++;
7183
+ continue;
7184
+ }
7185
+ cur += ch;
7186
+ }
7187
+ segments.push(cur);
7188
+ return segments.map((s) => s.trim()).filter((s) => s.length > 0);
7189
+ }
7190
+ function safeShlexSplit(segment) {
7191
+ try {
7192
+ return shlexSplit(segment);
7193
+ } catch {
7194
+ return null;
7195
+ }
7196
+ }
7197
+ function segmentCommandIs(segment, name2) {
7198
+ return new RegExp("^\\s*(?:\\S*[/\\\\])?" + name2 + "(?:\\.exe)?\\b", "i").test(segment);
7199
+ }
7200
+ function extractSedInPlaceFiles(segment) {
7201
+ if (!segmentCommandIs(segment, "sed")) return [];
7202
+ const tokens = safeShlexSplit(segment);
7203
+ if (tokens === null) return [];
7204
+ const rest = tokens.slice(1);
7205
+ const inPlaceIdx = rest.findIndex((t) => t === "--in-place" || t.startsWith("--in-place=") || /^-[a-zA-Z]*i/.test(t));
7206
+ if (inPlaceIdx === -1) return [];
7207
+ const files = [];
7208
+ let scriptTaken = rest.some((t) => t === "-e" || t === "-f" || t.startsWith("--expression") || t.startsWith("--file"));
7209
+ for (let i = 0; i < rest.length; i++) {
7210
+ const t = rest[i];
7211
+ if (i === inPlaceIdx) {
7212
+ if (t === "-i" && rest[i + 1] === "") i++;
7213
+ continue;
7214
+ }
7215
+ if (t === "-e" || t === "-f" || t === "--expression" || t === "--file") {
7216
+ i++;
7217
+ continue;
7218
+ }
7219
+ if (t.length > 1 && t.startsWith("-")) continue;
7220
+ if (!scriptTaken) {
7221
+ scriptTaken = true;
7222
+ continue;
7223
+ }
7224
+ files.push(t);
7225
+ }
7226
+ return files;
7227
+ }
7228
+ function extractTeeFiles(segment) {
7229
+ if (!segmentCommandIs(segment, "tee")) return [];
7230
+ const tokens = safeShlexSplit(segment);
7231
+ if (tokens === null) return [];
7232
+ return tokens.slice(1).filter((t) => t.length > 0 && !t.startsWith("-"));
7233
+ }
7234
+ function extractRedirectTargets(segment) {
7235
+ const targets = [];
7236
+ const re = /(?:^|\s)[0-9]?>>?\s*("[^"]*"|'[^']*'|[^\s|&<>]+)/g;
7237
+ let m;
7238
+ while ((m = re.exec(segment)) !== null) {
7239
+ const raw = m[1];
7240
+ targets.push(raw.length >= 2 && (raw[0] === '"' || raw[0] === "'") ? raw.slice(1, -1) : raw);
7241
+ }
7242
+ return targets;
7243
+ }
7244
+ function extractGitRestorePathspecs(cmd) {
7245
+ const m = /^\s*git\s+restore\b(.*)$/i.exec(cmd);
7246
+ if (m === null) return [];
7247
+ const tokens = safeShlexSplit(m[1] ?? "");
7248
+ if (tokens === null) return [];
7249
+ const dashDash = tokens.indexOf("--");
7250
+ if (dashDash !== -1) return tokens.slice(dashDash + 1);
7251
+ const specs = [];
7252
+ let skipNext = false;
7253
+ for (const t of tokens) {
7254
+ if (skipNext) {
7255
+ skipNext = false;
7256
+ continue;
7257
+ }
7258
+ if (t === "-s" || t === "--source") {
7259
+ skipNext = true;
7260
+ continue;
7261
+ }
7262
+ if (t.startsWith("-")) continue;
7263
+ specs.push(t);
7264
+ }
7265
+ return specs;
7266
+ }
7267
+ function gitRepoRoot(gitDir) {
7268
+ const toplevel = runGit(["rev-parse", "--show-toplevel"], { cwd: gitDir, timeoutMs: 5e3 });
7269
+ return toplevel.exitCode === 0 && toplevel.stdout.trim() !== "" ? toplevel.stdout.trim() : gitDir;
7270
+ }
7271
+ function expandGitPathspecs(specs, gitDir) {
7272
+ if (specs.length === 0) return [];
7273
+ const listed = runGit(["ls-files", "--full-name", "--", ...specs], { cwd: gitDir, timeoutMs: 5e3 });
7274
+ if (listed.exitCode !== 0) return [];
7275
+ const repoRoot = gitRepoRoot(gitDir);
7276
+ return listed.stdout.split("\n").map((l) => l.trim()).filter((l) => l.length > 0).map((rel) => resolveIndexPath(rel, repoRoot));
7277
+ }
7278
+ function workingTreeStatusPaths(gitDir) {
7279
+ const status = runGit(["status", "--porcelain"], { cwd: gitDir, timeoutMs: 5e3 });
7280
+ if (status.exitCode !== 0) return [];
7281
+ const repoRoot = gitRepoRoot(gitDir);
7282
+ const out = [];
7283
+ for (const line of status.stdout.split("\n")) {
7284
+ if (line.length < 4) continue;
7285
+ let rel = line.slice(3).trim();
7286
+ if (rel.length === 0) continue;
7287
+ const arrow = rel.lastIndexOf(" -> ");
7288
+ if (arrow !== -1) rel = rel.slice(arrow + 4);
7289
+ if (rel.startsWith('"')) {
7290
+ try {
7291
+ rel = JSON.parse(rel);
7292
+ } catch {
7293
+ continue;
7294
+ }
7295
+ }
7296
+ out.push(resolveIndexPath(rel, repoRoot));
7297
+ }
7298
+ return out;
7299
+ }
7300
+ function enqueueRewrittenPath(absPath) {
7301
+ if (/(?:^|[/\\])(?:NUL|nul)$/.test(absPath) || absPath.replace(/\\/g, "/").endsWith("/dev/null")) return;
7302
+ try {
7303
+ if (!statSync3(absPath).isFile()) return;
7304
+ } catch {
7305
+ return;
7306
+ }
7307
+ enqueueDirtyPathSafe(absPath, { alreadyResolved: true });
7308
+ }
7309
+ function enqueueNonHeadMovingRewrites(cmd, rawCmd, cwd) {
7310
+ const atCwd = (f) => resolveIndexPath(resolveCdHintPath(rawCmd, f, cwd), cwd);
7311
+ const paths = [];
7312
+ let needsStatusSweep = GIT_STASH_APPLY_RE.test(cmd);
7313
+ if (GIT_RESTORE_RE.test(cmd) && !GIT_RESTORE_STAGED_ONLY_RE.test(cmd)) {
7314
+ paths.push(...expandGitPathspecs(extractGitRestorePathspecs(cmd), cwd));
7315
+ }
7316
+ for (const segment of splitShellSegments(cmd)) {
7317
+ if (PATCH_APPLY_SEGMENT_RE.test(segment) && !PATCH_APPLY_INSPECT_ONLY_RE.test(segment)) needsStatusSweep = true;
7318
+ if (isFormatterWriteSegment(segment)) needsStatusSweep = true;
7319
+ for (const f of extractSedInPlaceFiles(segment)) paths.push(atCwd(f));
7320
+ for (const f of extractTeeFiles(segment)) paths.push(atCwd(f));
7321
+ for (const f of extractRedirectTargets(segment)) paths.push(atCwd(f));
7322
+ }
7323
+ if (needsStatusSweep) paths.push(...workingTreeStatusPaths(cwd));
7324
+ for (const p of paths) enqueueRewrittenPath(p);
7325
+ }
7145
7326
  function isTempPath(fp) {
7146
7327
  const norm = fp.replace(/\\/g, "/");
7147
7328
  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);
@@ -7256,9 +7437,126 @@ function extractWslCatFile(cmd) {
7256
7437
  if (flags === null) return null;
7257
7438
  return { filePath, ...flags };
7258
7439
  }
7440
+ function pythonScanText(text) {
7441
+ const dashC = /^python3?\s+-c\s*(['"])([\s\S]*)\1\s*$/.exec(text);
7442
+ if (dashC) {
7443
+ const source = dashC[2] ?? "";
7444
+ return { source, masked: maskPythonStrings(source) };
7445
+ }
7446
+ if (/^python3?\b/.test(text)) return { source: text, masked: text };
7447
+ return { source: text, masked: maskPythonStrings(text) };
7448
+ }
7449
+ function maskPythonStrings(text) {
7450
+ const out = text.split("");
7451
+ let i = 0;
7452
+ while (i < text.length) {
7453
+ const ch = text[i];
7454
+ if (ch !== "'" && ch !== '"') {
7455
+ i += 1;
7456
+ continue;
7457
+ }
7458
+ const triple = text.slice(i, i + 3);
7459
+ const delim = triple === ch + ch + ch ? triple : ch;
7460
+ let j = i + delim.length;
7461
+ while (j < text.length) {
7462
+ if (text[j] === "\\") {
7463
+ j += 2;
7464
+ continue;
7465
+ }
7466
+ if (text.slice(j, j + delim.length) === delim) break;
7467
+ out[j] = " ";
7468
+ j += 1;
7469
+ }
7470
+ i = j + delim.length;
7471
+ }
7472
+ return out.join("");
7473
+ }
7474
+ function pythonOpenCalls(text) {
7475
+ const { source, masked } = pythonScanText(text);
7476
+ const calls = [];
7477
+ for (const opener of masked.matchAll(/\bopen\s*\(/g)) {
7478
+ const start = (opener.index ?? 0) + opener[0].length;
7479
+ const args = [];
7480
+ let current = "";
7481
+ let depth = 0;
7482
+ let quote = "";
7483
+ let closed = false;
7484
+ for (let i = start; i < source.length; i++) {
7485
+ const ch = source[i] ?? "";
7486
+ if (quote !== "") {
7487
+ if (ch === "\\") {
7488
+ current += ch + (source[i + 1] ?? "");
7489
+ i += 1;
7490
+ continue;
7491
+ }
7492
+ if (ch === quote) quote = "";
7493
+ current += ch;
7494
+ continue;
7495
+ }
7496
+ if (ch === "'" || ch === '"') {
7497
+ quote = ch;
7498
+ current += ch;
7499
+ continue;
7500
+ }
7501
+ if (ch === "(" || ch === "[" || ch === "{") {
7502
+ depth += 1;
7503
+ current += ch;
7504
+ continue;
7505
+ }
7506
+ if (ch === ")" && depth === 0) {
7507
+ args.push(current);
7508
+ closed = true;
7509
+ break;
7510
+ }
7511
+ if (ch === ")" || ch === "]" || ch === "}") {
7512
+ depth -= 1;
7513
+ current += ch;
7514
+ continue;
7515
+ }
7516
+ if (ch === "," && depth === 0) {
7517
+ args.push(current);
7518
+ current = "";
7519
+ continue;
7520
+ }
7521
+ current += ch;
7522
+ }
7523
+ if (!closed) continue;
7524
+ const literalOf = (arg) => {
7525
+ const m = /^\s*r?(['"])([^'"]*)\1\s*$/.exec(arg);
7526
+ return m?.[2] ?? null;
7527
+ };
7528
+ const keyword = args.find((a) => /^\s*mode\s*=/.test(a));
7529
+ const positional = args[1] !== void 0 && /^\s*[A-Za-z_]\w*\s*=[^=]/.test(args[1]) ? void 0 : args[1];
7530
+ const modeArg = keyword !== void 0 ? keyword.replace(/^\s*mode\s*=/, "") : positional;
7531
+ calls.push({
7532
+ pathLiteral: args.length > 0 ? literalOf(args[0] ?? "") : null,
7533
+ hasMode: modeArg !== void 0,
7534
+ modeLiteral: modeArg === void 0 ? null : literalOf(modeArg)
7535
+ });
7536
+ }
7537
+ return calls;
7538
+ }
7539
+ function pythonOpenWritesAFile(text) {
7540
+ return pythonOpenCalls(text).some(
7541
+ (call) => call.hasMode && (call.modeLiteral === null || /[wax+]/.test(call.modeLiteral))
7542
+ );
7543
+ }
7544
+ function pythonWritesThroughFileObject(text) {
7545
+ const { masked } = pythonScanText(text);
7546
+ for (const call of masked.matchAll(/\.write(?:lines)?\s*\(/g)) {
7547
+ const receiver = masked.slice(0, call.index ?? 0);
7548
+ if (/(?:^|[^\w.])(?:sys\s*\.\s*)?(?:stdout|stderr|stdin)(?:\s*\.\s*buffer)?$/.test(receiver)) continue;
7549
+ return true;
7550
+ }
7551
+ return false;
7552
+ }
7553
+ function pythonOpenPathsAreAllLiteral(text) {
7554
+ const calls = pythonOpenCalls(text);
7555
+ return calls.length > 0 && calls.every((call) => call.pathLiteral !== null);
7556
+ }
7259
7557
  function extractPythonFileRead(cmd) {
7260
7558
  if (!/^python3?\b/.test(cmd)) return null;
7261
- if (/open\s*\([^)]*,\s*['"][wa]/i.test(cmd) || /\.write\s*\(/.test(cmd)) return null;
7559
+ if (pythonOpenWritesAFile(cmd) || pythonWritesThroughFileObject(cmd)) return null;
7262
7560
  const outputOpen = /open\s*\(\s*r?['"]([^'"]+\.output)['"]/i.exec(cmd);
7263
7561
  if (outputOpen?.[1]) {
7264
7562
  const filePath = outputOpen[1];
@@ -7269,7 +7567,7 @@ function extractPythonFileRead(cmd) {
7269
7567
  const heredocMatch = /^python3?\s+-\s+<<\s*'?(\w+)'?\s*\n([\s\S]*?)\n\1\s*$/.exec(cmd);
7270
7568
  if (heredocMatch) {
7271
7569
  const body = heredocMatch[2] ?? "";
7272
- if (/open\s*\([^)]*,\s*['"][wa]/i.test(body) || /\.write\s*\(/.test(body) || /\.writelines\s*\(/.test(body)) return null;
7570
+ if (pythonOpenWritesAFile(body) || pythonWritesThroughFileObject(body)) return null;
7273
7571
  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);
7274
7572
  if (heredocOpen?.[1]) {
7275
7573
  const filePath = heredocOpen[1];
@@ -7277,7 +7575,7 @@ function extractPythonFileRead(cmd) {
7277
7575
  const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
7278
7576
  return { filePath, isDoc, isTranscript: false };
7279
7577
  }
7280
- if (/open\s*\(/.test(body)) {
7578
+ if (/open\s*\(/.test(body) && !pythonOpenPathsAreAllLiteral(body)) {
7281
7579
  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);
7282
7580
  if (literal2?.[1]) {
7283
7581
  const filePath = literal2[1];
@@ -7298,7 +7596,7 @@ function extractPythonFileRead(cmd) {
7298
7596
  const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
7299
7597
  return { filePath, isDoc, isTranscript: false };
7300
7598
  }
7301
- if (/open\s*\(/.test(cmd)) {
7599
+ if (/open\s*\(/.test(cmd) && !pythonOpenPathsAreAllLiteral(cmd)) {
7302
7600
  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);
7303
7601
  if (literal2) {
7304
7602
  const filePath = literal2[1] ?? "";
@@ -7345,6 +7643,30 @@ function extractSedRange(cmd) {
7345
7643
  if (isTempPath(filePath)) return null;
7346
7644
  return { filePath, ranges };
7347
7645
  }
7646
+ function extractAwkRange(cmd) {
7647
+ const m = /^awk\s+(?:'([^']+)'|"([^"]+)")\s+(?:"([^"]+)"|'([^']+)'|(\S+))(?:\s+2>\/dev\/null)?\s*$/.exec(cmd);
7648
+ if (!m) return null;
7649
+ const program = m[1] ?? m[2];
7650
+ if (program === void 0) return null;
7651
+ const cmp = /^\s*NR\s*>=\s*(\d+)\s*&&\s*NR\s*<=\s*(\d+)\s*$/.exec(program);
7652
+ const rng = /^\s*NR\s*==\s*(\d+)\s*,\s*NR\s*==\s*(\d+)\s*$/.exec(program);
7653
+ const hit = cmp ?? rng;
7654
+ if (!hit) return null;
7655
+ const start = parseInt(hit[1], 10);
7656
+ const end = parseInt(hit[2], 10);
7657
+ if (start < 1 || end < start) return null;
7658
+ const filePath = m[3] ?? m[4] ?? m[5];
7659
+ if (filePath === void 0) return null;
7660
+ if (isTempPath(filePath)) return null;
7661
+ return { filePath, ranges: [[start, end]] };
7662
+ }
7663
+ function extractLineRangeRead(cmd) {
7664
+ const sed = extractSedRange(cmd);
7665
+ if (sed !== null) return { ...sed, tool: "sed" };
7666
+ const awk = extractAwkRange(cmd);
7667
+ if (awk !== null) return { ...awk, tool: "awk" };
7668
+ return null;
7669
+ }
7348
7670
  var SYMBOL_BEARING_LANGUAGES = /* @__PURE__ */ new Set([
7349
7671
  "python",
7350
7672
  "typescript",
@@ -7375,11 +7697,11 @@ var SYMBOL_BEARING_LANGUAGES = /* @__PURE__ */ new Set([
7375
7697
  "salesforce_metadata",
7376
7698
  "salesforce_markup"
7377
7699
  ]);
7378
- function sedRangeHint(filePath, ranges) {
7700
+ function sedRangeHint(filePath, ranges, tool) {
7379
7701
  const lang = detectLanguage(filePath);
7380
7702
  const rangeReads = ranges.map(([s, e]) => '`token-goat read "' + filePath + "@" + s + "-" + e + '"`');
7381
7703
  const allReads = rangeReads.length === 2 ? rangeReads.join(" and ") : rangeReads.length >= 3 ? rangeReads.slice(0, -1).join(", ") + ", and " + rangeReads[rangeReads.length - 1] : rangeReads[0];
7382
- const prefix = "`sed -n` line-range reads bypass read hooks. ";
7704
+ const prefix = "`" + (tool === "awk" ? "awk" : "sed -n") + "` line-range reads bypass read hooks. ";
7383
7705
  if (lang === "markdown") {
7384
7706
  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.";
7385
7707
  }
@@ -7404,7 +7726,7 @@ function findRangeOverlap(prior, start, end) {
7404
7726
  return best;
7405
7727
  }
7406
7728
  function sedOverlapHint(filePath, prior, start, end) {
7407
- const base = "You already read lines " + prior[0] + "-" + prior[1] + " of " + filePath + " via an earlier `sed` this session; this read (" + start + "-" + end + ") overlaps. ";
7729
+ 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. ";
7408
7730
  const segments = [];
7409
7731
  if (start < prior[0]) segments.push([start, Math.min(end, prior[0] - 1)]);
7410
7732
  if (end > prior[1]) segments.push([Math.max(start, prior[1] + 1), end]);
@@ -7705,11 +8027,12 @@ function maybeCompressRewrite(event, rawCmd, cmd) {
7705
8027
  }
7706
8028
  if (!cfg.enabled) return null;
7707
8029
  if (!canRunWrappedShell()) return null;
7708
- const detected = detectFromCommand(cmd, getCwd(event));
8030
+ const gateCmd = stripTrailingStderrRedirect(cmd);
8031
+ const detected = detectFromCommand(gateCmd, getCwd(event));
7709
8032
  let filterName;
7710
8033
  if (detected !== null) {
7711
8034
  filterName = detected.filter.name;
7712
- } else if (isCompressibleSingleCommand(cmd)) {
8035
+ } else if (isCompressibleSingleCommand(gateCmd)) {
7713
8036
  filterName = "generic";
7714
8037
  } else {
7715
8038
  return null;
@@ -7926,9 +8249,9 @@ function preBashHandlerInner(event) {
7926
8249
  "Use `token-goat outline <file>` to see symbol names and line counts without loading files."
7927
8250
  );
7928
8251
  }
7929
- const sedRange = extractSedRange(cmd);
8252
+ const sedRange = extractLineRangeRead(cmd);
7930
8253
  if (sedRange !== null) {
7931
- const { filePath, ranges } = sedRange;
8254
+ const { filePath, ranges, tool } = sedRange;
7932
8255
  const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
7933
8256
  recordStat("session_hint", 0, 0);
7934
8257
  const sedDedupKey = resolveIndexPath(hintPath, preHookCwd ?? process.cwd());
@@ -7944,7 +8267,7 @@ function preBashHandlerInner(event) {
7944
8267
  }
7945
8268
  }
7946
8269
  const hints = [...overlapHints];
7947
- if (freshRanges.length > 0) hints.push(sedRangeHint(hintPath, freshRanges));
8270
+ if (freshRanges.length > 0) hints.push(sedRangeHint(hintPath, freshRanges, tool));
7948
8271
  return contextOutput(hints.join(" "));
7949
8272
  }
7950
8273
  const catJsonPipe = extractCatJsonPipe(cmd);
@@ -8197,7 +8520,7 @@ function preBashHandlerInner(event) {
8197
8520
  );
8198
8521
  }
8199
8522
  }
8200
- if (!isBuildCommand(cmd) && detectFromCommand(cmd, preHookCwd ?? void 0) === null) return passOutput();
8523
+ if (!isBuildCommand(cmd) && detectFromCommand(stripTrailingStderrRedirect(cmd), preHookCwd ?? void 0) === null) return passOutput();
8201
8524
  const cmdHash = shortFingerprint(stripOutputPipeline(cmd));
8202
8525
  const outputId = getBashOutputId(cmdHash);
8203
8526
  const entryRaw = outputId !== null ? getBashOutput(outputId) : null;
@@ -8312,8 +8635,7 @@ async function postBashHandler(event) {
8312
8635
  }
8313
8636
  const mutationDiff = runGit(["diff", "--name-only", diffBase, "HEAD"], { cwd: gitDir, timeoutMs: 5e3 });
8314
8637
  if (mutationDiff.exitCode === 0) {
8315
- const toplevel = runGit(["rev-parse", "--show-toplevel"], { cwd: gitDir, timeoutMs: 5e3 });
8316
- const repoRoot = toplevel.exitCode === 0 && toplevel.stdout.trim() !== "" ? toplevel.stdout.trim() : gitDir;
8638
+ const repoRoot = gitRepoRoot(gitDir);
8317
8639
  for (const rel of mutationDiff.stdout.split("\n")) {
8318
8640
  const trimmed = rel.trim();
8319
8641
  if (trimmed.length === 0) continue;
@@ -8321,6 +8643,9 @@ async function postBashHandler(event) {
8321
8643
  }
8322
8644
  }
8323
8645
  }
8646
+ if (exitCode === null || exitCode === 0) {
8647
+ enqueueNonHeadMovingRewrites(cmd, rawCmd, cwd ?? process.cwd());
8648
+ }
8324
8649
  const curlDl = extractCurlDownload(cmd);
8325
8650
  if (curlDl !== null && (exitCode === null || exitCode === 0)) {
8326
8651
  const resolvedOutputPath = resolveIndexPath(curlDl.outputPath, cwd ?? process.cwd());
@@ -8968,8 +9293,21 @@ function compressGWorkspaceMcpResult(toolName, resultText) {
8968
9293
  const summarized = summarizeAttachments(trimmed);
8969
9294
  return finishWithGenericPass(resultText, summarized);
8970
9295
  }
9296
+ var DATA_URL_BASE64_RE = /data:([a-zA-Z0-9.+-]+\/[a-zA-Z0-9.+-]+);base64,([A-Za-z0-9+/=]+)/g;
9297
+ var MIN_BASE64_PAYLOAD_LEN = 100;
9298
+ function stripBase64DataUrls(resultText) {
9299
+ let changed = false;
9300
+ const out = resultText.replace(DATA_URL_BASE64_RE, (match, mime, payload) => {
9301
+ if (payload.length < MIN_BASE64_PAYLOAD_LEN) return match;
9302
+ changed = true;
9303
+ return `data:${mime};base64,<stripped ${payload.length} bytes>`;
9304
+ });
9305
+ return changed ? out : null;
9306
+ }
8971
9307
  function compressMcpResultWithPacks(toolName, resultText) {
8972
- return compressGithubMcpResult(toolName, resultText) ?? compressBrowserMcpResult(toolName, resultText) ?? compressGWorkspaceMcpResult(toolName, resultText);
9308
+ const base64Stripped = stripBase64DataUrls(resultText);
9309
+ const text = base64Stripped ?? resultText;
9310
+ return compressGithubMcpResult(toolName, text) ?? compressBrowserMcpResult(toolName, text) ?? compressGWorkspaceMcpResult(toolName, text) ?? base64Stripped;
8973
9311
  }
8974
9312
 
8975
9313
  // src/hooks_mcp.ts