token-goat 2.9.12 → 2.9.14

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 (45) hide show
  1. package/README.md +20 -1
  2. package/dist/token-goat-chunk-2ESBO4IN.mjs +209 -0
  3. package/dist/token-goat-chunk-3BTK54F3.mjs +1733 -0
  4. package/dist/{token-goat-chunk-6NIGPPN6.mjs → token-goat-chunk-3XQPEJMV.mjs} +1 -1
  5. package/dist/token-goat-chunk-4SDX3QP3.mjs +122 -0
  6. package/dist/token-goat-chunk-7OGKZ7AP.mjs +142 -0
  7. package/dist/token-goat-chunk-AH6QILZM.mjs +26 -0
  8. package/dist/token-goat-chunk-AMYCQJX4.mjs +1548 -0
  9. package/dist/{token-goat-chunk-OTW7LC4Y.mjs → token-goat-chunk-ASVVF4JV.mjs} +13 -10
  10. package/dist/{token-goat-chunk-GCXX67HM.mjs → token-goat-chunk-DAMXYVIW.mjs} +12477 -12156
  11. package/dist/token-goat-chunk-DBNY4RLN.mjs +308 -0
  12. package/dist/token-goat-chunk-EG3663UT.mjs +226 -0
  13. package/dist/token-goat-chunk-EZNVAIR3.mjs +21 -0
  14. package/dist/token-goat-chunk-GMOUBOX4.mjs +386 -0
  15. package/dist/{token-goat-chunk-6F5TLJC7.mjs → token-goat-chunk-HF6H7RNK.mjs} +1 -1
  16. package/dist/{token-goat-chunk-GM6QZWCF.mjs → token-goat-chunk-IT6O3PNN.mjs} +2660 -6877
  17. package/dist/token-goat-chunk-NEI4NC54.mjs +424 -0
  18. package/dist/token-goat-chunk-O5WAMISC.mjs +167 -0
  19. package/dist/{token-goat-chunk-LHLQFGWQ.mjs → token-goat-chunk-OMNQUUIT.mjs} +9 -27
  20. package/dist/{token-goat-chunk-SF2CKCFQ.mjs → token-goat-chunk-PGGDW7DZ.mjs} +30 -13
  21. package/dist/token-goat-chunk-POBYR64E.mjs +1632 -0
  22. package/dist/token-goat-chunk-PRJVGIC5.mjs +58 -0
  23. package/dist/{token-goat-chunk-4CK445AW.mjs → token-goat-chunk-RUDOKYPJ.mjs} +10074 -9765
  24. package/dist/token-goat-chunk-SAQ5PG4L.mjs +2634 -0
  25. package/dist/token-goat-chunk-T2IWWTHB.mjs +3155 -0
  26. package/dist/{token-goat-chunk-TOGYS5A7.mjs → token-goat-chunk-VZYD4OZB.mjs} +1469 -5752
  27. package/dist/token-goat-chunk-XEH6KBWW.mjs +23 -0
  28. package/dist/token-goat-chunk-XQF5J25J.mjs +33 -0
  29. package/dist/token-goat-chunk-XTQAOTSO.mjs +89 -0
  30. package/dist/{token-goat-chunk-THLHC6QJ.mjs → token-goat-chunk-XVZ4MNQC.mjs} +614 -603
  31. package/dist/token-goat-chunk-Y4AFKTHK.mjs +22 -0
  32. package/dist/token-goat-chunk-YHGTGG6K.mjs +396 -0
  33. package/dist/{token-goat-chunk-SBYRP3X4.mjs → token-goat-chunk-YTQHZJXW.mjs} +61 -27
  34. package/dist/token-goat-chunk-Z6UXPYJA.mjs +62 -0
  35. package/dist/token-goat-chunk-ZD4EM4LR.mjs +30 -0
  36. package/dist/token-goat-chunk-ZFM4PWXL.mjs +585 -0
  37. package/dist/{token-goat-chunk-A6QTLAWO.mjs → token-goat-chunk-ZYNNQ36L.mjs} +128 -328
  38. package/dist/token-goat-hook.mjs +12 -6
  39. package/dist/token-goat.core.mjs +22 -7
  40. package/docs/cli.md +12 -8
  41. package/docs/security.md +1 -1
  42. package/package.json +4 -2
  43. package/dist/token-goat-chunk-HKFOH6JH.mjs +0 -28
  44. package/dist/token-goat-chunk-LOCOX2ML.mjs +0 -3564
  45. package/dist/token-goat-chunk-VRWX6QYW.mjs +0 -24
@@ -4,31 +4,25 @@ import {
4
4
  canRunWrappedShell,
5
5
  clipToDeliveryCap,
6
6
  deliveredOutputBytes
7
- } from "./token-goat-chunk-6NIGPPN6.mjs";
7
+ } from "./token-goat-chunk-3XQPEJMV.mjs";
8
8
  import {
9
9
  DEFAULT_RECONCILE_BUDGET_MS,
10
10
  HOOK_EVENTS,
11
- accumulateResidentLines,
12
11
  buildCommandManifest,
13
12
  checkSymbolBodySize,
14
13
  flattenCommandNames,
15
14
  getWebOutput,
16
15
  isReconcileClean,
17
- lineMayCarryResidentSignal,
18
16
  readStdinJson,
19
- readTranscriptTail,
20
17
  reconcileProject,
21
- repeatedSkillBodyHint,
22
- storeWebOutput,
23
- summarizeResidentContext,
24
- taskListPruneHint
25
- } from "./token-goat-chunk-A6QTLAWO.mjs";
18
+ storeWebOutput
19
+ } from "./token-goat-chunk-ZYNNQ36L.mjs";
26
20
  import {
27
- BASH_OUTPUT_SUBDIR,
28
21
  GENERIC_SERVED_OUTPUT_KEY,
29
22
  OUTLINE_MAX_REPLACEMENT_RATIO,
30
23
  OUTLINE_MIN_HEADINGS,
31
24
  WEB_FETCH_KEY_SEP,
25
+ accumulateResidentLines,
32
26
  appendDirtyPath,
33
27
  applyHintTracking,
34
28
  buildDeltaCapsule,
@@ -36,10 +30,8 @@ import {
36
30
  classifyBashHint,
37
31
  classifyEditHint,
38
32
  clearCurlDownload,
39
- commandHash,
40
33
  compactPathFor,
41
34
  computeAdaptiveBudget,
42
- countSymbols,
43
35
  enqueueDirtyPathSafe,
44
36
  ensureWorkerAlive,
45
37
  extractCompactFromMarker,
@@ -53,7 +45,6 @@ import {
53
45
  formatHeadingTreeParts,
54
46
  formatProjectMap,
55
47
  formatShrinkSummary,
56
- getBashOutput,
57
48
  getBashOutputId,
58
49
  getContextPressure,
59
50
  getCurlDownloadPath,
@@ -61,7 +52,6 @@ import {
61
52
  getFileServedOutputs,
62
53
  getGlobMatchCount,
63
54
  getGrepMatchCount,
64
- getMonitoringRecallHint,
65
55
  getOutstandingAgentSpawns,
66
56
  getSessionBashOutputs,
67
57
  getSessionBashReruns,
@@ -71,15 +61,11 @@ import {
71
61
  hasSessionOutput,
72
62
  imageQualifiesForShrink,
73
63
  incrementSkillHit,
74
- indexRecallEntry,
75
64
  indexServedBody,
76
65
  installedSkillPath,
77
- isBashEntryStale,
78
- isBuildCommand,
79
- isScopedGitStatusOrDiffStatCommand,
80
66
  isStructuralRewriteAccepted,
81
- isTestRunnerCommand,
82
67
  lastTabContextMatches,
68
+ lineMayCarryResidentSignal,
83
69
  listSiblingSessionStates,
84
70
  loadSessionCache,
85
71
  loadSessionState,
@@ -96,6 +82,7 @@ import {
96
82
  planSourceSkeleton,
97
83
  preToolPathDeclined,
98
84
  probeImageMeta,
85
+ readTranscriptTail,
99
86
  recordBashOutput,
100
87
  recordBashRerun,
101
88
  recordCliRead,
@@ -113,74 +100,96 @@ import {
113
100
  recordSymbolRead,
114
101
  recordWebFetch,
115
102
  removeOutstandingAgentSpawn,
103
+ repeatedSkillBodyHint,
116
104
  saveSessionState,
117
105
  scanAndRecord,
118
106
  servedRunNotice,
119
107
  sessionOutputBodyBytes,
120
108
  sessionSidecarPath,
121
109
  setLastTabContext,
110
+ setTranscriptPath,
122
111
  shrinkImage,
123
- storeBashOutput,
124
- storeBlob,
125
112
  storeOutput,
126
- summarizeOutputDelta,
113
+ summarizeResidentContext,
127
114
  takePendingLargeFileHint,
115
+ taskListPruneHint,
128
116
  visionTokens,
129
117
  visionTokensSaved,
130
118
  vscodePathAllowed,
131
119
  wasCliReadThisSession,
132
120
  wasFileReadThisSession,
133
121
  wasHintShown
134
- } from "./token-goat-chunk-GM6QZWCF.mjs";
122
+ } from "./token-goat-chunk-IT6O3PNN.mjs";
135
123
  import {
124
+ BASH_OUTPUT_SUBDIR,
136
125
  BODY_FIRST_TOOL_RESPONSE_KEYS,
137
126
  OUTPUT_FIRST_TOOL_RESPONSE_KEYS,
127
+ commandHash,
138
128
  compressOutput,
139
129
  contextOutput,
130
+ countSymbols,
140
131
  dedupeConsecutive,
141
132
  denyOutput,
142
133
  detectFromCommand,
143
- detectHarness,
144
- detectLanguage,
145
134
  emitRewrite,
146
135
  emitRewriteIfChanged,
147
- envBool,
148
- envInt,
149
136
  estimateTokens,
150
137
  extractToolResponseField,
151
138
  extractToolResultText,
152
139
  filterByName,
153
- findProject,
140
+ getBashOutput,
154
141
  getCwd,
155
- getDb,
156
142
  getFilePath,
157
- getHarnessName,
143
+ getMonitoringRecallHint,
158
144
  getToolInput,
159
145
  getToolName,
146
+ getTranscriptPath,
160
147
  hasBareBackgroundOrNewline,
161
148
  hasUnquotedOperator,
149
+ indexRecallEntry,
150
+ isBashEntryStale,
151
+ isBuildCommand,
162
152
  isMcpErrorResponse,
163
153
  isRewriteWorthwhile,
164
- isUnderSystemTemp,
165
- languageHasFlag,
166
- loadConfig,
154
+ isScopedGitStatusOrDiffStatCommand,
155
+ isTestRunnerCommand,
156
+ isTextualContentBlock,
167
157
  makeDedupHintHandlers,
158
+ mcpContentBlocks,
168
159
  normalizePayload,
169
160
  passOutput,
170
161
  readCopilotHooksOwners,
171
- recordStat,
172
- redactSecrets,
173
162
  registerHook,
174
163
  resolveMinNetSavingsBytes,
175
164
  runHook,
176
- savedTokensFromBytes,
177
165
  serializeOutput,
178
166
  sessionStateKey,
179
167
  shlexSplit,
180
- shortFingerprint,
181
168
  splitOwnTrailingNotices,
169
+ storeBashOutput,
170
+ summarizeOutputDelta,
171
+ withPreservedBlocks
172
+ } from "./token-goat-chunk-VZYD4OZB.mjs";
173
+ import {
174
+ detectLanguage,
175
+ getDb,
176
+ languageHasFlag,
177
+ recordStat,
178
+ redactSecrets,
179
+ savedTokensFromBytes,
180
+ storeBlob,
182
181
  stripAnsiEscapes
183
- } from "./token-goat-chunk-TOGYS5A7.mjs";
182
+ } from "./token-goat-chunk-T2IWWTHB.mjs";
183
+ import {
184
+ detectHarness,
185
+ envBool,
186
+ envInt,
187
+ findProject,
188
+ getHarnessName,
189
+ isUnderSystemTemp,
190
+ loadConfig,
191
+ shortFingerprint
192
+ } from "./token-goat-chunk-SAQ5PG4L.mjs";
184
193
  import {
185
194
  ENV_KEYS,
186
195
  IDENTICAL_READ_MIN_BODY_BYTES,
@@ -207,7 +216,7 @@ import {
207
216
  runGit,
208
217
  toDisplayPath,
209
218
  toKB
210
- } from "./token-goat-chunk-LOCOX2ML.mjs";
219
+ } from "./token-goat-chunk-AMYCQJX4.mjs";
211
220
  import {
212
221
  init_define_import_meta_env
213
222
  } from "./token-goat-chunk-A37V4PBF.mjs";
@@ -740,7 +749,7 @@ function mergeManifestFiles(parent, siblingFiles) {
740
749
  }
741
750
  return Array.from(byPath.values());
742
751
  }
743
- function buildManifest(sessionId, cwd) {
752
+ function buildManifest(sessionId, cwd, transcriptPath) {
744
753
  const ownFiles = [...getSessionFiles().values()];
745
754
  const siblingFiles = sessionId !== void 0 ? listSiblingSessionStates(sessionId).flatMap((s) => s.files) : [];
746
755
  const files = siblingFiles.length > 0 ? mergeManifestFiles(ownFiles, siblingFiles) : ownFiles;
@@ -772,7 +781,7 @@ function buildManifest(sessionId, cwd) {
772
781
  );
773
782
  lines.push(...buildSafeToDiscardSection(files));
774
783
  lines.push(...buildMemEpochSection());
775
- return capManifestChars(lines.join("\n"), sessionId, cwd);
784
+ return capManifestChars(lines.join("\n"), sessionId, cwd, transcriptPath);
776
785
  }
777
786
  function gitDirtySignals(cwd) {
778
787
  const timeoutMs = loadConfig().hints.git_hint_max_ms;
@@ -790,11 +799,11 @@ function gitDirtySignals(cwd) {
790
799
  }
791
800
  return { hasPendingDiff, hasUncommittedChanges };
792
801
  }
793
- function adaptiveCharBonus(sessionId, cwd) {
802
+ function adaptiveCharBonus(sessionId, cwd, transcriptPath) {
794
803
  if (!cwd) return 0;
795
804
  const cache = loadSessionCache(sessionId ?? "") ?? {};
796
805
  const ageSecs = cache.created_ts !== void 0 ? Math.max(0, Date.now() / 1e3 - cache.created_ts) : 0;
797
- const contextPressure = getContextPressure(cache);
806
+ const contextPressure = getContextPressure(cache, transcriptPath);
798
807
  const { hasPendingDiff, hasUncommittedChanges } = gitDirtySignals(cwd);
799
808
  if (!hasPendingDiff && !hasUncommittedChanges) return 0;
800
809
  const baseline = computeAdaptiveBudget(cache, ageSecs, { contextPressure });
@@ -802,11 +811,11 @@ function adaptiveCharBonus(sessionId, cwd) {
802
811
  const deltaTokens = Math.max(0, withGitSignal - baseline);
803
812
  return deltaTokens * 3;
804
813
  }
805
- function capManifestChars(manifest, sessionId, cwd) {
814
+ function capManifestChars(manifest, sessionId, cwd, transcriptPath) {
806
815
  const cap = loadConfig().compact_assist.max_manifest_chars;
807
816
  if (cap <= 0) return manifest;
808
817
  if (manifest.length <= cap) return manifest;
809
- const effectiveCap = cap + adaptiveCharBonus(sessionId, cwd);
818
+ const effectiveCap = cap + adaptiveCharBonus(sessionId, cwd, transcriptPath);
810
819
  if (manifest.length <= effectiveCap) return manifest;
811
820
  const omitted = manifest.length - effectiveCap;
812
821
  return manifest.slice(0, effectiveCap) + `
@@ -879,7 +888,7 @@ function summaryBudgetDirective(budgetChars) {
879
888
  function preCompactHandler(event) {
880
889
  const out = loadConfig().compact_assist.enabled ? contextOutput(`${MANIFEST_PREAMBLE}${summaryBudgetDirective(loadConfig().compact_assist.summary_budget_chars)}
881
890
 
882
- ${buildManifest(event.sessionId, getCwd(event))}`) : passOutput();
891
+ ${buildManifest(event.sessionId, getCwd(event), getTranscriptPath(event))}`) : passOutput();
883
892
  markCompacted();
884
893
  return out;
885
894
  }
@@ -927,7 +936,7 @@ init_define_import_meta_env();
927
936
  var TRACKED_SKILL = "token-goat";
928
937
  var MAX_COMMANDS_SHOWN = 8;
929
938
  async function currentCommandNames() {
930
- const { buildProgram } = await import("./token-goat-chunk-HKFOH6JH.mjs");
939
+ const { buildProgram } = await import("./token-goat-chunk-O5WAMISC.mjs");
931
940
  return flattenCommandNames(buildCommandManifest(buildProgram()));
932
941
  }
933
942
  async function recordSkillVersionSnapshot(sessionId, skillName) {
@@ -7638,115 +7647,21 @@ registerHook("post_tool_use", postSkillHandler, { toolName: "Skill" });
7638
7647
 
7639
7648
  // src/hooks_bash.ts
7640
7649
  init_define_import_meta_env();
7641
- import { statSync as statSync4, existsSync, openSync as openSync2, readSync, closeSync as closeSync2 } from "node:fs";
7642
- var CD_PREFIX_RE = /^(?:cd\s+(?:"[^"]*"|'[^']*'|\S+)[ \t]*(?:&&|;|\r?\n)\s*)+/;
7643
- function stripCdPrefix(cmd) {
7644
- const stripped = cmd.replace(CD_PREFIX_RE, "");
7645
- return stripped.trim() || cmd;
7646
- }
7647
- function stripTrailingStderrRedirect(cmd) {
7648
- const stripped = cmd.replace(/\s*2>&1\s*$/, "");
7649
- return stripped.trim() || cmd;
7650
- }
7651
- function hasTestRunScopeOrBudget(cmd) {
7652
- const hasTimeout = /(?:^|\s)(?:timeout(?:\.exe)?\s+\S+|--(?:test)?timeout(?:=|\s)\S+)/i.test(cmd);
7653
- 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);
7654
- return hasTimeout || hasSelector;
7655
- }
7656
- function isDirectTestRunnerCommand(cmd) {
7657
- return /^(?:pytest|(?:npx\s+)?(?:jest|vitest)|go\s+test|cargo\s+test)\b/i.test(cmd);
7658
- }
7659
- function extractCdPrefixDirs(rawCmd) {
7660
- const prefixMatch = rawCmd.match(CD_PREFIX_RE);
7661
- if (prefixMatch === null) return [];
7662
- const dirs = [];
7663
- const segmentPattern = /cd\s+(?:"([^"]*)"|'([^']*)'|(\S+))[ \t]*(?:&&|;|\r?\n)/g;
7664
- let match;
7665
- while ((match = segmentPattern.exec(prefixMatch[0])) !== null) {
7666
- const dir = match[1] ?? match[2] ?? match[3];
7667
- if (dir !== void 0) dirs.push(dir);
7668
- }
7669
- return dirs;
7670
- }
7671
- function resolveCdHintPath(rawCmd, filePath, cwd) {
7672
- if (extractCdPrefixDirs(rawCmd).length === 0) return filePath;
7673
- return resolveIndexPath(filePath, cdPrefixCwd(rawCmd, cwd));
7674
- }
7675
- function cdPrefixCwd(rawCmd, cwd) {
7676
- let dir = cwd;
7677
- for (const target of extractCdPrefixDirs(rawCmd)) dir = resolveIndexPath(target, dir);
7678
- return dir;
7679
- }
7650
+ import { statSync as statSync6, existsSync } from "node:fs";
7651
+
7652
+ // src/hooks_bash_commands.ts
7653
+ init_define_import_meta_env();
7654
+ import { statSync as statSync5 } from "node:fs";
7655
+
7656
+ // src/bash_extractors.ts
7657
+ init_define_import_meta_env();
7658
+ import { statSync as statSync4, openSync as openSync2, readSync, closeSync as closeSync2 } from "node:fs";
7680
7659
  function surgicalHintFor(hintPath, isEnv, isConfig, isDoc) {
7681
7660
  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.';
7682
7661
  }
7683
7662
  function surgicalHintForConfigDoc(filePath, isConfig, isDoc, isSql) {
7684
7663
  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.';
7685
7664
  }
7686
- function stripOutputPipeline(cmd) {
7687
- let inSingle = false;
7688
- let inDouble = false;
7689
- let cut = cmd.length;
7690
- let backslashes = 0;
7691
- for (let i = 0; i < cmd.length; i++) {
7692
- const ch = cmd[i];
7693
- if (ch === "\\") {
7694
- backslashes++;
7695
- continue;
7696
- }
7697
- const escaped = backslashes % 2 === 1;
7698
- backslashes = 0;
7699
- if (ch === "'" && !inDouble) {
7700
- if (!escaped) inSingle = !inSingle;
7701
- } else if (ch === '"' && !inSingle) {
7702
- if (!escaped) inDouble = !inDouble;
7703
- } else if (ch === "|" && !inSingle && !inDouble) {
7704
- if (cmd[i + 1] === "|") {
7705
- i++;
7706
- continue;
7707
- }
7708
- cut = i;
7709
- break;
7710
- }
7711
- }
7712
- let base = cmd.slice(0, cut);
7713
- let prev;
7714
- do {
7715
- prev = base;
7716
- const masked = base.replace(/"((?:[^"\\]|\\.)*)"/g, (_m, inner) => '"' + " ".repeat(inner.length) + '"').replace(/'([^']*)'/g, (_m, inner) => "'" + " ".repeat(inner.length) + "'");
7717
- const newMasked = masked.replace(/\s*(?:[0-9]*>&[0-9]+|[0-9&]*>>?\s*(?:"[^"]*"|'[^']*'|[^\s|]+))\s*$/, "");
7718
- if (newMasked.length < masked.length) {
7719
- base = base.slice(0, newMasked.length);
7720
- }
7721
- } while (base !== prev);
7722
- return base.trim();
7723
- }
7724
- function pipelineDivergenceNote(cmd, entryCommand) {
7725
- if (entryCommand === cmd) return "";
7726
- const preview = entryCommand.length > 60 ? entryCommand.slice(0, 57) + "..." : entryCommand;
7727
- return " (cached from a differently-piped run, `" + preview + "` \u2014 verify it covers what you need before trusting it)";
7728
- }
7729
- function extractCommand(event) {
7730
- const cmd = event.toolInput["command"];
7731
- return typeof cmd === "string" && cmd.trim() !== "" ? cmd.trim() : void 0;
7732
- }
7733
- var HEAD_MOVING_GIT_RE = /^\s*git\s+(?:checkout|switch|pull|merge|rebase|reset|cherry-pick)\b/i;
7734
- var PATH_SCOPED_CHECKOUT_RE = /^\s*git\s+checkout\s+(?:.*\s)?--(?:\s|$)/i;
7735
- function isHeadMovingGitCommand(cmd) {
7736
- if (!HEAD_MOVING_GIT_RE.test(cmd)) return false;
7737
- if (PATH_SCOPED_CHECKOUT_RE.test(cmd)) return false;
7738
- return true;
7739
- }
7740
- var ORIG_HEAD_ELIGIBLE_GIT_RE = /^\s*git\s+(?:pull|merge|rebase)\b/i;
7741
- var ORIG_HEAD_REFLOG_MSG_RE = /^(merge\s|rebase\s\(|pull\s)/i;
7742
- var GIT_RESTORE_RE = /^\s*git\s+restore\b/i;
7743
- var GIT_RESTORE_STAGED_ONLY_RE = /(?:^|\s)(?:--staged|-S)(?:\s|$)/;
7744
- var GIT_STASH_APPLY_RE = /^\s*git\s+stash\s+(?:pop|apply)\b/i;
7745
- var PATCH_APPLY_SEGMENT_RE = /^\s*(?:\S*[/\\])?(?:git\s+apply|patch)\b/i;
7746
- var PATCH_APPLY_INSPECT_ONLY_RE = /(?:^|\s)--(?:check|stat|numstat|summary|dry-run)(?:\s|=|$)/i;
7747
- function isFormatterWriteSegment(segment) {
7748
- return /(?:^|\s|[/\\])(?:prettier|eslint)\b/i.test(segment) && /(?:^|\s)--(?:write|fix)(?:\s|$)/i.test(segment);
7749
- }
7750
7665
  function splitShellSegments(cmd) {
7751
7666
  const segments = [];
7752
7667
  let cur = "";
@@ -7779,174 +7694,38 @@ function splitShellSegments(cmd) {
7779
7694
  segments.push(cur);
7780
7695
  return segments.map((s) => s.trim()).filter((s) => s.length > 0);
7781
7696
  }
7782
- function safeShlexSplit(segment) {
7783
- try {
7784
- return shlexSplit(segment);
7785
- } catch {
7786
- return null;
7787
- }
7697
+ function isTempPath(fp) {
7698
+ const norm = fp.replace(/\\/g, "/");
7699
+ return /^\/tmp\//i.test(norm) || /\/var\/folders\//i.test(norm) || // Anchored to a local drive root on purpose. Unanchored, this clause said yes to
7700
+ // `//host/share/AppData/Local/Temp/x.md`, and the caller's answer to yes is to stat the path --
7701
+ // which on Windows opens an SMB session to whatever host a command named. A temp directory is
7702
+ // somewhere on a local volume; nothing that starts with two separators is one.
7703
+ /^[a-z]:\/(?:[^/]+\/)*AppData\/Local\/Temp\//i.test(norm) || norm.startsWith("/c/Users/") && norm.includes("/AppData/Local/Temp/") || isUnderSystemTemp(fp);
7788
7704
  }
7789
- function segmentCommandIs(segment, name2) {
7790
- return new RegExp("^\\s*(?:\\S*[/\\\\])?" + name2 + "(?:\\.exe)?\\b", "i").test(segment);
7705
+ function isOrchestratorStateFile(filePath) {
7706
+ const basename3 = (filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1)) ?? filePath;
7707
+ return /^\.improve-state-/.test(basename3);
7791
7708
  }
7792
- function extractSedInPlaceFiles(segment) {
7793
- if (!segmentCommandIs(segment, "sed")) return [];
7794
- const tokens = safeShlexSplit(segment);
7795
- if (tokens === null) return [];
7796
- const rest = tokens.slice(1);
7797
- const inPlaceIdx = rest.findIndex((t) => t === "--in-place" || t.startsWith("--in-place=") || /^-[a-zA-Z]*i/.test(t));
7798
- if (inPlaceIdx === -1) return [];
7799
- const files = [];
7800
- let scriptTaken = rest.some((t) => t === "-e" || t === "-f" || t.startsWith("--expression") || t.startsWith("--file"));
7801
- for (let i = 0; i < rest.length; i++) {
7802
- const t = rest[i];
7803
- if (i === inPlaceIdx) {
7804
- if (t === "-i" && rest[i + 1] === "") i++;
7805
- continue;
7806
- }
7807
- if (t === "-e" || t === "-f" || t === "--expression" || t === "--file") {
7808
- i++;
7809
- continue;
7810
- }
7811
- if (t.length > 1 && t.startsWith("-")) continue;
7812
- if (!scriptTaken) {
7813
- scriptTaken = true;
7814
- continue;
7815
- }
7816
- files.push(t);
7817
- }
7818
- return files;
7709
+ function extractCatSourceFile(cmd) {
7710
+ const m = /^cat\s+(\S+\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|css|scss|sass|less))\s*$/.exec(cmd);
7711
+ return m?.[1] ?? null;
7819
7712
  }
7820
- function extractTeeFiles(segment) {
7821
- if (!segmentCommandIs(segment, "tee")) return [];
7822
- const tokens = safeShlexSplit(segment);
7823
- if (tokens === null) return [];
7824
- return tokens.slice(1).filter((t) => t.length > 0 && !t.startsWith("-"));
7713
+ function classifyFileExtensions(filePath) {
7714
+ const basename3 = (filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1)) ?? filePath;
7715
+ const isEnvFile = /^\.env(\.\w+)?$/i.test(basename3);
7716
+ const hasKnownExt = /\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|css|scss|sass|less|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties|sql|ps1|psm1|env)$/i.test(filePath);
7717
+ if (!hasKnownExt && !isEnvFile) return null;
7718
+ const isSql = /\.sql$/i.test(filePath);
7719
+ const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
7720
+ const isEnv = isEnvFile || /\.env$/i.test(filePath);
7721
+ const isConfig = /\.(?:json|yaml|yml|toml|conf|cfg|ini|properties)$/i.test(filePath);
7722
+ return { isDoc, isEnv, isConfig, isSql };
7825
7723
  }
7826
- function extractRedirectTargets(segment) {
7827
- const targets = [];
7828
- const re = /(?:^|\s)[0-9]?>>?\s*("[^"]*"|'[^']*'|[^\s|&<>]+)/g;
7829
- let m;
7830
- while ((m = re.exec(segment)) !== null) {
7831
- const raw = m[1];
7832
- targets.push(raw.length >= 2 && (raw[0] === '"' || raw[0] === "'") ? raw.slice(1, -1) : raw);
7833
- }
7834
- return targets;
7835
- }
7836
- function extractGitRestorePathspecs(cmd) {
7837
- const m = /^\s*git\s+restore\b(.*)$/i.exec(cmd);
7838
- if (m === null) return [];
7839
- const tokens = safeShlexSplit(m[1] ?? "");
7840
- if (tokens === null) return [];
7841
- const dashDash = tokens.indexOf("--");
7842
- if (dashDash !== -1) return tokens.slice(dashDash + 1);
7843
- const specs = [];
7844
- let skipNext = false;
7845
- for (const t of tokens) {
7846
- if (skipNext) {
7847
- skipNext = false;
7848
- continue;
7849
- }
7850
- if (t === "-s" || t === "--source") {
7851
- skipNext = true;
7852
- continue;
7853
- }
7854
- if (t.startsWith("-")) continue;
7855
- specs.push(t);
7856
- }
7857
- return specs;
7858
- }
7859
- function gitRepoRoot(gitDir) {
7860
- const toplevel = runGit(["rev-parse", "--show-toplevel"], { cwd: gitDir, timeoutMs: 5e3 });
7861
- return toplevel.exitCode === 0 && toplevel.stdout.trim() !== "" ? toplevel.stdout.trim() : gitDir;
7862
- }
7863
- function expandGitPathspecs(specs, gitDir) {
7864
- if (specs.length === 0) return [];
7865
- const listed = runGit(["ls-files", "--full-name", "--", ...specs], { cwd: gitDir, timeoutMs: 5e3 });
7866
- if (listed.exitCode !== 0) return [];
7867
- const repoRoot = gitRepoRoot(gitDir);
7868
- return listed.stdout.split("\n").map((l) => l.trim()).filter((l) => l.length > 0).map((rel) => resolveIndexPath(rel, repoRoot));
7869
- }
7870
- function workingTreeStatusPaths(gitDir) {
7871
- const status = runGit(["status", "--porcelain"], { cwd: gitDir, timeoutMs: 5e3 });
7872
- if (status.exitCode !== 0) return [];
7873
- const repoRoot = gitRepoRoot(gitDir);
7874
- const out = [];
7875
- for (const line of status.stdout.split("\n")) {
7876
- if (line.length < 4) continue;
7877
- let rel = line.slice(3).trim();
7878
- if (rel.length === 0) continue;
7879
- const arrow = rel.lastIndexOf(" -> ");
7880
- if (arrow !== -1) rel = rel.slice(arrow + 4);
7881
- if (rel.startsWith('"')) {
7882
- try {
7883
- rel = JSON.parse(rel);
7884
- } catch {
7885
- continue;
7886
- }
7887
- }
7888
- out.push(resolveIndexPath(rel, repoRoot));
7889
- }
7890
- return out;
7891
- }
7892
- function enqueueRewrittenPath(absPath) {
7893
- if (/(?:^|[/\\])(?:NUL|nul)$/.test(absPath) || absPath.replace(/\\/g, "/").endsWith("/dev/null")) return;
7894
- try {
7895
- if (!statSync4(absPath).isFile()) return;
7896
- } catch {
7897
- return;
7898
- }
7899
- enqueueDirtyPathSafe(absPath, { alreadyResolved: true });
7900
- }
7901
- function enqueueNonHeadMovingRewrites(cmd, rawCmd, cwd) {
7902
- const atCwd = (f) => resolveIndexPath(resolveCdHintPath(rawCmd, f, cwd), cwd);
7903
- const paths = [];
7904
- let needsStatusSweep = GIT_STASH_APPLY_RE.test(cmd);
7905
- if (GIT_RESTORE_RE.test(cmd) && !GIT_RESTORE_STAGED_ONLY_RE.test(cmd)) {
7906
- paths.push(...expandGitPathspecs(extractGitRestorePathspecs(cmd), cwd));
7907
- }
7908
- for (const segment of splitShellSegments(cmd)) {
7909
- if (PATCH_APPLY_SEGMENT_RE.test(segment) && !PATCH_APPLY_INSPECT_ONLY_RE.test(segment)) needsStatusSweep = true;
7910
- if (isFormatterWriteSegment(segment)) needsStatusSweep = true;
7911
- for (const f of extractSedInPlaceFiles(segment)) paths.push(atCwd(f));
7912
- for (const f of extractTeeFiles(segment)) paths.push(atCwd(f));
7913
- for (const f of extractRedirectTargets(segment)) paths.push(atCwd(f));
7914
- }
7915
- if (needsStatusSweep) paths.push(...workingTreeStatusPaths(cwd));
7916
- for (const p of paths) enqueueRewrittenPath(p);
7917
- }
7918
- function isTempPath(fp) {
7919
- const norm = fp.replace(/\\/g, "/");
7920
- return /^\/tmp\//i.test(norm) || /\/var\/folders\//i.test(norm) || // Anchored to a local drive root on purpose. Unanchored, this clause said yes to
7921
- // `//host/share/AppData/Local/Temp/x.md`, and the caller's answer to yes is to stat the path --
7922
- // which on Windows opens an SMB session to whatever host a command named. A temp directory is
7923
- // somewhere on a local volume; nothing that starts with two separators is one.
7924
- /^[a-z]:\/(?:[^/]+\/)*AppData\/Local\/Temp\//i.test(norm) || norm.startsWith("/c/Users/") && norm.includes("/AppData/Local/Temp/") || isUnderSystemTemp(fp);
7925
- }
7926
- function isOrchestratorStateFile(filePath) {
7927
- const basename3 = (filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1)) ?? filePath;
7928
- return /^\.improve-state-/.test(basename3);
7929
- }
7930
- function extractCatSourceFile(cmd) {
7931
- const m = /^cat\s+(\S+\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|css|scss|sass|less))\s*$/.exec(cmd);
7932
- return m?.[1] ?? null;
7933
- }
7934
- function classifyFileExtensions(filePath) {
7935
- const basename3 = (filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1)) ?? filePath;
7936
- const isEnvFile = /^\.env(\.\w+)?$/i.test(basename3);
7937
- const hasKnownExt = /\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|css|scss|sass|less|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties|sql|ps1|psm1|env)$/i.test(filePath);
7938
- if (!hasKnownExt && !isEnvFile) return null;
7939
- const isSql = /\.sql$/i.test(filePath);
7940
- const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
7941
- const isEnv = isEnvFile || /\.env$/i.test(filePath);
7942
- const isConfig = /\.(?:json|yaml|yml|toml|conf|cfg|ini|properties)$/i.test(filePath);
7943
- return { isDoc, isEnv, isConfig, isSql };
7944
- }
7945
- function classifyDocConfig(filePath) {
7946
- const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
7947
- const isConfig = /\.(?:json|yaml|yml|toml|conf|cfg|ini|properties)$/i.test(filePath);
7948
- const isSql = /\.sql$/i.test(filePath);
7949
- return { isDoc, isConfig, isSql };
7724
+ function classifyDocConfig(filePath) {
7725
+ const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
7726
+ const isConfig = /\.(?:json|yaml|yml|toml|conf|cfg|ini|properties)$/i.test(filePath);
7727
+ const isSql = /\.sql$/i.test(filePath);
7728
+ return { isDoc, isConfig, isSql };
7950
7729
  }
7951
7730
  function classifyCatPath(filePath, cmd0) {
7952
7731
  if (isTempPath(filePath)) return null;
@@ -8817,21 +8596,504 @@ function buildRecallHint(cmd, outputId) {
8817
8596
  if (isTscCommand(cmd)) {
8818
8597
  return "Output from a prior `" + cmdPreview + "` run is cached. Use `token-goat bash-output " + outputId + ' --grep "error TS"` to filter TypeScript errors, or `--grep "Cannot find"` for missing module errors.';
8819
8598
  }
8820
- if (isDevServerCommand(cmd)) {
8821
- return "Dev server output cached (`" + cmdPreview + "`). Use `token-goat bash-output " + outputId + ' --tail 20` to see the latest output, or `--grep "error\\|warn"` to filter issues.';
8599
+ if (isDevServerCommand(cmd)) {
8600
+ return "Dev server output cached (`" + cmdPreview + "`). Use `token-goat bash-output " + outputId + ' --tail 20` to see the latest output, or `--grep "error\\|warn"` to filter issues.';
8601
+ }
8602
+ return "Output from a prior `" + cmdPreview + "` run is cached. Use `token-goat bash-output " + outputId + "` (or `--tail 50`, `--grep ERROR`) to re-inspect it without re-running.";
8603
+ }
8604
+ function shellQuoteSingle(s) {
8605
+ return `'${s.replace(/'/g, `'\\''`)}'`;
8606
+ }
8607
+ function isCompressibleSingleCommand(cmd) {
8608
+ if (!cmd || cmd.length > 65536) return false;
8609
+ if (["$(", "`"].some((op) => cmd.includes(op))) return false;
8610
+ if (hasUnquotedOperator(cmd, ["&&", "||", "|", ";", "<", ">"])) return false;
8611
+ if (hasBareBackgroundOrNewline(cmd)) return false;
8612
+ return true;
8613
+ }
8614
+
8615
+ // src/hooks_bash_commands.ts
8616
+ var CD_PREFIX_RE = /^(?:cd\s+(?:"[^"]*"|'[^']*'|\S+)[ \t]*(?:&&|;|\r?\n)\s*)+/;
8617
+ function stripCdPrefix(cmd) {
8618
+ const stripped = cmd.replace(CD_PREFIX_RE, "");
8619
+ return stripped.trim() || cmd;
8620
+ }
8621
+ function stripTrailingStderrRedirect(cmd) {
8622
+ const stripped = cmd.replace(/\s*2>&1\s*$/, "");
8623
+ return stripped.trim() || cmd;
8624
+ }
8625
+ function hasTestRunScopeOrBudget(cmd) {
8626
+ const hasTimeout = /(?:^|\s)(?:timeout(?:\.exe)?\s+\S+|--(?:test)?timeout(?:=|\s)\S+)/i.test(cmd);
8627
+ 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);
8628
+ return hasTimeout || hasSelector;
8629
+ }
8630
+ function isDirectTestRunnerCommand(cmd) {
8631
+ return /^(?:pytest|(?:npx\s+)?(?:jest|vitest)|go\s+test|cargo\s+test)\b/i.test(cmd);
8632
+ }
8633
+ function extractCdPrefixDirs(rawCmd) {
8634
+ const prefixMatch = rawCmd.match(CD_PREFIX_RE);
8635
+ if (prefixMatch === null) return [];
8636
+ const dirs = [];
8637
+ const segmentPattern = /cd\s+(?:"([^"]*)"|'([^']*)'|(\S+))[ \t]*(?:&&|;|\r?\n)/g;
8638
+ let match;
8639
+ while ((match = segmentPattern.exec(prefixMatch[0])) !== null) {
8640
+ const dir = match[1] ?? match[2] ?? match[3];
8641
+ if (dir !== void 0) dirs.push(dir);
8642
+ }
8643
+ return dirs;
8644
+ }
8645
+ function resolveCdHintPath(rawCmd, filePath, cwd) {
8646
+ if (extractCdPrefixDirs(rawCmd).length === 0) return filePath;
8647
+ return resolveIndexPath(filePath, cdPrefixCwd(rawCmd, cwd));
8648
+ }
8649
+ function cdPrefixCwd(rawCmd, cwd) {
8650
+ let dir = cwd;
8651
+ for (const target of extractCdPrefixDirs(rawCmd)) dir = resolveIndexPath(target, dir);
8652
+ return dir;
8653
+ }
8654
+ function stripOutputPipeline(cmd) {
8655
+ let inSingle = false;
8656
+ let inDouble = false;
8657
+ let cut = cmd.length;
8658
+ let backslashes = 0;
8659
+ for (let i = 0; i < cmd.length; i++) {
8660
+ const ch = cmd[i];
8661
+ if (ch === "\\") {
8662
+ backslashes++;
8663
+ continue;
8664
+ }
8665
+ const escaped = backslashes % 2 === 1;
8666
+ backslashes = 0;
8667
+ if (ch === "'" && !inDouble) {
8668
+ if (!escaped) inSingle = !inSingle;
8669
+ } else if (ch === '"' && !inSingle) {
8670
+ if (!escaped) inDouble = !inDouble;
8671
+ } else if (ch === "|" && !inSingle && !inDouble) {
8672
+ if (cmd[i + 1] === "|") {
8673
+ i++;
8674
+ continue;
8675
+ }
8676
+ cut = i;
8677
+ break;
8678
+ }
8679
+ }
8680
+ let base = cmd.slice(0, cut);
8681
+ let prev;
8682
+ do {
8683
+ prev = base;
8684
+ const masked = base.replace(/"((?:[^"\\]|\\.)*)"/g, (_m, inner) => '"' + " ".repeat(inner.length) + '"').replace(/'([^']*)'/g, (_m, inner) => "'" + " ".repeat(inner.length) + "'");
8685
+ const newMasked = masked.replace(/\s*(?:[0-9]*>&[0-9]+|[0-9&]*>>?\s*(?:"[^"]*"|'[^']*'|[^\s|]+))\s*$/, "");
8686
+ if (newMasked.length < masked.length) {
8687
+ base = base.slice(0, newMasked.length);
8688
+ }
8689
+ } while (base !== prev);
8690
+ return base.trim();
8691
+ }
8692
+ function pipelineDivergenceNote(cmd, entryCommand) {
8693
+ if (entryCommand === cmd) return "";
8694
+ const preview = entryCommand.length > 60 ? entryCommand.slice(0, 57) + "..." : entryCommand;
8695
+ return " (cached from a differently-piped run, `" + preview + "` \u2014 verify it covers what you need before trusting it)";
8696
+ }
8697
+ function extractCommand(event) {
8698
+ const cmd = event.toolInput["command"];
8699
+ return typeof cmd === "string" && cmd.trim() !== "" ? cmd.trim() : void 0;
8700
+ }
8701
+ var HEAD_MOVING_GIT_RE = /^\s*git\s+(?:checkout|switch|pull|merge|rebase|reset|cherry-pick)\b/i;
8702
+ var PATH_SCOPED_CHECKOUT_RE = /^\s*git\s+checkout\s+(?:\S.*?\s)?--(?:\s|$)/i;
8703
+ function isHeadMovingGitCommand(cmd) {
8704
+ if (!HEAD_MOVING_GIT_RE.test(cmd)) return false;
8705
+ if (PATH_SCOPED_CHECKOUT_RE.test(cmd)) return false;
8706
+ return true;
8707
+ }
8708
+ var ORIG_HEAD_ELIGIBLE_GIT_RE = /^\s*git\s+(?:pull|merge|rebase)\b/i;
8709
+ var ORIG_HEAD_REFLOG_MSG_RE = /^(merge\s|rebase\s\(|pull\s)/i;
8710
+ var GIT_RESTORE_RE = /^\s*git\s+restore\b/i;
8711
+ var GIT_RESTORE_STAGED_ONLY_RE = /(?:^|\s)(?:--staged|-S)(?:\s|$)/;
8712
+ var GIT_STASH_APPLY_RE = /^\s*git\s+stash\s+(?:pop|apply)\b/i;
8713
+ var PATCH_APPLY_SEGMENT_RE = /^\s*(?:\S*[/\\])?(?:git\s+apply|patch)\b/i;
8714
+ var PATCH_APPLY_INSPECT_ONLY_RE = /(?:^|\s)--(?:check|stat|numstat|summary|dry-run)(?:\s|=|$)/i;
8715
+ function isFormatterWriteSegment(segment) {
8716
+ return /(?:^|\s|[/\\])(?:prettier|eslint)\b/i.test(segment) && /(?:^|\s)--(?:write|fix)(?:\s|$)/i.test(segment);
8717
+ }
8718
+ function safeShlexSplit(segment) {
8719
+ try {
8720
+ return shlexSplit(segment);
8721
+ } catch {
8722
+ return null;
8723
+ }
8724
+ }
8725
+ function segmentCommandIs(segment, name2) {
8726
+ return new RegExp("^\\s*(?:\\S*[/\\\\])?" + name2 + "(?:\\.exe)?\\b", "i").test(segment);
8727
+ }
8728
+ function extractSedInPlaceFiles(segment) {
8729
+ if (!segmentCommandIs(segment, "sed")) return [];
8730
+ const tokens = safeShlexSplit(segment);
8731
+ if (tokens === null) return [];
8732
+ const rest = tokens.slice(1);
8733
+ const inPlaceIdx = rest.findIndex((t) => t === "--in-place" || t.startsWith("--in-place=") || /^-[a-zA-Z]*i/.test(t));
8734
+ if (inPlaceIdx === -1) return [];
8735
+ const files = [];
8736
+ let scriptTaken = rest.some((t) => t === "-e" || t === "-f" || t.startsWith("--expression") || t.startsWith("--file"));
8737
+ for (let i = 0; i < rest.length; i++) {
8738
+ const t = rest[i];
8739
+ if (i === inPlaceIdx) {
8740
+ if (t === "-i" && rest[i + 1] === "") i++;
8741
+ continue;
8742
+ }
8743
+ if (t === "-e" || t === "-f" || t === "--expression" || t === "--file") {
8744
+ i++;
8745
+ continue;
8746
+ }
8747
+ if (t.length > 1 && t.startsWith("-")) continue;
8748
+ if (!scriptTaken) {
8749
+ scriptTaken = true;
8750
+ continue;
8751
+ }
8752
+ files.push(t);
8753
+ }
8754
+ return files;
8755
+ }
8756
+ function extractTeeFiles(segment) {
8757
+ if (!segmentCommandIs(segment, "tee")) return [];
8758
+ const tokens = safeShlexSplit(segment);
8759
+ if (tokens === null) return [];
8760
+ return tokens.slice(1).filter((t) => t.length > 0 && !t.startsWith("-"));
8761
+ }
8762
+ function extractRedirectTargets(segment) {
8763
+ const targets = [];
8764
+ const re = /(?:^|\s)[0-9]?>>?\s*("[^"]*"|'[^']*'|[^\s|&<>]+)/g;
8765
+ let m;
8766
+ while ((m = re.exec(segment)) !== null) {
8767
+ const raw = m[1];
8768
+ targets.push(raw.length >= 2 && (raw[0] === '"' || raw[0] === "'") ? raw.slice(1, -1) : raw);
8769
+ }
8770
+ return targets;
8771
+ }
8772
+ function extractGitRestorePathspecs(cmd) {
8773
+ const m = /^\s*git\s+restore\b(.*)$/i.exec(cmd);
8774
+ if (m === null) return [];
8775
+ const tokens = safeShlexSplit(m[1] ?? "");
8776
+ if (tokens === null) return [];
8777
+ const dashDash = tokens.indexOf("--");
8778
+ if (dashDash !== -1) return tokens.slice(dashDash + 1);
8779
+ const specs = [];
8780
+ let skipNext = false;
8781
+ for (const t of tokens) {
8782
+ if (skipNext) {
8783
+ skipNext = false;
8784
+ continue;
8785
+ }
8786
+ if (t === "-s" || t === "--source") {
8787
+ skipNext = true;
8788
+ continue;
8789
+ }
8790
+ if (t.startsWith("-")) continue;
8791
+ specs.push(t);
8792
+ }
8793
+ return specs;
8794
+ }
8795
+ function gitRepoRoot(gitDir) {
8796
+ const toplevel = runGit(["rev-parse", "--show-toplevel"], { cwd: gitDir, timeoutMs: 5e3 });
8797
+ return toplevel.exitCode === 0 && toplevel.stdout.trim() !== "" ? toplevel.stdout.trim() : gitDir;
8798
+ }
8799
+ function expandGitPathspecs(specs, gitDir) {
8800
+ if (specs.length === 0) return [];
8801
+ const listed = runGit(["ls-files", "--full-name", "--", ...specs], { cwd: gitDir, timeoutMs: 5e3 });
8802
+ if (listed.exitCode !== 0) return [];
8803
+ const repoRoot = gitRepoRoot(gitDir);
8804
+ return listed.stdout.split("\n").map((l) => l.trim()).filter((l) => l.length > 0).map((rel) => resolveIndexPath(rel, repoRoot));
8805
+ }
8806
+ function workingTreeStatusPaths(gitDir) {
8807
+ const status = runGit(["status", "--porcelain"], { cwd: gitDir, timeoutMs: 5e3 });
8808
+ if (status.exitCode !== 0) return [];
8809
+ const repoRoot = gitRepoRoot(gitDir);
8810
+ const out = [];
8811
+ for (const line of status.stdout.split("\n")) {
8812
+ if (line.length < 4) continue;
8813
+ let rel = line.slice(3).trim();
8814
+ if (rel.length === 0) continue;
8815
+ const arrow = rel.lastIndexOf(" -> ");
8816
+ if (arrow !== -1) rel = rel.slice(arrow + 4);
8817
+ if (rel.startsWith('"')) {
8818
+ try {
8819
+ rel = JSON.parse(rel);
8820
+ } catch {
8821
+ continue;
8822
+ }
8823
+ }
8824
+ out.push(resolveIndexPath(rel, repoRoot));
8825
+ }
8826
+ return out;
8827
+ }
8828
+ function enqueueRewrittenPath(absPath) {
8829
+ if (/(?:^|[/\\])(?:NUL|nul)$/.test(absPath) || absPath.replace(/\\/g, "/").endsWith("/dev/null")) return;
8830
+ try {
8831
+ if (!statSync5(absPath).isFile()) return;
8832
+ } catch {
8833
+ return;
8834
+ }
8835
+ enqueueDirtyPathSafe(absPath, { alreadyResolved: true });
8836
+ }
8837
+ function enqueueNonHeadMovingRewrites(cmd, rawCmd, cwd) {
8838
+ const atCwd = (f) => resolveIndexPath(resolveCdHintPath(rawCmd, f, cwd), cwd);
8839
+ const paths = [];
8840
+ let needsStatusSweep = GIT_STASH_APPLY_RE.test(cmd);
8841
+ if (GIT_RESTORE_RE.test(cmd) && !GIT_RESTORE_STAGED_ONLY_RE.test(cmd)) {
8842
+ paths.push(...expandGitPathspecs(extractGitRestorePathspecs(cmd), cwd));
8843
+ }
8844
+ for (const segment of splitShellSegments(cmd)) {
8845
+ if (PATCH_APPLY_SEGMENT_RE.test(segment) && !PATCH_APPLY_INSPECT_ONLY_RE.test(segment)) needsStatusSweep = true;
8846
+ if (isFormatterWriteSegment(segment)) needsStatusSweep = true;
8847
+ for (const f of extractSedInPlaceFiles(segment)) paths.push(atCwd(f));
8848
+ for (const f of extractTeeFiles(segment)) paths.push(atCwd(f));
8849
+ for (const f of extractRedirectTargets(segment)) paths.push(atCwd(f));
8850
+ }
8851
+ if (needsStatusSweep) paths.push(...workingTreeStatusPaths(cwd));
8852
+ for (const p of paths) enqueueRewrittenPath(p);
8853
+ }
8854
+ var PIPELINE_PASSTHROUGH_HEADS = /* @__PURE__ */ new Set(["head", "tail", "cat", "tee", "less", "more"]);
8855
+ var CI_FILTER_NAMES = /^(?:generic-ci|jest|vitest|pytest|go_test|cargo_test|cargo|go|make|cmake|gradle|maven|dotnet|turbo|nx|lerna|webpack|eslint|ruff|clippy|flake8|mypy|prettier|tsc)$/;
8856
+ function isCiBuildTestSegment(cleaned, cwd) {
8857
+ if (isTestRunnerCommand(cleaned) || isBuildCommand(cleaned) || isTscCommand(cleaned)) return true;
8858
+ if (/^\s*(npm|pnpm|yarn|bun)\s+(run\s+)?(test|build|lint|typecheck|check|ci|guards)\b/i.test(cleaned)) return true;
8859
+ if (/^\s*(cargo|go|dotnet|make|gradle|mvn|pytest|vitest|jest|eslint|ruff)\b/i.test(cleaned)) return true;
8860
+ const detected = detectFromCommand(cleaned, cwd ?? void 0);
8861
+ if (detected !== null && CI_FILTER_NAMES.test(detected.filter.name)) return true;
8862
+ return false;
8863
+ }
8864
+ function pipelineShapeFilter(cmd, cwd) {
8865
+ if (hasUnquotedOperator(cmd, ["&&", "||", ";"])) {
8866
+ const forSplit2 = cmd.replace(/\s2>(?:&1|\/dev\/null)/g, "");
8867
+ if (hasBareBackgroundOrNewline(forSplit2)) return null;
8868
+ const segments2 = splitShellSegments(forSplit2);
8869
+ if (segments2.length >= 2) {
8870
+ let recognizedCiCount = 0;
8871
+ for (const segment of segments2) {
8872
+ const cleaned = stripOutputPipeline(segment.trim());
8873
+ if (cleaned.length === 0) continue;
8874
+ if (isCiBuildTestSegment(cleaned, cwd)) {
8875
+ recognizedCiCount++;
8876
+ }
8877
+ }
8878
+ if (recognizedCiCount > 0) {
8879
+ return filterByName("generic-ci");
8880
+ }
8881
+ }
8882
+ return null;
8883
+ }
8884
+ const forSplit = cmd.replace(/\s2>(?:&1|\/dev\/null)/g, "");
8885
+ if (hasBareBackgroundOrNewline(forSplit)) return null;
8886
+ const segments = splitShellSegments(forSplit);
8887
+ if (segments.length < 2) return null;
8888
+ for (const segment of segments.slice(1)) {
8889
+ const head = safeShlexSplit(segment)?.[0];
8890
+ if (head === void 0) return null;
8891
+ if (!PIPELINE_PASSTHROUGH_HEADS.has(head.replace(/^.*[/\\]/, ""))) return null;
8892
+ }
8893
+ const detected = detectFromCommand(stripOutputPipeline(cmd), cwd ?? void 0);
8894
+ return detected === null ? null : detected.filter;
8895
+ }
8896
+ function pureFileReadPath(cmd) {
8897
+ const single = extractCatFile(cmd)?.filePath ?? extractHeadFile(cmd)?.filePath ?? extractTailFile(cmd)?.filePath ?? extractLineRangeRead(cmd)?.filePath;
8898
+ if (single !== void 0) return single;
8899
+ return singleFileCompoundReadPath(cmd);
8900
+ }
8901
+ function singleFileCompoundReadPath(cmd) {
8902
+ const reads = extractLineRangeReadsCompound(cmd);
8903
+ if (reads === null || reads.length !== 1) return null;
8904
+ return reads[0]?.filePath ?? null;
8905
+ }
8906
+ function deliveredLineNumbers(cmd, lineCount) {
8907
+ const ranged = extractLineRangeRead(cmd);
8908
+ if (ranged !== null) {
8909
+ const nums = [];
8910
+ for (const [lo, hi] of ranged.ranges) for (let n = lo; n <= hi; n++) nums.push(n);
8911
+ if (nums.length === 0) return null;
8912
+ while (nums.length < lineCount) nums.push((nums[nums.length - 1] ?? 0) + 1);
8913
+ return nums;
8914
+ }
8915
+ if (extractCatFile(cmd) !== null || extractHeadFile(cmd) !== null) return Array.from({ length: lineCount }, (_, i) => i + 1);
8916
+ if (singleFileCompoundReadPath(cmd) !== null) return Array.from({ length: lineCount }, () => null);
8917
+ return null;
8918
+ }
8919
+ var WHOLE_FILE_DUMP_RE = /^(?:(?:cat|type|Get-Content|gc)|(?:head|tail)[ \t]+-n[ \t]+\d+)[ \t]+(?:"([A-Za-z0-9._\-/\\:+@ ]+)"|'([A-Za-z0-9._\-/\\:+@ ]+)'|([A-Za-z0-9._\-/\\:+@]+))(?:[ \t]+2>(?:&1|\/dev\/null))?[ \t]*$/i;
8920
+ function isWholeFileDump(cmd, extractedPath) {
8921
+ const m = WHOLE_FILE_DUMP_RE.exec(cmd);
8922
+ if (m === null) return false;
8923
+ const shaped = m[1] ?? m[2] ?? m[3];
8924
+ return shaped !== void 0 && shaped === extractedPath;
8925
+ }
8926
+ function unwrapCompressCommand(executed) {
8927
+ const t = executed.trim();
8928
+ if (!/^(?:token-goat|tg)\s+compress\b/.test(t)) return null;
8929
+ let argv;
8930
+ try {
8931
+ argv = shlexSplit(t);
8932
+ } catch {
8933
+ return null;
8934
+ }
8935
+ for (let i = 0; i + 1 < argv.length; i++) {
8936
+ const tok = argv[i];
8937
+ if (tok === "-c" || tok === "--cmd") return argv[i + 1] ?? null;
8938
+ if (tok === "--cmd-b64") {
8939
+ try {
8940
+ return Buffer.from(argv[i + 1] ?? "", "base64").toString("utf8");
8941
+ } catch {
8942
+ return null;
8943
+ }
8944
+ }
8945
+ }
8946
+ return null;
8947
+ }
8948
+ function detectUnbalancedShellSyntax(cmd) {
8949
+ let inSingle = false;
8950
+ let inDouble = false;
8951
+ let i = 0;
8952
+ while (i < cmd.length) {
8953
+ const ch = cmd[i];
8954
+ if (inSingle) {
8955
+ if (ch === "'") {
8956
+ inSingle = false;
8957
+ }
8958
+ i++;
8959
+ continue;
8960
+ }
8961
+ if (inDouble) {
8962
+ if (ch === "\\" && i + 1 < cmd.length) {
8963
+ i += 2;
8964
+ continue;
8965
+ }
8966
+ if (ch === '"') {
8967
+ inDouble = false;
8968
+ }
8969
+ i++;
8970
+ continue;
8971
+ }
8972
+ if (ch === "\\" && i + 1 < cmd.length) {
8973
+ i += 2;
8974
+ continue;
8975
+ }
8976
+ if (ch === "$" && cmd[i + 1] === "(" && cmd[i + 2] === "(") {
8977
+ let depth = 2;
8978
+ let j = i + 3;
8979
+ while (j < cmd.length && depth > 0) {
8980
+ if (cmd[j] === "(") depth++;
8981
+ else if (cmd[j] === ")") depth--;
8982
+ j++;
8983
+ }
8984
+ i = j;
8985
+ continue;
8986
+ }
8987
+ if (ch === "$" && cmd[i + 1] === "'") {
8988
+ let j = i + 2;
8989
+ let closed = false;
8990
+ while (j < cmd.length) {
8991
+ if (cmd[j] === "\\") {
8992
+ j += 2;
8993
+ continue;
8994
+ }
8995
+ if (cmd[j] === "'") {
8996
+ j++;
8997
+ closed = true;
8998
+ break;
8999
+ }
9000
+ j++;
9001
+ }
9002
+ if (!closed) {
9003
+ inSingle = true;
9004
+ break;
9005
+ }
9006
+ i = j;
9007
+ continue;
9008
+ }
9009
+ if (ch === "#" && (i === 0 || /\s/.test(cmd[i - 1] ?? ""))) {
9010
+ const nl2 = cmd.indexOf("\n", i);
9011
+ i = nl2 === -1 ? cmd.length : nl2 + 1;
9012
+ continue;
9013
+ }
9014
+ if (ch === "'") {
9015
+ inSingle = true;
9016
+ i++;
9017
+ continue;
9018
+ }
9019
+ if (ch === '"') {
9020
+ inDouble = true;
9021
+ i++;
9022
+ continue;
9023
+ }
9024
+ if (ch === "<" && i + 1 < cmd.length && cmd[i + 1] === "<") {
9025
+ if (i + 2 < cmd.length && cmd[i + 2] === "<") {
9026
+ i += 3;
9027
+ continue;
9028
+ }
9029
+ let j = i + 2;
9030
+ let hasIndentModifier = false;
9031
+ if (j < cmd.length && (cmd[j] === "-" || cmd[j] === "~")) {
9032
+ hasIndentModifier = true;
9033
+ j++;
9034
+ }
9035
+ while (j < cmd.length && cmd[j] === " ") {
9036
+ j++;
9037
+ }
9038
+ if (j < cmd.length) {
9039
+ let delimStart = j;
9040
+ if (cmd[j] === '"' || cmd[j] === "'") {
9041
+ delimStart = j + 1;
9042
+ const quoteChar = cmd[j];
9043
+ let delimEnd = delimStart;
9044
+ while (delimEnd < cmd.length && cmd[delimEnd] !== quoteChar) {
9045
+ delimEnd++;
9046
+ }
9047
+ if (delimEnd >= cmd.length) {
9048
+ return "an unclosed quote in a heredoc opener";
9049
+ }
9050
+ j = delimEnd + 1;
9051
+ } else {
9052
+ let delimEnd = delimStart;
9053
+ while (delimEnd < cmd.length && /\w/.test(cmd.charAt(delimEnd))) {
9054
+ delimEnd++;
9055
+ }
9056
+ j = delimEnd;
9057
+ }
9058
+ const delimiter = cmd.slice(delimStart, j).replace(/["']/g, "");
9059
+ if (delimiter) {
9060
+ let scanPos = j;
9061
+ let terminatorEnd = -1;
9062
+ while (scanPos <= cmd.length) {
9063
+ const nl2 = cmd.indexOf("\n", scanPos);
9064
+ const lineEnd = nl2 === -1 ? cmd.length : nl2;
9065
+ let line = cmd.slice(scanPos, lineEnd);
9066
+ if (line.endsWith("\r")) {
9067
+ line = line.slice(0, -1);
9068
+ }
9069
+ const isMatch = hasIndentModifier ? line.trim() === delimiter : line === delimiter;
9070
+ if (isMatch) {
9071
+ terminatorEnd = nl2 === -1 ? cmd.length : nl2 + 1;
9072
+ break;
9073
+ }
9074
+ if (nl2 === -1) break;
9075
+ scanPos = nl2 + 1;
9076
+ }
9077
+ if (terminatorEnd === -1) {
9078
+ return `an unterminated heredoc (${delimiter} never appears on its own line)`;
9079
+ }
9080
+ i = terminatorEnd;
9081
+ continue;
9082
+ }
9083
+ }
9084
+ }
9085
+ i++;
9086
+ }
9087
+ if (inSingle) {
9088
+ return "an unclosed single quote";
8822
9089
  }
8823
- return "Output from a prior `" + cmdPreview + "` run is cached. Use `token-goat bash-output " + outputId + "` (or `--tail 50`, `--grep ERROR`) to re-inspect it without re-running.";
8824
- }
8825
- function shellQuoteSingle(s) {
8826
- return `'${s.replace(/'/g, `'\\''`)}'`;
8827
- }
8828
- function isCompressibleSingleCommand(cmd) {
8829
- if (!cmd || cmd.length > 65536) return false;
8830
- if (["$(", "`"].some((op) => cmd.includes(op))) return false;
8831
- if (hasUnquotedOperator(cmd, ["&&", "||", "|", ";", "<", ">"])) return false;
8832
- if (hasBareBackgroundOrNewline(cmd)) return false;
8833
- return true;
9090
+ if (inDouble) {
9091
+ return "an unclosed double quote";
9092
+ }
9093
+ return null;
8834
9094
  }
9095
+
9096
+ // src/hooks_bash.ts
8835
9097
  function maybeCompressRewrite(event, rawCmd, cmd) {
8836
9098
  if (process.env["TOKEN_GOAT_BASH_COMPRESS"] === "0") return null;
8837
9099
  if (event.raw["_tg_harness"] === "vscode") return null;
@@ -8857,96 +9119,6 @@ function maybeCompressRewrite(event, rawCmd, cmd) {
8857
9119
  const wrapped = `token-goat compress -f ${filterName} --timeout ${cfg.timeout_seconds} -c ${shellQuoteSingle(rawCmd)}`;
8858
9120
  return { hookType: "rewriteInput", updatedInput: { ...event.toolInput, command: wrapped } };
8859
9121
  }
8860
- var PIPELINE_PASSTHROUGH_HEADS = /* @__PURE__ */ new Set(["head", "tail", "cat", "tee", "less", "more"]);
8861
- var CI_BUILD_TEST_FILTER_NAMES = /* @__PURE__ */ new Set([
8862
- "generic-ci",
8863
- "jest",
8864
- "vitest",
8865
- "pytest",
8866
- "go_test",
8867
- "cargo_test",
8868
- "cargo",
8869
- "go",
8870
- "make",
8871
- "cmake",
8872
- "gradle",
8873
- "maven",
8874
- "dotnet",
8875
- "turbo",
8876
- "nx",
8877
- "lerna",
8878
- "webpack",
8879
- "eslint",
8880
- "ruff",
8881
- "clippy",
8882
- "flake8",
8883
- "mypy",
8884
- "prettier",
8885
- "tsc"
8886
- ]);
8887
- function isCiBuildTestSegment(cleaned, cwd) {
8888
- if (isTestRunnerCommand(cleaned) || isBuildCommand(cleaned) || isTscCommand(cleaned)) return true;
8889
- if (/^\s*(npm|pnpm|yarn|bun)\s+(run\s+)?(test|build|lint|typecheck|check|ci|guards)\b/i.test(cleaned)) return true;
8890
- if (/^\s*(cargo|go|dotnet|make|gradle|mvn|pytest|vitest|jest|eslint|ruff)\b/i.test(cleaned)) return true;
8891
- const detected = detectFromCommand(cleaned, cwd ?? void 0);
8892
- if (detected !== null && CI_BUILD_TEST_FILTER_NAMES.has(detected.filter.name)) return true;
8893
- return false;
8894
- }
8895
- function pipelineShapeFilter(cmd, cwd) {
8896
- if (hasUnquotedOperator(cmd, ["&&", "||", ";"])) {
8897
- const forSplit2 = cmd.replace(/\s2>(?:&1|\/dev\/null)/g, "");
8898
- if (hasBareBackgroundOrNewline(forSplit2)) return null;
8899
- const segments2 = splitShellSegments(forSplit2);
8900
- if (segments2.length >= 2) {
8901
- let recognizedCiCount = 0;
8902
- for (const segment of segments2) {
8903
- const cleaned = stripOutputPipeline(segment.trim());
8904
- if (cleaned.length === 0) continue;
8905
- if (isCiBuildTestSegment(cleaned, cwd)) {
8906
- recognizedCiCount++;
8907
- }
8908
- }
8909
- if (recognizedCiCount > 0) {
8910
- return filterByName("generic-ci");
8911
- }
8912
- }
8913
- return null;
8914
- }
8915
- const forSplit = cmd.replace(/\s2>(?:&1|\/dev\/null)/g, "");
8916
- if (hasBareBackgroundOrNewline(forSplit)) return null;
8917
- const segments = splitShellSegments(forSplit);
8918
- if (segments.length < 2) return null;
8919
- for (const segment of segments.slice(1)) {
8920
- const head = safeShlexSplit(segment)?.[0];
8921
- if (head === void 0) return null;
8922
- if (!PIPELINE_PASSTHROUGH_HEADS.has(head.replace(/^.*[/\\]/, ""))) return null;
8923
- }
8924
- const detected = detectFromCommand(stripOutputPipeline(cmd), cwd ?? void 0);
8925
- return detected === null ? null : detected.filter;
8926
- }
8927
- function pureFileReadPath(cmd) {
8928
- const single = extractCatFile(cmd)?.filePath ?? extractHeadFile(cmd)?.filePath ?? extractTailFile(cmd)?.filePath ?? extractLineRangeRead(cmd)?.filePath;
8929
- if (single !== void 0) return single;
8930
- return singleFileCompoundReadPath(cmd);
8931
- }
8932
- function singleFileCompoundReadPath(cmd) {
8933
- const reads = extractLineRangeReadsCompound(cmd);
8934
- if (reads === null || reads.length !== 1) return null;
8935
- return reads[0]?.filePath ?? null;
8936
- }
8937
- function deliveredLineNumbers(cmd, lineCount) {
8938
- const ranged = extractLineRangeRead(cmd);
8939
- if (ranged !== null) {
8940
- const nums = [];
8941
- for (const [lo, hi] of ranged.ranges) for (let n = lo; n <= hi; n++) nums.push(n);
8942
- if (nums.length === 0) return null;
8943
- while (nums.length < lineCount) nums.push((nums[nums.length - 1] ?? 0) + 1);
8944
- return nums;
8945
- }
8946
- if (extractCatFile(cmd) !== null || extractHeadFile(cmd) !== null) return Array.from({ length: lineCount }, (_, i) => i + 1);
8947
- if (singleFileCompoundReadPath(cmd) !== null) return Array.from({ length: lineCount }, () => null);
8948
- return null;
8949
- }
8950
9122
  function elideServedShellLines(cmd, output, priorIds, unknownLineNumbers = false) {
8951
9123
  if (priorIds.length === 0) return null;
8952
9124
  const lines = output.split("\n");
@@ -8999,19 +9171,12 @@ function foldShellReadBodies(cmd, output, fileKey, cwd) {
8999
9171
  if (folded === null) return null;
9000
9172
  return { text: folded.numbered.join("\n"), folds: folded.folds };
9001
9173
  }
9002
- var WHOLE_FILE_DUMP_RE = /^(?:(?:cat|type|Get-Content|gc)|(?:head|tail)[ \t]+-n[ \t]+\d+)[ \t]+(?:"([A-Za-z0-9._\-/\\:+@ ]+)"|'([A-Za-z0-9._\-/\\:+@ ]+)'|([A-Za-z0-9._\-/\\:+@]+))(?:[ \t]+2>(?:&1|\/dev\/null))?[ \t]*$/i;
9003
- function isWholeFileDump(cmd, extractedPath) {
9004
- const m = WHOLE_FILE_DUMP_RE.exec(cmd);
9005
- if (m === null) return false;
9006
- const shaped = m[1] ?? m[2] ?? m[3];
9007
- return shaped !== void 0 && shaped === extractedPath;
9008
- }
9009
9174
  function foldShellReadStructure(cmd, filePath, output, fileKey, cwd) {
9010
9175
  if (!isWholeFileDump(cmd, filePath)) return null;
9011
9176
  const originalBytes = Buffer.byteLength(output, "utf-8");
9012
9177
  let onDisk;
9013
9178
  try {
9014
- onDisk = statSync4(fileKey).size;
9179
+ onDisk = statSync6(fileKey).size;
9015
9180
  } catch {
9016
9181
  return null;
9017
9182
  }
@@ -9209,168 +9374,6 @@ function maybeFoldCurlHtml(cmd, output, id) {
9209
9374
  }
9210
9375
  return emitRewrite(notice + fenced, "curl HTML body cleaned", { kind: "bash_compress:curl-html", originalBytes: deliveredOutputBytes(originalBytes) });
9211
9376
  }
9212
- function unwrapCompressCommand(executed) {
9213
- const t = executed.trim();
9214
- if (!/^(?:token-goat|tg)\s+compress\b/.test(t)) return null;
9215
- let argv;
9216
- try {
9217
- argv = shlexSplit(t);
9218
- } catch {
9219
- return null;
9220
- }
9221
- for (let i = 0; i + 1 < argv.length; i++) {
9222
- const tok = argv[i];
9223
- if (tok === "-c" || tok === "--cmd") return argv[i + 1] ?? null;
9224
- }
9225
- return null;
9226
- }
9227
- function detectUnbalancedShellSyntax(cmd) {
9228
- let inSingle = false;
9229
- let inDouble = false;
9230
- let i = 0;
9231
- while (i < cmd.length) {
9232
- const ch = cmd[i];
9233
- if (inSingle) {
9234
- if (ch === "'") {
9235
- inSingle = false;
9236
- }
9237
- i++;
9238
- continue;
9239
- }
9240
- if (inDouble) {
9241
- if (ch === "\\" && i + 1 < cmd.length) {
9242
- i += 2;
9243
- continue;
9244
- }
9245
- if (ch === '"') {
9246
- inDouble = false;
9247
- }
9248
- i++;
9249
- continue;
9250
- }
9251
- if (ch === "\\" && i + 1 < cmd.length) {
9252
- i += 2;
9253
- continue;
9254
- }
9255
- if (ch === "$" && cmd[i + 1] === "(" && cmd[i + 2] === "(") {
9256
- let depth = 2;
9257
- let j = i + 3;
9258
- while (j < cmd.length && depth > 0) {
9259
- if (cmd[j] === "(") depth++;
9260
- else if (cmd[j] === ")") depth--;
9261
- j++;
9262
- }
9263
- i = j;
9264
- continue;
9265
- }
9266
- if (ch === "$" && cmd[i + 1] === "'") {
9267
- let j = i + 2;
9268
- let closed = false;
9269
- while (j < cmd.length) {
9270
- if (cmd[j] === "\\") {
9271
- j += 2;
9272
- continue;
9273
- }
9274
- if (cmd[j] === "'") {
9275
- j++;
9276
- closed = true;
9277
- break;
9278
- }
9279
- j++;
9280
- }
9281
- if (!closed) {
9282
- inSingle = true;
9283
- break;
9284
- }
9285
- i = j;
9286
- continue;
9287
- }
9288
- if (ch === "#" && (i === 0 || /\s/.test(cmd[i - 1] ?? ""))) {
9289
- const nl2 = cmd.indexOf("\n", i);
9290
- i = nl2 === -1 ? cmd.length : nl2 + 1;
9291
- continue;
9292
- }
9293
- if (ch === "'") {
9294
- inSingle = true;
9295
- i++;
9296
- continue;
9297
- }
9298
- if (ch === '"') {
9299
- inDouble = true;
9300
- i++;
9301
- continue;
9302
- }
9303
- if (ch === "<" && i + 1 < cmd.length && cmd[i + 1] === "<") {
9304
- if (i + 2 < cmd.length && cmd[i + 2] === "<") {
9305
- i += 3;
9306
- continue;
9307
- }
9308
- let j = i + 2;
9309
- let hasIndentModifier = false;
9310
- if (j < cmd.length && (cmd[j] === "-" || cmd[j] === "~")) {
9311
- hasIndentModifier = true;
9312
- j++;
9313
- }
9314
- while (j < cmd.length && cmd[j] === " ") {
9315
- j++;
9316
- }
9317
- if (j < cmd.length) {
9318
- let delimStart = j;
9319
- if (cmd[j] === '"' || cmd[j] === "'") {
9320
- delimStart = j + 1;
9321
- const quoteChar = cmd[j];
9322
- let delimEnd = delimStart;
9323
- while (delimEnd < cmd.length && cmd[delimEnd] !== quoteChar) {
9324
- delimEnd++;
9325
- }
9326
- if (delimEnd >= cmd.length) {
9327
- return "an unclosed quote in a heredoc opener";
9328
- }
9329
- j = delimEnd + 1;
9330
- } else {
9331
- let delimEnd = delimStart;
9332
- while (delimEnd < cmd.length && /\w/.test(cmd.charAt(delimEnd))) {
9333
- delimEnd++;
9334
- }
9335
- j = delimEnd;
9336
- }
9337
- const delimiter = cmd.slice(delimStart, j).replace(/["']/g, "");
9338
- if (delimiter) {
9339
- let scanPos = j;
9340
- let terminatorEnd = -1;
9341
- while (scanPos <= cmd.length) {
9342
- const nl2 = cmd.indexOf("\n", scanPos);
9343
- const lineEnd = nl2 === -1 ? cmd.length : nl2;
9344
- let line = cmd.slice(scanPos, lineEnd);
9345
- if (line.endsWith("\r")) {
9346
- line = line.slice(0, -1);
9347
- }
9348
- const isMatch = hasIndentModifier ? line.trim() === delimiter : line === delimiter;
9349
- if (isMatch) {
9350
- terminatorEnd = nl2 === -1 ? cmd.length : nl2 + 1;
9351
- break;
9352
- }
9353
- if (nl2 === -1) break;
9354
- scanPos = nl2 + 1;
9355
- }
9356
- if (terminatorEnd === -1) {
9357
- return `an unterminated heredoc (${delimiter} never appears on its own line)`;
9358
- }
9359
- i = terminatorEnd;
9360
- continue;
9361
- }
9362
- }
9363
- }
9364
- i++;
9365
- }
9366
- if (inSingle) {
9367
- return "an unclosed single quote";
9368
- }
9369
- if (inDouble) {
9370
- return "an unclosed double quote";
9371
- }
9372
- return null;
9373
- }
9374
9377
  function preBashHandlerInner(event) {
9375
9378
  const rawCmd = extractCommand(event);
9376
9379
  if (rawCmd === void 0) return passOutput();
@@ -9401,7 +9404,7 @@ function preBashHandlerInner(event) {
9401
9404
  }
9402
9405
  const toolResults = extractToolResultsFile(cmd);
9403
9406
  if (toolResults !== null) {
9404
- const { path: outPath } = toolResults;
9407
+ const outPath = displaySafePath(toolResults.path);
9405
9408
  recordStat("session_hint", 0, 0);
9406
9409
  return contextOutput(
9407
9410
  "Tool output " + outPath + ' is a plain-text artifact. Use `token-goat bash-output --file "' + outPath + '"` to read it with surgical narrowing via `--grep PATTERN` or `--tail N`, instead of reading the whole file.'
@@ -9652,7 +9655,7 @@ function preBashHandlerInner(event) {
9652
9655
  const prevResolvedPath = prevPath !== null && commandPathIsTouchable(prevPath, event) ? resolveIndexPath(prevPath, preHookCwd ?? process.cwd()) : null;
9653
9656
  if (prevPath !== null && prevResolvedPath !== null && !existsSync(prevResolvedPath)) {
9654
9657
  clearCurlDownload(curlDl.url);
9655
- } else if (prevPath !== null && prevResolvedPath !== null && statSync4(prevResolvedPath).size >= loadConfig().hints.bash_dedup_min_bytes) {
9658
+ } else if (prevPath !== null && prevResolvedPath !== null && statSync6(prevResolvedPath).size >= loadConfig().hints.bash_dedup_min_bytes) {
9656
9659
  recordStat("session_hint", 0, 0);
9657
9660
  return denyOutput(
9658
9661
  "Already downloaded to " + prevPath + " earlier this session. Use `rg '<pattern>' " + prevPath + '` to search it, or `token-goat read "' + prevPath + '::SectionName"` to read a part of it.'
@@ -10144,7 +10147,7 @@ registerHook("post_tool_use", postTaskOutputHandler, { toolName: "TaskOutput" })
10144
10147
 
10145
10148
  // src/hooks_tool_failure.ts
10146
10149
  init_define_import_meta_env();
10147
- import { existsSync as existsSync2, readFileSync as readFileSync3, statSync as statSync5, writeFileSync as writeFileSync2 } from "node:fs";
10150
+ import { existsSync as existsSync2, readFileSync as readFileSync3, statSync as statSync7, writeFileSync as writeFileSync2 } from "node:fs";
10148
10151
  import { dirname as dirname2, relative } from "node:path";
10149
10152
  var FAILURE_SUFFIX = ".tool-failures.json";
10150
10153
  var MAX_TRACKED_FAILURES = 64;
@@ -10213,7 +10216,7 @@ function diagnoseEditFailure(event, errorText) {
10213
10216
  const absPath = normalizePath(filePath);
10214
10217
  if (!existsSync2(absPath)) return null;
10215
10218
  try {
10216
- const stat = statSync5(absPath);
10219
+ const stat = statSync7(absPath);
10217
10220
  if (stat.size > MAX_EDIT_DIAGNOSE_BYTES) return null;
10218
10221
  const fileContent = readFileSync3(absPath, "utf8");
10219
10222
  const relDisplay = displaySafeText(relative(process.cwd(), absPath).replace(/\\/g, "/") || absPath);
@@ -10745,10 +10748,14 @@ function postMcpHandler(event) {
10745
10748
  if (!resultText) return passOutput();
10746
10749
  const injectionMatches = scanAndRecord(resultText);
10747
10750
  const redactedResult = redactSecrets(resultText);
10748
- const fenced = () => emitRewriteIfChanged(
10749
- resultText,
10750
- fenceWithMatches(redactedResult.text, injectionMatches, UNTRUSTED_TOOL_TAG),
10751
- "mcp"
10751
+ const preserved = (mcpContentBlocks(event.raw) ?? []).filter((b) => !isTextualContentBlock(b));
10752
+ const preserve = (output) => withPreservedBlocks(output, preserved);
10753
+ const fenced = () => preserve(
10754
+ emitRewriteIfChanged(
10755
+ resultText,
10756
+ fenceWithMatches(redactedResult.text, injectionMatches, UNTRUSTED_TOOL_TAG),
10757
+ "mcp"
10758
+ )
10752
10759
  );
10753
10760
  const passOrFence = () => fenced();
10754
10761
  if (!event.sessionId) return passOrFence();
@@ -10776,10 +10783,12 @@ function postMcpHandler(event) {
10776
10783
  minNetSavingsBytes: resolveMinNetSavingsBytes()
10777
10784
  });
10778
10785
  if (worthwhile) {
10779
- return emitRewrite(
10780
- `${notice}${fenceWithMatches(redactedBody, injectionMatches, UNTRUSTED_TOOL_TAG)}`,
10781
- "mcp",
10782
- { kind: "mcp:compress", originalBytes }
10786
+ return preserve(
10787
+ emitRewrite(
10788
+ `${notice}${fenceWithMatches(redactedBody, injectionMatches, UNTRUSTED_TOOL_TAG)}`,
10789
+ "mcp",
10790
+ { kind: "mcp:compress", originalBytes }
10791
+ )
10783
10792
  );
10784
10793
  }
10785
10794
  }
@@ -10877,6 +10886,7 @@ import { createHash } from "node:crypto";
10877
10886
  function getResponseContentBlocks(raw) {
10878
10887
  const tr = raw["tool_response"];
10879
10888
  if (!tr || typeof tr !== "object") return null;
10889
+ if (Array.isArray(tr)) return tr;
10880
10890
  const content = tr["content"];
10881
10891
  return Array.isArray(content) ? content : null;
10882
10892
  }
@@ -10915,10 +10925,7 @@ async function shrinkImageBlock(block) {
10915
10925
  changed: true,
10916
10926
  // Decoded image bytes, not base64 characters. Both branches of this function file under the same `image_shrink` kind, and the shrink branch below credits `result.originalBytes - result.shrunkBytes` -- decoded bytes, the convention documented at image_shrink.ts's own recordStat call. Base64 inflates by 4/3, so crediting `originalDataUrl.length` here booked roughly 33% more for a repeat screenshot than an identical shrink of the same image, and the ledger summed the two units into one row. The gate above deliberately still measures the data URL: it decides whether the rewrite pays off on the wire, where base64 characters are what is actually sent.
10917
10927
  savedBytes: buffer.length - Buffer.byteLength(SCREENSHOT_REPEAT_NOTICE, "utf-8"),
10918
- // The whole image is withheld here rather than resized, so the visual tokens saved are its
10919
- // entire billed cost less the notice standing in for it. Billed cost, not raw patch count:
10920
- // an oversized screenshot would have been capped by the API's own downscale, so pricing the
10921
- // untouched dimensions would credit a bill that was never going to be sent.
10928
+ // The whole image is withheld here rather than resized, so the visual tokens saved are its entire billed cost less the notice standing in for it. Billed cost, not raw patch count: an oversized screenshot would have been capped by the API's own downscale, so pricing the untouched dimensions would credit a bill that was never going to be sent.
10922
10929
  savedTokens: Math.max(0, visionTokens(meta?.width ?? 0, meta?.height ?? 0, tier) - savedTokensFromBytes(Buffer.byteLength(SCREENSHOT_REPEAT_NOTICE, "utf-8")))
10923
10930
  };
10924
10931
  }
@@ -11408,6 +11415,10 @@ async function relayInProcess(eventName, rawPayload) {
11408
11415
  if (!process.env["CLAUDE_CODE_SESSION_ID"] && event.sessionId) {
11409
11416
  process.env["CLAUDE_CODE_SESSION_ID"] = event.sessionId;
11410
11417
  }
11418
+ const rawTranscriptPath = event.raw["transcript_path"];
11419
+ if (typeof rawTranscriptPath === "string" && rawTranscriptPath !== "") {
11420
+ setTranscriptPath(rawTranscriptPath, event.sessionId ?? "");
11421
+ }
11411
11422
  if (!process.env["TRACEPARENT"] && event.traceparent) {
11412
11423
  process.env["TRACEPARENT"] = event.traceparent;
11413
11424
  }