token-goat 2.8.6 → 2.9.1

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.
@@ -1,32 +1,26 @@
1
1
  import { createRequire as __cjsRequire } from 'node:module';
2
2
  const require = __cjsRequire(import.meta.url);
3
3
  import {
4
- BASH_OUTPUT_SUBDIR,
4
+ DEFAULT_RECONCILE_BUDGET_MS,
5
5
  HOOK_EVENTS,
6
6
  accumulateResidentLines,
7
7
  buildCommandManifest,
8
8
  checkSymbolBodySize,
9
- commandHash,
10
9
  flattenCommandNames,
11
- getBashOutput,
12
10
  getWebOutput,
13
- indexRecallEntry,
14
- isBashEntryStale,
15
- isScopedGitStatusOrDiffStatCommand,
11
+ isReconcileClean,
16
12
  lineMayCarryResidentSignal,
17
13
  normalizePayload,
18
14
  readStdinJson,
19
15
  readTranscriptTail,
16
+ reconcileProject,
20
17
  repeatedSkillBodyHint,
21
- storeBashOutput,
22
18
  storeWebOutput,
23
- summarizeOutputDelta,
24
19
  summarizeResidentContext,
25
20
  taskListPruneHint
26
- } from "./token-goat-chunk-TELKICYU.mjs";
21
+ } from "./token-goat-chunk-MR7MNSDR.mjs";
27
22
  import {
28
- BODY_FIRST_TOOL_RESPONSE_KEYS,
29
- OUTPUT_FIRST_TOOL_RESPONSE_KEYS,
23
+ BASH_OUTPUT_SUBDIR,
30
24
  UNTRUSTED_TOOL_TAG,
31
25
  WEB_FETCH_KEY_SEP,
32
26
  appendDirtyPath,
@@ -36,28 +30,23 @@ import {
36
30
  classifyBashHint,
37
31
  classifyEditHint,
38
32
  clearCurlDownload,
33
+ commandHash,
39
34
  compactPathFor,
40
35
  computeAdaptiveBudget,
41
- contextOutput,
42
36
  countSymbols,
43
- denyOutput,
44
- emitRewrite,
45
- emitRewriteIfChanged,
46
37
  enqueueDirtyPathSafe,
47
38
  ensureWorkerAlive,
48
39
  estimateTokens,
49
40
  extractCompactFromMarker,
50
- extractToolResponseField,
51
- extractToolResultText,
52
41
  fenceWithMatches,
53
42
  formatProjectMap,
54
43
  formatShrinkSummary,
44
+ getBashOutput,
55
45
  getBashOutputId,
56
46
  getContextPressure,
57
47
  getCurlDownloadPath,
58
- getCwd,
59
48
  getFileLineRanges,
60
- getFilePath,
49
+ getFileServedOutputs,
61
50
  getGlobMatchCount,
62
51
  getGrepMatchCount,
63
52
  getLastTabContext,
@@ -67,20 +56,19 @@ import {
67
56
  getSessionBashReruns,
68
57
  getSessionFiles,
69
58
  getSessionWebFetches,
70
- getToolInput,
71
- getToolName,
72
59
  hasSeenImage,
73
60
  hasSessionOutput,
74
61
  imageQualifiesForShrink,
75
62
  incrementSkillHit,
63
+ indexRecallEntry,
76
64
  installedSkillPath,
65
+ isBashEntryStale,
77
66
  isBuildCommand,
78
- isMcpErrorResponse,
67
+ isScopedGitStatusOrDiffStatCommand,
79
68
  isTestRunnerCommand,
80
69
  listSiblingSessionStates,
81
70
  loadSessionCache,
82
71
  loadSessionState,
83
- makeDedupHintHandlers,
84
72
  markCompactStale,
85
73
  markCompacted,
86
74
  markFileTruncated,
@@ -88,7 +76,6 @@ import {
88
76
  matchesAllowPattern,
89
77
  matchesDenyPattern,
90
78
  meetsSavingsFloor,
91
- passOutput,
92
79
  probeImageMeta,
93
80
  recordBashOutput,
94
81
  recordBashRerun,
@@ -97,6 +84,7 @@ import {
97
84
  recordFileEdit,
98
85
  recordFileLineRange,
99
86
  recordFileRead,
87
+ recordFileServedOutput,
100
88
  recordGlobQuery,
101
89
  recordGrepQuery,
102
90
  recordKnownRootThrottled,
@@ -105,58 +93,84 @@ import {
105
93
  recordSeenImage,
106
94
  recordSymbolRead,
107
95
  recordWebFetch,
108
- registerHook,
109
96
  removeOutstandingAgentSpawn,
110
- runHook,
111
97
  saveSessionState,
112
98
  scanAndRecord,
113
- serializeOutput,
114
99
  sessionOutputBodyBytes,
115
100
  sessionSidecarPath,
116
101
  setLastTabContext,
117
102
  shrinkImage,
103
+ storeBashOutput,
118
104
  storeBlob,
119
105
  storeOutput,
106
+ summarizeOutputDelta,
120
107
  takePendingLargeFileHint,
121
108
  visionTokens,
122
109
  visionTokensSaved,
123
110
  wasCliReadThisSession,
124
111
  wasFileReadThisSession,
125
112
  wasHintShown
126
- } from "./token-goat-chunk-DK4VLLYB.mjs";
113
+ } from "./token-goat-chunk-FDXINYK4.mjs";
127
114
  import {
128
115
  canRunWrappedShell,
129
- compressOutput,
130
- dedupeConsecutive,
131
- detectFromCommand,
132
- filterByName,
133
- hasBareBackgroundOrNewline,
134
- isRewriteWorthwhile,
135
- resolveMinNetSavingsBytes,
136
- shlexSplit
137
- } from "./token-goat-chunk-GUNYAGOZ.mjs";
116
+ deliveredOutputBytes
117
+ } from "./token-goat-chunk-LHMC5ENL.mjs";
138
118
  import {
119
+ BODY_FIRST_TOOL_RESPONSE_KEYS,
120
+ ENV_KEYS,
121
+ IDENTICAL_READ_MIN_BODY_BYTES,
122
+ OUTPUT_FIRST_TOOL_RESPONSE_KEYS,
139
123
  PER_FILE_COUNTERFACTUAL_CEILING,
140
124
  VERSION,
125
+ compressOutput,
126
+ containsLineRun,
127
+ contextOutput,
128
+ countNoun,
129
+ dedupeConsecutive,
130
+ denyOutput,
131
+ detectFromCommand,
141
132
  detectHarness,
142
133
  detectLanguage,
143
134
  displaySafePath,
135
+ emitRewrite,
136
+ emitRewriteIfChanged,
137
+ envBool,
138
+ envInt,
144
139
  extractErrorMessage,
140
+ extractToolResponseField,
141
+ extractToolResultText,
142
+ filterByName,
145
143
  foldPath,
144
+ getCwd,
146
145
  getDb,
146
+ getFilePath,
147
147
  getHarnessName,
148
+ getToolInput,
149
+ getToolName,
148
150
  globalDbPath,
151
+ hasBareBackgroundOrNewline,
152
+ isMcpErrorResponse,
153
+ isRewriteWorthwhile,
149
154
  isUnderSystemTemp,
150
155
  loadConfig,
156
+ makeDedupHintHandlers,
151
157
  normalizePath,
158
+ passOutput,
152
159
  recordStat,
153
160
  redactSecrets,
161
+ registerHook,
154
162
  resolveIndexPath,
163
+ resolveMinNetSavingsBytes,
155
164
  runGit,
165
+ runHook,
156
166
  savedTokensFromBytes,
167
+ serializeOutput,
168
+ sessionStateKey,
169
+ shlexSplit,
157
170
  shortFingerprint,
171
+ stripAnsiEscapes,
158
172
  toKB
159
- } from "./token-goat-chunk-2JZ66BBE.mjs";
173
+ } from "./token-goat-chunk-4ZDRTNGV.mjs";
160
174
 
161
175
  // src/hooks_grep.ts
162
176
  function grepIntInput(toolInput, key) {
@@ -631,7 +645,7 @@ import crypto from "node:crypto";
631
645
  var TRACKED_SKILL = "token-goat";
632
646
  var MAX_COMMANDS_SHOWN = 8;
633
647
  async function currentCommandNames() {
634
- const { buildProgram } = await import("./token-goat-chunk-SHN4UTL4.mjs");
648
+ const { buildProgram } = await import("./token-goat-chunk-V5T3YNYR.mjs");
635
649
  return flattenCommandNames(buildCommandManifest(buildProgram()));
636
650
  }
637
651
  async function recordSkillVersionSnapshot(sessionId, skillName) {
@@ -887,23 +901,47 @@ registerHook("post_tool_use", pendingContextHandler, { advisory: true, followsMa
887
901
  // src/hooks_session_start.ts
888
902
  var GENERIC_REMINDER = 'token-goat: prefer surgical reads over the Read/Grep tools on this codebase; shell commands like `rg`, `grep`, `fd`, `sed`, `cat`, `find`, and `ls` are just commands, not tool names -- `token-goat symbol <name>`, `token-goat read "file::symbol"`, `token-goat section "file::Heading"`, `token-goat semantic "description"`, `token-goat outline <file>`. Run `token-goat index .` if this project is not indexed yet.';
889
903
  var INDEXED_REMINDER = 'token-goat: this project is indexed. Prefer `symbol <name>`, `read "file::symbol"`, `section "file::Heading"`, `semantic "description"`, or `outline <file>` over a full Read/Grep tool call; for JSON/YAML use `json-query file \'a.b.c\'` or `yaml-query` (nested keys are not symbols); shell commands like `rg`, `grep`, `fd`, `sed`, `cat`, `find`, and `ls` are still just commands.';
890
- function buildReminder(cwd) {
891
- if (cwd === void 0) return GENERIC_REMINDER;
892
- let symbolCount;
904
+ function isIndexedProject(cwd) {
905
+ if (cwd === void 0) return false;
906
+ try {
907
+ return countSymbols({ rootDir: cwd }, globalDbPath()) > 0;
908
+ } catch {
909
+ return false;
910
+ }
911
+ }
912
+ function buildReminder(indexed) {
913
+ return indexed ? INDEXED_REMINDER : GENERIC_REMINDER;
914
+ }
915
+ function reconcileNote(cwd, indexed) {
916
+ if (!indexed) return null;
917
+ if (!envBool(ENV_KEYS.RECONCILE, true)) return null;
893
918
  try {
894
- symbolCount = countSymbols({ rootDir: cwd }, globalDbPath());
919
+ const budgetMs = envInt(ENV_KEYS.RECONCILE_BUDGET_MS, DEFAULT_RECONCILE_BUDGET_MS, 0, 6e4);
920
+ const result = reconcileProject({ cwd, budgetMs });
921
+ if (isReconcileClean(result)) return null;
922
+ const parts = [];
923
+ if (result.changed.length > 0) parts.push(`${result.changed.length} changed`);
924
+ if (result.added.length > 0) parts.push(`${result.added.length} new`);
925
+ if (result.removed.length > 0) parts.push(`${result.removed.length} removed`);
926
+ const breakdown = parts.length > 1 ? ` (${parts.join(", ")})` : "";
927
+ const truncated = result.budgetExhausted ? ` (sweep stopped at its time budget with ${countNoun(result.unscanned, "file")} unchecked, so there may be more)` : "";
928
+ const total = result.changed.length + result.added.length + result.removed.length;
929
+ return `token-goat: reindexing ${countNoun(total, "file")} that changed outside this session${breakdown}${truncated}. Symbol lookups may be briefly stale.`;
895
930
  } catch {
896
- return GENERIC_REMINDER;
931
+ return null;
897
932
  }
898
- if (symbolCount <= 0) return GENERIC_REMINDER;
899
- return INDEXED_REMINDER;
900
933
  }
901
934
  function sessionStartHandler(event) {
902
935
  try {
903
936
  if (!loadConfig().hints.session_start_reminder) return passOutput();
904
937
  const cwd = getCwd(event);
905
- let context = buildReminder(cwd);
938
+ const indexed = isIndexedProject(cwd);
939
+ let context = buildReminder(indexed);
906
940
  if (cwd !== void 0) {
941
+ const drift = reconcileNote(cwd, indexed);
942
+ if (drift !== null) context += `
943
+
944
+ ${drift}`;
907
945
  const capsule = buildDeltaCapsule(cwd);
908
946
  if (capsule !== null) context += `
909
947
 
@@ -7130,7 +7168,7 @@ async function preSkillHandler(event) {
7130
7168
  const denyCredit = cachedBytes !== null ? Math.min(cachedBytes, PER_FILE_COUNTERFACTUAL_CEILING) : 0;
7131
7169
  recordStat("session_hint", denyCredit, savedTokensFromBytes(denyCredit));
7132
7170
  return denyOutput(
7133
- "Skill `" + skillName + "` was already loaded this session and is cached. Use `token-goat skill-body " + skillName + " --compact` to recall the compact slice (or `token-goat skill-body " + skillName + "` for the full body) instead of re-loading it."
7171
+ "Skill `" + skillName + "` was already loaded this session and is cached. Use `token-goat skill-section " + skillName + " '<heading>'` to recall a section, `token-goat skill-body " + skillName + " --compact` to recall the compact slice, or `token-goat skill-body " + skillName + "` for the full body instead of re-loading it."
7134
7172
  );
7135
7173
  }
7136
7174
  const sourcePath = await installedSkillPath(skillName);
@@ -7145,12 +7183,12 @@ async function preSkillHandler(event) {
7145
7183
  const savedBytes = bodyBytes - compactBytes;
7146
7184
  recordStat("skill_compact_inlined", savedBytes, savedTokensFromBytes(savedBytes));
7147
7185
  return denyOutput(
7148
- "Skill `" + skillName + "` is large (" + bodyBytes + " bytes); its compact slice (" + compactBytes + " bytes) is inlined below instead of the full body. Run `token-goat skill-body " + skillName + "` if you need the full body.\n\n" + compact
7186
+ "Skill `" + skillName + "` is large (" + bodyBytes + " bytes); its compact slice (" + compactBytes + " bytes) is inlined below instead of the full body. For a specific section, run `token-goat skill-section " + skillName + " '<heading>'`, or `token-goat skill-body " + skillName + "` if you need the full body.\n\n" + compact
7149
7187
  );
7150
7188
  }
7151
7189
  recordStat("skill_oversized_first_load");
7152
7190
  return denyOutput(
7153
- "Skill `" + skillName + "` is large (" + bodyBytes + " bytes) and has a compact slice available. Use `token-goat skill-body " + skillName + " --compact` to load the compact slice instead of the full body."
7191
+ "Skill `" + skillName + "` is large (" + bodyBytes + " bytes) and has a compact slice available. Use `token-goat skill-section " + skillName + " '<heading>'` to load a specific section, `token-goat skill-body " + skillName + " --compact` to load the compact slice, or `token-goat skill-body " + skillName + "` for the full body."
7154
7192
  );
7155
7193
  }
7156
7194
  } catch {
@@ -8383,6 +8421,40 @@ function maybeCompressRewrite(event, rawCmd, cmd) {
8383
8421
  const wrapped = `token-goat compress -f ${filterName} --timeout ${cfg.timeout_seconds} -c ${shellQuoteSingle(rawCmd)}`;
8384
8422
  return { hookType: "rewriteInput", updatedInput: { ...event.toolInput, command: wrapped } };
8385
8423
  }
8424
+ function pureFileReadPath(cmd) {
8425
+ return extractCatFile(cmd)?.filePath ?? extractHeadFile(cmd)?.filePath ?? extractTailFile(cmd)?.filePath ?? extractLineRangeRead(cmd)?.filePath ?? null;
8426
+ }
8427
+ async function maybeCollapseIdenticalRead(cmd, output, exitCode, cwd, cacheMinBytes) {
8428
+ if (process.env["TOKEN_GOAT_BASH_COMPRESS"] === "0") return null;
8429
+ if (exitCode !== null && exitCode !== 0) return null;
8430
+ const filePath = pureFileReadPath(cmd);
8431
+ if (filePath === null) return null;
8432
+ const originalBytes = Buffer.byteLength(output, "utf-8");
8433
+ if (originalBytes < Math.max(cacheMinBytes, IDENTICAL_READ_MIN_BODY_BYTES)) return null;
8434
+ const fileKey = resolveIndexPath(filePath, cwd ?? process.cwd());
8435
+ const priorIds = getFileServedOutputs(fileKey);
8436
+ let containerId = null;
8437
+ let identical = false;
8438
+ for (let i = priorIds.length - 1; i >= 0; i--) {
8439
+ const id = priorIds[i];
8440
+ if (id === void 0) continue;
8441
+ const prior = getBashOutput(id);
8442
+ if (prior === null || !containsLineRun(prior.output, output)) continue;
8443
+ containerId = id;
8444
+ identical = prior.output === output;
8445
+ break;
8446
+ }
8447
+ const sessionKey = shortFingerprint(stripOutputPipeline(cmd));
8448
+ if (containerId === null) {
8449
+ const storedId = await storeBashOutput(cmd, output, exitCode ?? 0, cwd);
8450
+ recordBashOutput(sessionKey, storedId, originalBytes);
8451
+ recordFileServedOutput(fileKey, storedId);
8452
+ return null;
8453
+ }
8454
+ const pointer = identical ? "[token-goat] Identical to an earlier run of this command in this session; the file has not changed since. " + originalBytes + " bytes withheld -- recall them with `token-goat bash-output " + containerId + "`." : "[token-goat] These " + originalBytes + " bytes already appear verbatim inside a wider read of " + filePath + " served earlier in this session. Withheld -- recall the full earlier output with `token-goat bash-output " + containerId + "`.";
8455
+ if (!isRewriteWorthwhile({ originalBytes, rewrittenBytes: Buffer.byteLength(pointer, "utf-8"), noticeBytes: 0, minNetSavingsBytes: resolveMinNetSavingsBytes() })) return null;
8456
+ return emitRewrite(pointer, identical ? "identical file re-read collapsed" : "already-served file lines collapsed", { kind: identical ? "bash_compress:identical-reread" : "bash_compress:contained-reread", originalBytes: deliveredOutputBytes(originalBytes) });
8457
+ }
8386
8458
  async function maybeCompressCompoundOutput(cmd, output, exitCode, cwd, cacheMinBytes) {
8387
8459
  if (process.env["TOKEN_GOAT_BASH_COMPRESS"] === "0") return null;
8388
8460
  if (isCompressibleSingleCommand(cmd)) return null;
@@ -8415,7 +8487,28 @@ async function maybeCompressCompoundOutput(cmd, output, exitCode, cwd, cacheMinB
8415
8487
  return null;
8416
8488
  }
8417
8489
  await storeBashOutput(cmd, output, exitCode ?? 0, cwd);
8418
- return emitRewrite(body, "bash", { kind: "bash_compress:generic", originalBytes: compressed.originalBytes });
8490
+ return emitRewrite(body, "bash", { kind: "bash_compress:generic", originalBytes: deliveredOutputBytes(compressed.originalBytes) });
8491
+ }
8492
+ function maybeStripAnsiOnly(output) {
8493
+ if (!output.includes("\x1B")) return null;
8494
+ let cfg;
8495
+ try {
8496
+ cfg = loadConfig().bash_compress;
8497
+ } catch {
8498
+ return null;
8499
+ }
8500
+ if (!cfg.enabled || cfg.disabled_filters.includes("ansi")) return null;
8501
+ const stripped = stripAnsiEscapes(output);
8502
+ const originalBytes = Buffer.byteLength(output, "utf-8");
8503
+ if (!isRewriteWorthwhile({
8504
+ originalBytes,
8505
+ rewrittenBytes: Buffer.byteLength(stripped, "utf-8"),
8506
+ noticeBytes: 0,
8507
+ minNetSavingsBytes: resolveMinNetSavingsBytes()
8508
+ })) {
8509
+ return null;
8510
+ }
8511
+ return emitRewrite(stripped, "ansi escapes stripped", { kind: "bash_compress:ansi", originalBytes: deliveredOutputBytes(originalBytes) }, "counted-elsewhere");
8419
8512
  }
8420
8513
  function unwrapCompressCommand(executed) {
8421
8514
  const t = executed.trim();
@@ -9102,9 +9195,11 @@ async function postBashHandler(event) {
9102
9195
  }
9103
9196
  const isMonitoring = getMonitoringRecallHint(cmd) !== null;
9104
9197
  if (!isMonitoring && !isBuildCommand(cmd) && !isCurlGetCommand(cmd)) {
9198
+ const identical = await maybeCollapseIdenticalRead(cmd, output, exitCode, cwd, cacheMinBytes);
9199
+ if (identical !== null) return identical;
9105
9200
  const compound = await maybeCompressCompoundOutput(cmd, output, exitCode, cwd, cacheMinBytes);
9106
9201
  if (compound !== null) return compound;
9107
- return passOutput();
9202
+ return maybeStripAnsiOnly(output) ?? passOutput();
9108
9203
  }
9109
9204
  if (Buffer.byteLength(output, "utf-8") < cacheMinBytes) return passOutput();
9110
9205
  const cacheKey = isCurlGetCommand(cmd) ? extractCurlUrl(cmd) ?? cmd : stripOutputPipeline(cmd);
@@ -9121,6 +9216,8 @@ async function postBashHandler(event) {
9121
9216
  return contextOutput(delta + " \u2014 full output: bash-output " + id);
9122
9217
  }
9123
9218
  }
9219
+ const ansiOnly = maybeStripAnsiOnly(output);
9220
+ if (ansiOnly !== null) return ansiOnly;
9124
9221
  } catch {
9125
9222
  }
9126
9223
  return passOutput();
@@ -10427,9 +10524,6 @@ function buildEvent(eventName, payload) {
10427
10524
  const tracestate = typeof rawTracestate === "string" && rawTracestate.trim() !== "" ? rawTracestate.trim() : void 0;
10428
10525
  return { eventName, toolName, toolInput, sessionId, agentId, traceparent, tracestate, raw: obj };
10429
10526
  }
10430
- function sessionStateKey(event) {
10431
- return event.agentId !== void 0 ? `${event.sessionId}:agent:${event.agentId}` : event.sessionId;
10432
- }
10433
10527
  function harnessForNormalization() {
10434
10528
  const detected = detectHarness();
10435
10529
  if (detected === "codex") return "codex";
@@ -10467,7 +10561,7 @@ async function relayInProcess(eventName, rawPayload) {
10467
10561
  saveSessionState(stateKey);
10468
10562
  } catch {
10469
10563
  }
10470
- return serializeOutput(output, event.eventName, harness);
10564
+ return serializeOutput(output, event.eventName, harness, event);
10471
10565
  } catch {
10472
10566
  return "{}";
10473
10567
  }