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
@@ -12,7 +12,7 @@ import {
12
12
  parseOoxmlPart,
13
13
  readOoxmlZip,
14
14
  sortNumberedParts
15
- } from "./token-goat-chunk-BTKDZW7F.mjs";
15
+ } from "./token-goat-chunk-QTIRVK3M.mjs";
16
16
  import {
17
17
  compileGuardedRegex
18
18
  } from "./token-goat-chunk-GMOUBOX4.mjs";
@@ -11,7 +11,7 @@ import {
11
11
  ooxmlPartBudget,
12
12
  parseOoxmlPart,
13
13
  readOoxmlZip
14
- } from "./token-goat-chunk-BTKDZW7F.mjs";
14
+ } from "./token-goat-chunk-QTIRVK3M.mjs";
15
15
  import {
16
16
  displaySafeText
17
17
  } from "./token-goat-chunk-NMTKNYGF.mjs";
@@ -5,11 +5,11 @@ import {
5
5
  isSupportedOcrLang,
6
6
  loadConfig,
7
7
  resolveOcrLangs
8
- } from "./token-goat-chunk-SOKAPOOC.mjs";
8
+ } from "./token-goat-chunk-M6CDVASA.mjs";
9
9
  import {
10
10
  ensureDirSync,
11
11
  tokenGoatHome
12
- } from "./token-goat-chunk-ZGQXRYKE.mjs";
12
+ } from "./token-goat-chunk-TGBSMHPM.mjs";
13
13
  import {
14
14
  fenceUntrustedOcrText
15
15
  } from "./token-goat-chunk-NMTKNYGF.mjs";
@@ -0,0 +1,248 @@
1
+ import { createRequire as __cjsRequire } from 'node:module';
2
+ const require = __cjsRequire(import.meta.url);
3
+ import {
4
+ WEB_FETCH_KEY_SEP,
5
+ computeAdaptiveBudget,
6
+ getContextPressure,
7
+ getSessionBashOutputs,
8
+ getSessionBashReruns,
9
+ getSessionFiles,
10
+ getSessionWebFetches,
11
+ isNoisePath,
12
+ listSiblingSessionStates,
13
+ loadSessionCache
14
+ } from "./token-goat-chunk-MECDAPXB.mjs";
15
+ import {
16
+ getBashOutput
17
+ } from "./token-goat-chunk-M43UJVYQ.mjs";
18
+ import {
19
+ loadConfig
20
+ } from "./token-goat-chunk-M6CDVASA.mjs";
21
+ import {
22
+ foldPath,
23
+ runGit,
24
+ toKB
25
+ } from "./token-goat-chunk-TGBSMHPM.mjs";
26
+ import {
27
+ displaySafePath,
28
+ displaySafeText,
29
+ neutralizeSpokenMarkers
30
+ } from "./token-goat-chunk-NMTKNYGF.mjs";
31
+ import {
32
+ init_define_import_meta_env
33
+ } from "./token-goat-chunk-A37V4PBF.mjs";
34
+
35
+ // src/manifest.ts
36
+ init_define_import_meta_env();
37
+ import { spawnSync } from "node:child_process";
38
+ var MEM_EPOCH_TIMEOUT_MS = 800;
39
+ var MAX_ROWS = 40;
40
+ function appendCappedSection(lines, header, rows, cap) {
41
+ if (rows.length === 0) return;
42
+ lines.push("");
43
+ lines.push(header);
44
+ for (const row of rows.slice(0, cap)) lines.push(row);
45
+ if (rows.length > cap) lines.push(`- ...and ${rows.length - cap} more`);
46
+ }
47
+ function renderReadRow(entry) {
48
+ const kb = Math.max(1, toKB(entry.sizeBytes));
49
+ const plural = entry.readCount === 1 ? "read" : "reads";
50
+ const edited = entry.wasEdited ? ", edited" : "";
51
+ return `- ${displaySafePath(entry.path)} (${kb}kb, ${entry.readCount} ${plural}${edited})`;
52
+ }
53
+ function renderSymbolReadRow(entry) {
54
+ return `- ${displaySafePath(entry.path)} (symbols: ${(entry.symbols_read ?? []).map(displaySafeText).join(", ")})`;
55
+ }
56
+ function mergeManifestFiles(parent, siblingFiles) {
57
+ const byPath = /* @__PURE__ */ new Map();
58
+ for (const f of parent) byPath.set(foldPath(f.path), f);
59
+ for (const f of siblingFiles) {
60
+ const key = foldPath(f.path);
61
+ const prev = byPath.get(key);
62
+ if (prev === void 0) {
63
+ byPath.set(key, f);
64
+ continue;
65
+ }
66
+ const mergedSymbols = [.../* @__PURE__ */ new Set([...prev.symbols_read ?? [], ...f.symbols_read ?? []])];
67
+ byPath.set(key, {
68
+ path: prev.path,
69
+ readCount: Math.max(prev.readCount, f.readCount),
70
+ lastReadAt: Math.max(prev.lastReadAt, f.lastReadAt),
71
+ wasEdited: prev.wasEdited || f.wasEdited,
72
+ sizeBytes: f.lastReadAt >= prev.lastReadAt ? f.sizeBytes : prev.sizeBytes,
73
+ ...prev.wasTruncated || f.wasTruncated ? { wasTruncated: true } : {},
74
+ ...mergedSymbols.length > 0 ? { symbols_read: mergedSymbols } : {}
75
+ });
76
+ }
77
+ return Array.from(byPath.values());
78
+ }
79
+ function renderWebFetchRow(key, cacheId) {
80
+ const [url = key, prompt = ""] = key.split(WEB_FETCH_KEY_SEP);
81
+ const promptSuffix = prompt ? `, prompt: ${neutralizeSpokenMarkers(JSON.stringify(prompt))}` : "";
82
+ return `- ${displaySafeText(url)} (cacheId: ${cacheId}${promptSuffix})`;
83
+ }
84
+ function selectManifestFiles(siblings) {
85
+ const ownFiles = [...getSessionFiles().values()];
86
+ const siblingFiles = siblings.flatMap((s) => s.files);
87
+ const files = siblingFiles.length > 0 ? mergeManifestFiles(ownFiles, siblingFiles) : ownFiles;
88
+ const editedFiles = files.filter((f) => f.wasEdited);
89
+ const readFiles = files.filter((f) => f.readCount > 0 && !f.wasEdited && !isNoisePath(f.path));
90
+ const symbolOnlyFiles = files.filter((f) => f.readCount === 0 && !f.wasEdited && (f.symbols_read?.length ?? 0) > 0);
91
+ return { files, readFiles, editedFiles, symbolOnlyFiles };
92
+ }
93
+ function selectManifestWebFetches(siblings) {
94
+ const merged = /* @__PURE__ */ new Map();
95
+ for (const sibling of siblings) for (const [key, cacheId] of sibling.webFetches) merged.set(key, cacheId);
96
+ for (const [key, cacheId] of getSessionWebFetches()) merged.set(key, cacheId);
97
+ return [...merged.entries()];
98
+ }
99
+ function manifestPrintedPaths(sessionId, limit, cwd, transcriptPath) {
100
+ const { printed } = buildManifestParts(sessionId, cwd, transcriptPath);
101
+ return printed.slice(0, limit);
102
+ }
103
+ function buildManifestParts(sessionId, cwd, transcriptPath) {
104
+ const siblings = sessionId !== void 0 ? listSiblingSessionStates(sessionId) : [];
105
+ const { files, readFiles, editedFiles, symbolOnlyFiles } = selectManifestFiles(siblings);
106
+ const webFetches = selectManifestWebFetches(siblings);
107
+ const lines = [];
108
+ lines.push("## Session context");
109
+ lines.push(`Files read: ${readFiles.length}`);
110
+ lines.push(`Files edited: ${editedFiles.length}`);
111
+ const fileRows = [];
112
+ const appendFileSection = (header, entries, render) => {
113
+ const rows = entries.map(render);
114
+ appendCappedSection(lines, header, rows, MAX_ROWS);
115
+ for (let i = 0; i < Math.min(rows.length, MAX_ROWS); i++) fileRows.push({ path: displaySafePath(entries[i].path), row: rows[i] });
116
+ };
117
+ appendFileSection("### Edited files", editedFiles, (entry) => `- ${displaySafePath(entry.path)}`);
118
+ appendFileSection("### Surgically read files (symbol/section reads, never read whole)", symbolOnlyFiles, renderSymbolReadRow);
119
+ appendFileSection("### Read files", readFiles, renderReadRow);
120
+ appendCappedSection(lines, "### Web URLs fetched", webFetches.map(([key, cacheId]) => renderWebFetchRow(key, cacheId)), MAX_ROWS);
121
+ lines.push(...buildSafeToDiscardSection(files));
122
+ lines.push(...buildMemEpochSection());
123
+ const text = capManifestChars(lines.join("\n"), sessionId, cwd, transcriptPath);
124
+ const seen = /* @__PURE__ */ new Set();
125
+ const printed = [];
126
+ for (const { path, row } of fileRows) {
127
+ if (seen.has(path) || !(text.includes(row + "\n") || text.endsWith(row))) continue;
128
+ seen.add(path);
129
+ printed.push(path);
130
+ }
131
+ return { text, printed };
132
+ }
133
+ function buildManifest(sessionId, cwd, transcriptPath) {
134
+ return buildManifestParts(sessionId, cwd, transcriptPath).text;
135
+ }
136
+ function gitDirtySignals(cwd) {
137
+ const timeoutMs = loadConfig().hints.git_hint_max_ms;
138
+ let hasPendingDiff = false;
139
+ try {
140
+ const diffResult = runGit(["diff", "--no-color", "--stat", "HEAD"], { cwd, timeoutMs });
141
+ hasPendingDiff = diffResult.exitCode === 0 && diffResult.stdout.trim() !== "";
142
+ } catch {
143
+ }
144
+ let hasUncommittedChanges = false;
145
+ try {
146
+ const statusResult = runGit(["status", "--porcelain"], { cwd, timeoutMs });
147
+ hasUncommittedChanges = statusResult.exitCode === 0 && statusResult.stdout.trim() !== "";
148
+ } catch {
149
+ }
150
+ return { hasPendingDiff, hasUncommittedChanges };
151
+ }
152
+ function adaptiveCharBonus(sessionId, cwd, transcriptPath) {
153
+ if (!cwd) return 0;
154
+ const cache = loadSessionCache(sessionId ?? "") ?? {};
155
+ const ageSecs = cache.created_ts !== void 0 ? Math.max(0, Date.now() / 1e3 - cache.created_ts) : 0;
156
+ const contextPressure = getContextPressure(cache, transcriptPath);
157
+ const { hasPendingDiff, hasUncommittedChanges } = gitDirtySignals(cwd);
158
+ if (!hasPendingDiff && !hasUncommittedChanges) return 0;
159
+ const baseline = computeAdaptiveBudget(cache, ageSecs, { contextPressure });
160
+ const withGitSignal = computeAdaptiveBudget(cache, ageSecs, { hasPendingDiff, hasUncommittedChanges, contextPressure });
161
+ const deltaTokens = Math.max(0, withGitSignal - baseline);
162
+ return deltaTokens * 3;
163
+ }
164
+ function capManifestChars(manifest, sessionId, cwd, transcriptPath) {
165
+ const cap = loadConfig().compact_assist.max_manifest_chars;
166
+ if (cap <= 0) return manifest;
167
+ if (manifest.length <= cap) return manifest;
168
+ const effectiveCap = cap + adaptiveCharBonus(sessionId, cwd, transcriptPath);
169
+ if (manifest.length <= effectiveCap) return manifest;
170
+ const omitted = manifest.length - effectiveCap;
171
+ return manifest.slice(0, effectiveCap) + `
172
+ ...(manifest truncated at ${effectiveCap} chars; ${omitted} chars omitted)`;
173
+ }
174
+ function buildSafeToDiscardSection(files) {
175
+ const rerunHashes = getSessionBashReruns();
176
+ const bashOutputs = getSessionBashOutputs();
177
+ const rerunHashSet = new Set(rerunHashes);
178
+ const rerunRows = [];
179
+ for (const hash of rerunHashes) {
180
+ const id = bashOutputs.find(([h]) => h === hash)?.[1];
181
+ if (id === void 0) continue;
182
+ const entry = getBashOutput(id);
183
+ if (entry === null) continue;
184
+ const flatCommand = neutralizeSpokenMarkers(entry.command.replace(/[\t\r\n]+/g, " "));
185
+ rerunRows.push("- `" + flatCommand + "` \u2014 an older run of this exact command was superseded; recall the surviving copy with `bash-output " + id + " --full`");
186
+ }
187
+ const supersededReadRows = [];
188
+ for (const f of files) {
189
+ if (f.readCount > 1 || f.wasEdited) {
190
+ const reason = f.wasEdited ? "edited after being read" : "re-read " + f.readCount + "x";
191
+ supersededReadRows.push("- " + displaySafePath(f.path) + " (" + reason + " \u2014 only the latest content already in context is current)");
192
+ }
193
+ }
194
+ const cachedOutputRows = [];
195
+ for (const [hash, id] of bashOutputs) {
196
+ if (rerunHashSet.has(hash)) continue;
197
+ const entry = getBashOutput(id);
198
+ if (entry === null) continue;
199
+ const flatCommand = neutralizeSpokenMarkers(entry.command.replace(/[\t\r\n]+/g, " "));
200
+ cachedOutputRows.push("- `" + flatCommand + "` \u2014 recallable via `bash-output " + id + " --full`");
201
+ }
202
+ const total = rerunRows.length + supersededReadRows.length + cachedOutputRows.length;
203
+ if (total === 0) return [];
204
+ const lines = [];
205
+ lines.push("");
206
+ lines.push("### SAFE_TO_DISCARD (" + total + " items \u2014 provably inert; each is recallable, not gone)");
207
+ appendCappedSection(lines, "Superseded reruns (" + rerunRows.length + "):", rerunRows, MAX_ROWS);
208
+ appendCappedSection(lines, "Superseded file reads (" + supersededReadRows.length + "):", supersededReadRows, MAX_ROWS);
209
+ appendCappedSection(lines, "Other cached bash outputs (" + cachedOutputRows.length + "):", cachedOutputRows, MAX_ROWS);
210
+ return lines;
211
+ }
212
+ function buildMemEpochSection() {
213
+ let result;
214
+ try {
215
+ result = spawnSync("mem", ["epoch"], {
216
+ encoding: "utf-8",
217
+ timeout: MEM_EPOCH_TIMEOUT_MS,
218
+ windowsHide: true
219
+ });
220
+ } catch {
221
+ return [];
222
+ }
223
+ if (result.error !== void 0 || result.status !== 0) return [];
224
+ const stdout = typeof result.stdout === "string" ? result.stdout.trim() : "";
225
+ if (!/^\d+$/.test(stdout)) return [];
226
+ return [
227
+ "",
228
+ "### mem epoch",
229
+ `mem epoch: ${stdout} (no live TGMEM block is tracked in this session -- only the epoch counter is folded in; see buildMemEpochSection's doc comment)`
230
+ ];
231
+ }
232
+ 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.";
233
+ var MANIFEST_RECOVERY_PREAMBLE = "The conversation was just compacted. Below is what this session touched, recovered from token-goat's own ledger rather than from the summary -- use it to re-establish paths and symbol names the summary may have dropped.";
234
+ var BUDGET_ESCALATION_MARKER = "TG-BUDGET-ESCALATION:";
235
+ function summaryBudgetDirective(budgetChars) {
236
+ if (budgetChars <= 0) return "";
237
+ 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>\`.`;
238
+ }
239
+
240
+ export {
241
+ renderWebFetchRow,
242
+ manifestPrintedPaths,
243
+ buildManifest,
244
+ MANIFEST_PREAMBLE,
245
+ MANIFEST_RECOVERY_PREAMBLE,
246
+ BUDGET_ESCALATION_MARKER,
247
+ summaryBudgetDirective
248
+ };
@@ -2,11 +2,11 @@ import { createRequire as __cjsRequire } from 'node:module';
2
2
  const require = __cjsRequire(import.meta.url);
3
3
  import {
4
4
  getDb
5
- } from "./token-goat-chunk-JNIFSVWM.mjs";
5
+ } from "./token-goat-chunk-2ZHD3XCD.mjs";
6
6
  import {
7
7
  getHarnessName,
8
8
  loadConfig
9
- } from "./token-goat-chunk-SOKAPOOC.mjs";
9
+ } from "./token-goat-chunk-M6CDVASA.mjs";
10
10
  import {
11
11
  registerReset
12
12
  } from "./token-goat-chunk-EEIDFMEM.mjs";
@@ -23,7 +23,7 @@ import {
23
23
  extractErrorMessage,
24
24
  sanitizeIdForFilename,
25
25
  tokenGoatHome
26
- } from "./token-goat-chunk-ZGQXRYKE.mjs";
26
+ } from "./token-goat-chunk-TGBSMHPM.mjs";
27
27
  import {
28
28
  displaySafeText
29
29
  } from "./token-goat-chunk-NMTKNYGF.mjs";
@@ -54,12 +54,6 @@ function stripAnsiEscapes(s) {
54
54
  }
55
55
  return s.replace(_ANSI_ESCAPE_RE, "");
56
56
  }
57
- function stripAnsi(s) {
58
- if (!s.includes("\x1B")) {
59
- return s;
60
- }
61
- return stripAnsiEscapes(s).replace(_PUA_RE, "");
62
- }
63
57
  function fmtBytes(n) {
64
58
  let value = n;
65
59
  const units = ["B", "KB", "MB", "GB", "TB"];
@@ -75,7 +69,7 @@ function fg(r, g, b) {
75
69
  return `${_E}[38;2;${r};${g};${b}m`;
76
70
  }
77
71
  function vlen(s) {
78
- return stripAnsi(s).length;
72
+ return stripAnsiEscapes(s).replace(_PUA_RE, "").length;
79
73
  }
80
74
  function padR(s, w) {
81
75
  return s + " ".repeat(Math.max(0, w - vlen(s)));
@@ -115,6 +109,41 @@ init_define_import_meta_env();
115
109
  import * as fs from "node:fs";
116
110
  import * as path from "node:path";
117
111
 
112
+ // src/token_estimate.ts
113
+ init_define_import_meta_env();
114
+ var BYTES_PER_TOKEN = {
115
+ text: 4,
116
+ dense: 1.45
117
+ };
118
+ var GUARD_MARGIN = 0.75;
119
+ function creditDivisor(cls = "text") {
120
+ return BYTES_PER_TOKEN[cls];
121
+ }
122
+ function guardDivisor(cls = "text") {
123
+ return BYTES_PER_TOKEN[cls] * GUARD_MARGIN;
124
+ }
125
+ var DENSE_ALPHABET = /^[A-Za-z0-9+/=_-]$/;
126
+ var MIN_DENSE_SAMPLE = 200;
127
+ var MIN_DENSE_DISTINCT = 16;
128
+ var CLASSIFY_SAMPLE_CHARS = 4096;
129
+ function classifyContent(text) {
130
+ const sample = text.slice(0, CLASSIFY_SAMPLE_CHARS);
131
+ if (sample.length < MIN_DENSE_SAMPLE) return "text";
132
+ let whitespace = 0;
133
+ let dense = 0;
134
+ const distinct = /* @__PURE__ */ new Set();
135
+ for (const ch of sample) {
136
+ if (ch === " " || ch === "\n" || ch === " " || ch === "\r") whitespace++;
137
+ else if (DENSE_ALPHABET.test(ch)) {
138
+ dense++;
139
+ distinct.add(ch);
140
+ }
141
+ }
142
+ if (whitespace / sample.length >= 0.05) return "text";
143
+ if (distinct.size < MIN_DENSE_DISTINCT) return "text";
144
+ return dense / sample.length >= 0.95 ? "dense" : "text";
145
+ }
146
+
118
147
  // src/render/stats_renderer.ts
119
148
  init_define_import_meta_env();
120
149
  var _STATS_MESSAGES_FALLBACK = {
@@ -777,7 +806,7 @@ function _renderByProjectSection(stats) {
777
806
  nameColor: C.TEXT_PRIMARY
778
807
  })
779
808
  );
780
- lines.push(`${_M} ${fg(...C.TEXT_DIM)}\u2514\u2500 ${p.hash} ${displaySafeText(stripAnsi(p.path))}${RESET}`);
809
+ lines.push(`${_M} ${fg(...C.TEXT_DIM)}\u2514\u2500 ${p.hash} ${displaySafeText(stripAnsiEscapes(p.path))}${RESET}`);
781
810
  }
782
811
  return lines;
783
812
  }
@@ -871,8 +900,8 @@ var SOURCE_CONTENT = "content";
871
900
  var SOURCE_OTHER = "other";
872
901
  var _BYTES_MODE_ONLY_KINDS = /* @__PURE__ */ new Set(["webfetch_image", "gdrive_image"]);
873
902
  var COUNT_ONLY_KINDS = /* @__PURE__ */ new Set(["secret_redacted"]);
874
- function savedTokensFromBytes(bytes) {
875
- return Math.round(Math.max(0, bytes) / 4);
903
+ function savedTokensFromBytes(bytes, cls = "text") {
904
+ return Math.round(Math.max(0, bytes) / creditDivisor(cls));
876
905
  }
877
906
  var KIND_TO_SOURCE = {
878
907
  image_shrink: SOURCE_IMAGE,
@@ -2141,10 +2170,12 @@ function sweepCacheRoots(extraRoots = []) {
2141
2170
  }
2142
2171
 
2143
2172
  export {
2173
+ BYTES_PER_TOKEN,
2174
+ guardDivisor,
2175
+ classifyContent,
2144
2176
  colorStdout,
2145
2177
  RESET,
2146
2178
  stripAnsiEscapes,
2147
- stripAnsi,
2148
2179
  fg,
2149
2180
  C,
2150
2181
  SOURCE_HINT,