token-goat 2.8.4 → 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.
@@ -5,6 +5,7 @@ import {
5
5
  AMBIGUOUS_HEADING_LIMIT,
6
6
  CONTENT_MAX_INPUT_CHARS,
7
7
  WHOLE_FILE_NOTE_SYMBOL,
8
+ buildImportGraph,
8
9
  compressText,
9
10
  computeFileFingerprint,
10
11
  createHandoff,
@@ -15,7 +16,6 @@ import {
15
16
  extractTranscriptText,
16
17
  filterSimilarHeadings,
17
18
  findSpecSeparator,
18
- getTrackedFiles,
19
19
  healStaleIndex,
20
20
  isAvailable as isAvailable2,
21
21
  listSections,
@@ -86,42 +86,38 @@ import {
86
86
  runZipRead,
87
87
  symbolNamesInFile,
88
88
  upsertNote
89
- } from "./token-goat-chunk-RE7S7H26.mjs";
89
+ } from "./token-goat-chunk-SXSWQMNP.mjs";
90
90
  import {
91
- BASH_OUTPUT_SUBDIR,
91
+ DEFAULT_RECONCILE_BUDGET_MS,
92
92
  GEMINI_TOOL_NAME_MAP,
93
93
  HOOK_EVENTS,
94
94
  WEB_OUTPUT_SUBDIR,
95
95
  accumulateResidentLine,
96
96
  buildCommandManifest,
97
97
  checkSymbolBodySize,
98
- commandHash,
99
98
  createResidentContextStats,
100
99
  filterCommandManifest,
101
100
  formatBytes,
102
101
  formatCommandManifest,
103
102
  formatTokenEstimate,
104
- getBashOutput,
105
103
  getWebOutput,
106
104
  getWebOutputByUrlFromDisk,
107
105
  getWebOutputRaw,
108
- isRecallCacheType,
109
- listRecentRecall,
110
- normalizeCommandForCacheKey,
111
106
  readCopilotMcpTools,
112
107
  readStdinJson,
113
- searchRecall,
108
+ runReconcile,
114
109
  storeWebOutput,
115
110
  summarizeResidentContext
116
- } from "./token-goat-chunk-IZRXU64B.mjs";
111
+ } from "./token-goat-chunk-MR7MNSDR.mjs";
117
112
  import {
118
113
  AGENT_SALT_MARKER,
114
+ BASH_OUTPUT_SUBDIR,
119
115
  CONTEXT_AUTOCOMPACT_TOKENS,
120
116
  DEFAULT_MAX_AGE_MS,
121
117
  DEFAULT_MAX_COUNT,
122
- MATERIALIZE_SHRUNK_IMAGE_JS,
123
118
  MAX_FILES_SCANNED,
124
119
  PARSER_FINGERPRINT,
120
+ RECALL_DEFAULT_LIMIT,
125
121
  SESSIONS_SUBDIR,
126
122
  SKILLS_OUTPUT_SUBDIR,
127
123
  SKIP_DIRS,
@@ -130,7 +126,7 @@ import {
130
126
  UNTRUSTED_WEB_TAG,
131
127
  WORKER_HEARTBEAT_STALE_MS,
132
128
  WorkerAlreadyRunningError,
133
- anchoredMarkerPattern,
129
+ applyIndexingPriority,
134
130
  auditClaudeMd,
135
131
  buildExtractiveCompact,
136
132
  buildLineDiff,
@@ -138,14 +134,11 @@ import {
138
134
  buildProjectMap,
139
135
  buildStats,
140
136
  capJsonRows,
137
+ commandHash,
141
138
  compactDoc,
142
139
  compactPathFor,
143
140
  confirmAndApply,
144
141
  contentHash,
145
- copilotCliConfigPath,
146
- copilotCliMcpToolsDir,
147
- copilotCliScriptPath,
148
- copilotCliUserRoot,
149
142
  detectWalkMode,
150
143
  dirtyQueuePathFor,
151
144
  docxOutline,
@@ -160,6 +153,7 @@ import {
160
153
  extractChecklistSection,
161
154
  extractCompactFromMarker,
162
155
  extractNamedSection,
156
+ fenceUntrusted,
163
157
  fenceUntrustedContent,
164
158
  fenceUntrustedOcrText,
165
159
  findClaudeMdFiles,
@@ -168,15 +162,16 @@ import {
168
162
  findMemSuggestionCandidates,
169
163
  findMemoryMd,
170
164
  findOrphanedChunkPaths,
171
- findStrayClaudeMdBlocks,
172
165
  findSystemTempFiles,
173
166
  formatAge,
174
167
  formatCsvTable,
175
168
  formatMemSuggestions,
176
169
  formatProjectMap,
177
170
  formatXlsxRange,
171
+ getBashOutput,
178
172
  getContextPressure,
179
173
  getDirtyPathsFor,
174
+ getEmbeddingCoverage,
180
175
  getFileEntry,
181
176
  getHintStatsSummary,
182
177
  getHintStatsTotals,
@@ -184,34 +179,24 @@ import {
184
179
  getSessionFiles,
185
180
  getSessionId,
186
181
  getSkillFilePath,
187
- groupHasTokenGoat,
182
+ getTrackedFiles,
188
183
  headSheet,
189
184
  incrementSkillHit,
190
185
  indexFileEmbeddings,
191
186
  indexFileSync,
192
187
  indexedPathSpellingIsStale,
193
- installClaudeMd,
194
- installCodex,
195
- installCopilotCli,
196
- installGrok,
197
- installHooks,
198
- installKimi,
199
- installSkill,
200
- installVscode,
188
+ injectionFencingEnabled,
201
189
  isAvailable,
202
- isCodexInstalled,
203
190
  isCompactFresh,
204
- isCopilotCliInstalled,
205
191
  isEmbedFresh,
206
192
  isEmbeddableDocument,
207
- isGrokInstalled,
208
193
  isHintCategory,
209
- isInstalled,
210
- isKimiInstalled,
211
194
  isParseSkipEligible,
195
+ isRecallCacheType,
212
196
  isWorkerRunning,
213
197
  listBlobs,
214
198
  listOutputs,
199
+ listRecentRecall,
215
200
  listSheets,
216
201
  listSkills,
217
202
  loadBlob,
@@ -219,7 +204,7 @@ import {
219
204
  mapLookupBytesSaved,
220
205
  markCategoryEffective,
221
206
  markCategoryIneffective,
222
- otherScopeHasManagedServer,
207
+ normalizeCommandForCacheKey,
223
208
  parseWhereSpecs,
224
209
  pptxNotesText,
225
210
  pptxOutline,
@@ -239,7 +224,8 @@ import {
239
224
  resetHintStats,
240
225
  runContextStats,
241
226
  runDetachedWorkerDaemon,
242
- scanForInjectionPatterns,
227
+ scanAndRecord,
228
+ searchRecall,
243
229
  shrinkImage,
244
230
  skillOutputsDir,
245
231
  startDetachedWorker,
@@ -248,33 +234,32 @@ import {
248
234
  storeOutput,
249
235
  tokenGoatHome,
250
236
  trimToBudget,
251
- uninstallClaudeMd,
252
- uninstallCodex,
253
- uninstallCopilotCli,
254
- uninstallGrok,
255
- uninstallHooks,
256
- uninstallKimi,
257
- uninstallSkill,
258
- uninstallVscode,
259
237
  urlPolicyDenialReason,
260
- vscodeDecoderConfigured,
238
+ visionTokensSavedByText,
261
239
  walkProject,
262
240
  writeCompact
263
- } from "./token-goat-chunk-4OTIB7SB.mjs";
241
+ } from "./token-goat-chunk-FDXINYK4.mjs";
264
242
  import {
265
243
  C,
266
244
  CONFIG_KEY_ENV_OVERRIDES,
267
245
  ENV_KEYS,
268
246
  FILTERS,
269
247
  LOCK_WAIT_MS_HARDENED,
248
+ MATERIALIZE_SHRUNK_IMAGE_JS,
270
249
  RESET,
271
250
  VERSION,
272
251
  _useRichStats,
252
+ anchoredMarkerPattern,
273
253
  atomicWriteBytes,
274
254
  atomicWriteText,
275
255
  canonicalize,
256
+ cappedSourceBytesSaved,
276
257
  colorStdout,
277
258
  configPath,
259
+ copilotCliConfigPath,
260
+ copilotCliMcpToolsDir,
261
+ copilotCliScriptPath,
262
+ copilotCliUserRoot,
278
263
  countNoun,
279
264
  dataDir,
280
265
  decodeSource,
@@ -289,6 +274,7 @@ import {
289
274
  fg,
290
275
  fileIsAbsent,
291
276
  findProject,
277
+ findStrayClaudeMdBlocks,
292
278
  fingerprintContent,
293
279
  fingerprintFile,
294
280
  foldPath,
@@ -299,9 +285,24 @@ import {
299
285
  getLastProjectConfigParseError,
300
286
  getProjectConfigInfo,
301
287
  globalDbPath,
288
+ groupHasTokenGoat,
289
+ installClaudeMd,
290
+ installCodex,
291
+ installCopilotCli,
292
+ installGrok,
293
+ installHooks,
294
+ installKimi,
302
295
  installSingleFilePlugin,
296
+ installSkill,
297
+ installVscode,
303
298
  invalidateConfigCache,
299
+ isCodexInstalled,
300
+ isCopilotCliInstalled,
304
301
  isDotenvPath,
302
+ isGrokInstalled,
303
+ isInstalled,
304
+ isKimiInstalled,
305
+ isTestFile,
305
306
  isUnderBlockedRoot,
306
307
  isWindows,
307
308
  lastProjectConfigLockedKeys,
@@ -309,6 +310,7 @@ import {
309
310
  loadPersistedConfig,
310
311
  normalizeDarwinSystemAlias,
311
312
  normalizePath,
313
+ otherScopeHasManagedServer,
312
314
  pad,
313
315
  parse,
314
316
  pushAll,
@@ -336,14 +338,23 @@ import {
336
338
  summarize,
337
339
  toDisplayPath,
338
340
  toKB,
341
+ uninstallClaudeMd,
342
+ uninstallCodex,
343
+ uninstallCopilotCli,
344
+ uninstallGrok,
345
+ uninstallHooks,
346
+ uninstallKimi,
339
347
  uninstallSingleFilePlugin,
348
+ uninstallSkill,
349
+ uninstallVscode,
340
350
  validateEnumField,
341
351
  validateNumericField,
352
+ vscodeDecoderConfigured,
342
353
  withExtension,
343
354
  withFileLock,
344
355
  withRetryOnLock,
345
356
  writeJsonSettings
346
- } from "./token-goat-chunk-E76UNTVK.mjs";
357
+ } from "./token-goat-chunk-4ZDRTNGV.mjs";
347
358
  import {
348
359
  __export
349
360
  } from "./token-goat-chunk-AEX54RUZ.mjs";
@@ -3462,9 +3473,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3462
3473
  * @param {string} [path]
3463
3474
  * @return {(string|null|Command)}
3464
3475
  */
3465
- executableDir(path29) {
3466
- if (path29 === void 0) return this._executableDir;
3467
- this._executableDir = path29;
3476
+ executableDir(path30) {
3477
+ if (path30 === void 0) return this._executableDir;
3478
+ this._executableDir = path30;
3468
3479
  return this;
3469
3480
  }
3470
3481
  /**
@@ -3764,7 +3775,7 @@ function attemptedCommandName(argv) {
3764
3775
 
3765
3776
  // src/cli.ts
3766
3777
  import * as fs28 from "fs";
3767
- import * as path28 from "path";
3778
+ import * as path29 from "path";
3768
3779
  import { homedir as homedir13 } from "os";
3769
3780
 
3770
3781
  // src/walk_index.ts
@@ -5652,9 +5663,110 @@ function formatCues(cues) {
5652
5663
  return cues.map((cue) => `[${formatTimestamp(cue.startSeconds)}]${cue.speaker !== null ? ` ${cue.speaker}:` : ""} ${cue.text}`).join("\n");
5653
5664
  }
5654
5665
 
5666
+ // src/affected.ts
5667
+ import * as path9 from "node:path";
5668
+ var DEFAULT_AFFECTED_DEPTH = 5;
5669
+ function emit(text) {
5670
+ const payload = colorStdout() ? text : stripAnsi(text);
5671
+ process.stdout.write(ensureNewline(payload));
5672
+ }
5673
+ function emitErr(text) {
5674
+ process.stderr.write(ensureNewline(text));
5675
+ }
5676
+ function computeAffected(opts) {
5677
+ const cwd = opts.cwd ?? process.cwd();
5678
+ const depth = opts.depth ?? DEFAULT_AFFECTED_DEPTH;
5679
+ const { files, importedBy } = buildImportGraph(cwd);
5680
+ const trackedByFolded = /* @__PURE__ */ new Map();
5681
+ for (const f of files) trackedByFolded.set(foldPath(normalizePath(f)), f);
5682
+ const unknownSeeds = [];
5683
+ let frontier = [];
5684
+ const seen = /* @__PURE__ */ new Set();
5685
+ for (const raw of opts.files) {
5686
+ const abs = normalizePath(path9.resolve(cwd, raw));
5687
+ const tracked = trackedByFolded.get(foldPath(abs));
5688
+ if (tracked === void 0) {
5689
+ unknownSeeds.push(raw);
5690
+ continue;
5691
+ }
5692
+ if (seen.has(tracked)) continue;
5693
+ seen.add(tracked);
5694
+ frontier.push(tracked);
5695
+ }
5696
+ let hops = 0;
5697
+ while (frontier.length > 0 && hops < depth) {
5698
+ const next = [];
5699
+ for (const file of frontier) {
5700
+ for (const importer of importedBy.get(file) ?? []) {
5701
+ if (seen.has(importer)) continue;
5702
+ seen.add(importer);
5703
+ next.push(importer);
5704
+ }
5705
+ }
5706
+ frontier = next;
5707
+ hops++;
5708
+ }
5709
+ const depthLimited = frontier.length > 0;
5710
+ const matcher = opts.filter === void 0 ? null : new RegExp(opts.filter);
5711
+ const isAffectedTest = (f) => matcher === null ? isTestFile(f) : matcher.test(f);
5712
+ const root = getDisplayRoot(cwd);
5713
+ const testFiles = [...seen].filter(isAffectedTest).map((f) => toDisplayPath(root, f)).sort();
5714
+ return {
5715
+ testFiles,
5716
+ reachedCount: seen.size,
5717
+ unknownSeeds,
5718
+ depthLimited,
5719
+ unexploredAtFrontier: frontier.length,
5720
+ depth
5721
+ };
5722
+ }
5723
+ function runAffected(opts) {
5724
+ if (opts.depth !== void 0 && opts.depth <= 0) {
5725
+ emitErr(`--depth must be a positive number, got: ${opts.depth}`);
5726
+ return 1;
5727
+ }
5728
+ if (opts.files.length === 0) {
5729
+ emitErr("No changed files given. Pass paths as arguments, or pipe them in with --stdin.");
5730
+ return 1;
5731
+ }
5732
+ if (opts.filter !== void 0) {
5733
+ try {
5734
+ new RegExp(opts.filter);
5735
+ } catch (err2) {
5736
+ emitErr(`--filter is not a valid regular expression: ${err2 instanceof Error ? err2.message : String(err2)}`);
5737
+ return 1;
5738
+ }
5739
+ }
5740
+ const result = computeAffected(opts);
5741
+ if (opts.json === true) {
5742
+ emit(JSON.stringify(result, null, 2));
5743
+ return 0;
5744
+ }
5745
+ if (opts.quiet === true) {
5746
+ for (const f of result.testFiles) emit(f);
5747
+ } else if (result.testFiles.length === 0) {
5748
+ const traced = opts.files.length - result.unknownSeeds.length;
5749
+ if (traced === 0) {
5750
+ emit(`Nothing could be traced: ${result.unknownSeeds.length === 1 ? "the file given is not tracked" : "none of the files given are tracked"} in this project.`);
5751
+ } else {
5752
+ emit(`No test files import ${countNoun(traced, "changed file")} (within ${countNoun(result.depth, "hop")}).`);
5753
+ }
5754
+ } else {
5755
+ emit(`${countNoun(result.testFiles.length, "affected test file")} (${countNoun(result.reachedCount, "file")} reached within ${countNoun(result.depth, "hop")}):`);
5756
+ for (const f of result.testFiles) emit(` ${f}`);
5757
+ }
5758
+ if (result.unknownSeeds.length > 0) {
5759
+ emitErr(`Not tracked in this project, so nothing was traced from ${result.unknownSeeds.length === 1 ? "it" : "them"}: ${result.unknownSeeds.join(", ")}`);
5760
+ }
5761
+ if (result.depthLimited) {
5762
+ emitErr(`Stopped at --depth ${result.depth} with ${countNoun(result.unexploredAtFrontier, "file")} still unexplored; more tests may be affected (raise --depth).`);
5763
+ }
5764
+ return 0;
5765
+ }
5766
+
5655
5767
  // src/purge.ts
5656
5768
  import * as fs10 from "node:fs";
5657
- import * as path9 from "node:path";
5769
+ import * as path10 from "node:path";
5658
5770
  function directorySize(root) {
5659
5771
  let total = 0;
5660
5772
  let entries;
@@ -5664,7 +5776,7 @@ function directorySize(root) {
5664
5776
  return 0;
5665
5777
  }
5666
5778
  for (const entry of entries) {
5667
- const full = path9.join(root, entry.name);
5779
+ const full = path10.join(root, entry.name);
5668
5780
  if (entry.isDirectory()) {
5669
5781
  total += directorySize(full);
5670
5782
  continue;
@@ -5707,7 +5819,7 @@ function formatBytes2(bytes) {
5707
5819
 
5708
5820
  // src/cli_stats.ts
5709
5821
  import * as fs11 from "node:fs";
5710
- import * as path10 from "node:path";
5822
+ import * as path11 from "node:path";
5711
5823
  function writeRaw(text) {
5712
5824
  const payload = colorStdout() ? text : stripAnsi(text);
5713
5825
  process.stdout.write(ensureNewline(payload));
@@ -5719,7 +5831,7 @@ function formatTopFiles(ranked) {
5719
5831
  if (ranked.length === 0) return "";
5720
5832
  const lines = ["Top files this session:"];
5721
5833
  for (const { path: filePath, count } of ranked) {
5722
- const basename8 = path10.basename(filePath);
5834
+ const basename8 = path11.basename(filePath);
5723
5835
  lines.push(` ${count.toString().padStart(3)}x ${basename8} (${filePath})`);
5724
5836
  }
5725
5837
  return lines.join("\n");
@@ -5736,12 +5848,12 @@ function renderTopSessionFiles(topN = 5) {
5736
5848
  }
5737
5849
  function renderTopSessionFilesFromDisk(topN = 5, overrideSessionsDir) {
5738
5850
  try {
5739
- const sessionsDir = overrideSessionsDir ?? path10.join(dataDir(), "sessions");
5851
+ const sessionsDir = overrideSessionsDir ?? path11.join(dataDir(), "sessions");
5740
5852
  if (!fs11.existsSync(sessionsDir)) return "";
5741
- const files = fs11.readdirSync(sessionsDir).filter((f) => f.endsWith(".json")).map((f) => ({ name: f, mtime: fs11.statSync(path10.join(sessionsDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).slice(0, 3);
5853
+ const files = fs11.readdirSync(sessionsDir).filter((f) => f.endsWith(".json")).map((f) => ({ name: f, mtime: fs11.statSync(path11.join(sessionsDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).slice(0, 3);
5742
5854
  for (const { name } of files) {
5743
5855
  try {
5744
- const raw = fs11.readFileSync(path10.join(sessionsDir, name), "utf-8");
5856
+ const raw = fs11.readFileSync(path11.join(sessionsDir, name), "utf-8");
5745
5857
  const data = JSON.parse(raw);
5746
5858
  const filesList = data["files"];
5747
5859
  if (!Array.isArray(filesList)) continue;
@@ -5761,8 +5873,8 @@ function renderTopSessionFilesFromDisk(topN = 5, overrideSessionsDir) {
5761
5873
  var METHODOLOGY = {
5762
5874
  estimate_scope: "Local estimate of content avoided or reduced by token-goat.",
5763
5875
  billing: "They are not GitHub Copilot usage, provider-reported token consumption, or billing data.",
5764
- byte_derived_formula: "Most read, hook, and command entries go through savedTokensFromBytes in src/stats.ts, which is Math.round(bytes_saved / 4).",
5765
- filter_estimates: "Output compressors record their filter-calculated delta; image entries use the byte-derived approximation unless the source provides a narrower estimate.",
5876
+ byte_derived_formula: "Most read, hook, and command entries go through savedTokensFromBytes in src/stats.ts, which is Math.round(bytes_saved / 4). Image entries are the exception: they are priced in visual tokens, not bytes.",
5877
+ filter_estimates: "Output compressors record their filter-calculated delta, measured against what the harness would actually have delivered rather than the full command output. Image entries are priced in visual tokens (28x28-pixel patches, capped at the configured vision tier), because an image is not billed by its byte size; where the pixel dimensions are unavailable the tier ceiling is used.",
5766
5878
  advisory_events: "Zero-byte, zero-token advisory events show that guidance fired, not that an agent followed it.",
5767
5879
  audit: "Use stats --full or stats --json for source and command breakdowns; reconcile billing with provider-exported usage data."
5768
5880
  };
@@ -5830,12 +5942,12 @@ function runStats(opts = {}) {
5830
5942
  // src/cli_doctor.ts
5831
5943
  import * as fs12 from "fs";
5832
5944
  import * as os8 from "os";
5833
- import * as path11 from "path";
5945
+ import * as path12 from "path";
5834
5946
  import { execSync, spawnSync as spawnSync2 } from "child_process";
5835
5947
  function globalMcpConfigPath() {
5836
5948
  const copilotHome = process.env["COPILOT_HOME"];
5837
- const root = copilotHome !== void 0 && copilotHome.trim() !== "" ? path11.resolve(copilotHome) : path11.join(os8.homedir(), ".copilot");
5838
- return path11.join(root, "mcp-config.json");
5949
+ const root = copilotHome !== void 0 && copilotHome.trim() !== "" ? path12.resolve(copilotHome) : path12.join(os8.homedir(), ".copilot");
5950
+ return path12.join(root, "mcp-config.json");
5839
5951
  }
5840
5952
  function checkGlobalMcpConfig(configPath2 = globalMcpConfigPath()) {
5841
5953
  if (!fs12.existsSync(configPath2)) {
@@ -5952,7 +6064,7 @@ function checkWorkerRunning(dataDir2) {
5952
6064
  }
5953
6065
  var DB_SIZE_WARN_BYTES = 1024 * 1024 * 1024;
5954
6066
  function checkDbExists(dataDir2) {
5955
- const dbPath = path11.join(dataDir2, "global.db");
6067
+ const dbPath = path12.join(dataDir2, "global.db");
5956
6068
  if (!fs12.existsSync(dbPath)) {
5957
6069
  return {
5958
6070
  name: "Database",
@@ -6027,6 +6139,42 @@ function checkSymbolCount(dbPath, rootDir) {
6027
6139
  };
6028
6140
  }
6029
6141
  }
6142
+ var EMBED_COVERAGE_WARN_FRACTION = 0.25;
6143
+ function checkEmbeddingCoverage(dbPath, rootDir) {
6144
+ if (!fs12.existsSync(dbPath)) {
6145
+ return { name: "Embedding coverage", status: "ok", message: "no database yet" };
6146
+ }
6147
+ const cfg = loadConfig();
6148
+ if (!cfg.indexing.embeddings_enabled) {
6149
+ return { name: "Embedding coverage", status: "ok", message: "disabled (indexing.embeddings_enabled = false)" };
6150
+ }
6151
+ try {
6152
+ const { indexedFiles, embeddedFiles } = getEmbeddingCoverage(dbPath, rootDir);
6153
+ if (indexedFiles === 0) {
6154
+ return { name: "Embedding coverage", status: "ok", message: "no indexed files yet" };
6155
+ }
6156
+ const pct2 = Math.round(embeddedFiles / indexedFiles * 100);
6157
+ const sizeKb = cfg.indexing.large_file_symbol_only_kb;
6158
+ if (embeddedFiles / indexedFiles < EMBED_COVERAGE_WARN_FRACTION) {
6159
+ return {
6160
+ name: "Embedding coverage",
6161
+ status: "warn",
6162
+ message: `only ${embeddedFiles} of ${indexedFiles} indexed file(s) (${pct2}%) have embeddings \u2014 'semantic' searches those files only, and reports finding nothing in the same words it uses after searching everything. Files over indexing.large_file_symbol_only_kb (currently ${sizeKb} KB) are indexed for symbols only and are the usual reason; raise it with 'token-goat config set indexing.large_file_symbol_only_kb <KB>' and re-embed with 'token-goat index --force' to widen coverage. Exact symbol lookups are unaffected`
6163
+ };
6164
+ }
6165
+ return {
6166
+ name: "Embedding coverage",
6167
+ status: "ok",
6168
+ message: `${embeddedFiles} of ${indexedFiles} indexed file(s) (${pct2}%) have embeddings`
6169
+ };
6170
+ } catch (err2) {
6171
+ return {
6172
+ name: "Embedding coverage",
6173
+ status: "warn",
6174
+ message: `could not query embedding coverage: ${extractErrorMessage(err2)}`
6175
+ };
6176
+ }
6177
+ }
6030
6178
  var DIRTY_QUEUE_BACKLOG_WARN_THRESHOLD = 500;
6031
6179
  function checkDirtyQueueHealth(dataDir2) {
6032
6180
  let pendingCount = 0;
@@ -6387,14 +6535,15 @@ function runDoctor(dataDir2, configPath2, rootDir, processes) {
6387
6535
  results.push(checkStrayClaudeMdBlocks());
6388
6536
  results.push(checkWorkerRunning(actualDataDir) ? { name: "Worker", status: "ok", message: "running" } : { name: "Worker", status: "warn", message: "not running" });
6389
6537
  results.push(checkDbExists(actualDataDir));
6390
- results.push(checkSymbolBodySize(path11.join(actualDataDir, "global.db")));
6391
- results.push(checkSymbolCount(path11.join(actualDataDir, "global.db"), rootDir));
6538
+ results.push(checkSymbolBodySize(path12.join(actualDataDir, "global.db")));
6539
+ results.push(checkSymbolCount(path12.join(actualDataDir, "global.db"), rootDir));
6392
6540
  results.push(checkDirtyQueueHealth(actualDataDir));
6393
- results.push(checkCompactionChannel(path11.join(actualDataDir, "global.db")));
6394
- results.push(checkUnmappedTools(path11.join(actualDataDir, "global.db")));
6541
+ results.push(checkCompactionChannel(path12.join(actualDataDir, "global.db")));
6542
+ results.push(checkUnmappedTools(path12.join(actualDataDir, "global.db")));
6395
6543
  const actualConfigPath = configPath2 || configPath();
6396
6544
  results.push(checkConfigValid(actualConfigPath));
6397
6545
  results.push(checkEmbeddings(loadConfig(rootDir)));
6546
+ results.push(checkEmbeddingCoverage(path12.join(actualDataDir, "global.db"), rootDir));
6398
6547
  for (const result of checkSecurityPosture(loadConfig(rootDir), actualDataDir)) results.push(result);
6399
6548
  results.push(checkDiskSpace(actualDataDir));
6400
6549
  const copilotResult = checkCopilotCli(copilotCliConfigPath(), copilotCliScriptPath());
@@ -6435,7 +6584,7 @@ async function runDoctorAndExit(opts) {
6435
6584
  console.log();
6436
6585
  try {
6437
6586
  const dir = skillOutputsDir();
6438
- const pregenPath = path11.join(dir, "pregen.json");
6587
+ const pregenPath = path12.join(dir, "pregen.json");
6439
6588
  if (fs12.existsSync(pregenPath)) {
6440
6589
  const content = JSON.parse(fs12.readFileSync(pregenPath, "utf-8"));
6441
6590
  const pregenNames = new Set(content.names || []);
@@ -6445,7 +6594,7 @@ async function runDoctorAndExit(opts) {
6445
6594
  for (const entry of entries) {
6446
6595
  if (!entry.isFile() || !entry.name.endsWith(".meta")) continue;
6447
6596
  try {
6448
- const meta = JSON.parse(fs12.readFileSync(path11.join(dir, entry.name), "utf-8"));
6597
+ const meta = JSON.parse(fs12.readFileSync(path12.join(dir, entry.name), "utf-8"));
6449
6598
  if (meta.skillName && !pregenNames.has(meta.skillName)) {
6450
6599
  skillsSeen.add(meta.skillName);
6451
6600
  }
@@ -6472,7 +6621,7 @@ import { existsSync as existsSync7, readdirSync as readdirSync5, statSync as sta
6472
6621
  import * as http from "http";
6473
6622
  import * as https from "https";
6474
6623
  import { isIPv4, isIPv6 } from "net";
6475
- import { resolve as resolve4, join as join10 } from "path";
6624
+ import { resolve as resolve5, join as join10 } from "path";
6476
6625
  import { URL as URL2 } from "url";
6477
6626
  import { promisify } from "util";
6478
6627
  import { lookup as dnsLookup } from "dns";
@@ -6661,7 +6810,7 @@ function cleanupStaleDownloads() {
6661
6810
  const files = readdirSync5(cacheDir);
6662
6811
  for (const file of files) {
6663
6812
  if (file.endsWith(".tmp")) {
6664
- const filePath = resolve4(cacheDir, file);
6813
+ const filePath = resolve5(cacheDir, file);
6665
6814
  try {
6666
6815
  const stat = statSync4(filePath);
6667
6816
  if (Date.now() - stat.mtimeMs < STALE_DOWNLOAD_AGE_MS) continue;
@@ -6927,7 +7076,7 @@ async function getSectionContent(fileId, heading, opts = {}) {
6927
7076
 
6928
7077
  // src/pack.ts
6929
7078
  import * as fs13 from "node:fs";
6930
- import * as path13 from "node:path";
7079
+ import * as path14 from "node:path";
6931
7080
 
6932
7081
  // node_modules/minimatch/dist/esm/index.js
6933
7082
  var esm_exports = {};
@@ -8051,11 +8200,11 @@ var qmarksTestNoExtDot = ([$0]) => {
8051
8200
  return (f) => f.length === len && f !== "." && f !== "..";
8052
8201
  };
8053
8202
  var defaultPlatform = typeof process === "object" && process ? typeof process.env === "object" && process.env && process.env.__MINIMATCH_TESTING_PLATFORM__ || process.platform : "posix";
8054
- var path12 = {
8203
+ var path13 = {
8055
8204
  win32: { sep: "\\" },
8056
8205
  posix: { sep: "/" }
8057
8206
  };
8058
- var sep2 = defaultPlatform === "win32" ? path12.win32.sep : path12.posix.sep;
8207
+ var sep2 = defaultPlatform === "win32" ? path13.win32.sep : path13.posix.sep;
8059
8208
  minimatch.sep = sep2;
8060
8209
  var GLOBSTAR = /* @__PURE__ */ Symbol("globstar **");
8061
8210
  minimatch.GLOBSTAR = GLOBSTAR;
@@ -8840,7 +8989,7 @@ var LANG_MAP = {
8840
8989
  ".exs": "elixir"
8841
8990
  };
8842
8991
  function getLang(filePath) {
8843
- const ext2 = path13.extname(filePath).toLowerCase();
8992
+ const ext2 = path14.extname(filePath).toLowerCase();
8844
8993
  return LANG_MAP[ext2] ?? "";
8845
8994
  }
8846
8995
  function matches(rel, patterns) {
@@ -8850,8 +8999,8 @@ function matches(rel, patterns) {
8850
8999
  return patterns.some((pat) => mm.minimatch(norm, pat) || mm.minimatch(base, pat));
8851
9000
  }
8852
9001
  function isPathWithinRoot(rootReal, resolvedPath) {
8853
- const rel = path13.relative(rootReal, resolvedPath);
8854
- return rel !== "" && rel !== ".." && !rel.startsWith(".." + path13.sep) && !path13.isAbsolute(rel);
9002
+ const rel = path14.relative(rootReal, resolvedPath);
9003
+ return rel !== "" && rel !== ".." && !rel.startsWith(".." + path14.sep) && !path14.isAbsolute(rel);
8855
9004
  }
8856
9005
  function openWithinRoot(rootReal, p) {
8857
9006
  let fd;
@@ -8891,11 +9040,11 @@ function openWithinRoot(rootReal, p) {
8891
9040
  }
8892
9041
  function* resolveOpenCandidates(projectRoot, patterns, rootReal, ignorePatterns, seen, skipped) {
8893
9042
  for (const pattern of patterns) {
8894
- const p = path13.isAbsolute(pattern) ? pattern : path13.join(projectRoot, pattern);
9043
+ const p = path14.isAbsolute(pattern) ? pattern : path14.join(projectRoot, pattern);
8895
9044
  if (seen.has(p)) continue;
8896
9045
  let rel;
8897
9046
  try {
8898
- rel = path13.relative(projectRoot, p).replace(/\\/g, "/");
9047
+ rel = path14.relative(projectRoot, p).replace(/\\/g, "/");
8899
9048
  } catch {
8900
9049
  skipped.push(`${p} (outside project root)`);
8901
9050
  continue;
@@ -8969,7 +9118,7 @@ function stripBlockComments(content, pattern, lineStates) {
8969
9118
  );
8970
9119
  }
8971
9120
  function stripComments(content, filePath) {
8972
- const ext2 = path13.extname(filePath).toLowerCase();
9121
+ const ext2 = path14.extname(filePath).toLowerCase();
8973
9122
  const lineStates = computeLineStartQuoteStates(content);
8974
9123
  if (ext2 === ".py") {
8975
9124
  return stripLineComments(content, PY_LINE_COMMENT_RE, lineStates);
@@ -9007,7 +9156,7 @@ var SAFE_EXTS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp
9007
9156
  function scanSecrets(files) {
9008
9157
  const hits = [];
9009
9158
  for (const pf of files) {
9010
- if (SAFE_EXTS.has(path13.extname(pf.path).toLowerCase())) {
9159
+ if (SAFE_EXTS.has(path14.extname(pf.path).toLowerCase())) {
9011
9160
  continue;
9012
9161
  }
9013
9162
  for (const [lineno, line] of pf.content.split("\n").entries()) {
@@ -9030,7 +9179,7 @@ function scanSecrets(files) {
9030
9179
  function collectFiles(projectRoot, patterns, opts = {}) {
9031
9180
  const result = { files: [], skipped: [], total_lines: 0, total_tokens: 0 };
9032
9181
  const seen = /* @__PURE__ */ new Set();
9033
- const rootResolved = path13.resolve(projectRoot);
9182
+ const rootResolved = path14.resolve(projectRoot);
9034
9183
  let rootReal;
9035
9184
  try {
9036
9185
  rootReal = fs13.realpathSync(rootResolved);
@@ -9205,7 +9354,7 @@ function formatPack(result, style, opts = {}) {
9205
9354
  function estimateBudget(projectRoot, patterns, opts = {}) {
9206
9355
  const result = { entries: [], skipped: [], total_lines: 0, total_tokens: 0 };
9207
9356
  const seen = /* @__PURE__ */ new Set();
9208
- const rootResolved = path13.resolve(projectRoot);
9357
+ const rootResolved = path14.resolve(projectRoot);
9209
9358
  let rootReal;
9210
9359
  try {
9211
9360
  rootReal = fs13.realpathSync(rootResolved);
@@ -9702,7 +9851,7 @@ function formatFailureDeltaJson(delta, runner) {
9702
9851
 
9703
9852
  // src/failures_state.ts
9704
9853
  import * as fs14 from "node:fs";
9705
- import * as path14 from "node:path";
9854
+ import * as path15 from "node:path";
9706
9855
  var KEY_RE = /^[A-Za-z0-9_-]{1,80}$/;
9707
9856
  var DEFAULT_FAILURES_STATE_KEY = "default";
9708
9857
  function validateKey(key) {
@@ -9714,7 +9863,7 @@ function validateKey(key) {
9714
9863
  }
9715
9864
  function failuresStatePath(projectHash, key) {
9716
9865
  validateKey(key);
9717
- return path14.join(dataDir(), "projects", `${projectHash}_failures_${key}.json`);
9866
+ return path15.join(dataDir(), "projects", `${projectHash}_failures_${key}.json`);
9718
9867
  }
9719
9868
  function loadFailureSnapshot(projectHash, key) {
9720
9869
  const p = failuresStatePath(projectHash, key);
@@ -9735,7 +9884,7 @@ function loadFailureSnapshot(projectHash, key) {
9735
9884
  }
9736
9885
  function saveFailureSnapshot(projectHash, key, snapshot) {
9737
9886
  const p = failuresStatePath(projectHash, key);
9738
- const dir = path14.dirname(p);
9887
+ const dir = path15.dirname(p);
9739
9888
  ensureDirSync(dir);
9740
9889
  const content = JSON.stringify(snapshot);
9741
9890
  const doSave = () => {
@@ -9747,18 +9896,18 @@ function saveFailureSnapshot(projectHash, key, snapshot) {
9747
9896
 
9748
9897
  // src/text_commands.ts
9749
9898
  import * as fs16 from "fs";
9750
- import * as path16 from "path";
9899
+ import * as path17 from "path";
9751
9900
 
9752
9901
  // src/project_memory.ts
9753
9902
  import * as fs15 from "node:fs";
9754
- import * as path15 from "node:path";
9903
+ import * as path16 from "node:path";
9755
9904
  var MAX_ENTRIES = 30;
9756
9905
  var KEY_RE2 = /^[A-Za-z0-9_-]{1,80}$/;
9757
9906
  function ordinal(a, b) {
9758
9907
  return a < b ? -1 : a > b ? 1 : 0;
9759
9908
  }
9760
9909
  function memoryPath(projectHash) {
9761
- return path15.join(dataDir(), "projects", `${projectHash}_memory.toml`);
9910
+ return path16.join(dataDir(), "projects", `${projectHash}_memory.toml`);
9762
9911
  }
9763
9912
  function validateKey2(key) {
9764
9913
  if (!KEY_RE2.test(key)) {
@@ -9817,7 +9966,7 @@ function save(filePath, entries) {
9817
9966
  lines.push(`${k} = "${escaped}"`);
9818
9967
  }
9819
9968
  const content = lines.length > 0 ? lines.join("\n") + "\n" : "";
9820
- const dir = path15.dirname(filePath);
9969
+ const dir = path16.dirname(filePath);
9821
9970
  ensureDirSync(dir);
9822
9971
  atomicWriteText(filePath, content);
9823
9972
  }
@@ -9827,7 +9976,7 @@ function loadEntries(projectHash) {
9827
9976
  function setEntry(projectHash, key, value) {
9828
9977
  validateKey2(key);
9829
9978
  const p = memoryPath(projectHash);
9830
- const dir = path15.dirname(p);
9979
+ const dir = path16.dirname(p);
9831
9980
  ensureDirSync(dir);
9832
9981
  const doSet = () => {
9833
9982
  const entries = loadRaw(p);
@@ -9925,7 +10074,7 @@ function collectTodoFiles(patterns) {
9925
10074
  if (patterns.length > 0) {
9926
10075
  const results = [];
9927
10076
  for (const p of patterns) {
9928
- const abs = path16.resolve(p);
10077
+ const abs = path17.resolve(p);
9929
10078
  try {
9930
10079
  const stat = fs16.statSync(abs);
9931
10080
  if (stat.isDirectory()) {
@@ -10196,7 +10345,7 @@ function isProjectFrame(framePath, cwd) {
10196
10345
  if (framePath.includes("/rustc/")) return false;
10197
10346
  if (framePath.includes(".cargo/registry") || framePath.includes(".cargo\\registry")) return false;
10198
10347
  if (/^<.+>$/.test(framePath)) return false;
10199
- if (!path16.isAbsolute(framePath) && !framePath.startsWith("..")) return true;
10348
+ if (!path17.isAbsolute(framePath) && !framePath.startsWith("..")) return true;
10200
10349
  return false;
10201
10350
  }
10202
10351
  function resolveFrameSymbol(frame, projectRoot) {
@@ -10366,14 +10515,23 @@ function applyFiltersAndFold(lines, noNormalize, foldRepeats) {
10366
10515
  }
10367
10516
  function cmdLogfold(src, opts) {
10368
10517
  const text = readInput(src);
10369
- let lines = splitLines(text);
10518
+ const rawLines = splitLines(text);
10519
+ const allLines = rawLines.length > 1 && rawLines[rawLines.length - 1] === "" ? rawLines.slice(0, -1) : rawLines;
10520
+ let lines = allLines;
10370
10521
  if (opts.tail !== void 0) {
10371
10522
  const n = requireNonNegativeStrictInt("--tail", opts.tail);
10372
10523
  lines = lines.slice(Math.max(0, lines.length - n));
10373
10524
  }
10525
+ const inputLines = allLines.length;
10526
+ const shownLines = lines.length;
10527
+ const truncated = shownLines < inputLines;
10374
10528
  const folded = applyFiltersAndFold(lines, opts.noNormalize === true, opts.foldRepeats === true);
10529
+ if (truncated) {
10530
+ process.stderr.write(`Showing last ${shownLines} of ${inputLines} lines (raise --tail to see more).
10531
+ `);
10532
+ }
10375
10533
  if (opts.json === true) {
10376
- process.stdout.write(JSON.stringify({ lines: folded }, null, 2) + "\n");
10534
+ process.stdout.write(JSON.stringify({ lines: folded, truncated, inputLines, shownLines }, null, 2) + "\n");
10377
10535
  return;
10378
10536
  }
10379
10537
  for (const item of folded) {
@@ -10400,17 +10558,17 @@ function findLockfile(startPath) {
10400
10558
  if (stat !== void 0 && stat.isFile()) {
10401
10559
  return { file: startPath, others: [] };
10402
10560
  }
10403
- const dir = stat !== void 0 && stat.isDirectory() ? startPath : path16.dirname(startPath);
10561
+ const dir = stat !== void 0 && stat.isDirectory() ? startPath : path17.dirname(startPath);
10404
10562
  const found = [];
10405
10563
  for (const name of LOCK_PRIORITY) {
10406
- const candidate = path16.join(dir, name);
10564
+ const candidate = path17.join(dir, name);
10407
10565
  if (fs16.existsSync(candidate)) found.push(candidate);
10408
10566
  }
10409
10567
  try {
10410
10568
  const entries = fs16.readdirSync(dir);
10411
10569
  for (const e of entries) {
10412
10570
  if (/^requirements.*\.txt$/.test(e)) {
10413
- const p = path16.join(dir, e);
10571
+ const p = path17.join(dir, e);
10414
10572
  if (!found.includes(p)) found.push(p);
10415
10573
  }
10416
10574
  }
@@ -10607,7 +10765,7 @@ function parsePnpmLock(content) {
10607
10765
  return deps;
10608
10766
  }
10609
10767
  function parseLockFile(filePath) {
10610
- const base = path16.basename(filePath);
10768
+ const base = path17.basename(filePath);
10611
10769
  const content = fs16.readFileSync(filePath, "utf8");
10612
10770
  if (base === "package-lock.json") return { format: "npm", deps: parsePackageLockJson(content) };
10613
10771
  if (base === "yarn.lock") return { format: "yarn", deps: parseYarnLock(content) };
@@ -10723,7 +10881,7 @@ Depended on by direct/top-level deps (${dependedOnBy.length}):
10723
10881
  `);
10724
10882
  }
10725
10883
  function cmdLockdeps(filePath, opts) {
10726
- const target = filePath !== void 0 ? path16.resolve(filePath) : process.cwd();
10884
+ const target = filePath !== void 0 ? path17.resolve(filePath) : process.cwd();
10727
10885
  const found = findLockfile(target);
10728
10886
  if (found === null) {
10729
10887
  throw new Error("No lockfile found. Expected one of: " + LOCK_PRIORITY.join(", ") + ", requirements*.txt");
@@ -10811,7 +10969,7 @@ function cmdNote(action, key, value, opts) {
10811
10969
  throw new Error(`Unknown note action: ${action}. Use: set, get, unset, list, clear`);
10812
10970
  }
10813
10971
  function loadAllSessionReadCounts() {
10814
- const sessionsDir = path16.join(tokenGoatHome(), "sessions");
10972
+ const sessionsDir = path17.join(tokenGoatHome(), "sessions");
10815
10973
  const totals = /* @__PURE__ */ new Map();
10816
10974
  let entries;
10817
10975
  try {
@@ -10821,7 +10979,7 @@ function loadAllSessionReadCounts() {
10821
10979
  }
10822
10980
  for (const entry of entries) {
10823
10981
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
10824
- const filePath = path16.join(sessionsDir, entry.name);
10982
+ const filePath = path17.join(sessionsDir, entry.name);
10825
10983
  let raw;
10826
10984
  try {
10827
10985
  raw = JSON.parse(fs16.readFileSync(filePath, "utf8"));
@@ -10859,9 +11017,11 @@ function cmdHot(opts) {
10859
11017
  }
10860
11018
  }
10861
11019
  entries.sort((a, b) => b.readCount - a.readCount);
11020
+ const eligibleCount = entries.length;
10862
11021
  entries = entries.slice(0, limit);
11022
+ const truncated = entries.length < eligibleCount;
10863
11023
  if (opts.json === true) {
10864
- process.stdout.write(JSON.stringify({ entries }, null, 2) + "\n");
11024
+ process.stdout.write(JSON.stringify({ entries, truncated, totalCount: eligibleCount }, null, 2) + "\n");
10865
11025
  return;
10866
11026
  }
10867
11027
  if (entries.length === 0) {
@@ -10877,6 +11037,10 @@ function cmdHot(opts) {
10877
11037
  const hotDisplayRoot = getDisplayRoot();
10878
11038
  for (const e of entries) {
10879
11039
  process.stdout.write(`${e.readCount} ${toDisplayPath(hotDisplayRoot, e.path)}
11040
+ `);
11041
+ }
11042
+ if (truncated) {
11043
+ process.stdout.write(`...and ${eligibleCount - entries.length} more (raise --limit to see them).
10880
11044
  `);
10881
11045
  }
10882
11046
  }
@@ -10951,7 +11115,7 @@ function cmdIgnores(opts) {
10951
11115
  }
10952
11116
  function countPackIgnorePatterns(root) {
10953
11117
  try {
10954
- return fs16.readFileSync(path16.join(root, ".tokengoatignore"), "utf8").split("\n").map((ln) => ln.trim()).filter((ln) => ln.length > 0 && !ln.startsWith("#")).length;
11118
+ return fs16.readFileSync(path17.join(root, ".tokengoatignore"), "utf8").split("\n").map((ln) => ln.trim()).filter((ln) => ln.length > 0 && !ln.startsWith("#")).length;
10955
11119
  } catch {
10956
11120
  return 0;
10957
11121
  }
@@ -10960,7 +11124,7 @@ function countPackIgnorePatterns(root) {
10960
11124
  // src/dep_docs.ts
10961
11125
  import { createRequire } from "node:module";
10962
11126
  import * as fs17 from "node:fs";
10963
- import * as path17 from "node:path";
11127
+ import * as path18 from "node:path";
10964
11128
  var _require = createRequire(import.meta.url);
10965
11129
  var _ts = null;
10966
11130
  var _tsLoadAttempted = false;
@@ -11011,7 +11175,7 @@ function listInstalledPackageNames(nodeModulesDir) {
11011
11175
  if (entry.name.startsWith("@")) {
11012
11176
  let scoped;
11013
11177
  try {
11014
- scoped = fs17.readdirSync(path17.join(nodeModulesDir, entry.name), { withFileTypes: true });
11178
+ scoped = fs17.readdirSync(path18.join(nodeModulesDir, entry.name), { withFileTypes: true });
11015
11179
  } catch {
11016
11180
  continue;
11017
11181
  }
@@ -11028,7 +11192,7 @@ var README_NAME_RE = /^readme(\.(md|markdown|txt))?$/i;
11028
11192
  function findReadmeFile(pkgDir) {
11029
11193
  const direct = findReadmeIn(pkgDir);
11030
11194
  if (direct !== null) return direct;
11031
- return findReadmeIn(path17.join(pkgDir, "docs"));
11195
+ return findReadmeIn(path18.join(pkgDir, "docs"));
11032
11196
  }
11033
11197
  function findReadmeIn(dir) {
11034
11198
  let entries;
@@ -11045,7 +11209,7 @@ function findReadmeIn(dir) {
11045
11209
  return 1;
11046
11210
  };
11047
11211
  matches2.sort((a, b) => rank(a.name) - rank(b.name));
11048
- return path17.join(dir, matches2[0].name);
11212
+ return path18.join(dir, matches2[0].name);
11049
11213
  }
11050
11214
  function typesPackageDirName(pkgName) {
11051
11215
  if (pkgName.startsWith("@")) {
@@ -11072,30 +11236,30 @@ function resolveTypesLocation(pkgDir, pkgJson, nodeModulesDir, pkgName) {
11072
11236
  if (declared !== void 0) {
11073
11237
  const candidates = [declared, declared.endsWith(".d.ts") ? declared : `${declared}.d.ts`, declared.replace(/\.[cm]?[jt]s$/, ".d.ts")];
11074
11238
  for (const c of candidates) {
11075
- const p = path17.join(pkgDir, c);
11239
+ const p = path18.join(pkgDir, c);
11076
11240
  if (fileExists(p)) return { path: p, source: "bundled" };
11077
11241
  }
11078
11242
  }
11079
11243
  const main = typeof pkgJson["main"] === "string" ? pkgJson["main"] : "index.js";
11080
- const mainDts = path17.join(pkgDir, main.replace(/\.[cm]?js$/, ".d.ts"));
11244
+ const mainDts = path18.join(pkgDir, main.replace(/\.[cm]?js$/, ".d.ts"));
11081
11245
  if (fileExists(mainDts)) return { path: mainDts, source: "bundled" };
11082
- const indexDts = path17.join(pkgDir, "index.d.ts");
11246
+ const indexDts = path18.join(pkgDir, "index.d.ts");
11083
11247
  if (fileExists(indexDts)) return { path: indexDts, source: "bundled" };
11084
11248
  const typesDirName = typesPackageDirName(pkgName);
11085
- const typesPkgDir = path17.join(nodeModulesDir, "@types", typesDirName);
11086
- const typesPkgJsonRaw = readFileTextOrNull(path17.join(typesPkgDir, "package.json"));
11249
+ const typesPkgDir = path18.join(nodeModulesDir, "@types", typesDirName);
11250
+ const typesPkgJsonRaw = readFileTextOrNull(path18.join(typesPkgDir, "package.json"));
11087
11251
  if (typesPkgJsonRaw !== null) {
11088
11252
  try {
11089
11253
  const typesPkgJson = JSON.parse(typesPkgJsonRaw);
11090
11254
  const entry = (typeof typesPkgJson["types"] === "string" ? typesPkgJson["types"] : void 0) ?? (typeof typesPkgJson["main"] === "string" ? typesPkgJson["main"] : void 0) ?? "index.d.ts";
11091
11255
  const entryCandidates = [entry, entry.endsWith(".d.ts") ? entry : `${entry}.d.ts`, entry.replace(/\.[cm]?[jt]s$/, ".d.ts")];
11092
11256
  for (const c of entryCandidates) {
11093
- const entryPath = path17.join(typesPkgDir, c);
11257
+ const entryPath = path18.join(typesPkgDir, c);
11094
11258
  if (fileExists(entryPath)) return { path: entryPath, source: "@types" };
11095
11259
  }
11096
11260
  } catch {
11097
11261
  }
11098
- const fallback = path17.join(typesPkgDir, "index.d.ts");
11262
+ const fallback = path18.join(typesPkgDir, "index.d.ts");
11099
11263
  if (fileExists(fallback)) return { path: fallback, source: "@types" };
11100
11264
  }
11101
11265
  return null;
@@ -11162,9 +11326,9 @@ function recordDepDocsStat(fullSourceBytes, emittedText, packageName) {
11162
11326
  }
11163
11327
  function runDepDocs(opts) {
11164
11328
  const root = resolveProjectRoot({ project: opts.projectRoot ?? process.cwd() });
11165
- const nodeModulesDir = path17.join(root, "node_modules");
11166
- const pkgDir = path17.join(nodeModulesDir, opts.packageName);
11167
- const pkgJsonPath = path17.join(pkgDir, "package.json");
11329
+ const nodeModulesDir = path18.join(root, "node_modules");
11330
+ const pkgDir = path18.join(nodeModulesDir, opts.packageName);
11331
+ const pkgJsonPath = path18.join(pkgDir, "package.json");
11168
11332
  if (!dirExists(pkgDir) || !fileExists(pkgJsonPath)) {
11169
11333
  const known = listInstalledPackageNames(nodeModulesDir);
11170
11334
  const suggestions = suggestPackageNames(opts.packageName, known);
@@ -11327,20 +11491,27 @@ async function buildResumePacket(sessionId) {
11327
11491
  }
11328
11492
 
11329
11493
  // src/cache_session_commands.ts
11330
- function emitErr(text) {
11494
+ function emitErr2(text) {
11331
11495
  process.stderr.write(ensureNewline(text));
11332
11496
  }
11497
+ function capAndNote(rows, limit) {
11498
+ const shown = rows.slice(0, limit);
11499
+ if (shown.length < rows.length) {
11500
+ emitErr2(`Showing ${shown.length} of ${rows.length} entries (raise --limit to see the rest).`);
11501
+ }
11502
+ return shown;
11503
+ }
11333
11504
  function parseLimitOpt(cmdName, limitStr, dflt = 30) {
11334
11505
  if (limitStr === void 0) return dflt;
11335
11506
  let n;
11336
11507
  try {
11337
11508
  n = requireNonNegativeStrictInt("--limit", limitStr);
11338
11509
  } catch (e) {
11339
- emitErr(`${cmdName}: --limit must be a positive number, got: "${limitStr}"`);
11510
+ emitErr2(`${cmdName}: --limit must be a positive number, got: "${limitStr}"`);
11340
11511
  throw new Error(`invalid --limit: ${limitStr}`, { cause: e });
11341
11512
  }
11342
11513
  if (n === 0) {
11343
- emitErr(`${cmdName}: --limit must be a positive number, got: "${limitStr}"`);
11514
+ emitErr2(`${cmdName}: --limit must be a positive number, got: "${limitStr}"`);
11344
11515
  throw new Error(`invalid --limit: ${limitStr}`);
11345
11516
  }
11346
11517
  return n;
@@ -11369,7 +11540,7 @@ function getNewestSessionFiles() {
11369
11540
  function cmdBashHistory(opts) {
11370
11541
  const limit = parseLimitOpt("bash-history", opts.limit);
11371
11542
  const blobs = listBlobs(BASH_OUTPUT_SUBDIR);
11372
- const items = blobs.map(({ id, mtime, value }) => {
11543
+ const allItems = blobs.map(({ id, mtime, value }) => {
11373
11544
  if (typeof value !== "object" || value === null) return null;
11374
11545
  const v = value;
11375
11546
  return {
@@ -11379,7 +11550,8 @@ function cmdBashHistory(opts) {
11379
11550
  exitCode: typeof v["exitCode"] === "number" ? v["exitCode"] : -1,
11380
11551
  sizeBytes: typeof v["sizeBytes"] === "number" ? v["sizeBytes"] : 0
11381
11552
  };
11382
- }).filter((x) => x !== null).sort((a, b) => b.storedAt - a.storedAt).slice(0, limit);
11553
+ }).filter((x) => x !== null).sort((a, b) => b.storedAt - a.storedAt);
11554
+ const items = capAndNote(allItems, limit);
11383
11555
  if (opts.json === true) {
11384
11556
  process.stdout.write(JSON.stringify(items, null, 2) + "\n");
11385
11557
  return;
@@ -11400,7 +11572,7 @@ function cmdBashHistory(opts) {
11400
11572
  function cmdWebHistory(opts) {
11401
11573
  const limit = parseLimitOpt("web-history", opts.limit);
11402
11574
  const blobs = listBlobs(WEB_OUTPUT_SUBDIR);
11403
- const items = blobs.map(({ id, mtime, value }) => {
11575
+ const allItems = blobs.map(({ id, mtime, value }) => {
11404
11576
  if (typeof value !== "object" || value === null) return null;
11405
11577
  const v = value;
11406
11578
  return {
@@ -11409,7 +11581,8 @@ function cmdWebHistory(opts) {
11409
11581
  bytes: typeof v["content"] === "string" ? Buffer.byteLength(v["content"], "utf8") : 0,
11410
11582
  storedAt: mtime
11411
11583
  };
11412
- }).filter((x) => x !== null).sort((a, b) => b.storedAt - a.storedAt).slice(0, limit);
11584
+ }).filter((x) => x !== null).sort((a, b) => b.storedAt - a.storedAt);
11585
+ const items = capAndNote(allItems, limit);
11413
11586
  if (opts.json === true) {
11414
11587
  process.stdout.write(JSON.stringify(items, null, 2) + "\n");
11415
11588
  return;
@@ -11428,7 +11601,7 @@ function cmdWebHistory(opts) {
11428
11601
  function cmdMcpHistory(opts) {
11429
11602
  const limit = parseLimitOpt("mcp-history", opts.limit);
11430
11603
  const blobs = listBlobs(BASH_OUTPUT_SUBDIR).filter((b) => b.id.startsWith("mcp_"));
11431
- const items = blobs.map(({ id, mtime, value }) => {
11604
+ const allItems = blobs.map(({ id, mtime, value }) => {
11432
11605
  if (typeof value !== "object" || value === null) return null;
11433
11606
  const v = value;
11434
11607
  const command = typeof v["command"] === "string" ? v["command"] : "";
@@ -11439,7 +11612,8 @@ function cmdMcpHistory(opts) {
11439
11612
  storedAt: typeof v["storedAt"] === "number" ? v["storedAt"] : mtime,
11440
11613
  sizeBytes: typeof v["sizeBytes"] === "number" ? v["sizeBytes"] : 0
11441
11614
  };
11442
- }).filter((x) => x !== null).sort((a, b) => b.storedAt - a.storedAt).slice(0, limit);
11615
+ }).filter((x) => x !== null).sort((a, b) => b.storedAt - a.storedAt);
11616
+ const items = capAndNote(allItems, limit);
11443
11617
  if (opts.json === true) {
11444
11618
  process.stdout.write(JSON.stringify(items, null, 2) + "\n");
11445
11619
  return;
@@ -11671,7 +11845,7 @@ function cmdBaseline(opts) {
11671
11845
 
11672
11846
  // src/index_reclaim.ts
11673
11847
  import * as fs18 from "node:fs";
11674
- import * as path18 from "node:path";
11848
+ import * as path19 from "node:path";
11675
11849
  var DERIVED_TABLES = ["chunk_vectors", "chunks", "refs", "symbols", "files"];
11676
11850
  function indexSizeBytes(dbPath) {
11677
11851
  let total = 0;
@@ -11755,7 +11929,7 @@ function mb(bytes) {
11755
11929
  }
11756
11930
  function cmdReclaimIndex(opts) {
11757
11931
  const dbPath = opts.dbPath ?? globalDbPath();
11758
- if (opts.force !== true && isWorkerRunning(path18.dirname(dbPath))) {
11932
+ if (opts.force !== true && isWorkerRunning(path19.dirname(dbPath))) {
11759
11933
  throw new Error(
11760
11934
  "reclaim-index: the worker daemon is running and writing to this index. Stop it first with 'token-goat worker stop', then re-run. Pass --force to proceed anyway (results may be inaccurate if the worker is really live)."
11761
11935
  );
@@ -11789,7 +11963,7 @@ function cmdReclaimIndex(opts) {
11789
11963
  }
11790
11964
  if (result.checkpointBusy) {
11791
11965
  process.stdout.write(
11792
- ` note: a concurrent reader blocked WAL truncation, so some space is still held in ${path18.basename(dbPath)}-wal and the figures above count the main file only. The next checkpoint releases it
11966
+ ` note: a concurrent reader blocked WAL truncation, so some space is still held in ${path19.basename(dbPath)}-wal and the figures above count the main file only. The next checkpoint releases it
11793
11967
  `
11794
11968
  );
11795
11969
  }
@@ -11798,16 +11972,16 @@ function cmdReclaimIndex(opts) {
11798
11972
  // src/config_commands.ts
11799
11973
  import * as fs19 from "node:fs";
11800
11974
  import * as os9 from "node:os";
11801
- import * as path19 from "node:path";
11802
- function emit(text) {
11975
+ import * as path20 from "node:path";
11976
+ function emit2(text) {
11803
11977
  const payload = colorStdout() ? text : stripAnsi(text);
11804
11978
  process.stdout.write(ensureNewline(payload));
11805
11979
  }
11806
- function emitErr2(text) {
11980
+ function emitErr3(text) {
11807
11981
  process.stderr.write(ensureNewline(text));
11808
11982
  }
11809
11983
  function saveConfigSafe(cfg) {
11810
- ensureDirSync(path19.dirname(configPath()));
11984
+ ensureDirSync(path20.dirname(configPath()));
11811
11985
  saveConfig(cfg);
11812
11986
  }
11813
11987
  function levenshtein(a, b, cap = 3) {
@@ -11969,16 +12143,16 @@ function cmdConfig(opts) {
11969
12143
  if (state.layer !== "global" && state.layer !== "project-unparsed") sources[k] = layerJson(state);
11970
12144
  }
11971
12145
  if (Object.keys(sources).length > 0) payload["_sources"] = sources;
11972
- emit(JSON.stringify(payload, null, 2));
12146
+ emit2(JSON.stringify(payload, null, 2));
11973
12147
  return;
11974
12148
  }
11975
12149
  for (const [k, v] of pairs) {
11976
12150
  const state = resolveConfigKeyLayer(k, v, cfg, projectInfo);
11977
- emit(`${k} = ${JSON.stringify(v)}${state.layer === "project-unparsed" ? "" : layerAnnotation(state)}`);
12151
+ emit2(`${k} = ${JSON.stringify(v)}${state.layer === "project-unparsed" ? "" : layerAnnotation(state)}`);
11978
12152
  }
11979
12153
  if (projectInfo !== null) {
11980
- emit("");
11981
- emit(
12154
+ emit2("");
12155
+ emit2(
11982
12156
  projectInfo.parseError !== null ? `# project override ${projectInfo.path} failed to parse (${projectInfo.parseError}); ignored` : `# project override: ${projectInfo.path}`
11983
12157
  );
11984
12158
  }
@@ -11996,11 +12170,11 @@ function cmdConfig(opts) {
11996
12170
  }
11997
12171
  const getState = resolveConfigKeyLayer(opts.key, result.value, cfg, getProjectConfigInfo());
11998
12172
  if (opts.json === true) {
11999
- emit(JSON.stringify({ key: opts.key, value: result.value, ...layerJson(getState) }, null, 2));
12173
+ emit2(JSON.stringify({ key: opts.key, value: result.value, ...layerJson(getState) }, null, 2));
12000
12174
  return;
12001
12175
  }
12002
12176
  const rendered = typeof result.value === "string" ? result.value : JSON.stringify(result.value);
12003
- emit(`${rendered}${layerAnnotation(getState)}`);
12177
+ emit2(`${rendered}${layerAnnotation(getState)}`);
12004
12178
  return;
12005
12179
  }
12006
12180
  if (action === "set") {
@@ -12019,7 +12193,7 @@ function cmdConfig(opts) {
12019
12193
  if (parseErrAtLoad !== null) {
12020
12194
  try {
12021
12195
  fs19.copyFileSync(configPath(), `${configPath()}.bak`);
12022
- emitErr2(`config set: warning: config.toml failed to parse (${parseErrAtLoad}); backed up the original to config.toml.bak and rewriting it from defaults`);
12196
+ emitErr3(`config set: warning: config.toml failed to parse (${parseErrAtLoad}); backed up the original to config.toml.bak and rewriting it from defaults`);
12023
12197
  } catch {
12024
12198
  }
12025
12199
  }
@@ -12054,8 +12228,8 @@ function cmdConfig(opts) {
12054
12228
  saveConfigSafe(cfg);
12055
12229
  return coercedValue;
12056
12230
  };
12057
- ensureDirSync(path19.dirname(configPath()));
12058
- const lockPath = path19.join(path19.dirname(configPath()), ".config.lock");
12231
+ ensureDirSync(path20.dirname(configPath()));
12232
+ const lockPath = path20.join(path20.dirname(configPath()), ".config.lock");
12059
12233
  const lockResult = withFileLock(lockPath, applySet, { waitMs: LOCK_WAIT_MS_HARDENED });
12060
12234
  const coerced = lockResult === void 0 ? applySet() : lockResult;
12061
12235
  invalidateConfigCache();
@@ -12064,19 +12238,19 @@ function cmdConfig(opts) {
12064
12238
  const setState = resolveConfigKeyLayer(key, setEffective.found ? setEffective.value : void 0, effectiveCfg, getProjectConfigInfo());
12065
12239
  const shadowed = setEffective.found && setEffective.value !== coerced;
12066
12240
  if (shadowed && setState.layer === "env") {
12067
- emitErr2(`config set: warning: ${key} was saved to config.toml, but ${setState.envVar} is currently set and overrides it at runtime \u2014 unset ${setState.envVar} for this change to take effect`);
12241
+ emitErr3(`config set: warning: ${key} was saved to config.toml, but ${setState.envVar} is currently set and overrides it at runtime \u2014 unset ${setState.envVar} for this change to take effect`);
12068
12242
  } else if (shadowed && setState.layer === "env-invalid") {
12069
- emitErr2(`config set: warning: ${key} was saved to config.toml, but ${setState.envVar} is currently set to ${JSON.stringify(setState.rawValue)}${setState.reason !== null ? ` (${setState.reason})` : ""} and still overrides it at runtime, taking effect as ${JSON.stringify(setState.effectiveValue)} \u2014 unset ${setState.envVar} for this change to take effect`);
12243
+ emitErr3(`config set: warning: ${key} was saved to config.toml, but ${setState.envVar} is currently set to ${JSON.stringify(setState.rawValue)}${setState.reason !== null ? ` (${setState.reason})` : ""} and still overrides it at runtime, taking effect as ${JSON.stringify(setState.effectiveValue)} \u2014 unset ${setState.envVar} for this change to take effect`);
12070
12244
  } else if (setState.layer === "project") {
12071
- emitErr2(`config set: warning: ${key} was saved to config.toml, but ${setState.path} also sets it and overrides it in this project \u2014 remove it there for this change to take effect here`);
12245
+ emitErr3(`config set: warning: ${key} was saved to config.toml, but ${setState.path} also sets it and overrides it in this project \u2014 remove it there for this change to take effect here`);
12072
12246
  } else if (setState.layer === "project-invalid") {
12073
- emitErr2(`config set: warning: ${key} was saved to config.toml, but ${setState.path} sets it to ${JSON.stringify(setState.rawValue)}${setState.reason !== null ? ` (${setState.reason})` : ""} and still overrides it in this project, taking effect as ${JSON.stringify(setState.effectiveValue)} \u2014 remove it there for this change to take effect here`);
12247
+ emitErr3(`config set: warning: ${key} was saved to config.toml, but ${setState.path} sets it to ${JSON.stringify(setState.rawValue)}${setState.reason !== null ? ` (${setState.reason})` : ""} and still overrides it in this project, taking effect as ${JSON.stringify(setState.effectiveValue)} \u2014 remove it there for this change to take effect here`);
12074
12248
  }
12075
12249
  if (opts.json === true) {
12076
- emit(JSON.stringify({ key, value: coerced }, null, 2));
12250
+ emit2(JSON.stringify({ key, value: coerced }, null, 2));
12077
12251
  return;
12078
12252
  }
12079
- emit(`${key} = ${JSON.stringify(coerced)}`);
12253
+ emit2(`${key} = ${JSON.stringify(coerced)}`);
12080
12254
  return;
12081
12255
  }
12082
12256
  if (action === "validate") {
@@ -12144,19 +12318,19 @@ function cmdConfig(opts) {
12144
12318
  }
12145
12319
  if (findings.length > 0) process.exitCode = 1;
12146
12320
  if (opts.json === true) {
12147
- emit(JSON.stringify({ findings, ok: findings.length === 0 }, null, 2));
12321
+ emit2(JSON.stringify({ findings, ok: findings.length === 0 }, null, 2));
12148
12322
  return;
12149
12323
  }
12150
12324
  if (findings.length === 0) {
12151
- emit("config validate: no issues found");
12325
+ emit2("config validate: no issues found");
12152
12326
  return;
12153
12327
  }
12154
12328
  for (const f of findings) {
12155
12329
  const isNearMiss = f.kind === "unknown_section" || f.kind === "unknown_key";
12156
12330
  const hint = f.suggestion === void 0 ? "" : isNearMiss ? ` (did you mean: ${f.suggestion}?)` : ` (${f.suggestion})`;
12157
- emit(`[${f.kind}] ${f.key}${hint}`);
12331
+ emit2(`[${f.kind}] ${f.key}${hint}`);
12158
12332
  }
12159
- emit(`config validate: ${findings.length} issue(s) found`);
12333
+ emit2(`config validate: ${findings.length} issue(s) found`);
12160
12334
  return;
12161
12335
  }
12162
12336
  throw new Error(`config: unknown action '${action}'. Use list, get, set, or validate.`);
@@ -12168,33 +12342,33 @@ function cmdProject(opts) {
12168
12342
  const active = findProject(process.cwd());
12169
12343
  const blocked = cfg.worker.blocked_roots;
12170
12344
  if (opts.json === true) {
12171
- emit(JSON.stringify({ active: active ? { root: active.root, hash: active.hash, marker: active.marker } : null, blocked_roots: blocked }, null, 2));
12345
+ emit2(JSON.stringify({ active: active ? { root: active.root, hash: active.hash, marker: active.marker } : null, blocked_roots: blocked }, null, 2));
12172
12346
  return;
12173
12347
  }
12174
12348
  if (active) {
12175
- emit(`Active project: ${active.root}`);
12176
- emit(` marker: ${active.marker}`);
12349
+ emit2(`Active project: ${active.root}`);
12350
+ emit2(` marker: ${active.marker}`);
12177
12351
  } else {
12178
- emit("Active project: (none \u2014 not inside a recognized project root)");
12352
+ emit2("Active project: (none \u2014 not inside a recognized project root)");
12179
12353
  }
12180
12354
  if (blocked.length === 0) {
12181
- emit("Blocked roots: (none)");
12355
+ emit2("Blocked roots: (none)");
12182
12356
  } else {
12183
- emit("Blocked roots:");
12184
- for (const r of blocked) emit(` ${r}`);
12357
+ emit2("Blocked roots:");
12358
+ for (const r of blocked) emit2(` ${r}`);
12185
12359
  }
12186
12360
  return;
12187
12361
  }
12188
12362
  if (action === "exclude") {
12189
12363
  if (!opts.pathArg) {
12190
- emitErr2("project exclude requires a path argument");
12364
+ emitErr3("project exclude requires a path argument");
12191
12365
  throw new Error("missing path");
12192
12366
  }
12193
- const target = path19.resolve(opts.pathArg);
12367
+ const target = path20.resolve(opts.pathArg);
12194
12368
  const cfg = loadPersistedConfig();
12195
12369
  const targetFolded = foldPath(normalizePath(target));
12196
12370
  if (cfg.worker.blocked_roots.some((r) => foldPath(normalizePath(r)) === targetFolded)) {
12197
- emit(`Already excluded: ${target}`);
12371
+ emit2(`Already excluded: ${target}`);
12198
12372
  return;
12199
12373
  }
12200
12374
  cfg.worker.blocked_roots = [...cfg.worker.blocked_roots, target];
@@ -12202,12 +12376,12 @@ function cmdProject(opts) {
12202
12376
  invalidateConfigCache();
12203
12377
  const purged = pruneBlockedRoot(target);
12204
12378
  if (opts.json === true) {
12205
- emit(JSON.stringify({ excluded: target, purgedFromIndex: purged.length, blocked_roots: cfg.worker.blocked_roots }, null, 2));
12379
+ emit2(JSON.stringify({ excluded: target, purgedFromIndex: purged.length, blocked_roots: cfg.worker.blocked_roots }, null, 2));
12206
12380
  return;
12207
12381
  }
12208
- emit(`Excluded: ${target}`);
12382
+ emit2(`Excluded: ${target}`);
12209
12383
  if (purged.length > 0) {
12210
- emit(`Removed ${purged.length} already-indexed file${purged.length === 1 ? "" : "s"} under it from the index.`);
12384
+ emit2(`Removed ${purged.length} already-indexed file${purged.length === 1 ? "" : "s"} under it from the index.`);
12211
12385
  }
12212
12386
  return;
12213
12387
  }
@@ -12227,26 +12401,26 @@ function cmdProject(opts) {
12227
12401
  const orphanChunkPaths = findOrphanedChunkPaths();
12228
12402
  if (opts.dryRun === true) {
12229
12403
  if (opts.json === true) {
12230
- emit(JSON.stringify({ dryRun: true, wouldPrune: removed, stale, wouldPruneTempFiles: staleTempFiles.length, staleTempFiles, wouldPruneOrphanChunkFiles: orphanChunkPaths.length, orphanChunkPaths, blocked_roots: before }, null, 2));
12404
+ emit2(JSON.stringify({ dryRun: true, wouldPrune: removed, stale, wouldPruneTempFiles: staleTempFiles.length, staleTempFiles, wouldPruneOrphanChunkFiles: orphanChunkPaths.length, orphanChunkPaths, blocked_roots: before }, null, 2));
12231
12405
  return;
12232
12406
  }
12233
12407
  if (removed === 0) {
12234
- emit("Would prune 0 stale root(s). Nothing to do.");
12408
+ emit2("Would prune 0 stale root(s). Nothing to do.");
12235
12409
  } else {
12236
- emit(`Would prune ${removed} stale root(s):`);
12237
- for (const r of stale) emit(` ${r}`);
12410
+ emit2(`Would prune ${removed} stale root(s):`);
12411
+ for (const r of stale) emit2(` ${r}`);
12238
12412
  }
12239
12413
  if (staleTempFiles.length === 0) {
12240
- emit("Would prune 0 stale indexed temp-dir file(s). Nothing to do.");
12414
+ emit2("Would prune 0 stale indexed temp-dir file(s). Nothing to do.");
12241
12415
  } else {
12242
- emit(`Would prune ${staleTempFiles.length} stale indexed temp-dir file(s):`);
12243
- for (const p of staleTempFiles) emit(` ${p}`);
12416
+ emit2(`Would prune ${staleTempFiles.length} stale indexed temp-dir file(s):`);
12417
+ for (const p of staleTempFiles) emit2(` ${p}`);
12244
12418
  }
12245
12419
  if (orphanChunkPaths.length === 0) {
12246
- emit("Would prune 0 orphaned embedding chunk file(s). Nothing to do.");
12420
+ emit2("Would prune 0 orphaned embedding chunk file(s). Nothing to do.");
12247
12421
  } else {
12248
- emit(`Would prune orphaned embedding chunks for ${orphanChunkPaths.length} file(s):`);
12249
- for (const p of orphanChunkPaths) emit(` ${p}`);
12422
+ emit2(`Would prune orphaned embedding chunks for ${orphanChunkPaths.length} file(s):`);
12423
+ for (const p of orphanChunkPaths) emit2(` ${p}`);
12250
12424
  }
12251
12425
  return;
12252
12426
  }
@@ -12256,37 +12430,37 @@ function cmdProject(opts) {
12256
12430
  const prunedTempFiles = pruneSystemTempFiles();
12257
12431
  const prunedOrphanChunks = pruneOrphanedChunks();
12258
12432
  if (opts.json === true) {
12259
- emit(JSON.stringify({ pruned: removed, blocked_roots: after, prunedTempFiles: prunedTempFiles.length, prunedOrphanChunkFiles: prunedOrphanChunks.length }, null, 2));
12433
+ emit2(JSON.stringify({ pruned: removed, blocked_roots: after, prunedTempFiles: prunedTempFiles.length, prunedOrphanChunkFiles: prunedOrphanChunks.length }, null, 2));
12260
12434
  return;
12261
12435
  }
12262
- emit(`Pruned ${removed} stale root(s). Remaining: ${after.length}`);
12263
- emit(`Pruned ${prunedTempFiles.length} stale indexed temp-dir file(s).`);
12264
- emit(`Pruned orphaned embedding chunks for ${prunedOrphanChunks.length} file(s).`);
12436
+ emit2(`Pruned ${removed} stale root(s). Remaining: ${after.length}`);
12437
+ emit2(`Pruned ${prunedTempFiles.length} stale indexed temp-dir file(s).`);
12438
+ emit2(`Pruned orphaned embedding chunks for ${prunedOrphanChunks.length} file(s).`);
12265
12439
  return;
12266
12440
  }
12267
- emitErr2(`project: unknown action '${action}'. Use list, exclude, or prune.`);
12441
+ emitErr3(`project: unknown action '${action}'. Use list, exclude, or prune.`);
12268
12442
  throw new Error(`unknown project action: ${action}`);
12269
12443
  }
12270
12444
  function recordCompactDocStat(fullSourceBytes, emittedText, detail) {
12271
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(emittedText, "utf8"));
12445
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(emittedText, "utf8"));
12272
12446
  recordStat("compact_doc", bytesSaved, savedTokensFromBytes(bytesSaved), void 0, detail);
12273
12447
  }
12274
12448
  function cmdCompactDoc(opts) {
12275
- const resolved = path19.resolve(opts.filePath);
12449
+ const resolved = path20.resolve(opts.filePath);
12276
12450
  if (opts.heading !== void 0) {
12277
12451
  const result = compactDoc(resolved, opts.heading);
12278
12452
  if (result === null) {
12279
- emitErr2(`compact-doc: could not read or compact '${resolved}'`);
12453
+ emitErr3(`compact-doc: could not read or compact '${resolved}'`);
12280
12454
  throw new Error(`could not compact: ${resolved}`);
12281
12455
  }
12282
12456
  const legacyFullBytes = fs19.statSync(resolved).size;
12283
12457
  if (opts.json === true) {
12284
12458
  const jsonText = JSON.stringify({ path: resolved, compact: result }, null, 2);
12285
- emit(jsonText);
12459
+ emit2(jsonText);
12286
12460
  recordCompactDocStat(legacyFullBytes, jsonText, resolved);
12287
12461
  return;
12288
12462
  }
12289
- emit(result);
12463
+ emit2(result);
12290
12464
  recordCompactDocStat(legacyFullBytes, result, resolved);
12291
12465
  return;
12292
12466
  }
@@ -12295,7 +12469,7 @@ function cmdCompactDoc(opts) {
12295
12469
  try {
12296
12470
  sentences = requirePositiveStrictInt("--sentences", opts.sentences);
12297
12471
  } catch (e) {
12298
- emitErr2(`compact-doc: --sentences must be a positive number, got: "${opts.sentences}"`);
12472
+ emitErr3(`compact-doc: --sentences must be a positive number, got: "${opts.sentences}"`);
12299
12473
  throw new Error(`invalid --sentences: ${opts.sentences}`, { cause: e });
12300
12474
  }
12301
12475
  }
@@ -12308,7 +12482,7 @@ function cmdCompactDoc(opts) {
12308
12482
  try {
12309
12483
  sourceText = fs19.readFileSync(resolved, "utf-8");
12310
12484
  } catch {
12311
- emitErr2(`compact-doc: could not read or compact '${resolved}'`);
12485
+ emitErr3(`compact-doc: could not read or compact '${resolved}'`);
12312
12486
  throw new Error(`could not compact: ${resolved}`);
12313
12487
  }
12314
12488
  body = buildExtractiveCompact(sourceText, sentences);
@@ -12317,7 +12491,7 @@ function cmdCompactDoc(opts) {
12317
12491
  } else {
12318
12492
  const existing = readCompactBody(compactPath);
12319
12493
  if (existing === null) {
12320
- emitErr2(`compact-doc: could not read or compact '${resolved}'`);
12494
+ emitErr3(`compact-doc: could not read or compact '${resolved}'`);
12321
12495
  throw new Error(`could not compact: ${resolved}`);
12322
12496
  }
12323
12497
  body = existing;
@@ -12325,16 +12499,16 @@ function cmdCompactDoc(opts) {
12325
12499
  const extractiveFullBytes = fs19.statSync(resolved).size;
12326
12500
  if (opts.json === true) {
12327
12501
  const jsonText = JSON.stringify({ path: resolved, compactPath, rebuilt, compact: body }, null, 2);
12328
- emit(jsonText);
12502
+ emit2(jsonText);
12329
12503
  recordCompactDocStat(extractiveFullBytes, jsonText, resolved);
12330
12504
  return;
12331
12505
  }
12332
12506
  if (opts.show === true) {
12333
- emit(body);
12507
+ emit2(body);
12334
12508
  recordCompactDocStat(extractiveFullBytes, body, resolved);
12335
12509
  return;
12336
12510
  }
12337
- emit(
12511
+ emit2(
12338
12512
  `Compact sidecar ${rebuilt ? "built" : "already fresh"} at ${compactPath} (source: ${resolved}). Use --show to print it, --force to rebuild.`
12339
12513
  );
12340
12514
  }
@@ -12375,11 +12549,11 @@ async function cmdFetchImage(opts) {
12375
12549
  try {
12376
12550
  fetched = await fetchBuffer(opts.url);
12377
12551
  } catch (e) {
12378
- emitErr2(`fetch-image: network error \u2014 ${extractErrorMessage(e)}`);
12552
+ emitErr3(`fetch-image: network error \u2014 ${extractErrorMessage(e)}`);
12379
12553
  throw new Error(`fetch failed: ${opts.url}`, { cause: e });
12380
12554
  }
12381
12555
  const buf = fetched.body;
12382
- const outPath = opts.out ?? path19.join(os9.tmpdir(), `tg-fetch-${Date.now()}${extensionForContentType(fetched.contentType)}`);
12556
+ const outPath = opts.out ?? path20.join(os9.tmpdir(), `tg-fetch-${Date.now()}${extensionForContentType(fetched.contentType)}`);
12383
12557
  const originalBytes = buf.length;
12384
12558
  let shrunkBytes;
12385
12559
  let outData;
@@ -12402,11 +12576,11 @@ async function cmdFetchImage(opts) {
12402
12576
  }
12403
12577
  atomicWriteBytes(finalPath, outData);
12404
12578
  if (opts.json === true) {
12405
- emit(JSON.stringify({ url: opts.url, out: finalPath, originalBytes, shrunkBytes, wasShrunk }, null, 2));
12579
+ emit2(JSON.stringify({ url: opts.url, out: finalPath, originalBytes, shrunkBytes, wasShrunk }, null, 2));
12406
12580
  return;
12407
12581
  }
12408
12582
  const savings = wasShrunk ? ` (saved ${originalBytes - shrunkBytes} bytes)` : " (not shrunk \u2014 already small or unsupported format)";
12409
- emit(`Fetched ${originalBytes} bytes \u2192 ${finalPath}${savings}`);
12583
+ emit2(`Fetched ${originalBytes} bytes \u2192 ${finalPath}${savings}`);
12410
12584
  }
12411
12585
  function cmdHistory(opts) {
12412
12586
  let limit = 30;
@@ -12414,11 +12588,11 @@ function cmdHistory(opts) {
12414
12588
  try {
12415
12589
  limit = requireNonNegativeStrictInt("--limit", opts.limit);
12416
12590
  } catch (e) {
12417
- emitErr2(`history: --limit must be a non-negative number, got: "${opts.limit}"`);
12591
+ emitErr3(`history: --limit must be a non-negative number, got: "${opts.limit}"`);
12418
12592
  throw new Error(`invalid --limit: ${opts.limit}`, { cause: e });
12419
12593
  }
12420
12594
  if (limit === 0) {
12421
- emitErr2(`history: --limit must be a positive number, got: "${opts.limit}"`);
12595
+ emitErr3(`history: --limit must be a positive number, got: "${opts.limit}"`);
12422
12596
  throw new Error(`invalid --limit: ${opts.limit}`);
12423
12597
  }
12424
12598
  }
@@ -12442,27 +12616,32 @@ function cmdHistory(opts) {
12442
12616
  summary: typeof v["url"] === "string" ? v["url"] : ""
12443
12617
  };
12444
12618
  }).filter((x) => x !== null);
12445
- const items = [...bashItems, ...webItems].sort((a, b) => b.storedAt - a.storedAt).slice(0, limit);
12619
+ const allItems = [...bashItems, ...webItems].sort((a, b) => b.storedAt - a.storedAt);
12620
+ const items = allItems.slice(0, limit);
12621
+ if (items.length < allItems.length) {
12622
+ process.stderr.write(`Showing ${items.length} of ${allItems.length} entries (raise --limit to see the rest).
12623
+ `);
12624
+ }
12446
12625
  if (opts.json === true) {
12447
12626
  process.stdout.write(JSON.stringify(items, null, 2) + "\n");
12448
12627
  return;
12449
12628
  }
12450
12629
  if (items.length === 0) {
12451
- emit("No history entries found (no cached bash outputs or web fetches).");
12630
+ emit2("No history entries found (no cached bash outputs or web fetches).");
12452
12631
  return;
12453
12632
  }
12454
12633
  const pad2 = (s, n) => s.length >= n ? s : s + " ".repeat(n - s.length);
12455
- emit(`${pad2("type", 5)} ${pad2("id", 18)} summary`);
12634
+ emit2(`${pad2("type", 5)} ${pad2("id", 18)} summary`);
12456
12635
  for (const item of items) {
12457
12636
  const preview = item.summary.length > 80 ? item.summary.slice(0, 77) + "..." : item.summary;
12458
- emit(`${pad2(item.type, 5)} ${pad2(item.id, 18)} ${preview}`);
12637
+ emit2(`${pad2(item.type, 5)} ${pad2(item.id, 18)} ${preview}`);
12459
12638
  }
12460
12639
  }
12461
12640
 
12462
12641
  // src/cli_bootstrap_audit.ts
12463
12642
  import * as fs20 from "node:fs";
12464
12643
  import * as os10 from "node:os";
12465
- import * as path20 from "node:path";
12644
+ import * as path21 from "node:path";
12466
12645
  import * as readline from "node:readline";
12467
12646
  var FRONTMATTER_KEYS = /* @__PURE__ */ new Set(["description", "tools", "allowed-tools", "allowed_tools"]);
12468
12647
  function parseBudget(name, value) {
@@ -12521,17 +12700,17 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
12521
12700
  logicalRoot = await fs20.promises.realpath(root);
12522
12701
  } catch (error) {
12523
12702
  if (error.code === "ENOENT") return entries;
12524
- diagnostics.push({ path: path20.resolve(root), reason: "unreadable root" });
12703
+ diagnostics.push({ path: path21.resolve(root), reason: "unreadable root" });
12525
12704
  return entries;
12526
12705
  }
12527
12706
  if (rootIsLink && !followLinks) {
12528
- diagnostics.push({ path: path20.resolve(root), reason: "external root link skipped (use --follow-links)" });
12707
+ diagnostics.push({ path: path21.resolve(root), reason: "external root link skipped (use --follow-links)" });
12529
12708
  return entries;
12530
12709
  }
12531
12710
  const canonicalKey = (candidate) => process.platform === "win32" ? candidate.toLocaleLowerCase() : candidate;
12532
12711
  const isContained = (candidate, allowedRoot) => {
12533
- const relative3 = path20.relative(canonicalKey(allowedRoot), canonicalKey(candidate));
12534
- return relative3 === "" || relative3 !== ".." && !relative3.startsWith(`..${path20.sep}`) && !path20.isAbsolute(relative3);
12712
+ const relative3 = path21.relative(canonicalKey(allowedRoot), canonicalKey(candidate));
12713
+ return relative3 === "" || relative3 !== ".." && !relative3.startsWith(`..${path21.sep}`) && !path21.isAbsolute(relative3);
12535
12714
  };
12536
12715
  async function visit(candidate, allowedRoot, topLevel = false) {
12537
12716
  let real;
@@ -12540,13 +12719,13 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
12540
12719
  candidateIsLink = (await fs20.promises.lstat(candidate)).isSymbolicLink();
12541
12720
  real = await fs20.promises.realpath(candidate);
12542
12721
  } catch {
12543
- diagnostics.push({ path: path20.resolve(candidate), reason: "broken or unreadable link" });
12722
+ diagnostics.push({ path: path21.resolve(candidate), reason: "broken or unreadable link" });
12544
12723
  return;
12545
12724
  }
12546
12725
  const withinAllowedRoot = allowedRoot === void 0 ? isContained(real, logicalRoot) : isContained(real, allowedRoot);
12547
12726
  const canFollowExternal = followLinks && topLevel;
12548
12727
  if (!withinAllowedRoot && !canFollowExternal) {
12549
- diagnostics.push({ path: path20.resolve(candidate), reason: candidateIsLink ? "external link skipped (use --follow-links)" : "canonical target outside scan root" });
12728
+ diagnostics.push({ path: path21.resolve(candidate), reason: candidateIsLink ? "external link skipped (use --follow-links)" : "canonical target outside scan root" });
12550
12729
  return;
12551
12730
  }
12552
12731
  const trustedRoot = !withinAllowedRoot && canFollowExternal ? real : allowedRoot ?? real;
@@ -12554,7 +12733,7 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
12554
12733
  try {
12555
12734
  stat = await fs20.promises.stat(real);
12556
12735
  } catch {
12557
- diagnostics.push({ path: path20.resolve(candidate), reason: "unreadable" });
12736
+ diagnostics.push({ path: path21.resolve(candidate), reason: "unreadable" });
12558
12737
  return;
12559
12738
  }
12560
12739
  const canonical = process.platform === "win32" ? real.toLocaleLowerCase() : real;
@@ -12565,14 +12744,14 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
12565
12744
  try {
12566
12745
  children = await fs20.promises.readdir(real, { withFileTypes: true });
12567
12746
  } catch {
12568
- diagnostics.push({ path: path20.resolve(candidate), reason: "unreadable directory" });
12747
+ diagnostics.push({ path: path21.resolve(candidate), reason: "unreadable directory" });
12569
12748
  return;
12570
12749
  }
12571
12750
  children.sort((a, b) => a.name.localeCompare(b.name));
12572
- for (const child of children) await visit(path20.join(real, child.name), trustedRoot);
12751
+ for (const child of children) await visit(path21.join(real, child.name), trustedRoot);
12573
12752
  return;
12574
12753
  }
12575
- if (!stat.isFile() || path20.extname(real).toLowerCase() !== ".md") return;
12754
+ if (!stat.isFile() || path21.extname(real).toLowerCase() !== ".md") return;
12576
12755
  if (seenFiles.has(canonical)) return;
12577
12756
  seenFiles.add(canonical);
12578
12757
  try {
@@ -12582,36 +12761,38 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
12582
12761
  const toolsBytes = Buffer.byteLength(metadata.tools, "utf8");
12583
12762
  entries.push({
12584
12763
  kind,
12585
- path: path20.resolve(real),
12764
+ path: path21.resolve(real),
12586
12765
  description_bytes: descriptionBytes,
12587
12766
  tools_bytes: toolsBytes,
12588
12767
  metadata_bytes: descriptionBytes + toolsBytes,
12589
12768
  estimated_tokens: Math.floor((descriptionBytes + toolsBytes) / 4)
12590
12769
  });
12591
12770
  } catch {
12592
- diagnostics.push({ path: path20.resolve(candidate), reason: "unreadable file" });
12771
+ diagnostics.push({ path: path21.resolve(candidate), reason: "unreadable file" });
12593
12772
  }
12594
12773
  }
12595
12774
  try {
12596
12775
  const children = await fs20.promises.readdir(logicalRoot, { withFileTypes: true });
12597
12776
  children.sort((a, b) => a.name.localeCompare(b.name));
12598
- for (const child of children) await visit(path20.join(logicalRoot, child.name), void 0, true);
12777
+ for (const child of children) await visit(path21.join(logicalRoot, child.name), void 0, true);
12599
12778
  } catch {
12600
- diagnostics.push({ path: path20.resolve(root), reason: "unreadable root" });
12779
+ diagnostics.push({ path: path21.resolve(root), reason: "unreadable root" });
12601
12780
  }
12602
12781
  return entries;
12603
12782
  }
12604
12783
  async function buildBootstrapAudit(opts = {}) {
12605
12784
  const project = resolveProjectRoot(opts.project === void 0 ? {} : { project: opts.project });
12606
- const home = path20.resolve(opts.home ?? os10.homedir());
12785
+ const home = path21.resolve(opts.home ?? os10.homedir());
12607
12786
  const context = buildStats(project, home, opts.project === void 0 ? [] : [opts.project]);
12608
12787
  const diagnostics = [];
12609
12788
  const top = opts.top === void 0 ? 10 : parseBudget("--top", opts.top) ?? 10;
12610
12789
  const visitedDirs = /* @__PURE__ */ new Set();
12611
12790
  const seenFiles = /* @__PURE__ */ new Set();
12612
- const agents = await scanMetadataRoot(path20.join(home, ".claude", "agents"), "agent", diagnostics, visitedDirs, seenFiles, opts.followLinks === true);
12613
- const skills = await scanMetadataRoot(path20.join(home, ".claude", "skills"), "skill", diagnostics, visitedDirs, seenFiles, opts.followLinks === true);
12614
- const largest = [...agents, ...skills].sort((a, b) => b.metadata_bytes - a.metadata_bytes || a.path.localeCompare(b.path)).slice(0, top);
12791
+ const agents = await scanMetadataRoot(path21.join(home, ".claude", "agents"), "agent", diagnostics, visitedDirs, seenFiles, opts.followLinks === true);
12792
+ const skills = await scanMetadataRoot(path21.join(home, ".claude", "skills"), "skill", diagnostics, visitedDirs, seenFiles, opts.followLinks === true);
12793
+ const rankedEntries = [...agents, ...skills].sort((a, b) => b.metadata_bytes - a.metadata_bytes || a.path.localeCompare(b.path));
12794
+ const largestTotal = rankedEntries.length;
12795
+ const largest = rankedEntries.slice(0, top);
12615
12796
  const metadataBytes = [...agents, ...skills].reduce((sum, entry) => sum + entry.metadata_bytes, 0);
12616
12797
  const totalTokens = context.total_tokens + Math.floor(metadataBytes / 4);
12617
12798
  const warnTokens = parseBudget("--warn-tokens", opts.warnTokens);
@@ -12635,6 +12816,8 @@ async function buildBootstrapAudit(opts = {}) {
12635
12816
  total_estimated_tokens: totalTokens,
12636
12817
  counts: { agents: agents.length, skills: skills.length, metadata_files: agents.length + skills.length },
12637
12818
  largest,
12819
+ largestTotal,
12820
+ largestTruncated: largest.length < largestTotal,
12638
12821
  diagnostics,
12639
12822
  budgets: { warn_tokens: warnTokens, fail_tokens: failTokens, warn_bytes: warnBytes, fail_bytes: failBytes, warnings, failures }
12640
12823
  };
@@ -12658,6 +12841,8 @@ async function runBootstrapAudit(opts = {}) {
12658
12841
  `);
12659
12842
  process.stdout.write("Largest metadata entries:\n");
12660
12843
  for (const entry of result.largest) process.stdout.write(` ${entry.metadata_bytes.toString().padStart(7)} bytes ${entry.path}
12844
+ `);
12845
+ if (result.largestTruncated) process.stdout.write(` ...and ${result.largestTotal - result.largest.length} more (raise --top to see them).
12661
12846
  `);
12662
12847
  for (const diagnostic of result.diagnostics) process.stderr.write(`token-goat: bootstrap-audit: skipped ${diagnostic.path} (${diagnostic.reason})
12663
12848
  `);
@@ -12671,7 +12856,7 @@ async function runBootstrapAudit(opts = {}) {
12671
12856
 
12672
12857
  // src/cli_memory.ts
12673
12858
  import * as fs21 from "node:fs";
12674
- import * as path21 from "node:path";
12859
+ import * as path22 from "node:path";
12675
12860
  function removeExactDupLines(text, report) {
12676
12861
  if (report.exactDupLines.length === 0) return text;
12677
12862
  const toRemove = new Set(report.exactDupLines.map(([, dupIdx]) => dupIdx));
@@ -12729,7 +12914,7 @@ function printReport(reports, clusters) {
12729
12914
  } else {
12730
12915
  for (const cluster of clusters) {
12731
12916
  w(
12732
- ` [${cluster.method}, similarity ${cluster.similarity}, ~${cluster.tokens} tok] ${cluster.members.map((m) => path21.basename(m)).join(", ")}
12917
+ ` [${cluster.method}, similarity ${cluster.similarity}, ~${cluster.tokens} tok] ${cluster.members.map((m) => path22.basename(m)).join(", ")}
12733
12918
  `
12734
12919
  );
12735
12920
  }
@@ -12744,7 +12929,7 @@ async function runMemoryCommand(opts = {}) {
12744
12929
  const claudeMds = findClaudeMdFiles(projectRoot);
12745
12930
  const reports = auditClaudeMd(claudeMds);
12746
12931
  const memoryMdPath = findMemoryMd(projectRoot);
12747
- const clusters = memoryMdPath !== null ? await findContentDuplicates(path21.dirname(memoryMdPath)) : [];
12932
+ const clusters = memoryMdPath !== null ? await findContentDuplicates(path22.dirname(memoryMdPath)) : [];
12748
12933
  printReport(reports, clusters);
12749
12934
  if (opts.fix !== true) return;
12750
12935
  const changes = [];
@@ -12790,16 +12975,16 @@ async function runMemoryCommand(opts = {}) {
12790
12975
 
12791
12976
  // src/cli_waste.ts
12792
12977
  import * as fs24 from "node:fs";
12793
- import * as path24 from "node:path";
12978
+ import * as path25 from "node:path";
12794
12979
 
12795
12980
  // src/waste.ts
12796
12981
  import * as fs22 from "node:fs";
12797
12982
  import * as os11 from "node:os";
12798
- import * as path22 from "node:path";
12983
+ import * as path23 from "node:path";
12799
12984
  function projectTranscriptsDir(projectRoot) {
12800
- const rootStr = path22.resolve(projectRoot);
12985
+ const rootStr = path23.resolve(projectRoot);
12801
12986
  const slug = rootStr.replace(/[^A-Za-z0-9]/g, "-");
12802
- return path22.join(os11.homedir(), ".claude", "projects", slug);
12987
+ return path23.join(os11.homedir(), ".claude", "projects", slug);
12803
12988
  }
12804
12989
  function findLatestTranscript(projectRoot) {
12805
12990
  const dir = projectTranscriptsDir(projectRoot);
@@ -12811,7 +12996,7 @@ function findLatestTranscript(projectRoot) {
12811
12996
  }
12812
12997
  let best = null;
12813
12998
  for (const name of entries) {
12814
- const full = path22.join(dir, name);
12999
+ const full = path23.join(dir, name);
12815
13000
  let stat;
12816
13001
  try {
12817
13002
  stat = fs22.statSync(full);
@@ -13041,7 +13226,7 @@ async function buildWasteReport(transcriptPath, opts = {}) {
13041
13226
 
13042
13227
  // src/copilot_waste.ts
13043
13228
  import * as fs23 from "node:fs";
13044
- import * as path23 from "node:path";
13229
+ import * as path24 from "node:path";
13045
13230
  var HOOK_RECORD_TYPES = /* @__PURE__ */ new Set(["hook.start", "hook.end"]);
13046
13231
  function splitInjectedBlocks(transformed) {
13047
13232
  const out2 = [];
@@ -13060,7 +13245,7 @@ function splitInjectedBlocks(transformed) {
13060
13245
  return out2;
13061
13246
  }
13062
13247
  function findLatestCopilotSession() {
13063
- const root = path23.join(copilotCliUserRoot(), "session-state");
13248
+ const root = path24.join(copilotCliUserRoot(), "session-state");
13064
13249
  let entries;
13065
13250
  try {
13066
13251
  entries = fs23.readdirSync(root);
@@ -13070,7 +13255,7 @@ function findLatestCopilotSession() {
13070
13255
  let best = null;
13071
13256
  let bestMtime = -Infinity;
13072
13257
  for (const entry of entries) {
13073
- const candidate = path23.join(root, entry, "events.jsonl");
13258
+ const candidate = path24.join(root, entry, "events.jsonl");
13074
13259
  try {
13075
13260
  const st = fs23.statSync(candidate);
13076
13261
  if (!st.isFile()) continue;
@@ -13091,7 +13276,7 @@ function buildCopilotWasteReport(eventsPath) {
13091
13276
  const raw = fs23.readFileSync(eventsPath, "utf-8");
13092
13277
  const report = {
13093
13278
  sessionPath: eventsPath,
13094
- sessionId: path23.basename(path23.dirname(eventsPath)),
13279
+ sessionId: path24.basename(path24.dirname(eventsPath)),
13095
13280
  turns: 0,
13096
13281
  tokens: null,
13097
13282
  blocks: [],
@@ -13370,7 +13555,7 @@ function printResidentContext(resident, w) {
13370
13555
  }
13371
13556
  async function runWasteCommand(opts = {}) {
13372
13557
  if (opts.copilot === true) {
13373
- const eventsPath = opts.transcript !== void 0 ? path24.resolve(opts.transcript) : findLatestCopilotSession();
13558
+ const eventsPath = opts.transcript !== void 0 ? path25.resolve(opts.transcript) : findLatestCopilotSession();
13374
13559
  if (eventsPath === null || !fs24.existsSync(eventsPath)) {
13375
13560
  const detail = eventsPath === null ? "no Copilot CLI session found under <copilot-home>/session-state" : `Copilot session event log not found: ${eventsPath}`;
13376
13561
  if (opts.json === true) {
@@ -13394,7 +13579,7 @@ async function runWasteCommand(opts = {}) {
13394
13579
  return;
13395
13580
  }
13396
13581
  const projectRoot = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
13397
- const transcriptPath = opts.transcript !== void 0 ? path24.resolve(opts.transcript) : findLatestTranscript(projectRoot);
13582
+ const transcriptPath = opts.transcript !== void 0 ? path25.resolve(opts.transcript) : findLatestTranscript(projectRoot);
13398
13583
  if (transcriptPath === null) {
13399
13584
  if (opts.json === true) {
13400
13585
  process.stdout.write(`${JSON.stringify({ error: "no session transcript found", project: projectRoot })}
@@ -13425,7 +13610,7 @@ async function runWasteCommand(opts = {}) {
13425
13610
 
13426
13611
  // src/session_read.ts
13427
13612
  import * as fs25 from "node:fs";
13428
- import * as path25 from "node:path";
13613
+ import * as path26 from "node:path";
13429
13614
  import * as readline2 from "node:readline";
13430
13615
  function resolveSessionTranscript(arg, opts = {}) {
13431
13616
  if (arg !== void 0 && arg !== "") {
@@ -13433,7 +13618,7 @@ function resolveSessionTranscript(arg, opts = {}) {
13433
13618
  const projectRoot2 = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
13434
13619
  const dir = projectTranscriptsDir(projectRoot2);
13435
13620
  const byId = arg.endsWith(".jsonl") ? arg : `${arg}.jsonl`;
13436
- const candidate = path25.join(dir, byId);
13621
+ const candidate = path26.join(dir, byId);
13437
13622
  if (fs25.existsSync(candidate) && fs25.statSync(candidate).isFile()) return candidate;
13438
13623
  return null;
13439
13624
  }
@@ -13592,7 +13777,7 @@ function formatSessionSlice(turns) {
13592
13777
  // src/session_audit.ts
13593
13778
  import * as fs26 from "node:fs";
13594
13779
  import * as os12 from "node:os";
13595
- import * as path26 from "node:path";
13780
+ import * as path27 from "node:path";
13596
13781
  import * as readline3 from "node:readline";
13597
13782
  function emptyMeasured() {
13598
13783
  return { apiCalls: 0, inputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, outputTokens: 0 };
@@ -13639,6 +13824,64 @@ var CACHE_READ_MULTIPLIER = 0.1;
13639
13824
  var READ_DIVERT_MARKER_RE = /(?:was already read this session|Already read |You've already read|Use `token-goat (?:section|read|bash-output|config-get|skeleton)|token-goat bash-output --file)/;
13640
13825
  var READ_DIVERT_MAX_BYTES = 2500;
13641
13826
  var READ_FULL_SERVE_MIN_BYTES = 10240;
13827
+ var DENY_TEMPLATES = [
13828
+ { kind: "node_modules_deny", re: /node_modules is typically noise/ },
13829
+ { kind: "lock_file_deny", re: /Lock files are rarely useful to read in full/ },
13830
+ { kind: "tsbuildinfo_deny", re: /TypeScript incremental build cache file/ },
13831
+ { kind: "generated_build_deny", re: /Generated\/build artifact — read the source file instead\./ },
13832
+ { kind: "compact_sidecar_served", re: /Serving the extractive compact sidecar in place of the full file/ },
13833
+ { kind: "notebook_sidecar_served", re: /Serving the output-stripped notebook in place of the full file/ },
13834
+ // Must stay ABOVE markdown_heading_tree_deny: this deny's message embeds the same "Large markdown file (N headings)" guidance block, so the generic alternative would swallow it and the new intervention would be uncountable. DENY_TEMPLATES is scanned with `.find`, so the more specific wording has to come first.
13835
+ { kind: "subagent_markdown_first_read_deny", re: /Subagent first read of a large markdown file/ },
13836
+ { kind: "markdown_heading_tree_deny", re: /Large markdown file \(\d+ headings\)/ },
13837
+ // The first alternative is a wording hooks_read.ts no longer emits (it claimed a compact-manifest
13838
+ // section that never existed, reworded in 3d044feb). It stays because this table classifies a
13839
+ // historical corpus, not just today's output: transcripts written before the rewording still
13840
+ // carry the old text, and dropping the alternative silently shrank this kind from 51 events to
13841
+ // 30 -- 41% of its history -- with a green suite. A superseded alternative is only removable
13842
+ // once no transcript contains it, which source code cannot tell you. See the superseded-wording
13843
+ // test in tests/deny_outcomes.test.ts.
13844
+ { kind: "memory_md_reread_deny", re: /MEMORY\.md was read this session\. Its content is in the compact manifest|already read this session\. Memory files rarely change mid-session/ },
13845
+ { kind: "improve_state_reread_deny", re: /Orchestrator state already read this session/ },
13846
+ { kind: "env_reread_deny", re: /Environment files rarely change mid-session/ },
13847
+ { kind: "session_artifact_truncated_deny", re: /File was truncated on last read\. Use `token-goat bash-output/ },
13848
+ { kind: "session_artifact_unchanged_deny", re: /is unchanged since last read\. Use `token-goat bash-output/ },
13849
+ { kind: "session_artifact_diff_deny", re: /Content changed since last read of [\s\S]*?bash-output --file/ },
13850
+ { kind: "session_artifact_large_deny", re: /(?:Session transcript|Tool-result file) is large \(/ },
13851
+ { kind: "session_artifact_generic_reread_deny", re: /already read this session\. Use `token-goat bash-output --file/ },
13852
+ { kind: "truncated_read_deny", re: /File was truncated on last read \(>33K tokens\)/ },
13853
+ { kind: "doc_unchanged_deny", re: /is unchanged since last read\. Use `token-goat (?:section|read)/ },
13854
+ { kind: "doc_diff_deny", re: /Content changed since last read of [\s\S]*?Use `token-goat (?:section|read)/ },
13855
+ { kind: "read_served_deny", re: /was already served in this session, byte for byte/ },
13856
+ { kind: "markdown_already_read_deny", re: /Markdown file already read this session\. Use `token-goat section/ },
13857
+ { kind: "read_count_deny", re: /Read this file \d+ times already/ },
13858
+ { kind: "generic_reread_deny", re: /was already read this session \(\d+ read/ },
13859
+ { kind: "large_file_deny", re: /is very large \(\d+(?:\.\d+)?KB\)\./ },
13860
+ { kind: "file_type_handler_deny", re: /too large to preview \(exceeds the in-hook scan cap\)|cannot be read as text\.|Read cannot return spreadsheet content|Read cannot return slide content|Read cannot return document content|Use Read with offset and limit parameters to read specific line ranges/ }
13861
+ ];
13862
+ var DENY_KINDS_WITH_SIZE = /* @__PURE__ */ new Set(["large_file_deny", "session_artifact_large_deny", "file_type_handler_deny"]);
13863
+ var DENY_SIZE_RE = /(\d+(?:\.\d+)?)\s*(B|KB|MB|GB|TB)\b/;
13864
+ function parseWithheldBytes(kind, text) {
13865
+ if (!DENY_KINDS_WITH_SIZE.has(kind)) return null;
13866
+ const m = DENY_SIZE_RE.exec(text);
13867
+ if (m === null) return null;
13868
+ const n = Number.parseFloat(m[1]);
13869
+ const unit = m[2].toUpperCase();
13870
+ const mult = unit === "B" ? 1 : unit === "KB" ? 1024 : unit === "MB" ? 1024 * 1024 : unit === "GB" ? 1024 ** 3 : 1024 ** 4;
13871
+ return Math.round(n * mult);
13872
+ }
13873
+ var SURGICAL_COMMAND_RE = /(?:^|[\s;&|(])(?:token-goat|tg|node\s+\S*token-goat\.mjs)\s+(read|section|symbol|skeleton|outline|bash-output|config-get|skill-section|skill-body)\b/;
13874
+ var SHELL_READER_TOKEN_RE = /\b(?:sed|grep|cat|head|tail|awk|rg|less|nl|python3?|node)\b/;
13875
+ function escapeRegExp2(s) {
13876
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13877
+ }
13878
+ function shellReadMatch(command, basename8) {
13879
+ if (basename8 === "" || !SHELL_READER_TOKEN_RE.test(command)) return "none";
13880
+ if (!command.includes(basename8)) return "none";
13881
+ const escaped = escapeRegExp2(basename8);
13882
+ const strict = new RegExp(`(?:^|["'\`/\\\\\\s])${escaped}(?:$|["'\`/\\\\\\s,;:)])`);
13883
+ return strict.test(command) ? "match" : "ambiguous";
13884
+ }
13642
13885
  var BASH_FILTER_MARKER_RE = /\[token-goat[:\]]/;
13643
13886
  var BASH_SMALL_RESULT_MAX_BYTES = 1024;
13644
13887
  function commandHead(raw) {
@@ -13693,7 +13936,7 @@ function listCorpusTranscripts(corpusDir) {
13693
13936
  const found = [];
13694
13937
  const walk = (dir, entries) => {
13695
13938
  for (const entry of entries) {
13696
- const full = path26.join(dir, entry.name);
13939
+ const full = path27.join(dir, entry.name);
13697
13940
  if (entry.name.endsWith(".jsonl")) {
13698
13941
  found.push(full);
13699
13942
  } else if (entry.isDirectory()) {
@@ -13711,9 +13954,9 @@ function listCorpusTranscripts(corpusDir) {
13711
13954
  return found.sort();
13712
13955
  }
13713
13956
  function defaultCorpusDir() {
13714
- return path26.join(os12.homedir(), ".claude", "projects");
13957
+ return path27.join(os12.homedir(), ".claude", "projects");
13715
13958
  }
13716
- async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneObservations, bashHeadMap) {
13959
+ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneObservations, bashHeadMap, denyRows) {
13717
13960
  const isLane = filePath.split(/[\\/]/).includes("subagents");
13718
13961
  let laneFirstPrefix = null;
13719
13962
  let laneBriefBytes = -1;
@@ -13721,8 +13964,12 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
13721
13964
  const bashHeadById = /* @__PURE__ */ new Map();
13722
13965
  let pendingBashReread = [];
13723
13966
  const readCallById = /* @__PURE__ */ new Map();
13967
+ const readPathById = /* @__PURE__ */ new Map();
13968
+ const editPathById = /* @__PURE__ */ new Map();
13969
+ const editErrorById = /* @__PURE__ */ new Map();
13724
13970
  const readPathEpoch = /* @__PURE__ */ new Map();
13725
13971
  let compactEpoch = 0;
13972
+ const openDenies = [];
13726
13973
  let sawTokenGoatHook = false;
13727
13974
  let fileRepeats = 0;
13728
13975
  let fileRepeatsFullNoRange = 0;
@@ -13817,13 +14064,38 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
13817
14064
  toolNameById.set(block["id"], name);
13818
14065
  toolRollup(toolMap, name).calls += 1;
13819
14066
  const input = block["input"] !== null && typeof block["input"] === "object" ? block["input"] : {};
14067
+ let readNorm;
14068
+ let editNorm;
13820
14069
  if (name === "Bash" && typeof input["command"] === "string") {
13821
14070
  bashHeadById.set(block["id"], commandHead(input["command"]));
13822
14071
  } else if (name === "Read" && typeof input["file_path"] === "string") {
13823
- const norm = normalizeReadPath(input["file_path"]);
13824
- const priorEpoch = readPathEpoch.get(norm);
14072
+ readNorm = normalizeReadPath(input["file_path"]);
14073
+ const priorEpoch = readPathEpoch.get(readNorm);
13825
14074
  readCallById.set(block["id"], { wasSeenBefore: priorEpoch !== void 0, hasRange: input["offset"] !== void 0 || input["limit"] !== void 0, afterCompaction: priorEpoch !== void 0 && compactEpoch > priorEpoch });
13826
- readPathEpoch.set(norm, compactEpoch);
14075
+ readPathById.set(block["id"], readNorm);
14076
+ readPathEpoch.set(readNorm, compactEpoch);
14077
+ } else if (name === "Edit" && typeof input["file_path"] === "string") {
14078
+ editNorm = normalizeReadPath(input["file_path"]);
14079
+ editPathById.set(block["id"], editNorm);
14080
+ s.editErrorBaseline.totalEdits += 1;
14081
+ }
14082
+ if (openDenies.length > 0) {
14083
+ const bashCommand = name === "Bash" && typeof input["command"] === "string" ? input["command"] : void 0;
14084
+ for (const o of openDenies) {
14085
+ if (o.toolCalls.length >= 10) continue;
14086
+ const isReadSamePath = readNorm !== void 0 && o.path !== "" && readNorm === o.path;
14087
+ const isSurgicalSamePath = bashCommand !== void 0 && o.basename !== "" && SURGICAL_COMMAND_RE.test(bashCommand) && bashCommand.includes(o.basename);
14088
+ const shellRead = bashCommand !== void 0 && o.basename !== "" ? shellReadMatch(bashCommand, o.basename) : "none";
14089
+ const isEditSamePath = editNorm !== void 0 && o.path !== "" && editNorm === o.path;
14090
+ o.toolCalls.push({
14091
+ isReadSamePath,
14092
+ isSurgicalSamePath,
14093
+ isShellReadSamePath: shellRead === "match",
14094
+ isShellReadAmbiguous: shellRead === "ambiguous",
14095
+ editCallId: isEditSamePath ? block["id"] : void 0
14096
+ });
14097
+ if (o.toolCalls.length === 3) o.compactEpochAtWindow = compactEpoch;
14098
+ }
13827
14099
  }
13828
14100
  }
13829
14101
  let inputBytes;
@@ -13852,8 +14124,27 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
13852
14124
  if (!toolNameById.has(id)) roll.calls += 1;
13853
14125
  roll.resultBytes += bytes;
13854
14126
  roll.resultEstTokens += estimateTokensFromLength(bytes);
14127
+ if (name === "Edit" && block["is_error"] === true) {
14128
+ s.editErrorBaseline.totalErrors += 1;
14129
+ editErrorById.set(id, true);
14130
+ }
13855
14131
  if (name === "Read") {
13856
14132
  s.readInterception.readResults += 1;
14133
+ const denyTemplate = DENY_TEMPLATES.find((t) => t.re.test(text));
14134
+ if (denyTemplate !== void 0) {
14135
+ const path30 = readPathById.get(id) ?? "";
14136
+ const lastSlash = path30.lastIndexOf("/");
14137
+ openDenies.push({
14138
+ kind: denyTemplate.kind,
14139
+ withheldBytes: parseWithheldBytes(denyTemplate.kind, text),
14140
+ path: path30,
14141
+ basename: lastSlash === -1 ? path30 : path30.slice(lastSlash + 1),
14142
+ callIndexAtDeny: perCall.length,
14143
+ compactEpochAtOpen: compactEpoch,
14144
+ compactEpochAtWindow: null,
14145
+ toolCalls: []
14146
+ });
14147
+ }
13857
14148
  if (bytes < READ_DIVERT_MAX_BYTES && READ_DIVERT_MARKER_RE.test(text)) {
13858
14149
  s.readInterception.divertedByMarker += 1;
13859
14150
  s.readInterception.divertedBytes += bytes;
@@ -13990,6 +14281,42 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
13990
14281
  }
13991
14282
  flushLane(0);
13992
14283
  flushLane(1);
14284
+ for (const o of openDenies) {
14285
+ if (o.compactEpochAtWindow === null) o.compactEpochAtWindow = compactEpoch;
14286
+ const windowCalls = o.toolCalls.slice(0, 3);
14287
+ let outcome;
14288
+ if (o.compactEpochAtWindow > o.compactEpochAtOpen) {
14289
+ outcome = "compacted";
14290
+ } else if (windowCalls.some((c) => c.isReadSamePath)) {
14291
+ outcome = "retried";
14292
+ } else if (windowCalls.some((c) => c.isSurgicalSamePath)) {
14293
+ outcome = "substituted";
14294
+ } else if (windowCalls.some((c) => c.isShellReadSamePath)) {
14295
+ outcome = "shell_read";
14296
+ } else if (o.toolCalls.length < 3) {
14297
+ outcome = "unresolved";
14298
+ } else {
14299
+ outcome = "abandoned";
14300
+ }
14301
+ const retriedWithin10 = o.toolCalls.some((c) => c.isReadSamePath);
14302
+ const shellReadAmbiguous = windowCalls.some((c) => c.isShellReadAmbiguous);
14303
+ const editWithin10 = o.toolCalls.some((c) => c.editCallId !== void 0);
14304
+ const editErrorWithin10 = o.toolCalls.some((c) => c.editCallId !== void 0 && editErrorById.get(c.editCallId) === true);
14305
+ const R = perCall.length - o.callIndexAtDeny;
14306
+ const nextCall = perCall[o.callIndexAtDeny];
14307
+ denyRows.push({
14308
+ kind: o.kind,
14309
+ withheldBytes: o.withheldBytes,
14310
+ R,
14311
+ nextCallInputTotal: nextCall?.inputTotal ?? 0,
14312
+ nextCallCacheRead: nextCall?.cacheRead ?? 0,
14313
+ outcome,
14314
+ retriedWithin10,
14315
+ shellReadAmbiguous,
14316
+ editWithin10,
14317
+ editErrorWithin10
14318
+ });
14319
+ }
13993
14320
  if (sawTokenGoatHook) {
13994
14321
  s.readInterception.repeatInHookedSessions += fileRepeats;
13995
14322
  s.readInterception.repeatFullNoRangeInHookedSessions += fileRepeatsFullNoRange;
@@ -14039,8 +14366,47 @@ function hookRollup(hookMap, origin, event) {
14039
14366
  }
14040
14367
  return roll;
14041
14368
  }
14369
+ function median(arr) {
14370
+ if (arr.length === 0) return 0;
14371
+ const sorted = [...arr].sort((a, b) => a - b);
14372
+ return sorted[Math.floor(sorted.length / 2)];
14373
+ }
14374
+ function aggregateDenyOutcomes(rows) {
14375
+ const byKind = /* @__PURE__ */ new Map();
14376
+ for (const r of rows) {
14377
+ const bucket = byKind.get(r.kind);
14378
+ if (bucket === void 0) byKind.set(r.kind, [r]);
14379
+ else bucket.push(r);
14380
+ }
14381
+ const result = [];
14382
+ for (const [kind, kindRows] of byKind) {
14383
+ const count = kindRows.length;
14384
+ const rateOf = (outcome) => kindRows.filter((r) => r.outcome === outcome).length / count;
14385
+ const knownBytes = kindRows.map((r) => r.withheldBytes).filter((b) => b !== null);
14386
+ result.push({
14387
+ kind,
14388
+ count,
14389
+ compactedRate: rateOf("compacted"),
14390
+ retriedRate: rateOf("retried"),
14391
+ substitutedRate: rateOf("substituted"),
14392
+ shellReadRate: rateOf("shell_read"),
14393
+ unresolvedRate: rateOf("unresolved"),
14394
+ abandonedRate: rateOf("abandoned"),
14395
+ retriedWithin10Rate: kindRows.filter((r) => r.retriedWithin10).length / count,
14396
+ medianWithheldBytes: knownBytes.length === 0 ? null : median(knownBytes),
14397
+ withheldBytesUnknownFraction: (count - knownBytes.length) / count,
14398
+ medianR: median(kindRows.map((r) => r.R)),
14399
+ medianNextCallInputTotal: median(kindRows.map((r) => r.nextCallInputTotal)),
14400
+ medianNextCallCacheRead: median(kindRows.map((r) => r.nextCallCacheRead)),
14401
+ shellReadAmbiguousCount: kindRows.filter((r) => r.shellReadAmbiguous).length,
14402
+ editWithin10Count: kindRows.filter((r) => r.editWithin10).length,
14403
+ editErrorWithin10Count: kindRows.filter((r) => r.editErrorWithin10).length
14404
+ });
14405
+ }
14406
+ return result.sort((a, b) => b.count - a.count || a.kind.localeCompare(b.kind));
14407
+ }
14042
14408
  async function auditSessionCorpus(opts = {}) {
14043
- const corpusDir = path26.resolve(opts.dir ?? defaultCorpusDir());
14409
+ const corpusDir = path27.resolve(opts.dir ?? defaultCorpusDir());
14044
14410
  if (!fs26.existsSync(corpusDir)) {
14045
14411
  throw new Error(`session corpus directory not found: ${corpusDir}`);
14046
14412
  }
@@ -14078,6 +14444,8 @@ async function auditSessionCorpus(opts = {}) {
14078
14444
  laneAgentTypes: [],
14079
14445
  readInterception: { readResults: 0, divertedByMarker: 0, divertedBytes: 0, fullServesOver10k: 0, fullServeBytesOver10k: 0, fullServesFirstRead: 0, fullServesRepeat: 0, repeatBytes: 0, repeatWithRange: 0, repeatFullNoRange: 0, repeatInHookedSessions: 0, repeatFullNoRangeInHookedSessions: 0, repeatFullNoRangeHookedAfterCompaction: 0, fullServesPathUnknown: 0 },
14080
14446
  bashInterception: { bashResults: 0, markedByFilter: 0, markedBytes: 0, smallUntouched: 0, smallUntouchedBytes: 0, untouched: 0, untouchedBytes: 0, untouchedEstTokens: 0, untouchedRereadTokens: 0, untouchedBilledEquivTokens: 0, untouchedHeads: [] },
14447
+ denyOutcomes: [],
14448
+ editErrorBaseline: { totalEdits: 0, totalErrors: 0, rate: 0 },
14081
14449
  lineTypes: {},
14082
14450
  omissionMarkers: { fires: 0, linesOmitted: 0 }
14083
14451
  };
@@ -14086,9 +14454,10 @@ async function auditSessionCorpus(opts = {}) {
14086
14454
  const hookMap = /* @__PURE__ */ new Map();
14087
14455
  const laneObservations = [];
14088
14456
  const bashHeadMap = /* @__PURE__ */ new Map();
14457
+ const denyRows = [];
14089
14458
  for (const file of files) {
14090
14459
  try {
14091
- await auditOneFile(file, summary, toolMap, attachmentMap, hookMap, laneObservations, bashHeadMap);
14460
+ await auditOneFile(file, summary, toolMap, attachmentMap, hookMap, laneObservations, bashHeadMap, denyRows);
14092
14461
  summary.filesScanned += 1;
14093
14462
  } catch {
14094
14463
  summary.filesFailed += 1;
@@ -14125,6 +14494,8 @@ async function auditSessionCorpus(opts = {}) {
14125
14494
  }
14126
14495
  summary.attachmentKinds = [...attachmentMap.values()].sort((a, b) => b.billedEquivTokens - a.billedEquivTokens || b.injections - a.injections || a.kind.localeCompare(b.kind));
14127
14496
  summary.hookOutputs = [...hookMap.values()].sort((a, b) => b.contextBytes - a.contextBytes || b.fires - a.fires || a.origin.localeCompare(b.origin) || a.event.localeCompare(b.event));
14497
+ summary.denyOutcomes = aggregateDenyOutcomes(denyRows);
14498
+ summary.editErrorBaseline.rate = summary.editErrorBaseline.totalEdits === 0 ? 0 : summary.editErrorBaseline.totalErrors / summary.editErrorBaseline.totalEdits;
14128
14499
  summary.runtimeMs = Date.now() - started;
14129
14500
  return summary;
14130
14501
  }
@@ -14210,6 +14581,15 @@ function formatSessionAudit(s) {
14210
14581
  lines.push(`Full-serve split (same transcript file; a session's lanes are separate files, so repeats UNDER-count): first read ${fmt(ri.fullServesFirstRead)}, repeat ${fmt(ri.fullServesRepeat)} (${fmt(ri.repeatBytes)} bytes), path unknown ${fmt(ri.fullServesPathUnknown)}`);
14211
14582
  lines.push(`Repeats: with offset/limit ${fmt(ri.repeatWithRange)} (deliberate paging), whole-file ${fmt(ri.repeatFullNoRange)} (divert-miss candidates), in sessions with a token-goat hook fire ${fmt(ri.repeatInHookedSessions)} (whole-file among them: ${fmt(ri.repeatFullNoRangeInHookedSessions)}, of which post-compaction and so correct by design: ${fmt(ri.repeatFullNoRangeHookedAfterCompaction)})`);
14212
14583
  lines.push("");
14584
+ lines.push("## Deny outcomes (what actually happened after a token-goat Read deny; raw tokens, not billed units)");
14585
+ const eb = s.editErrorBaseline;
14586
+ lines.push(`Edit-error baseline (all Edit tool_results, corpus-wide, independent of any deny): ${fmt(eb.totalErrors)}/${fmt(eb.totalEdits)} (${(eb.rate * 100).toFixed(1)}%) -- compare each row's edit-error<=10 ratio against this rate.`);
14587
+ for (const d of s.denyOutcomes) {
14588
+ const editErrorRatio = d.editWithin10Count > 0 ? `${fmt(d.editErrorWithin10Count)}/${fmt(d.editWithin10Count)}` : "n/a";
14589
+ lines.push(`${d.kind.padEnd(34)} n ${fmt(d.count).padStart(6)} compacted ${pct(d.compactedRate * d.count, d.count)} retried ${pct(d.retriedRate * d.count, d.count)} substituted ${pct(d.substitutedRate * d.count, d.count)} shell-read ${pct(d.shellReadRate * d.count, d.count)} unresolved ${pct(d.unresolvedRate * d.count, d.count)} abandoned ${pct(d.abandonedRate * d.count, d.count)} retried<=10 ${pct(d.retriedWithin10Rate * d.count, d.count)} median-withheld ${d.medianWithheldBytes === null ? "n/a" : fmt(d.medianWithheldBytes) + "B"} (unknown ${pct(d.withheldBytesUnknownFraction * d.count, d.count)}) median-R ${fmt(d.medianR)} shell-read-ambiguous ${fmt(d.shellReadAmbiguousCount)} edit-error<=10 ${editErrorRatio}`);
14590
+ }
14591
+ if (s.denyOutcomes.length === 0) lines.push("(no denies matched a known template)");
14592
+ lines.push("");
14213
14593
  lines.push("## Bash filter fire-rate (token-goat in-band markers inside Bash tool results)");
14214
14594
  const bi = s.bashInterception;
14215
14595
  lines.push(`Bash results: ${fmt(bi.bashResults)}; marked by a filter: ${fmt(bi.markedByFilter)} (${fmt(bi.markedBytes)} bytes); small unmarked <${fmt(BASH_SMALL_RESULT_MAX_BYTES)} B: ${fmt(bi.smallUntouched)} (${fmt(bi.smallUntouchedBytes)} bytes)`);
@@ -14228,7 +14608,7 @@ function formatSessionAudit(s) {
14228
14608
  // src/cli_mcp_audit.ts
14229
14609
  import * as fs27 from "node:fs";
14230
14610
  import * as os13 from "node:os";
14231
- import * as path27 from "node:path";
14611
+ import * as path28 from "node:path";
14232
14612
  function readMcpJsonFile(configPath2) {
14233
14613
  try {
14234
14614
  if (!fs27.existsSync(configPath2)) return null;
@@ -14267,8 +14647,8 @@ function readClaudeJsonConfig(claudeJsonPath, projectRoot) {
14267
14647
  }
14268
14648
  }
14269
14649
  function discoverMcpConfig(projectRoot, home) {
14270
- const mcpJsonPath = path27.join(projectRoot, ".mcp.json");
14271
- const claudeJsonPath = path27.join(home, ".claude.json");
14650
+ const mcpJsonPath = path28.join(projectRoot, ".mcp.json");
14651
+ const claudeJsonPath = path28.join(home, ".claude.json");
14272
14652
  const sourcesChecked = [mcpJsonPath, claudeJsonPath];
14273
14653
  const fromMcpJson = readMcpJsonFile(mcpJsonPath);
14274
14654
  if (fromMcpJson !== null) return { servers: fromMcpJson, sourcePath: mcpJsonPath, sourcesChecked };
@@ -14387,15 +14767,14 @@ var RECALL_COMMAND = {
14387
14767
  function fenceTagForCacheType(cacheType) {
14388
14768
  return cacheType === "web" ? UNTRUSTED_WEB_TAG : UNTRUSTED_TOOL_TAG;
14389
14769
  }
14770
+ function fenceRecallListing(text, hits) {
14771
+ const tags = new Set(hits.map((hit) => fenceTagForCacheType(hit.cacheType)));
14772
+ const tag = tags.size === 1 ? [...tags][0] ?? UNTRUSTED_TOOL_TAG : UNTRUSTED_TOOL_TAG;
14773
+ return fenceUntrusted(text, tag);
14774
+ }
14390
14775
  function fenceSnippetIfMatched(hit) {
14391
- let matches2 = [];
14392
- try {
14393
- if (loadConfig().injection.enabled) matches2 = scanForInjectionPatterns(hit.snippet);
14394
- } catch {
14395
- matches2 = [];
14396
- }
14776
+ const matches2 = scanAndRecord(hit.snippet);
14397
14777
  if (matches2.length === 0) return hit.snippet;
14398
- recordStat("injection_detected", 0, 0, void 0, matches2.join(","));
14399
14778
  return fenceUntrustedContent(hit.snippet, matches2, fenceTagForCacheType(hit.cacheType));
14400
14779
  }
14401
14780
  function printHits(query, hits) {
@@ -14407,16 +14786,15 @@ function printHits(query, hits) {
14407
14786
  `);
14408
14787
  return;
14409
14788
  }
14410
- for (const hit of hits) {
14789
+ const body = hits.map((hit) => {
14411
14790
  const label = hit.label.length > 80 ? hit.label.slice(0, 77) + "..." : hit.label;
14412
- w(`[${pad(hit.cacheType, 4)}] ${hit.id} (token-goat ${RECALL_COMMAND[hit.cacheType]} ${hit.id})
14413
- `);
14414
- w(` ${label}
14415
- `);
14416
- w(` ${fenceSnippetIfMatched(hit)}
14417
-
14791
+ return `[${pad(hit.cacheType, 4)}] ${hit.id} (token-goat ${RECALL_COMMAND[hit.cacheType]} ${hit.id})
14792
+ ${label}
14793
+ ${hit.snippet}
14794
+ `;
14795
+ }).join("\n");
14796
+ w(`${fenceRecallListing(body, hits)}
14418
14797
  `);
14419
- }
14420
14798
  }
14421
14799
  function runRecallCommand(query, opts = {}) {
14422
14800
  const browse = query === void 0 || query.trim() === "";
@@ -14424,7 +14802,13 @@ function runRecallCommand(query, opts = {}) {
14424
14802
  ...opts.type !== void 0 ? { type: opts.type } : {},
14425
14803
  ...opts.limit !== void 0 ? { limit: opts.limit } : {}
14426
14804
  };
14427
- const hits = browse ? listRecentRecall(scope) : searchRecall(query, scope);
14805
+ const effectiveLimit = opts.limit ?? RECALL_DEFAULT_LIMIT;
14806
+ const fetched = browse ? listRecentRecall({ ...scope, limit: effectiveLimit + 1 }) : searchRecall(query, { ...scope, limit: effectiveLimit + 1 });
14807
+ const hits = fetched.slice(0, effectiveLimit);
14808
+ if (fetched.length > hits.length) {
14809
+ process.stderr.write(`Showing ${hits.length} entries; more are available (raise --limit to see them).
14810
+ `);
14811
+ }
14428
14812
  if (opts.json === true) {
14429
14813
  const fenced = hits.map((hit) => ({ ...hit, snippet: fenceSnippetIfMatched(hit) }));
14430
14814
  process.stdout.write(`${JSON.stringify(fenced)}
@@ -14681,6 +15065,7 @@ async function cmdSemantic(query, opts) {
14681
15065
  process.exitCode = code;
14682
15066
  }
14683
15067
  async function cmdIndex(pathArg, opts = {}) {
15068
+ applyIndexingPriority();
14684
15069
  const root = pathArg ?? process.cwd();
14685
15070
  const dbPath = opts.dbPath ?? globalDbPath();
14686
15071
  const force = opts.force === true;
@@ -14823,7 +15208,7 @@ async function cmdMcpServe() {
14823
15208
  let StdioServerTransport;
14824
15209
  try {
14825
15210
  ;
14826
- ({ createMcpServer } = await import("./token-goat-chunk-LZOAPGWR.mjs"));
15211
+ ({ createMcpServer } = await import("./token-goat-chunk-CD6U5OD6.mjs"));
14827
15212
  ({ StdioServerTransport } = await import("./token-goat-chunk-324QOJYZ.mjs"));
14828
15213
  } catch (err2) {
14829
15214
  process.stderr.write(
@@ -14836,8 +15221,8 @@ async function cmdMcpServe() {
14836
15221
  const server = await createMcpServer();
14837
15222
  const transport = new StdioServerTransport();
14838
15223
  await server.connect(transport);
14839
- await new Promise((resolve13) => {
14840
- server.onclose = resolve13;
15224
+ await new Promise((resolve14) => {
15225
+ server.onclose = resolve14;
14841
15226
  });
14842
15227
  }
14843
15228
  function printBridgeVerificationNotice(harness) {
@@ -14848,7 +15233,7 @@ async function cmdHook(event, opts) {
14848
15233
  if (typeof opts.harness === "string" && opts.harness.length > 0) {
14849
15234
  process.env[ENV_KEYS.HARNESS_OVERRIDE] = opts.harness;
14850
15235
  }
14851
- const { relay } = await import("./token-goat-chunk-L6YP6FKQ.mjs");
15236
+ const { relay } = await import("./token-goat-chunk-AUM22UQ2.mjs");
14852
15237
  await relay(event);
14853
15238
  }
14854
15239
  async function cmdInstall(opts) {
@@ -14961,14 +15346,14 @@ async function cmdInstall(opts) {
14961
15346
  );
14962
15347
  }
14963
15348
  try {
14964
- const skillDir = path28.join(homedir13(), ".claude", "skills");
15349
+ const skillDir = path29.join(homedir13(), ".claude", "skills");
14965
15350
  if (fs28.existsSync(skillDir)) {
14966
15351
  const entries = fs28.readdirSync(skillDir, { withFileTypes: true });
14967
15352
  const skillNames = [];
14968
15353
  const sessionId = getSessionId();
14969
15354
  for (const entry of entries) {
14970
15355
  if (!entry.isDirectory()) continue;
14971
- const skillFile = path28.join(skillDir, entry.name, "SKILL.md");
15356
+ const skillFile = path29.join(skillDir, entry.name, "SKILL.md");
14972
15357
  if (fs28.existsSync(skillFile)) {
14973
15358
  const body = fs28.readFileSync(skillFile, "utf-8");
14974
15359
  const compact = extractCompactFromMarker(body);
@@ -14981,7 +15366,7 @@ async function cmdInstall(opts) {
14981
15366
  if (skillNames.length > 0) {
14982
15367
  const dir = skillOutputsDir();
14983
15368
  await fs28.promises.mkdir(dir, { recursive: true });
14984
- const pregenPath = path28.join(dir, "pregen.json");
15369
+ const pregenPath = path29.join(dir, "pregen.json");
14985
15370
  const pregenData = { ts: Date.now(), names: skillNames };
14986
15371
  await fs28.promises.writeFile(pregenPath, JSON.stringify(pregenData, null, 2));
14987
15372
  out(`Pre-generated ${skillNames.length} skill compacts.`);
@@ -15182,7 +15567,7 @@ async function cmdSessionOutline(sessionIdOrPath, opts = {}) {
15182
15567
  ${formatSessionOutline(turns)}`;
15183
15568
  out(text);
15184
15569
  const fullSourceBytes = sessionTranscriptSize(transcriptPath);
15185
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
15570
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
15186
15571
  recordStat("session_outline", bytesSaved, savedTokensFromBytes(bytesSaved));
15187
15572
  }
15188
15573
  function sessionTranscriptSize(transcriptPath) {
@@ -15204,7 +15589,7 @@ async function cmdSessionSlice(sessionIdOrPath, opts) {
15204
15589
  const text = opts.json === true ? JSON.stringify({ transcriptPath, turns }) : formatSessionSlice(turns);
15205
15590
  out(text);
15206
15591
  const fullSourceBytes = sessionTranscriptSize(transcriptPath);
15207
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
15592
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
15208
15593
  recordStat("session_slice", bytesSaved, savedTokensFromBytes(bytesSaved));
15209
15594
  }
15210
15595
  function cmdMcpAudit(opts = {}) {
@@ -15244,24 +15629,13 @@ function cmdHintStats(opts = {}) {
15244
15629
  ...opts.markIneffective !== void 0 && isHintCategory(opts.markIneffective) ? { markIneffective: opts.markIneffective } : {}
15245
15630
  });
15246
15631
  }
15247
- function _applyFiltersAndPrint(content, opts, fenceUntrusted = false, fenceTag = UNTRUSTED_WEB_TAG) {
15248
- const emit2 = (text) => {
15249
- if (!fenceUntrusted || text === "") {
15250
- out(text);
15251
- return text;
15252
- }
15253
- let matches2 = [];
15254
- try {
15255
- if (loadConfig().injection.enabled) matches2 = scanForInjectionPatterns(text);
15256
- } catch {
15257
- matches2 = [];
15258
- }
15259
- if (matches2.length === 0) {
15632
+ function _applyFiltersAndPrint(content, opts, fenceByProvenance = false, fenceTag = UNTRUSTED_WEB_TAG) {
15633
+ const emit3 = (text) => {
15634
+ if (!fenceByProvenance || text === "") {
15260
15635
  out(text);
15261
15636
  return text;
15262
15637
  }
15263
- recordStat("injection_detected", 0, 0, void 0, matches2.join(","));
15264
- const fenced = fenceUntrustedContent(text, matches2, fenceTag);
15638
+ const fenced = fenceUntrusted(text, fenceTag);
15265
15639
  out(fenced);
15266
15640
  return fenced;
15267
15641
  };
@@ -15293,12 +15667,17 @@ function _applyFiltersAndPrint(content, opts, fenceUntrusted = false, fenceTag =
15293
15667
  }
15294
15668
  const rawLines = content.split(/\r?\n/);
15295
15669
  if (opts.full === true) {
15296
- return emit2(rawLines.join("\n"));
15670
+ return emit3(rawLines.join("\n"));
15297
15671
  }
15298
15672
  const lines = rawLines.length > 1 && rawLines[rawLines.length - 1] === "" ? rawLines.slice(0, -1) : rawLines;
15299
15673
  const headN = opts.head !== void 0 ? requireNonNegativeInt("--head", opts.head) : 30;
15300
15674
  const tailN = opts.tail !== void 0 ? requireNonNegativeInt("--tail", opts.tail) : 80;
15301
15675
  const applyElision = (lines2, headN2, tailN2) => lines2.length > headN2 + tailN2 + 1 ? [...lines2.slice(0, headN2), "...(elided)...", ...lines2.slice(lines2.length - tailN2)] : lines2;
15676
+ const noteLineCap = (which, flag, shown, total) => {
15677
+ if (shown >= total) return;
15678
+ process.stderr.write(`Showing ${which} ${shown} of ${total} lines (raise --${flag}, or --full for the whole body).
15679
+ `);
15680
+ };
15302
15681
  let result = lines;
15303
15682
  if (opts.head === void 0 && opts.tail === void 0) {
15304
15683
  result = applyElision(lines, headN, tailN);
@@ -15306,20 +15685,19 @@ function _applyFiltersAndPrint(content, opts, fenceUntrusted = false, fenceTag =
15306
15685
  result = applyElision(lines, headN, tailN);
15307
15686
  } else if (opts.head !== void 0) {
15308
15687
  result = lines.slice(0, headN);
15688
+ noteLineCap("first", "head", result.length, lines.length);
15309
15689
  } else if (opts.tail !== void 0) {
15310
15690
  result = lines.slice(Math.max(0, lines.length - tailN));
15691
+ noteLineCap("last", "tail", result.length, lines.length);
15311
15692
  }
15312
- return emit2(result.join("\n"));
15693
+ return emit3(result.join("\n"));
15313
15694
  }
15314
- function fenceFileTextIfMatched(text) {
15315
- let matches2 = [];
15316
- try {
15317
- if (loadConfig().injection.enabled) matches2 = scanForInjectionPatterns(text);
15318
- } catch {
15319
- matches2 = [];
15320
- }
15695
+ function fenceFileText(text) {
15696
+ return fenceUntrusted(text, UNTRUSTED_FILE_TAG);
15697
+ }
15698
+ function fenceFileFieldIfMatched(text) {
15699
+ const matches2 = scanAndRecord(text);
15321
15700
  if (matches2.length === 0) return text;
15322
- recordStat("injection_detected", 0, 0, void 0, matches2.join(","));
15323
15701
  return fenceUntrustedContent(text, matches2, UNTRUSTED_FILE_TAG);
15324
15702
  }
15325
15703
  function fileSizeOrZero(filePath) {
@@ -15387,7 +15765,7 @@ async function cmdPdfExtract(file, opts) {
15387
15765
  const text = await runPdfExtractText(file, opts.pages, opts.layout === true);
15388
15766
  const printed = _applyFiltersAndPrint(text, opts, true, UNTRUSTED_FILE_TAG);
15389
15767
  const fullSourceBytes = fileSizeOrZero(file);
15390
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(printed, "utf8"));
15768
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(printed, "utf8"));
15391
15769
  recordStat("pdf_extract", bytesSaved, savedTokensFromBytes(bytesSaved));
15392
15770
  }
15393
15771
  async function cmdPdfLocate(file, pattern, opts) {
@@ -15398,24 +15776,26 @@ async function cmdPdfLocate(file, pattern, opts) {
15398
15776
  if (opts.context !== void 0) locateOpts.context = requirePositiveInt("--context", opts.context);
15399
15777
  if (opts.pages !== void 0) locateOpts.pages = opts.pages;
15400
15778
  const matches2 = await runPdfLocate(file, pattern, locateOpts);
15401
- const fencedMatches = matches2.map((m) => ({ ...m, snippet: fenceFileTextIfMatched(m.snippet) }));
15402
- const pages = fencedMatches.map((m) => m.page);
15779
+ const pages = matches2.map((m) => m.page);
15403
15780
  let printed;
15404
15781
  if (opts.json === true) {
15782
+ const fencedMatches = matches2.map((m) => ({ ...m, snippet: fenceFileFieldIfMatched(m.snippet) }));
15405
15783
  printed = JSON.stringify({ file, pattern, matchCount: fencedMatches.length, pages, matches: fencedMatches }, null, 2);
15406
15784
  out(printed);
15407
- } else if (fencedMatches.length === 0) {
15785
+ } else if (matches2.length === 0) {
15408
15786
  printed = "(no matches)";
15409
15787
  out(printed);
15410
15788
  } else {
15411
- const lines = fencedMatches.map((m) => `p${m.page}: ${m.snippet}`);
15412
- printed = `${lines.join("\n")}
15789
+ const lines = matches2.map((m) => `p${m.page}: ${m.snippet}`);
15790
+ printed = fenceFileText(
15791
+ `${lines.join("\n")}
15413
15792
 
15414
- ${countNoun(fencedMatches.length, "match", "matches")} across ${countNoun(pages.length, "page")}`;
15793
+ ${countNoun(matches2.length, "match", "matches")} across ${countNoun(pages.length, "page")}`
15794
+ );
15415
15795
  out(printed);
15416
15796
  }
15417
15797
  const fullSourceBytes = fileSizeOrZero(file);
15418
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(printed, "utf8"));
15798
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(printed, "utf8"));
15419
15799
  recordStat("pdf_locate", bytesSaved, savedTokensFromBytes(bytesSaved));
15420
15800
  }
15421
15801
  async function cmdPdfOutline(file, opts) {
@@ -15428,11 +15808,10 @@ async function cmdPdfOutline(file, opts) {
15428
15808
  }
15429
15809
  return;
15430
15810
  }
15431
- const fencedEntries = entries.map((e) => ({ ...e, title: fenceFileTextIfMatched(e.title) }));
15432
- const text = opts.json === true ? JSON.stringify(fencedEntries, null, 2) : fencedEntries.map((e) => `${" ".repeat(e.level)}${e.title}${e.page !== null ? ` (p.${e.page})` : ""}`).join("\n");
15811
+ const text = opts.json === true ? JSON.stringify(entries.map((e) => ({ ...e, title: fenceFileFieldIfMatched(e.title) })), null, 2) : fenceFileText(entries.map((e) => `${" ".repeat(e.level)}${e.title}${e.page !== null ? ` (p.${e.page})` : ""}`).join("\n"));
15433
15812
  out(text);
15434
15813
  const fullSourceBytes = fileSizeOrZero(file);
15435
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
15814
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
15436
15815
  recordStat("pdf_outline", bytesSaved, savedTokensFromBytes(bytesSaved));
15437
15816
  }
15438
15817
  async function cmdPdfMeta(file, opts = {}) {
@@ -15446,7 +15825,7 @@ async function cmdPdfMeta(file, opts = {}) {
15446
15825
  const text = opts.json === true ? JSON.stringify({ pageCount: meta.pageCount, title: meta.title, author: meta.author, hasTextLayer: meta.hasTextLayer }, null, 2) : lines.join("\n");
15447
15826
  out(text);
15448
15827
  const fullSourceBytes = fileSizeOrZero(file);
15449
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
15828
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
15450
15829
  recordStat("pdf_meta", bytesSaved, savedTokensFromBytes(bytesSaved));
15451
15830
  }
15452
15831
  async function cmdImageMeta(file, opts = {}) {
@@ -15468,17 +15847,12 @@ ${msg}`;
15468
15847
  const text = opts.json === true ? JSON.stringify(meta, null, 2) : lines.join("\n");
15469
15848
  out(text);
15470
15849
  const fullSourceBytes = fileSizeOrZero(file);
15471
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
15472
- recordStat("image_meta", bytesSaved, savedTokensFromBytes(bytesSaved));
15850
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
15851
+ recordStat("image_meta", bytesSaved, visionTokensSavedByText(meta.width, meta.height, Buffer.byteLength(text, "utf8"), loadConfig().image_shrink.vision_tier));
15473
15852
  }
15474
15853
  function fenceOcrText(text) {
15475
- try {
15476
- if (loadConfig().injection.enabled) {
15477
- const matches2 = scanForInjectionPatterns(text);
15478
- if (matches2.length > 0) recordStat("injection_detected", 0, 0, void 0, matches2.join(","));
15479
- }
15480
- } catch {
15481
- }
15854
+ if (!injectionFencingEnabled()) return text;
15855
+ scanAndRecord(text);
15482
15856
  return fenceUntrustedOcrText(text);
15483
15857
  }
15484
15858
  async function cmdImageText(file, opts = {}) {
@@ -15505,8 +15879,8 @@ ${fenceOcrText(result.text)}` : `${lines.join("\n")}
15505
15879
  }
15506
15880
  out(text);
15507
15881
  const fullSourceBytes = fileSizeOrZero(file);
15508
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
15509
- recordStat("image_text", bytesSaved, savedTokensFromBytes(bytesSaved));
15882
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
15883
+ recordStat("image_text", bytesSaved, visionTokensSavedByText(null, null, Buffer.byteLength(text, "utf8"), loadConfig().image_shrink.vision_tier));
15510
15884
  }
15511
15885
  function cmdVideoChapters(file) {
15512
15886
  const { chapters, subtitleStreams } = extractVideoChapters(file);
@@ -15531,7 +15905,7 @@ function cmdVideoChapters(file) {
15531
15905
  const text = lines.join("\n");
15532
15906
  out(text);
15533
15907
  const fullSourceBytes = fileSizeOrZero(file);
15534
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
15908
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
15535
15909
  recordStat("video_chapters", bytesSaved, savedTokensFromBytes(bytesSaved));
15536
15910
  }
15537
15911
  function formatVideoTimestamp(totalSeconds) {
@@ -15558,25 +15932,24 @@ function cmdSharepointResolve(url) {
15558
15932
  }
15559
15933
  function recordXlsxStat(kind, file, emitted) {
15560
15934
  const fullSourceBytes = fileSizeOrZero(file);
15561
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(emitted, "utf8"));
15935
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(emitted, "utf8"));
15562
15936
  recordStat(kind, bytesSaved, savedTokensFromBytes(bytesSaved));
15563
15937
  }
15564
15938
  async function cmdXlsxSheets(file, opts = {}) {
15565
15939
  const sheets = await listSheets(file);
15566
- const fencedSheets = sheets.map((s) => ({ ...s, name: fenceFileTextIfMatched(s.name) }));
15567
- const text = opts.json === true ? JSON.stringify(fencedSheets.map((s) => ({ name: s.name, ref: s.ref, rows: s.rows, cols: s.cols })), null, 2) : fencedSheets.map((s) => `${s.name} ${s.ref} (${s.rows} rows x ${s.cols} cols)`).join("\n");
15940
+ const text = opts.json === true ? JSON.stringify(sheets.map((s) => ({ name: fenceFileFieldIfMatched(s.name), ref: s.ref, rows: s.rows, cols: s.cols })), null, 2) : fenceFileText(sheets.map((s) => `${s.name} ${s.ref} (${s.rows} rows x ${s.cols} cols)`).join("\n"));
15568
15941
  out(text);
15569
15942
  recordXlsxStat("xlsx_sheets", file, text);
15570
15943
  }
15571
15944
  async function cmdXlsxHead(file, opts) {
15572
15945
  const rows = opts.rows !== void 0 ? requireNonNegativeInt("--rows", opts.rows) : 20;
15573
- const text = fenceFileTextIfMatched(await headSheet(file, opts.sheet, rows));
15946
+ const text = fenceFileText(await headSheet(file, opts.sheet, rows));
15574
15947
  out(text);
15575
15948
  recordXlsxStat("xlsx_head", file, text);
15576
15949
  }
15577
15950
  async function cmdXlsxRange(file, opts) {
15578
15951
  const result = await rangeSheet(file, opts.sheet, opts.range, opts.formulas === true);
15579
- const text = fenceFileTextIfMatched(formatXlsxRange(result));
15952
+ const text = fenceFileText(formatXlsxRange(result));
15580
15953
  out(text);
15581
15954
  recordXlsxStat("xlsx_range", file, text);
15582
15955
  }
@@ -15588,32 +15961,33 @@ async function cmdXlsxQuery(file, opts) {
15588
15961
  ...wheres !== void 0 ? { wheres } : {},
15589
15962
  ...opts.head !== void 0 ? { head: requireNonNegativeInt("--head", opts.head) } : {}
15590
15963
  });
15591
- const text = fenceFileTextIfMatched(formatCsvTable(result, (opts.where ?? []).map((w) => `--where ${w}`)));
15964
+ const text = fenceFileText(formatCsvTable(result, (opts.where ?? []).map((w) => `--where ${w}`)));
15592
15965
  out(text);
15593
15966
  recordXlsxStat("xlsx_query", file, text);
15594
15967
  }
15595
15968
  function recordDocStat(kind, file, emitted) {
15596
15969
  const fullSourceBytes = fileSizeOrZero(file);
15597
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(emitted, "utf8"));
15970
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(emitted, "utf8"));
15598
15971
  recordStat(kind, bytesSaved, savedTokensFromBytes(bytesSaved));
15599
15972
  }
15600
15973
  async function cmdPptxOutline(file, opts) {
15601
15974
  const slides = await pptxOutline(file);
15602
- const fencedSlides = slides.map((s) => ({ ...s, title: fenceFileTextIfMatched(s.title) }));
15603
- const text = opts.json === true ? JSON.stringify(fencedSlides, null, 2) : fencedSlides.map((s) => `${s.slide}. ${s.title || "(untitled)"} [${s.bodyChars} body chars${s.hasNotes ? ", has notes" : ""}]`).join("\n");
15975
+ const text = opts.json === true ? JSON.stringify(slides.map((s) => ({ ...s, title: fenceFileFieldIfMatched(s.title) })), null, 2) : fenceFileText(
15976
+ slides.map((s) => `${s.slide}. ${s.title || "(untitled)"} [${s.bodyChars} body chars${s.hasNotes ? ", has notes" : ""}]`).join("\n")
15977
+ );
15604
15978
  out(text);
15605
15979
  recordDocStat("pptx_outline", file, text);
15606
15980
  }
15607
15981
  async function cmdPptxSlide(file, opts) {
15608
15982
  const n = requireNonNegativeInt("--slide", opts.slide);
15609
- const text = fenceFileTextIfMatched(await pptxSlideText(file, n, opts.notes === true));
15983
+ const text = fenceFileText(await pptxSlideText(file, n, opts.notes === true));
15610
15984
  out(text);
15611
15985
  recordDocStat("pptx_slide", file, text);
15612
15986
  }
15613
15987
  async function cmdPptxNotes(file, opts) {
15614
15988
  const n = opts.slide !== void 0 ? requireNonNegativeInt("--slide", opts.slide) : void 0;
15615
15989
  const text = await pptxNotesText(file, n);
15616
- const printed = text.length > 0 ? fenceFileTextIfMatched(text) : "no speaker notes found";
15990
+ const printed = text.length > 0 ? fenceFileText(text) : "no speaker notes found";
15617
15991
  out(printed);
15618
15992
  recordDocStat("pptx_notes", file, printed);
15619
15993
  }
@@ -15623,7 +15997,7 @@ async function cmdPptxText(file, opts) {
15623
15997
  out("no matches");
15624
15998
  return;
15625
15999
  }
15626
- const text = fenceFileTextIfMatched(matches2.map((m) => `Slide ${m.slide}: ...${m.snippet}...`).join("\n"));
16000
+ const text = fenceFileText(matches2.map((m) => `Slide ${m.slide}: ...${m.snippet}...`).join("\n"));
15627
16001
  out(text);
15628
16002
  recordDocStat("pptx_text", file, text);
15629
16003
  }
@@ -15637,8 +16011,7 @@ async function cmdDocxOutline(file, opts) {
15637
16011
  }
15638
16012
  return;
15639
16013
  }
15640
- const fencedHeadings = headings.map((h) => ({ ...h, text: fenceFileTextIfMatched(h.text) }));
15641
- const text = opts.json === true ? JSON.stringify(fencedHeadings, null, 2) : fencedHeadings.map((h) => `${" ".repeat(h.level - 1)}${h.text}`).join("\n");
16014
+ const text = opts.json === true ? JSON.stringify(headings.map((h) => ({ ...h, text: fenceFileFieldIfMatched(h.text) })), null, 2) : fenceFileText(headings.map((h) => `${" ".repeat(h.level - 1)}${h.text}`).join("\n"));
15642
16015
  out(text);
15643
16016
  recordDocStat("docx_outline", file, text);
15644
16017
  }
@@ -15752,6 +16125,18 @@ function cmdConflicts(targetPath, opts) {
15752
16125
  async function cmdScreenshot(url, destPath, opts) {
15753
16126
  out(await runScreenshot(url, destPath, opts));
15754
16127
  }
16128
+ function readStdinPaths() {
16129
+ if (process.stdin.isTTY) {
16130
+ throw new CliError("--stdin requires piped input, e.g. `git diff --name-only | token-goat affected --stdin`");
16131
+ }
16132
+ let raw;
16133
+ try {
16134
+ raw = fs28.readFileSync(0, "utf8");
16135
+ } catch (e) {
16136
+ throw new CliError(`could not read the file list from stdin: ${extractErrorMessage(e)}`);
16137
+ }
16138
+ return raw.split("\n").map((line) => line.trim()).filter((line) => line !== "");
16139
+ }
15755
16140
  function runExit(fn) {
15756
16141
  try {
15757
16142
  process.exitCode = fn();
@@ -15782,7 +16167,7 @@ function emitExtraFileArgsNote(command, first, extras, opts = {}) {
15782
16167
  }
15783
16168
  async function cmdCompress(opts) {
15784
16169
  try {
15785
- const bashRunner = await import("./token-goat-chunk-NZFTWBGV.mjs");
16170
+ const bashRunner = await import("./token-goat-chunk-4KD2N46D.mjs");
15786
16171
  if (opts.compress === false) {
15787
16172
  process.exitCode = bashRunner.runRaw(opts.cmd, parseTimeout(opts.timeout, bashRunner.DEFAULT_TIMEOUT_SECONDS));
15788
16173
  return;
@@ -15880,8 +16265,8 @@ async function cmdSkillCompact(name, opts) {
15880
16265
  }
15881
16266
  throw new CliError(`failed to read skill file '${opts.path}': ${extractErrorMessage(e)}`);
15882
16267
  }
15883
- cacheName = name ?? path28.basename(path28.dirname(path28.resolve(opts.path)));
15884
- sourcePath = path28.resolve(opts.path);
16268
+ cacheName = name ?? path29.basename(path29.dirname(path29.resolve(opts.path)));
16269
+ sourcePath = path29.resolve(opts.path);
15885
16270
  } else {
15886
16271
  if (name === void 0 || !name.trim()) {
15887
16272
  throw new CliError("skill-compact requires a <name> or --path <file>");
@@ -16022,8 +16407,8 @@ async function cmdSkillDiff(name) {
16022
16407
  }
16023
16408
  const newer = versions[0];
16024
16409
  const older = versions[1];
16025
- const newerBody = await fs28.promises.readFile(path28.resolve(dir, `${newer.outputId}.txt`), "utf-8").catch(() => null);
16026
- const olderBody = await fs28.promises.readFile(path28.resolve(dir, `${older.outputId}.txt`), "utf-8").catch(() => null);
16410
+ const newerBody = await fs28.promises.readFile(path29.resolve(dir, `${newer.outputId}.txt`), "utf-8").catch(() => null);
16411
+ const olderBody = await fs28.promises.readFile(path29.resolve(dir, `${older.outputId}.txt`), "utf-8").catch(() => null);
16027
16412
  if (newerBody === null || olderBody === null) {
16028
16413
  out(`a cached version of '${name}' was evicted while diffing -- try again`);
16029
16414
  return;
@@ -16075,7 +16460,7 @@ function atomicWriteBuffer(dest, data) {
16075
16460
  if (e.code !== "ENOENT") throw e;
16076
16461
  }
16077
16462
  const rnd = randomBytes(4).toString("hex");
16078
- const tmp = path28.join(path28.dirname(path28.resolve(dest)), `.tmp.${process.pid}.${rnd}`);
16463
+ const tmp = path29.join(path29.dirname(path29.resolve(dest)), `.tmp.${process.pid}.${rnd}`);
16079
16464
  try {
16080
16465
  fs28.writeFileSync(tmp, data, { mode: 384 });
16081
16466
  try {
@@ -16113,9 +16498,9 @@ function mapFsError(e, src, dest, srcLabel = "source") {
16113
16498
  const fe = e;
16114
16499
  if (fe.code === "ENOENT") {
16115
16500
  const errPath = fe.path ?? "";
16116
- const isSource = src !== void 0 && path28.resolve(errPath) === path28.resolve(src);
16501
+ const isSource = src !== void 0 && path29.resolve(errPath) === path29.resolve(src);
16117
16502
  if (isSource) throw new CliError(`${/\bfile$/i.test(srcLabel) ? srcLabel : `${srcLabel} file`} not found: ${src}`);
16118
- const destDir = dest ? path28.dirname(path28.resolve(dest)) : path28.dirname(path28.resolve(errPath || "."));
16503
+ const destDir = dest ? path29.dirname(path29.resolve(dest)) : path29.dirname(path29.resolve(errPath || "."));
16119
16504
  throw new CliError(`destination directory does not exist: ${destDir}`);
16120
16505
  }
16121
16506
  if (fe.code === "ENOTDIR") {
@@ -16126,7 +16511,7 @@ function mapFsError(e, src, dest, srcLabel = "source") {
16126
16511
  }
16127
16512
  if (fe.code === "EISDIR") {
16128
16513
  const errPath = fe.path ?? "";
16129
- const isSource = src !== void 0 && (errPath === "" || path28.resolve(errPath) === path28.resolve(src));
16514
+ const isSource = src !== void 0 && (errPath === "" || path29.resolve(errPath) === path29.resolve(src));
16130
16515
  if (isSource) throw new CliError(`source is a directory, not a file: ${src}`);
16131
16516
  throw new CliError(`destination is a directory, not a file: ${dest ?? (errPath || "(unknown)")}`);
16132
16517
  }
@@ -16195,7 +16580,7 @@ function validateWritablePath(dest, label) {
16195
16580
  throw new CliError(`${label} path contains a null byte`);
16196
16581
  }
16197
16582
  if (isWindows()) {
16198
- const base = path28.basename(dest);
16583
+ const base = path29.basename(dest);
16199
16584
  const stem = base.replace(/\.[^.]*$/, "").toUpperCase();
16200
16585
  if (WIN_RESERVED.has(stem)) {
16201
16586
  throw new CliError(`${label} '${base}' is a reserved Windows device name`);
@@ -16336,7 +16721,7 @@ function cmdWriteFile(dest, opts) {
16336
16721
  throw new CliError(`TOKEN_GOAT_MAX_STDIN_MB must be a positive integer; got '${process.env["TOKEN_GOAT_MAX_STDIN_MB"] ?? ""}'`);
16337
16722
  }
16338
16723
  const maxBytes = maxMB * 1024 * 1024;
16339
- return new Promise((resolve13, reject) => {
16724
+ return new Promise((resolve14, reject) => {
16340
16725
  const chunks = [];
16341
16726
  let totalBytes = 0;
16342
16727
  let settled = false;
@@ -16360,7 +16745,7 @@ function cmdWriteFile(dest, opts) {
16360
16745
  try {
16361
16746
  atomicWriteBuffer(dest, Buffer.concat(chunks));
16362
16747
  enqueueDirtyPathSafe(dest);
16363
- resolve13();
16748
+ resolve14();
16364
16749
  } catch (e) {
16365
16750
  try {
16366
16751
  mapFsError(e, void 0, dest);
@@ -16710,19 +17095,9 @@ ${content}`;
16710
17095
  const sections = await getDocSections(fileId, { fresh: false });
16711
17096
  emitted = formatSections(sections);
16712
17097
  }
16713
- let toEmit = emitted;
16714
- try {
16715
- if (loadConfig().injection.enabled) {
16716
- const matches2 = scanForInjectionPatterns(emitted);
16717
- if (matches2.length > 0) {
16718
- recordStat("injection_detected", 0, 0, void 0, matches2.join(","));
16719
- toEmit = fenceUntrustedContent(emitted, matches2, UNTRUSTED_WEB_TAG);
16720
- }
16721
- }
16722
- } catch {
16723
- }
17098
+ const toEmit = fenceUntrusted(emitted, UNTRUSTED_WEB_TAG);
16724
17099
  out(toEmit);
16725
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(toEmit, "utf8"));
17100
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(toEmit, "utf8"));
16726
17101
  recordStat("gdrive_sections", bytesSaved, savedTokensFromBytes(bytesSaved));
16727
17102
  }
16728
17103
  function expandGlobs(root, patterns, globFnOverride) {
@@ -16732,17 +17107,17 @@ function expandGlobs(root, patterns, globFnOverride) {
16732
17107
  if (globFn !== void 0 && (p.includes("*") || p.includes("?") || p.includes("{"))) {
16733
17108
  try {
16734
17109
  const hits = globFn(p, { cwd: root });
16735
- for (const h of hits) out2.push(path28.isAbsolute(h) ? h : path28.join(root, h));
17110
+ for (const h of hits) out2.push(path29.isAbsolute(h) ? h : path29.join(root, h));
16736
17111
  continue;
16737
17112
  } catch {
16738
17113
  }
16739
17114
  }
16740
- out2.push(path28.isAbsolute(p) ? p : path28.join(root, p));
17115
+ out2.push(path29.isAbsolute(p) ? p : path29.join(root, p));
16741
17116
  }
16742
17117
  return out2;
16743
17118
  }
16744
17119
  function readIgnoreFile(root) {
16745
- const ignorePath = path28.join(root, ".tokengoatignore");
17120
+ const ignorePath = path29.join(root, ".tokengoatignore");
16746
17121
  let raw;
16747
17122
  try {
16748
17123
  raw = fs28.readFileSync(ignorePath, "utf8");
@@ -16803,15 +17178,17 @@ function cmdTokens(patterns, opts) {
16803
17178
  const result = estimateBudget(root, expandGlobs(root, patterns ?? []));
16804
17179
  let entries = [...result.entries];
16805
17180
  if (opts.asc === true) entries.reverse();
17181
+ const eligibleCount = entries.length;
16806
17182
  if (opts.top !== void 0) entries = entries.slice(0, requireNonNegativeInt("--top", opts.top));
17183
+ const truncated = entries.length < eligibleCount;
16807
17184
  if (opts.json === true) {
16808
- out(JSON.stringify({ entries, total_tokens: result.total_tokens, total_lines: result.total_lines }, null, 2));
17185
+ out(JSON.stringify({ entries, truncated, totalCount: eligibleCount, total_tokens: result.total_tokens, total_lines: result.total_lines }, null, 2));
16809
17186
  return;
16810
17187
  }
16811
17188
  if (opts.tree === true) {
16812
17189
  const dirs = /* @__PURE__ */ new Map();
16813
17190
  for (const e of entries) {
16814
- const dir = path28.dirname(e.rel_path);
17191
+ const dir = path29.dirname(e.rel_path);
16815
17192
  if (!dirs.has(dir)) dirs.set(dir, []);
16816
17193
  dirs.get(dir).push(e);
16817
17194
  }
@@ -16821,7 +17198,7 @@ function cmdTokens(patterns, opts) {
16821
17198
  const pct2 = result.total_tokens > 0 ? Math.round(dirTokens / result.total_tokens * 100) : 0;
16822
17199
  lines2.push(`${dir}/ (${dirTokens} tokens, ${pct2}%)`);
16823
17200
  for (const e of dirEntries) {
16824
- lines2.push(` ${path28.basename(e.rel_path).padEnd(30)} ${String(e.tokens).padStart(8)} tokens`);
17201
+ lines2.push(` ${path29.basename(e.rel_path).padEnd(30)} ${String(e.tokens).padStart(8)} tokens`);
16825
17202
  }
16826
17203
  }
16827
17204
  out(lines2.join("\n"));
@@ -16839,6 +17216,9 @@ function cmdTokens(patterns, opts) {
16839
17216
  for (const e of entries) {
16840
17217
  lines.push(`${e.rel_path.padEnd(colW)} ${String(e.tokens).padStart(8)} ${String(e.lines).padStart(6)}`);
16841
17218
  }
17219
+ if (truncated) {
17220
+ lines.push(`...and ${eligibleCount - entries.length} more (raise --top to see them).`);
17221
+ }
16842
17222
  out(lines.join("\n"));
16843
17223
  }
16844
17224
  function cmdBudget(patterns, opts) {
@@ -16869,6 +17249,63 @@ function cmdFailures(src, opts) {
16869
17249
  saveFailureSnapshot(project.hash, key, { signatures, runner: result.runner, storedAt: Date.now() });
16870
17250
  out(opts.json === true ? formatFailureDeltaJson(delta, result.runner) : formatFailureDeltaText(delta, result.runner));
16871
17251
  }
17252
+ var originalHelpInformation = null;
17253
+ function generateCompactHelp() {
17254
+ const lines = [
17255
+ "Usage: token-goat [options] [command]",
17256
+ "",
17257
+ "Surgical token-reduction companion for AI coding agents",
17258
+ "",
17259
+ "Options:",
17260
+ " -v, --version print the token-goat version",
17261
+ " --cwd <path> run as if invoked from this directory",
17262
+ " -h, --help display help for command",
17263
+ "",
17264
+ "Reads: symbol, read, brief, section, semantic, skeleton, outline, refs, scope,",
17265
+ " similar, context-for, call-chain, impact, callers, dead, test-for",
17266
+ "",
17267
+ "Analysis: arch, affected, blame, deps, types, coverage-gaps, changed, diff, log,",
17268
+ " exports, imports, find, grep",
17269
+ "",
17270
+ "File Formats: pdf-meta, pdf-outline, pdf-extract, pdf-locate, xlsx-sheets,",
17271
+ " xlsx-head, xlsx-query, xlsx-range, yaml-outline, yaml-query, json-outline,",
17272
+ " json-query, xml-outline, xml-query, docx-outline, docx-text, pptx-outline,",
17273
+ " pptx-slide, pptx-text, pptx-notes",
17274
+ "",
17275
+ "Index & Search: index, map, reconcile, doctor, commands, ask, pack, tokens,",
17276
+ " budget, failures, todo, trace, logfold, lockdeps, dep-docs, recent, hot,",
17277
+ " snapshot-snapshot, baseline, cost, coverage-report-gaps, clean-cache,",
17278
+ " cache-audit, reclaim-index, project",
17279
+ "",
17280
+ "Session: stats, waste, session-outline, session-audit, session-slice,",
17281
+ " session-summary, context-stats, bootstrap-audit, memory, hint-stats,",
17282
+ " mcp-audit",
17283
+ "",
17284
+ "Skills: skill-body, skill-compact, skill-list, skill-size, skill-history,",
17285
+ " skill-diff, skill-section",
17286
+ "",
17287
+ "Compression: compress-text, retrieve, handoff-create, handoff-resolve,",
17288
+ " compress, recall, bash-output, web-output, mcp-output, compress-text,",
17289
+ " compress",
17290
+ "",
17291
+ "Config & Integration: install, uninstall, mcp-serve, mcp-status, hook,",
17292
+ " bridges-status, worker, statusline, version, config, config-get, help",
17293
+ "",
17294
+ "Data: note, note-add, note-get, note-list, sqlite-query, sqlite-schema,",
17295
+ " csv-profile, csv-query",
17296
+ "",
17297
+ "Utils: ignores, bash-history, web-history, openapi-op, openapi-outline,",
17298
+ " gdrive-sections, screenshot, opencode-*, fetch-image, image-meta,",
17299
+ " image-text, start, stop, resume, replace, insert-section, pr-slice,",
17300
+ " sharepoint-resolve, transcript, transcript-outline, video-chapters,",
17301
+ " write-file, zip-list, zip-read, mcp-history, compact-doc, compact-hint,",
17302
+ " conflicts, prune-cache",
17303
+ "",
17304
+ "Tip: look up one command instead of this whole list: 'token-goat help <command>', or 'token-goat commands --grep <pattern>' anchored ('^read$' ~700B; bare 'read' matches descriptions too, ~7KB).",
17305
+ ""
17306
+ ];
17307
+ return lines.join("\n");
17308
+ }
16872
17309
  function buildProgram() {
16873
17310
  const program2 = new Command();
16874
17311
  program2.name("token-goat").description("Surgical token-reduction companion for AI coding agents").version(VERSION, "-v, --version", "print the token-goat version").option("--cwd <path>", "run as if invoked from this directory (overrides the real working directory)");
@@ -17047,6 +17484,15 @@ function buildProgram() {
17047
17484
  worker.command("stop").description("stop the background indexer").action(guard(cmdWorkerStop));
17048
17485
  worker.command("status").description("check if the indexer is running").action(guard(cmdWorkerStatus));
17049
17486
  program2.command("stats").description("show session statistics (bare = totals only; --full for the breakdown)").option("-j, --json", "output as JSON").option("--full", "show the full breakdown (by source, by command, by day)").option("--short", "force the rich short KPI view even when stdout is not a TTY (e.g. piped)").option("--methodology", "explain local savings estimates and their billing limits").option("--window-days <days>", "days to include (0 = all time)", "30").option("--home-dir <path>", "home directory (for testing)").action(guard(cmdStats));
17487
+ program2.command("reconcile").description("sweep this project for files that changed while token-goat was not watching, and queue them for reindexing").option("--dry-run", "report the drift without queueing anything").option("--budget-ms <ms>", `wall-clock budget for the sweep (default ${DEFAULT_RECONCILE_BUDGET_MS})`).option("-j, --json", "output as JSON").action(
17488
+ (opts) => runExit(
17489
+ () => runReconcile({
17490
+ ...opts.dryRun === true ? { dryRun: true } : {},
17491
+ ...opts.budgetMs !== void 0 ? { budgetMs: requireNonNegativeInt("--budget-ms", opts.budgetMs) } : {},
17492
+ ...opts.json === true ? { json: true } : {}
17493
+ })
17494
+ )
17495
+ );
17050
17496
  program2.command("doctor").description("diagnose token-goat health").option("--context", "include context footprint analysis").option("--json", "emit check results as JSON instead of text").action(guard(cmdDoctor));
17051
17497
  program2.command("context-stats").description("show context statistics").option("--project <path>", "project root to analyze").option("-j, --json", "output as JSON").option("--fix", "apply automatic fixes (confirm-gated; shows a diff before writing)").option("-y, --yes", "with --fix, apply without prompting (non-interactive / scripted use)").action(guard(cmdContextStats));
17052
17498
  program2.command("bootstrap-audit").description("audit Claude Code startup-context contributors without reading prompt bodies").option("--project <path>", "project root to analyze").option("--home <path>", "home directory override (for CI/testing)").option("--follow-links", "follow external symlink/junction roots and direct children").option("-j, --json", "output as JSON").option("--top <n>", "largest metadata entries to show (default 10)", "10").option("--warn-tokens <n>", "warn when total estimated startup tokens exceed n").option("--fail-tokens <n>", "fail when total estimated startup tokens exceed n").option("--warn-bytes <n>", "warn when agent/skill metadata bytes exceed n").option("--fail-bytes <n>", "fail when agent/skill metadata bytes exceed n").action(guard(cmdBootstrapAudit));
@@ -17197,10 +17643,22 @@ function buildProgram() {
17197
17643
  })
17198
17644
  )
17199
17645
  );
17200
- program2.command("arch").description("internal import graph analysis: hubs, entry points, cycles").option("--top <n>", "limit hubs and entry points to top N (default 10)").option("-j, --json", "output as JSON").action(
17646
+ program2.command("arch").description("internal import graph analysis: hubs, entry points, cycles, and with --modules the groups of files that mostly import each other").option("--top <n>", "limit hubs, entry points, modules and cross-module pairs to top N (default 10)").option("--modules", "also group files into modules by who imports whom, and report which modules reach into which (--json carries the full member list of each)").option("-j, --json", "output as JSON").action(
17201
17647
  (opts) => runExit(
17202
17648
  () => runArch({
17203
17649
  ...opts.top !== void 0 ? { top: requireNonNegativeInt("--top", opts.top) } : {},
17650
+ ...opts.json === true ? { json: true } : {},
17651
+ ...opts.modules === true ? { modules: true } : {}
17652
+ })
17653
+ )
17654
+ );
17655
+ program2.command("affected [files...]").description("test files that transitively import the given changed files (for narrowing a CI run)").option("--stdin", "read the changed-file list from stdin, one path per line").option("--depth <n>", `max reverse-import hops (default ${DEFAULT_AFFECTED_DEPTH})`).option("--filter <regex>", "regex deciding which reached files count as tests (default: the built-in test-file heuristic)").option("-q, --quiet", "print bare paths only, for piping into a test runner").option("-j, --json", "output as JSON").action(
17656
+ (files, opts) => runExit(
17657
+ () => runAffected({
17658
+ files: opts.stdin === true ? [...files, ...readStdinPaths()] : files,
17659
+ ...opts.depth !== void 0 ? { depth: requireNonNegativeInt("--depth", opts.depth) } : {},
17660
+ ...opts.filter !== void 0 ? { filter: opts.filter } : {},
17661
+ ...opts.quiet === true ? { quiet: true } : {},
17204
17662
  ...opts.json === true ? { json: true } : {}
17205
17663
  })
17206
17664
  )
@@ -17425,8 +17883,24 @@ function buildProgram() {
17425
17883
  out(VERSION);
17426
17884
  })
17427
17885
  );
17428
- const footer = "\nTip: look up one command instead of this whole list: 'token-goat help <command>', or 'token-goat commands --grep <pattern>' anchored ('^read$' ~700B; bare 'read' matches descriptions too, ~7KB).\n";
17429
- program2.addHelpText("after", footer);
17886
+ program2.command("help [command]").description("show help for a command").option("--full", "show full original help instead of compact summary").action(
17887
+ guard((cmd, opts) => {
17888
+ const options = opts;
17889
+ if (options?.["full"] && !cmd) {
17890
+ out(originalHelpInformation ? originalHelpInformation() : generateCompactHelp());
17891
+ return;
17892
+ }
17893
+ if (cmd) {
17894
+ const argv = [process.argv[0] ?? "node", process.argv[1] ?? "token-goat", cmd, "--help"];
17895
+ program2.parse(argv);
17896
+ } else {
17897
+ out(generateCompactHelp());
17898
+ }
17899
+ })
17900
+ );
17901
+ program2.helpOption("-h, --help", "display help for command");
17902
+ originalHelpInformation = program2.helpInformation.bind(program2);
17903
+ program2.helpInformation = () => generateCompactHelp();
17430
17904
  return program2;
17431
17905
  }
17432
17906
  function applyExitOverride(command) {