token-goat 2.9.19 → 2.9.21

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 (50) hide show
  1. package/README.md +3 -1
  2. package/dist/{token-goat-chunk-JNIFSVWM.mjs → token-goat-chunk-2ZHD3XCD.mjs} +5 -1
  3. package/dist/{token-goat-chunk-P46RTOAQ.mjs → token-goat-chunk-4IIU3GNS.mjs} +10 -7
  4. package/dist/{token-goat-chunk-2GTB5RBZ.mjs → token-goat-chunk-4V4OZHHV.mjs} +11 -271
  5. package/dist/token-goat-chunk-64OEF3UN.mjs +128 -0
  6. package/dist/{token-goat-chunk-AVRD7SBO.mjs → token-goat-chunk-6AQLZMCO.mjs} +270 -29
  7. package/dist/{token-goat-chunk-LACH3WKS.mjs → token-goat-chunk-BIEDTF6V.mjs} +3 -3
  8. package/dist/{token-goat-chunk-PIAESKVU.mjs → token-goat-chunk-BKHJRFDE.mjs} +568 -429
  9. package/dist/token-goat-chunk-CAHN4QU7.mjs +26 -0
  10. package/dist/{token-goat-chunk-MU6I4IDI.mjs → token-goat-chunk-CMAJGP3E.mjs} +11 -7
  11. package/dist/token-goat-chunk-CMHBTXMY.mjs +41 -0
  12. package/dist/{token-goat-chunk-HPMMLV37.mjs → token-goat-chunk-CZ5ZFJSN.mjs} +256 -341
  13. package/dist/{token-goat-chunk-7DGTVBWP.mjs → token-goat-chunk-DDP64XR7.mjs} +159 -1863
  14. package/dist/{token-goat-chunk-FX5DHKGF.mjs → token-goat-chunk-DISVHOW6.mjs} +4 -4
  15. package/dist/{token-goat-chunk-L2UOJBV4.mjs → token-goat-chunk-FZN77SZD.mjs} +27 -19
  16. package/dist/{token-goat-chunk-DA6TIDLI.mjs → token-goat-chunk-GO7OWONJ.mjs} +18 -13
  17. package/dist/{token-goat-chunk-EP7TCWHM.mjs → token-goat-chunk-IAMIQLCV.mjs} +83 -66
  18. package/dist/{token-goat-chunk-NJKNAC37.mjs → token-goat-chunk-IJUHQX2M.mjs} +1 -1
  19. package/dist/token-goat-chunk-KQI24O4B.mjs +292 -0
  20. package/dist/{token-goat-chunk-GYC34EGE.mjs → token-goat-chunk-KRPJIXCT.mjs} +2 -2
  21. package/dist/token-goat-chunk-KVAXTA2W.mjs +198 -0
  22. package/dist/{token-goat-chunk-FJZ5MZK7.mjs → token-goat-chunk-LK3YOKDF.mjs} +2 -2
  23. package/dist/token-goat-chunk-M43UJVYQ.mjs +1816 -0
  24. package/dist/{token-goat-chunk-SOKAPOOC.mjs → token-goat-chunk-M6CDVASA.mjs} +46 -47
  25. package/dist/token-goat-chunk-MECDAPXB.mjs +1989 -0
  26. package/dist/token-goat-chunk-MEKVJZUN.mjs +324 -0
  27. package/dist/token-goat-chunk-MQACN7VG.mjs +1379 -0
  28. package/dist/{token-goat-chunk-3W5P673V.mjs → token-goat-chunk-MZQORUX5.mjs} +1 -1
  29. package/dist/{token-goat-chunk-DHIXE6GV.mjs → token-goat-chunk-ORRDWKLA.mjs} +1 -1
  30. package/dist/{token-goat-chunk-F24NE3FO.mjs → token-goat-chunk-PCR43MCM.mjs} +2 -2
  31. package/dist/token-goat-chunk-PDYQA3AP.mjs +248 -0
  32. package/dist/{token-goat-chunk-S7RFIX4Y.mjs → token-goat-chunk-PHDNWISV.mjs} +45 -14
  33. package/dist/token-goat-chunk-PPYZS7TI.mjs +304 -0
  34. package/dist/{token-goat-chunk-BTKDZW7F.mjs → token-goat-chunk-QTIRVK3M.mjs} +1 -1
  35. package/dist/{token-goat-chunk-62VJ47WN.mjs → token-goat-chunk-QUM3UNWS.mjs} +5 -5
  36. package/dist/{token-goat-chunk-KWM6KBPA.mjs → token-goat-chunk-SHV6QQAS.mjs} +1 -1
  37. package/dist/{token-goat-chunk-FQYMZASI.mjs → token-goat-chunk-SMUYPBF5.mjs} +796 -4464
  38. package/dist/{token-goat-chunk-ZGQXRYKE.mjs → token-goat-chunk-TGBSMHPM.mjs} +1 -1
  39. package/dist/{token-goat-chunk-EXLLLIC3.mjs → token-goat-chunk-VXKT3W4C.mjs} +3 -3
  40. package/dist/{token-goat-chunk-WKO5TVNA.mjs → token-goat-chunk-XJUPKJF5.mjs} +544 -576
  41. package/dist/{token-goat-chunk-IBTFGZBX.mjs → token-goat-chunk-XQSSPX3X.mjs} +2 -2
  42. package/dist/{token-goat-chunk-VJQBYSXG.mjs → token-goat-chunk-ZJWPJL7O.mjs} +3 -3
  43. package/dist/token-goat-hook.mjs +17 -11
  44. package/dist/token-goat.core.mjs +27 -19
  45. package/docs/C4_RUNTIME_ARCHITECTURE.md +1 -1
  46. package/docs/cli.md +13 -12
  47. package/docs/install.md +1 -1
  48. package/docs/security.md +2 -2
  49. package/package.json +3 -2
  50. package/dist/token-goat-chunk-NYFYIRG3.mjs +0 -35
@@ -5,7 +5,7 @@ import {
5
5
  canRunWrappedShell,
6
6
  clipToDeliveryCap,
7
7
  deliveredOutputBytes
8
- } from "./token-goat-chunk-MU6I4IDI.mjs";
8
+ } from "./token-goat-chunk-CMAJGP3E.mjs";
9
9
  import {
10
10
  DEFAULT_RECONCILE_BUDGET_MS,
11
11
  HOOK_EVENTS,
@@ -17,29 +17,33 @@ import {
17
17
  flattenCommandNames,
18
18
  getWebOutput,
19
19
  isReconcileClean,
20
- projectTranscriptsDir,
21
20
  readStdinJson,
22
21
  reconcileProject,
23
22
  storeWebOutput
24
- } from "./token-goat-chunk-2GTB5RBZ.mjs";
23
+ } from "./token-goat-chunk-4V4OZHHV.mjs";
24
+ import {
25
+ projectTranscriptsDir
26
+ } from "./token-goat-chunk-KQI24O4B.mjs";
27
+ import {
28
+ BUDGET_ESCALATION_MARKER,
29
+ MANIFEST_PREAMBLE,
30
+ MANIFEST_RECOVERY_PREAMBLE,
31
+ buildManifest,
32
+ manifestPrintedPaths,
33
+ summaryBudgetDirective
34
+ } from "./token-goat-chunk-PDYQA3AP.mjs";
25
35
  import {
26
36
  BODY_FIRST_TOOL_RESPONSE_KEYS,
27
- GENERIC_SERVED_OUTPUT_KEY,
28
37
  OUTLINE_MAX_REPLACEMENT_RATIO,
29
38
  OUTLINE_MIN_HEADINGS,
30
39
  OUTPUT_FIRST_TOOL_RESPONSE_KEYS,
31
- WEB_FETCH_KEY_SEP,
32
- accumulateResidentLines,
33
40
  appendDirtyPath,
34
41
  applyHintTracking,
35
42
  buildDeltaCapsule,
36
43
  buildProjectMap,
37
44
  classifyBashHint,
38
45
  classifyEditHint,
39
- claudeConfigDir,
40
- clearCurlDownload,
41
46
  compactPathFor,
42
- computeAdaptiveBudget,
43
47
  contextOutput,
44
48
  denyOutput,
45
49
  emitRewrite,
@@ -47,8 +51,6 @@ import {
47
51
  enqueueDirtyPathSafe,
48
52
  ensureWorkerAlive,
49
53
  escapeHintName,
50
- estimateTokens,
51
- extractCompactFromMarker,
52
54
  extractToolResponseField,
53
55
  extractToolResultText,
54
56
  fenceUntrusted,
@@ -59,43 +61,19 @@ import {
59
61
  foldingEnabled,
60
62
  formatProjectMap,
61
63
  formatShrinkSummary,
62
- getBashOutputId,
63
- getContextPressure,
64
- getCurlDownloadPath,
65
64
  getCwd,
66
- getFileLineRanges,
67
65
  getFilePath,
68
- getFileServedOutputs,
69
- getGlobMatchCount,
70
- getGrepMatchCount,
71
- getOutstandingAgentSpawns,
72
- getSessionBashOutputs,
73
- getSessionBashReruns,
74
- getSessionFiles,
75
- getSessionWebFetches,
76
66
  getToolInput,
77
67
  getToolName,
78
68
  getTranscriptPath,
79
- hasSeenImage,
80
- hasSessionOutput,
81
69
  imageQualifiesForShrink,
82
- incrementSkillHit,
83
70
  indexServedBody,
84
- installedSkillPath,
85
71
  isMcpErrorResponse,
86
72
  isStructuralRewriteAccepted,
87
73
  isTextualContentBlock,
88
- lastTabContextMatches,
89
- lineMayCarryResidentSignal,
90
- listSiblingSessionStates,
91
- loadSessionCache,
92
- loadSessionState,
93
74
  logSuppressedDetection,
94
75
  makeDedupHintHandlers,
95
76
  markCompactStale,
96
- markCompacted,
97
- markFileTruncated,
98
- markHintShown,
99
77
  matchesAllowPattern,
100
78
  matchesDenyPattern,
101
79
  mcpContentBlocks,
@@ -108,8 +86,53 @@ import {
108
86
  planSourceSkeleton,
109
87
  preToolPathDeclined,
110
88
  probeImageMeta,
111
- readCopilotHooksOwners,
112
- readTranscriptTail,
89
+ recordKnownRootThrottled,
90
+ registerHook,
91
+ resolveLineRegions,
92
+ runHook,
93
+ scanAndRecord,
94
+ serializeOutput,
95
+ servedRunNotice,
96
+ sessionStateKey,
97
+ shrinkImage,
98
+ visionTokens,
99
+ visionTokensSaved,
100
+ vscodePathAllowed,
101
+ withPreservedBlocks
102
+ } from "./token-goat-chunk-SMUYPBF5.mjs";
103
+ import {
104
+ compressOutput,
105
+ detectFromCommand,
106
+ filterByName,
107
+ isRewriteWorthwhile,
108
+ looksLikePytestOutput,
109
+ pytestFilter,
110
+ resolveMinNetSavingsBytes
111
+ } from "./token-goat-chunk-DDP64XR7.mjs";
112
+ import {
113
+ readCopilotHooksOwners
114
+ } from "./token-goat-chunk-MQACN7VG.mjs";
115
+ import {
116
+ GENERIC_SERVED_OUTPUT_KEY,
117
+ clearCurlDownload,
118
+ extractCompactFromMarker,
119
+ getBashOutputId,
120
+ getCurlDownloadPath,
121
+ getFileLineRanges,
122
+ getFileServedOutputs,
123
+ getGlobMatchCount,
124
+ getGrepMatchCount,
125
+ getOutstandingAgentSpawns,
126
+ getSessionBashOutputs,
127
+ hasSeenImage,
128
+ hasSessionOutput,
129
+ incrementSkillHit,
130
+ installedSkillPath,
131
+ lastTabContextMatches,
132
+ loadSessionState,
133
+ markCompacted,
134
+ markFileTruncated,
135
+ markHintShown,
113
136
  recordBashOutput,
114
137
  recordBashRerun,
115
138
  recordCliRead,
@@ -120,47 +143,38 @@ import {
120
143
  recordFileServedOutput,
121
144
  recordGlobQuery,
122
145
  recordGrepQuery,
123
- recordKnownRootThrottled,
124
146
  recordOutstandingAgentSpawn,
125
147
  recordScheduledPrompt,
126
148
  recordSeenImage,
127
149
  recordSymbolRead,
128
150
  recordWebFetch,
129
- registerHook,
130
151
  removeOutstandingAgentSpawn,
131
- repeatedSkillBodyHint,
132
- resolveLineRegions,
133
- runHook,
134
152
  saveSessionState,
135
- scanAndRecord,
136
- serializeOutput,
137
- servedRunNotice,
138
153
  sessionOutputBodyBytes,
139
154
  sessionSidecarPath,
140
- sessionStateKey,
141
155
  setLastTabContext,
142
156
  setTranscriptPath,
143
- shrinkImage,
144
157
  storeOutput,
145
- summarizeResidentContext,
146
158
  takePendingLargeFileHint,
147
- taskListPruneHint,
148
- visionTokens,
149
- visionTokensSaved,
150
- vscodePathAllowed,
151
159
  wasCliReadThisSession,
152
160
  wasFileReadThisSession,
153
- wasHintShown,
154
- withPreservedBlocks
155
- } from "./token-goat-chunk-FQYMZASI.mjs";
161
+ wasHintShown
162
+ } from "./token-goat-chunk-MECDAPXB.mjs";
163
+ import {
164
+ accumulateResidentLines,
165
+ claudeConfigDir,
166
+ estimateTokens,
167
+ lineMayCarryResidentSignal,
168
+ readTranscriptTail,
169
+ repeatedSkillBodyHint,
170
+ summarizeResidentContext,
171
+ taskListPruneHint
172
+ } from "./token-goat-chunk-MEKVJZUN.mjs";
156
173
  import {
157
174
  BASH_OUTPUT_SUBDIR,
158
175
  bashOutputIdSync,
159
176
  commandHash,
160
- compressOutput,
161
177
  dedupeConsecutive,
162
- detectFromCommand,
163
- filterByName,
164
178
  getBashOutput,
165
179
  getMonitoringRecallHint,
166
180
  hasBareBackgroundOrNewline,
@@ -168,39 +182,36 @@ import {
168
182
  indexRecallEntry,
169
183
  isBashEntryStale,
170
184
  isBuildCommand,
171
- isRewriteWorthwhile,
185
+ isRedirectAmpersand,
172
186
  isScopedGitStatusOrDiffStatCommand,
173
187
  isTestRunnerCommand,
174
- looksLikePytestOutput,
175
188
  pathStem,
176
- pytestFilter,
177
- resolveMinNetSavingsBytes,
178
189
  shlexSplit,
179
190
  splitOwnTrailingNotices,
180
191
  storeBashOutput,
181
192
  summarizeOutputDelta
182
- } from "./token-goat-chunk-7DGTVBWP.mjs";
193
+ } from "./token-goat-chunk-M43UJVYQ.mjs";
183
194
  import {
184
195
  recordStat,
185
196
  redactSecrets,
186
197
  savedTokensFromBytes,
187
198
  storeBlob,
188
199
  stripAnsiEscapes
189
- } from "./token-goat-chunk-S7RFIX4Y.mjs";
200
+ } from "./token-goat-chunk-PHDNWISV.mjs";
190
201
  import {
191
202
  extractMarkdownHeadings,
192
203
  formatHeadingTreeParts
193
- } from "./token-goat-chunk-EP7TCWHM.mjs";
204
+ } from "./token-goat-chunk-IAMIQLCV.mjs";
194
205
  import {
195
206
  countSymbols,
196
207
  getFileEntry,
197
208
  querySymbols
198
- } from "./token-goat-chunk-IBTFGZBX.mjs";
209
+ } from "./token-goat-chunk-XQSSPX3X.mjs";
199
210
  import {
200
211
  detectLanguage,
201
212
  getDb,
202
213
  languageHasFlag
203
- } from "./token-goat-chunk-JNIFSVWM.mjs";
214
+ } from "./token-goat-chunk-2ZHD3XCD.mjs";
204
215
  import {
205
216
  detectHarness,
206
217
  envBool,
@@ -211,7 +222,7 @@ import {
211
222
  isUnderSystemTemp,
212
223
  loadConfig,
213
224
  shortFingerprint
214
- } from "./token-goat-chunk-SOKAPOOC.mjs";
225
+ } from "./token-goat-chunk-M6CDVASA.mjs";
215
226
  import {
216
227
  ENV_KEYS,
217
228
  IDENTICAL_READ_MIN_BODY_BYTES,
@@ -231,7 +242,7 @@ import {
231
242
  isInsideRoot,
232
243
  runGit,
233
244
  toKB
234
- } from "./token-goat-chunk-ZGQXRYKE.mjs";
245
+ } from "./token-goat-chunk-TGBSMHPM.mjs";
235
246
  import {
236
247
  TOOL_RESULTS_ID_CHARS,
237
248
  UNTRUSTED_TOOL_TAG,
@@ -292,6 +303,52 @@ function stripUnsafeSuggestions(text) {
292
303
  }
293
304
  }
294
305
 
306
+ // src/pending_context.ts
307
+ init_define_import_meta_env();
308
+ import { readFileSync, rmSync, writeFileSync } from "node:fs";
309
+ import { dirname } from "node:path";
310
+ var PENDING_SUFFIX = ".pending-context.txt";
311
+ var MAX_PENDING_CONTEXT_BYTES = 4096;
312
+ function queuePendingContext(stateKey, text) {
313
+ const trimmed = text.trim();
314
+ if (trimmed === "") return;
315
+ const target = sessionSidecarPath(stateKey, PENDING_SUFFIX);
316
+ if (target === null) return;
317
+ try {
318
+ const existing = readPending(target);
319
+ const merged = existing === null ? trimmed : `${existing}
320
+ ${trimmed}`;
321
+ const capped = merged.length <= MAX_PENDING_CONTEXT_BYTES ? merged : merged.slice(merged.length - MAX_PENDING_CONTEXT_BYTES);
322
+ ensureDirSync(dirname(target));
323
+ writeFileSync(target, capped, "utf8");
324
+ } catch {
325
+ }
326
+ }
327
+ function peekPendingContext(stateKey) {
328
+ const target = sessionSidecarPath(stateKey, PENDING_SUFFIX);
329
+ return target === null ? null : readPending(target);
330
+ }
331
+ function commitPendingContext(stateKey, delivered) {
332
+ if (delivered === null || delivered === "") return;
333
+ const target = sessionSidecarPath(stateKey, PENDING_SUFFIX);
334
+ if (target === null) return;
335
+ const queued = readPending(target);
336
+ if (queued === null) return;
337
+ if (!delivered.includes(queued) && !delivered.includes(stripUnsafeSuggestions(queued))) return;
338
+ try {
339
+ rmSync(target, { force: true });
340
+ } catch {
341
+ }
342
+ }
343
+ function readPending(target) {
344
+ try {
345
+ const raw = readFileSync(target, "utf8").trim();
346
+ return raw === "" ? null : raw;
347
+ } catch {
348
+ return null;
349
+ }
350
+ }
351
+
295
352
  // src/vscode_duplicate.ts
296
353
  init_define_import_meta_env();
297
354
  import * as fs from "node:fs";
@@ -647,7 +704,9 @@ function postEditHandlerInner(event) {
647
704
  editedSize = statSync2(normalized).size;
648
705
  } catch {
649
706
  }
650
- if (meetsSavingsFloor(editedSize)) {
707
+ if (!meetsSavingsFloor(editedSize)) {
708
+ logSuppressedDetection("edit_reread_suggest", event.sessionId, normalized);
709
+ } else {
651
710
  const escapedPath = displaySafePath(normalized).replace(/`/g, "\\`").replace(/"/g, '\\"');
652
711
  return contextOutput(
653
712
  displaySafePath(editedBasename) + ' was edited. Use `token-goat section "' + escapedPath + '::HeadingName"` to re-read a specific section rather than the full file.'
@@ -666,7 +725,7 @@ registerHook("post_tool_use", postEditHandler, { toolName: "NotebookEdit" });
666
725
 
667
726
  // src/hooks_write.ts
668
727
  init_define_import_meta_env();
669
- import { readFileSync, statSync as statSync3 } from "node:fs";
728
+ import { readFileSync as readFileSync2, statSync as statSync3 } from "node:fs";
670
729
  var MAX_LINES_FOR_DIFF = 4e3;
671
730
  var MAX_OLD_FILE_BYTES_FOR_DIFF = 4 * 1024 * 1024;
672
731
  function splitLines(text) {
@@ -707,7 +766,7 @@ function preWriteRewriteHandler(event) {
707
766
  if (stat.size > MAX_OLD_FILE_BYTES_FOR_DIFF) return passOutput();
708
767
  let oldContent;
709
768
  try {
710
- oldContent = readFileSync(filePath, "utf-8");
769
+ oldContent = readFileSync2(filePath, "utf-8");
711
770
  } catch {
712
771
  return passOutput();
713
772
  }
@@ -733,210 +792,48 @@ registerHook("pre_tool_use", preWriteRewriteHandler, { toolName: "Write" });
733
792
 
734
793
  // src/hooks_compact.ts
735
794
  init_define_import_meta_env();
736
- import { spawnSync } from "node:child_process";
737
795
  import * as path3 from "node:path";
738
- var MEM_EPOCH_TIMEOUT_MS = 800;
739
- var MAX_ROWS = 40;
740
- function appendCappedSection(lines, header, rows, cap) {
741
- if (rows.length === 0) return;
742
- lines.push("");
743
- lines.push(header);
744
- for (const row of rows.slice(0, cap)) lines.push(row);
745
- if (rows.length > cap) lines.push(`- ...and ${rows.length - cap} more`);
746
- }
747
- function renderReadRow(entry) {
748
- const kb = Math.max(1, toKB(entry.sizeBytes));
749
- const plural = entry.readCount === 1 ? "read" : "reads";
750
- const edited = entry.wasEdited ? ", edited" : "";
751
- return `- ${displaySafePath(entry.path)} (${kb}kb, ${entry.readCount} ${plural}${edited})`;
752
- }
753
- function renderSymbolReadRow(entry) {
754
- return `- ${displaySafePath(entry.path)} (symbols: ${(entry.symbols_read ?? []).map(displaySafeText).join(", ")})`;
755
- }
756
- function mergeManifestFiles(parent, siblingFiles) {
757
- const byPath = /* @__PURE__ */ new Map();
758
- for (const f of parent) byPath.set(foldPath(f.path), f);
759
- for (const f of siblingFiles) {
760
- const key = foldPath(f.path);
761
- const prev = byPath.get(key);
762
- if (prev === void 0) {
763
- byPath.set(key, f);
764
- continue;
765
- }
766
- const mergedSymbols = [.../* @__PURE__ */ new Set([...prev.symbols_read ?? [], ...f.symbols_read ?? []])];
767
- byPath.set(key, {
768
- path: prev.path,
769
- readCount: Math.max(prev.readCount, f.readCount),
770
- lastReadAt: Math.max(prev.lastReadAt, f.lastReadAt),
771
- wasEdited: prev.wasEdited || f.wasEdited,
772
- sizeBytes: f.lastReadAt >= prev.lastReadAt ? f.sizeBytes : prev.sizeBytes,
773
- ...prev.wasTruncated || f.wasTruncated ? { wasTruncated: true } : {},
774
- ...mergedSymbols.length > 0 ? { symbols_read: mergedSymbols } : {}
775
- });
776
- }
777
- return Array.from(byPath.values());
778
- }
779
- function buildManifest(sessionId, cwd, transcriptPath) {
780
- const ownFiles = [...getSessionFiles().values()];
781
- const siblingFiles = sessionId !== void 0 ? listSiblingSessionStates(sessionId).flatMap((s) => s.files) : [];
782
- const files = siblingFiles.length > 0 ? mergeManifestFiles(ownFiles, siblingFiles) : ownFiles;
783
- const editedFiles = files.filter((f) => f.wasEdited);
784
- const readFiles = files.filter((f) => f.readCount > 0 && !f.wasEdited);
785
- const symbolOnlyFiles = files.filter((f) => f.readCount === 0 && !f.wasEdited && (f.symbols_read?.length ?? 0) > 0);
786
- const webFetches = [...getSessionWebFetches().entries()];
787
- const lines = [];
788
- lines.push("## Session context");
789
- lines.push(`Files read: ${readFiles.length}`);
790
- lines.push(`Files edited: ${editedFiles.length}`);
791
- appendCappedSection(lines, "### Read files", readFiles.map(renderReadRow), MAX_ROWS);
792
- appendCappedSection(
793
- lines,
794
- "### Edited files",
795
- editedFiles.map((entry) => `- ${displaySafePath(entry.path)}`),
796
- MAX_ROWS
797
- );
798
- appendCappedSection(lines, "### Surgically read files (symbol/section reads, never read whole)", symbolOnlyFiles.map(renderSymbolReadRow), MAX_ROWS);
799
- appendCappedSection(
800
- lines,
801
- "### Web URLs fetched",
802
- webFetches.map(([key, cacheId]) => {
803
- const [url = key, prompt = ""] = key.split(WEB_FETCH_KEY_SEP);
804
- const promptSuffix = prompt ? `, prompt: ${neutralizeSpokenMarkers(JSON.stringify(prompt))}` : "";
805
- return `- ${displaySafeText(url)} (cacheId: ${cacheId}${promptSuffix})`;
806
- }),
807
- MAX_ROWS
808
- );
809
- lines.push(...buildSafeToDiscardSection(files));
810
- lines.push(...buildMemEpochSection());
811
- return capManifestChars(lines.join("\n"), sessionId, cwd, transcriptPath);
812
- }
813
- function gitDirtySignals(cwd) {
814
- const timeoutMs = loadConfig().hints.git_hint_max_ms;
815
- let hasPendingDiff = false;
816
- try {
817
- const diffResult = runGit(["diff", "--no-color", "--stat", "HEAD"], { cwd, timeoutMs });
818
- hasPendingDiff = diffResult.exitCode === 0 && diffResult.stdout.trim() !== "";
819
- } catch {
820
- }
821
- let hasUncommittedChanges = false;
822
- try {
823
- const statusResult = runGit(["status", "--porcelain"], { cwd, timeoutMs });
824
- hasUncommittedChanges = statusResult.exitCode === 0 && statusResult.stdout.trim() !== "";
825
- } catch {
826
- }
827
- return { hasPendingDiff, hasUncommittedChanges };
828
- }
829
- function adaptiveCharBonus(sessionId, cwd, transcriptPath) {
830
- if (!cwd) return 0;
831
- const cache = loadSessionCache(sessionId ?? "") ?? {};
832
- const ageSecs = cache.created_ts !== void 0 ? Math.max(0, Date.now() / 1e3 - cache.created_ts) : 0;
833
- const contextPressure = getContextPressure(cache, transcriptPath);
834
- const { hasPendingDiff, hasUncommittedChanges } = gitDirtySignals(cwd);
835
- if (!hasPendingDiff && !hasUncommittedChanges) return 0;
836
- const baseline = computeAdaptiveBudget(cache, ageSecs, { contextPressure });
837
- const withGitSignal = computeAdaptiveBudget(cache, ageSecs, { hasPendingDiff, hasUncommittedChanges, contextPressure });
838
- const deltaTokens = Math.max(0, withGitSignal - baseline);
839
- return deltaTokens * 3;
840
- }
841
- function capManifestChars(manifest, sessionId, cwd, transcriptPath) {
842
- const cap = loadConfig().compact_assist.max_manifest_chars;
843
- if (cap <= 0) return manifest;
844
- if (manifest.length <= cap) return manifest;
845
- const effectiveCap = cap + adaptiveCharBonus(sessionId, cwd, transcriptPath);
846
- if (manifest.length <= effectiveCap) return manifest;
847
- const omitted = manifest.length - effectiveCap;
848
- return manifest.slice(0, effectiveCap) + `
849
- ...(manifest truncated at ${effectiveCap} chars; ${omitted} chars omitted)`;
850
- }
851
- function buildSafeToDiscardSection(files) {
852
- const rerunHashes = getSessionBashReruns();
853
- const bashOutputs = getSessionBashOutputs();
854
- const rerunHashSet = new Set(rerunHashes);
855
- const rerunRows = [];
856
- for (const hash of rerunHashes) {
857
- const id = bashOutputs.find(([h]) => h === hash)?.[1];
858
- if (id === void 0) continue;
859
- const entry = getBashOutput(id);
860
- if (entry === null) continue;
861
- const flatCommand = neutralizeSpokenMarkers(entry.command.replace(/[\t\r\n]+/g, " "));
862
- rerunRows.push("- `" + flatCommand + "` \u2014 an older run of this exact command was superseded; recall the surviving copy with `bash-output " + id + " --full`");
863
- }
864
- const supersededReadRows = [];
865
- for (const f of files) {
866
- if (f.readCount > 1 || f.wasEdited) {
867
- const reason = f.wasEdited ? "edited after being read" : "re-read " + f.readCount + "x";
868
- supersededReadRows.push("- " + displaySafePath(f.path) + " (" + reason + " \u2014 only the latest content already in context is current)");
869
- }
870
- }
871
- const cachedOutputRows = [];
872
- for (const [hash, id] of bashOutputs) {
873
- if (rerunHashSet.has(hash)) continue;
874
- const entry = getBashOutput(id);
875
- if (entry === null) continue;
876
- const flatCommand = neutralizeSpokenMarkers(entry.command.replace(/[\t\r\n]+/g, " "));
877
- cachedOutputRows.push("- `" + flatCommand + "` \u2014 recallable via `bash-output " + id + " --full`");
878
- }
879
- const total = rerunRows.length + supersededReadRows.length + cachedOutputRows.length;
880
- if (total === 0) return [];
881
- const lines = [];
882
- lines.push("");
883
- lines.push("### SAFE_TO_DISCARD (" + total + " items \u2014 provably inert; each is recallable, not gone)");
884
- appendCappedSection(lines, "Superseded reruns (" + rerunRows.length + "):", rerunRows, MAX_ROWS);
885
- appendCappedSection(lines, "Superseded file reads (" + supersededReadRows.length + "):", supersededReadRows, MAX_ROWS);
886
- appendCappedSection(lines, "Other cached bash outputs (" + cachedOutputRows.length + "):", cachedOutputRows, MAX_ROWS);
887
- return lines;
796
+
797
+ // src/harness_channels.ts
798
+ init_define_import_meta_env();
799
+ var PROMPT_SUBMIT_CONTEXT_DROPPED = /* @__PURE__ */ new Set([]);
800
+ function dropsPromptSubmitContext() {
801
+ return PROMPT_SUBMIT_CONTEXT_DROPPED.has(getHarnessName());
888
802
  }
889
- function buildMemEpochSection() {
890
- let result;
891
- try {
892
- result = spawnSync("mem", ["epoch"], {
893
- encoding: "utf-8",
894
- timeout: MEM_EPOCH_TIMEOUT_MS,
895
- windowsHide: true
896
- });
897
- } catch {
898
- return [];
899
- }
900
- if (result.error !== void 0 || result.status !== 0) return [];
901
- const stdout = typeof result.stdout === "string" ? result.stdout.trim() : "";
902
- if (!/^\d+$/.test(stdout)) return [];
903
- return [
904
- "",
905
- "### mem epoch",
906
- `mem epoch: ${stdout} (no live TGMEM block is tracked in this session -- only the epoch counter is folded in; see buildMemEpochSection's doc comment)`
907
- ];
908
- }
909
- var MANIFEST_PREAMBLE = "When summarizing this session, keep the file paths and symbol names below exactly as written -- they are the handles the next turn needs to resume work. Do not paraphrase them into prose.";
910
- var BUDGET_ESCALATION_MARKER = "TG-BUDGET-ESCALATION:";
911
- function summaryBudgetDirective(budgetChars) {
912
- if (budgetChars <= 0) return "";
913
- return ` Aim for at most ${budgetChars} characters. Prefer a shorter summary that keeps every path, symbol, command and decision over a longer one that reproduces tool output verbatim: cite the recall id (\`token-goat bash-output <id> --full\`) rather than quoting the output again. If this session genuinely cannot be summarized within that target without losing state the next turn needs, exceed it and open the summary with a single line reading \`${BUDGET_ESCALATION_MARKER} <one-sentence reason>\`.`;
803
+ var PRE_COMPACT_CONTEXT_DROPPED = /* @__PURE__ */ new Set(["copilot_cli", "codex"]);
804
+ function dropsPreCompactContext() {
805
+ return PRE_COMPACT_CONTEXT_DROPPED.has(getHarnessName());
914
806
  }
807
+
808
+ // src/hooks_compact.ts
915
809
  function preCompactHandler(event) {
916
- const out = loadConfig().compact_assist.enabled ? contextOutput(`${MANIFEST_PREAMBLE}${summaryBudgetDirective(loadConfig().compact_assist.summary_budget_chars)}
810
+ const cfg = loadConfig().compact_assist;
811
+ let out = passOutput();
812
+ if (cfg.enabled) {
813
+ const manifest = buildManifest(event.sessionId, getCwd(event), getTranscriptPath(event));
814
+ if (!dropsPreCompactContext()) {
815
+ out = contextOutput(`${MANIFEST_PREAMBLE}${summaryBudgetDirective(cfg.summary_budget_chars)}
816
+
817
+ ${manifest}`);
818
+ } else if (event.sessionId) {
819
+ queuePendingContext(sessionStateKey(event), `${MANIFEST_RECOVERY_PREAMBLE}
917
820
 
918
- ${buildManifest(event.sessionId, getCwd(event), getTranscriptPath(event))}`) : passOutput();
821
+ ${manifest.slice(0, MAX_PENDING_CONTEXT_BYTES - MANIFEST_RECOVERY_PREAMBLE.length - 2)}`);
822
+ }
823
+ }
919
824
  markCompacted();
920
825
  return out;
921
826
  }
922
827
  registerHook("pre_compact", preCompactHandler);
923
828
  var MANIFEST_SURVIVAL_SAMPLE = 64;
924
- function manifestPathSample(sessionId) {
925
- const ownFiles = [...getSessionFiles().values()];
926
- const siblingFiles = sessionId !== void 0 ? listSiblingSessionStates(sessionId).flatMap((s) => s.files) : [];
927
- const files = siblingFiles.length > 0 ? mergeManifestFiles(ownFiles, siblingFiles) : ownFiles;
928
- const seen = /* @__PURE__ */ new Set();
929
- for (const entry of files) {
930
- if (entry.path) seen.add(entry.path);
931
- if (seen.size >= MANIFEST_SURVIVAL_SAMPLE) break;
932
- }
933
- return [...seen];
829
+ function manifestPathSample(event) {
830
+ return manifestPrintedPaths(event.sessionId, MANIFEST_SURVIVAL_SAMPLE, getCwd(event), getTranscriptPath(event));
934
831
  }
935
832
  function postCompactHandler(event) {
936
833
  const raw = event.raw["compact_summary"];
937
834
  const summary = typeof raw === "string" ? raw : "";
938
835
  const bytes = Buffer.byteLength(summary, "utf-8");
939
- const sample = manifestPathSample(event.sessionId);
836
+ const sample = typeof raw === "string" ? manifestPathSample(event) : [];
940
837
  const cwd = normalizePath(getCwd(event) ?? process.cwd());
941
838
  const spell = (p) => foldPath(p.replaceAll("\\", "/"));
942
839
  const haystack = spell(summary);
@@ -969,7 +866,7 @@ init_define_import_meta_env();
969
866
  var TRACKED_SKILL = "token-goat";
970
867
  var MAX_COMMANDS_SHOWN = 8;
971
868
  async function currentCommandNames() {
972
- const { buildProgram } = await import("./token-goat-chunk-L2UOJBV4.mjs");
869
+ const { buildProgram } = await import("./token-goat-chunk-FZN77SZD.mjs");
973
870
  return flattenCommandNames(buildCommandManifest(buildProgram()));
974
871
  }
975
872
  async function recordSkillVersionSnapshot(sessionId, skillName) {
@@ -1021,55 +918,11 @@ async function checkSkillVersionDrift(sessionId) {
1021
918
  }
1022
919
  }
1023
920
 
1024
- // src/pending_context.ts
1025
- init_define_import_meta_env();
1026
- import { readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync } from "node:fs";
1027
- import { dirname } from "node:path";
1028
- var PENDING_SUFFIX = ".pending-context.txt";
1029
- var MAX_PENDING_CONTEXT_BYTES = 4096;
1030
- function queuePendingContext(sessionId, text) {
1031
- const trimmed = text.trim();
1032
- if (trimmed === "") return;
1033
- const target = sessionSidecarPath(sessionId, PENDING_SUFFIX);
1034
- if (target === null) return;
1035
- try {
1036
- const existing = readPending(target);
1037
- const merged = existing === null ? trimmed : `${existing}
1038
- ${trimmed}`;
1039
- const capped = merged.length <= MAX_PENDING_CONTEXT_BYTES ? merged : merged.slice(merged.length - MAX_PENDING_CONTEXT_BYTES);
1040
- ensureDirSync(dirname(target));
1041
- writeFileSync(target, capped, "utf8");
1042
- } catch {
1043
- }
1044
- }
1045
- function drainPendingContext(sessionId) {
1046
- const target = sessionSidecarPath(sessionId, PENDING_SUFFIX);
1047
- if (target === null) return null;
1048
- const text = readPending(target);
1049
- try {
1050
- rmSync2(target, { force: true });
1051
- } catch {
1052
- }
1053
- return text;
1054
- }
1055
- function readPending(target) {
1056
- try {
1057
- const raw = readFileSync2(target, "utf8").trim();
1058
- return raw === "" ? null : raw;
1059
- } catch {
1060
- return null;
1061
- }
1062
- }
1063
-
1064
921
  // src/hooks_session.ts
1065
- var PROMPT_SUBMIT_CONTEXT_DROPPED = /* @__PURE__ */ new Set([]);
1066
- function dropsPromptSubmitContext() {
1067
- return PROMPT_SUBMIT_CONTEXT_DROPPED.has(getHarnessName());
1068
- }
1069
922
  function pendingContextHandler(event) {
1070
923
  try {
1071
- if (!event.sessionId || !dropsPromptSubmitContext()) return passOutput();
1072
- const pending = drainPendingContext(event.sessionId);
924
+ if (!event.sessionId || !dropsPromptSubmitContext() && !dropsPreCompactContext()) return passOutput();
925
+ const pending = peekPendingContext(sessionStateKey(event));
1073
926
  return pending === null ? passOutput() : contextOutput(pending);
1074
927
  } catch {
1075
928
  return passOutput();
@@ -1177,7 +1030,7 @@ async function userPromptSubmitHandler(event) {
1177
1030
  }
1178
1031
  const text = lines.join("\n");
1179
1032
  if (dropsPromptSubmitContext()) {
1180
- queuePendingContext(event.sessionId, text);
1033
+ queuePendingContext(sessionStateKey(event), text);
1181
1034
  return passOutput();
1182
1035
  }
1183
1036
  return contextOutput(text);
@@ -1280,9 +1133,20 @@ function reconcileNote(cwd, indexed) {
1280
1133
  return null;
1281
1134
  }
1282
1135
  }
1283
- function sessionStartHandler(event) {
1136
+ async function postCompactRecovery(event) {
1137
+ if (event.raw["source"] !== "compact" || event.sessionId === void 0) return null;
1138
+ if (!loadConfig().compact_assist.enabled) return null;
1284
1139
  try {
1285
- if (!loadConfig().hints.session_start_reminder) return passOutput();
1140
+ const { buildResumePacket } = await import("./token-goat-chunk-CAHN4QU7.mjs");
1141
+ return await buildResumePacket(event.sessionId);
1142
+ } catch {
1143
+ return null;
1144
+ }
1145
+ }
1146
+ async function sessionStartHandler(event) {
1147
+ const recovery = await postCompactRecovery(event);
1148
+ try {
1149
+ if (!loadConfig().hints.session_start_reminder) return recovery === null ? passOutput() : contextOutput(recovery);
1286
1150
  const cwd = getCwd(event);
1287
1151
  const indexed = isIndexedProject(cwd);
1288
1152
  let context = buildReminder(indexed);
@@ -1303,9 +1167,12 @@ ${capsule}`;
1303
1167
  }
1304
1168
  } catch {
1305
1169
  }
1170
+ if (recovery !== null) context += `
1171
+
1172
+ ${recovery}`;
1306
1173
  return contextOutput(context);
1307
1174
  } catch {
1308
- return passOutput();
1175
+ return recovery === null ? passOutput() : contextOutput(recovery);
1309
1176
  }
1310
1177
  }
1311
1178
  registerHook("session_start", sessionStartHandler);
@@ -7690,6 +7557,18 @@ init_define_import_meta_env();
7690
7557
 
7691
7558
  // src/bash_structural_index.ts
7692
7559
  init_define_import_meta_env();
7560
+
7561
+ // src/index_freshness.ts
7562
+ init_define_import_meta_env();
7563
+ function indexMatchesDisk(resolved) {
7564
+ const entry = getFileEntry(resolved);
7565
+ if (entry === null) return false;
7566
+ if (entry.sha === "") return true;
7567
+ const diskSha = fingerprintFile(resolved);
7568
+ return diskSha !== null && diskSha === entry.sha;
7569
+ }
7570
+
7571
+ // src/bash_structural_index.ts
7693
7572
  import { statSync as statSync4 } from "node:fs";
7694
7573
  var ALLOWED_BINARIES = /* @__PURE__ */ new Set(["rg", "grep", "egrep", "fgrep"]);
7695
7574
  var PY_DEF_RE = /^\^?(?:async\s+)?def\b(?:\s+([A-Za-z_]\w*))?$/;
@@ -7720,12 +7599,7 @@ function detectStructuralIndexRewrite(rawCmd, cwd) {
7720
7599
  return null;
7721
7600
  }
7722
7601
  if (!stat.isFile()) return null;
7723
- const entry = getFileEntry(resolved);
7724
- if (entry === null) return null;
7725
- if (entry.sha !== "") {
7726
- const diskSha = fingerprintFile(resolved);
7727
- if (diskSha === null || diskSha !== entry.sha) return null;
7728
- }
7602
+ if (!indexMatchesDisk(resolved)) return null;
7729
7603
  const pathArg = dualShellArg(rawPath);
7730
7604
  if (pathArg === null) return null;
7731
7605
  let target = null;
@@ -7773,12 +7647,7 @@ function rangeSubstituteFor(hintPath, cwd, ranges) {
7773
7647
  try {
7774
7648
  const resolved = resolveIndexPath(hintPath, cwd);
7775
7649
  if (!statSync5(resolved).isFile()) return null;
7776
- const entry = getFileEntry(resolved);
7777
- if (entry === null) return null;
7778
- if (entry.sha !== "") {
7779
- const diskSha = fingerprintFile(resolved);
7780
- if (diskSha === null || diskSha !== entry.sha) return null;
7781
- }
7650
+ if (!indexMatchesDisk(resolved)) return null;
7782
7651
  const lines = readFileSync3(resolved, "utf8").split("\n");
7783
7652
  const symbols = querySymbols({ filePath: resolved, limit: ALL_SYMBOLS_IN_FILE });
7784
7653
  if (symbols.length === 0) return null;
@@ -7850,6 +7719,10 @@ function splitShellSegments(cmd) {
7850
7719
  i++;
7851
7720
  continue;
7852
7721
  }
7722
+ if (ch === "&" && isRedirectAmpersand(cmd, i)) {
7723
+ cur += ch;
7724
+ continue;
7725
+ }
7853
7726
  if (ch === "|" || ch === ";" || ch === "\n" || ch === "&") {
7854
7727
  segments.push(cur);
7855
7728
  cur = "";
@@ -7900,6 +7773,7 @@ function classifyCatPath(filePath, cmd0) {
7900
7773
  return { filePath, ...flags, cmd0 };
7901
7774
  }
7902
7775
  function extractCatFile(cmd) {
7776
+ if (/-(?:TotalCount|Head|First|Tail)\b/i.test(cmd)) return null;
7903
7777
  const m = /^(cat|bat|type|Get-Content|gc)(?:\s+(?:-[a-zA-Z]+|--[a-zA-Z-]+))*\s+(?:"([^"]+)"|'([^']+)'|(\S+?))(?:\s+-[a-zA-Z].*?)?(?:\s+2>(&1|\/dev\/null))?\s*$/i.exec(cmd);
7904
7778
  if (!m) return null;
7905
7779
  const cmd0 = m[1];
@@ -7940,6 +7814,7 @@ function extractPowerShellWrappedGetContent(cmd, event) {
7940
7814
  if (!w) return null;
7941
7815
  const inner = (w[1] ?? w[2] ?? "").trim();
7942
7816
  if (!inner) return null;
7817
+ if (/-(?:TotalCount|Head|First|Tail)\b/i.test(inner)) return null;
7943
7818
  const m = PS_GETCONTENT_INNER_RE.exec(inner);
7944
7819
  if (!m) return null;
7945
7820
  const filePath = m[1] ?? m[2] ?? m[3];
@@ -8464,6 +8339,25 @@ function extractGetContentSelectFirst(cmd) {
8464
8339
  const { isDoc, isConfig, isSql, isXml } = classifyDocConfig(filePath);
8465
8340
  return { filePath, isDoc, isConfig, isSql, isXml, n };
8466
8341
  }
8342
+ function extractGetContentHead(cmd) {
8343
+ const headMatch = /-(?:TotalCount|Head|First)\s+(\d+)/i.exec(cmd);
8344
+ if (!headMatch) return null;
8345
+ const n = parseInt(headMatch[1], 10);
8346
+ if (n <= 10) return null;
8347
+ const getnMatch = /^(Get-Content|gc)\s+/i.exec(cmd);
8348
+ if (!getnMatch) return null;
8349
+ const afterCmd = cmd.slice(getnMatch[0].length).replace(/-Path\s+/i, "");
8350
+ const beforeHead = afterCmd.split(/-(?:TotalCount|Head|First)/i)[0]?.trim() ?? "";
8351
+ const afterHead = afterCmd.split(/-(?:TotalCount|Head|First)\s+\d+/i)[1]?.trim() ?? "";
8352
+ const cleanedBefore = beforeHead.replace(/-(?:Encoding|Delimiter|Wait)\s+\S+/gi, "").replace(/-[a-zA-Z]+/g, "").trim();
8353
+ const cleanedAfter = afterHead.replace(/-(?:Encoding|Delimiter|Wait)\s+\S+/gi, "").replace(/-[a-zA-Z]+/g, "").trim();
8354
+ const filePath = (cleanedBefore || cleanedAfter).replace(/^["']|["']$/g, "");
8355
+ if (!filePath) return null;
8356
+ if (isTempPath(filePath)) return null;
8357
+ if (!/\.(?:ts|tsx|js|jsx|py|go|java|rs|rb|cs|md|mdx|rst|txt|json|yaml|yml|toml|sql|sh|ps1|psm1|xml|dtsx|ampkg|xaml)$/i.test(filePath)) return null;
8358
+ const { isDoc, isConfig, isSql, isXml } = classifyDocConfig(filePath);
8359
+ return { filePath, isDoc, isConfig, isSql, isXml, n };
8360
+ }
8467
8361
  function taskOutputIsJsonlTranscript(outPath) {
8468
8362
  let fd = null;
8469
8363
  try {
@@ -8882,12 +8776,7 @@ function runnableTargetFor(hintPath, cwd, event) {
8882
8776
  if (!commandPathIsTouchable(hintPath, event)) return null;
8883
8777
  const resolved = resolveIndexPath(hintPath, cwd);
8884
8778
  if (!statSync7(resolved).isFile()) return null;
8885
- const entry = getFileEntry(resolved);
8886
- if (entry === null) return null;
8887
- if (entry.sha !== "") {
8888
- const diskSha = fingerprintFile(resolved);
8889
- if (diskSha === null || diskSha !== entry.sha) return null;
8890
- }
8779
+ if (!indexMatchesDisk(resolved)) return null;
8891
8780
  const symbols = querySymbols({ filePath: resolved, limit: ALL_SYMBOLS_IN_FILE2 });
8892
8781
  if (symbols.length === 0) return null;
8893
8782
  const inLineOrder = [...symbols].sort((a, b) => a.lineStart - b.lineStart);
@@ -9171,7 +9060,8 @@ function pipelineShapeFilter(cmd, cwd) {
9171
9060
  }
9172
9061
  }
9173
9062
  if (recognizedCiCount > 0) {
9174
- return filterByName("generic-ci");
9063
+ const ci = filterByName("generic-ci");
9064
+ return ci === null ? null : { filter: ci, argv: [] };
9175
9065
  }
9176
9066
  }
9177
9067
  return null;
@@ -9186,7 +9076,7 @@ function pipelineShapeFilter(cmd, cwd) {
9186
9076
  if (!PIPELINE_PASSTHROUGH_HEADS.has(head.replace(/^.*[/\\]/, ""))) return null;
9187
9077
  }
9188
9078
  const detected = detectFromCommand(stripOutputPipeline(cmd), cwd ?? void 0);
9189
- return detected === null ? null : detected.filter;
9079
+ return detected === null ? null : { filter: detected.filter, argv: detected.argv };
9190
9080
  }
9191
9081
  function isFullRecallCommand(cmd) {
9192
9082
  const forSplit = cmd.replace(/\s2>(?:&1|\/dev\/null)/g, "");
@@ -9470,7 +9360,7 @@ function foldShellReadBodies(cmd, output, fileKey, cwd) {
9470
9360
  if (no === void 0 || no === null) return null;
9471
9361
  rows.push({ no, text: lines[i] ?? "", raw: lines[i] ?? "" });
9472
9362
  }
9473
- const headRead = extractHeadFile(cmd);
9363
+ const headRead = extractHeadFile(cmd) ?? extractGetContentHead(cmd);
9474
9364
  const windowed = extractLineRangeRead(cmd) !== null || headRead !== null && lines.length >= headRead.n;
9475
9365
  const folded = foldDelivery(rows, fileKey, displaySafePath(toDisplayPath(findProject(cwd ?? process.cwd())?.root, fileKey)), windowed);
9476
9366
  if (folded === null) return null;
@@ -9599,10 +9489,12 @@ async function maybeCompressCompoundOutput(cmd, output, exitCode, cwd, cacheMinB
9599
9489
  }
9600
9490
  if (!cfg.enabled || cfg.disabled_filters.includes("generic")) return null;
9601
9491
  if (Buffer.byteLength(output, "utf-8") < cacheMinBytes) return null;
9602
- const shaped = pipelineShapeFilter(cmd, cwd) ?? (isUnwrapped ? detectFromCommand(cmd, cwd ?? void 0)?.filter ?? null : null);
9603
- const filter3 = shaped !== null && !cfg.disabled_filters.includes(shaped.name) ? shaped : filterByName("generic");
9492
+ const shaped = pipelineShapeFilter(cmd, cwd) ?? (isUnwrapped ? detectFromCommand(cmd, cwd ?? void 0) ?? null : null);
9493
+ const useShaped = shaped !== null && !cfg.disabled_filters.includes(shaped.filter.name);
9494
+ const filter3 = useShaped ? shaped.filter : filterByName("generic");
9604
9495
  if (filter3 === null) return null;
9605
- const compressed = compressOutput(filter3, output, "", exitCode ?? 0, [], {
9496
+ const filterArgv = useShaped ? shaped.argv : [];
9497
+ const compressed = compressOutput(filter3, output, "", exitCode ?? 0, filterArgv, {
9606
9498
  maxLines: cfg.max_lines,
9607
9499
  maxBytes: cfg.max_bytes
9608
9500
  });
@@ -9696,6 +9588,12 @@ function preBashHandlerInner(event) {
9696
9588
  for (const p of typeof paths === "string" ? [paths] : paths) logSuppressedDetection("bash_redirect", event.sessionId, p);
9697
9589
  return passOutput();
9698
9590
  };
9591
+ const recallWorthShowing = (outputId2, entry2) => {
9592
+ if (entry2.sizeBytes < loadConfig().hints.bash_dedup_min_bytes) return false;
9593
+ if (meetsSavingsFloor(entry2.sizeBytes)) return true;
9594
+ logSuppressedDetection("bash_recall", event.sessionId, outputId2);
9595
+ return false;
9596
+ };
9699
9597
  const cfg = loadConfig();
9700
9598
  if (cfg.hints.warn_unbalanced_shell_quoting) {
9701
9599
  const quoteError = detectUnbalancedShellSyntax(cmd);
@@ -9807,6 +9705,15 @@ function preBashHandlerInner(event) {
9807
9705
  recordStat("session_hint", 0, 0);
9808
9706
  return pathHint(hintPath, gcSelectHint);
9809
9707
  }
9708
+ const gcHeadResult = extractGetContentHead(cmd);
9709
+ if (gcHeadResult !== null) {
9710
+ const { filePath, n } = gcHeadResult;
9711
+ const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
9712
+ const gcHeadHint = leadingLinesHint("`Get-Content -TotalCount` bypasses read hooks. ", hintPath, 1, n, preHookCwd, pricedSubstitute(hintPath, 1, n));
9713
+ if (gcHeadHint === null) return declineUnpriced(hintPath);
9714
+ recordStat("session_hint", 0, 0);
9715
+ return pathHint(hintPath, gcHeadHint);
9716
+ }
9810
9717
  const catJsonPipe = extractCatJsonPipe(cmd);
9811
9718
  if (catJsonPipe !== null) {
9812
9719
  const { filePath, isDirectJq } = catJsonPipe;
@@ -9995,7 +9902,7 @@ function preBashHandlerInner(event) {
9995
9902
  const monOutputId = getBashOutputId(monCmdHash);
9996
9903
  const monEntryRaw = monOutputId !== null ? getBashOutput(monOutputId) : null;
9997
9904
  const monEntry = monEntryRaw !== null && !isBashEntryStale(monEntryRaw, cmd, preHookCwd) ? monEntryRaw : null;
9998
- if (monOutputId !== null && monEntry !== null && monEntry.sizeBytes >= loadConfig().hints.bash_dedup_min_bytes && meetsSavingsFloor(monEntry.sizeBytes)) {
9905
+ if (monOutputId !== null && monEntry !== null && recallWorthShowing(monOutputId, monEntry)) {
9999
9906
  const monBytes = monEntry.sizeBytes;
10000
9907
  const catFile = extractCatSourceFile(cmd);
10001
9908
  if (catFile !== null) {
@@ -10030,7 +9937,7 @@ function preBashHandlerInner(event) {
10030
9937
  const curlOutputId = getBashOutputId(curlHash);
10031
9938
  const curlEntryRaw = curlOutputId !== null ? getBashOutput(curlOutputId) : null;
10032
9939
  const curlEntry = curlEntryRaw !== null && !isBashEntryStale(curlEntryRaw, cmd, preHookCwd) ? curlEntryRaw : null;
10033
- if (curlOutputId !== null && curlEntry !== null && curlEntry.sizeBytes >= loadConfig().hints.bash_dedup_min_bytes && meetsSavingsFloor(curlEntry.sizeBytes)) {
9940
+ if (curlOutputId !== null && curlEntry !== null && recallWorthShowing(curlOutputId, curlEntry)) {
10034
9941
  const curlBytes = curlEntry.sizeBytes;
10035
9942
  recordStat("bash_compress:recall", curlBytes, savedTokensFromBytes(curlBytes));
10036
9943
  const curlPreview = cmd.length > 60 ? cmd.slice(0, 57) + "..." : cmd;
@@ -10044,7 +9951,7 @@ function preBashHandlerInner(event) {
10044
9951
  const ghOutputId = getBashOutputId(ghHash);
10045
9952
  const ghEntryRaw = ghOutputId !== null ? getBashOutput(ghOutputId) : null;
10046
9953
  const ghEntry = ghEntryRaw !== null && !isBashEntryStale(ghEntryRaw, cmd, preHookCwd) ? ghEntryRaw : null;
10047
- if (ghOutputId !== null && ghEntry !== null && ghEntry.sizeBytes >= loadConfig().hints.bash_dedup_min_bytes && meetsSavingsFloor(ghEntry.sizeBytes)) {
9954
+ if (ghOutputId !== null && ghEntry !== null && recallWorthShowing(ghOutputId, ghEntry)) {
10048
9955
  const ghBytes = ghEntry.sizeBytes;
10049
9956
  recordStat("bash_compress:recall", ghBytes, savedTokensFromBytes(ghBytes));
10050
9957
  const ghPreview = cmd.length > 60 ? cmd.slice(0, 57) + "..." : cmd;
@@ -10058,7 +9965,7 @@ function preBashHandlerInner(event) {
10058
9965
  const gitScopedOutputId = getBashOutputId(gitScopedHash);
10059
9966
  const gitScopedEntryRaw = gitScopedOutputId !== null ? getBashOutput(gitScopedOutputId) : null;
10060
9967
  const gitScopedEntry = gitScopedEntryRaw !== null && !isBashEntryStale(gitScopedEntryRaw, cmd, preHookCwd) ? gitScopedEntryRaw : null;
10061
- if (gitScopedOutputId !== null && gitScopedEntry !== null && gitScopedEntry.sizeBytes >= loadConfig().hints.bash_dedup_min_bytes && meetsSavingsFloor(gitScopedEntry.sizeBytes)) {
9968
+ if (gitScopedOutputId !== null && gitScopedEntry !== null && recallWorthShowing(gitScopedOutputId, gitScopedEntry)) {
10062
9969
  const gitScopedBytes = gitScopedEntry.sizeBytes;
10063
9970
  recordStat("bash_compress:recall", gitScopedBytes, savedTokensFromBytes(gitScopedBytes));
10064
9971
  const gitScopedPreview = cmd.length > 60 ? cmd.slice(0, 57) + "..." : cmd;
@@ -10100,7 +10007,7 @@ function preBashHandlerInner(event) {
10100
10007
  const outputId = getBashOutputId(cmdHash);
10101
10008
  const entryRaw = outputId !== null ? getBashOutput(outputId) : null;
10102
10009
  const entry = entryRaw !== null && !isBashEntryStale(entryRaw, cmd, preHookCwd) ? entryRaw : null;
10103
- if (outputId !== null && entry !== null && entry.sizeBytes >= loadConfig().hints.bash_dedup_min_bytes && meetsSavingsFloor(entry.sizeBytes)) {
10010
+ if (outputId !== null && entry !== null && recallWorthShowing(outputId, entry)) {
10104
10011
  const bytes = entry.sizeBytes;
10105
10012
  recordStat("bash_compress:recall", bytes, savedTokensFromBytes(bytes));
10106
10013
  return contextOutput(buildRecallHint(cmd, outputId));
@@ -10198,6 +10105,11 @@ function recordBashFileReadsForSessionCache(cmd, cwd) {
10198
10105
  recordFileLineRange(resolve2(gcSelect.filePath), 1, gcSelect.n);
10199
10106
  return;
10200
10107
  }
10108
+ const gcHead = extractGetContentHead(cmd);
10109
+ if (gcHead !== null) {
10110
+ recordFileLineRange(resolve2(gcHead.filePath), 1, gcHead.n);
10111
+ return;
10112
+ }
10201
10113
  const head = extractHeadFile(cmd);
10202
10114
  if (head !== null) {
10203
10115
  recordFileLineRange(resolve2(head.filePath), 1, head.n);
@@ -11926,6 +11838,9 @@ async function relayInProcess(eventName, rawPayload, harnessWaitMs) {
11926
11838
  } catch {
11927
11839
  }
11928
11840
  const output = safeSuggestions(await runHook(event));
11841
+ if (event.sessionId) {
11842
+ commitPendingContext(stateKey, output.hookType === "context" ? output.context : null);
11843
+ }
11929
11844
  try {
11930
11845
  saveSessionState(stateKey);
11931
11846
  } catch {