token-goat 2.8.6 → 2.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,43 +86,38 @@ import {
86
86
  runZipRead,
87
87
  symbolNamesInFile,
88
88
  upsertNote
89
- } from "./token-goat-chunk-XAWIELXH.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
- RECALL_DEFAULT_LIMIT,
95
94
  WEB_OUTPUT_SUBDIR,
96
95
  accumulateResidentLine,
97
96
  buildCommandManifest,
98
97
  checkSymbolBodySize,
99
- commandHash,
100
98
  createResidentContextStats,
101
99
  filterCommandManifest,
102
100
  formatBytes,
103
101
  formatCommandManifest,
104
102
  formatTokenEstimate,
105
- getBashOutput,
106
103
  getWebOutput,
107
104
  getWebOutputByUrlFromDisk,
108
105
  getWebOutputRaw,
109
- isRecallCacheType,
110
- listRecentRecall,
111
- normalizeCommandForCacheKey,
112
106
  readCopilotMcpTools,
113
107
  readStdinJson,
114
- searchRecall,
108
+ runReconcile,
115
109
  storeWebOutput,
116
110
  summarizeResidentContext
117
- } from "./token-goat-chunk-TELKICYU.mjs";
111
+ } from "./token-goat-chunk-MR7MNSDR.mjs";
118
112
  import {
119
113
  AGENT_SALT_MARKER,
114
+ BASH_OUTPUT_SUBDIR,
120
115
  CONTEXT_AUTOCOMPACT_TOKENS,
121
116
  DEFAULT_MAX_AGE_MS,
122
117
  DEFAULT_MAX_COUNT,
123
- MATERIALIZE_SHRUNK_IMAGE_JS,
124
118
  MAX_FILES_SCANNED,
125
119
  PARSER_FINGERPRINT,
120
+ RECALL_DEFAULT_LIMIT,
126
121
  SESSIONS_SUBDIR,
127
122
  SKILLS_OUTPUT_SUBDIR,
128
123
  SKIP_DIRS,
@@ -131,7 +126,6 @@ import {
131
126
  UNTRUSTED_WEB_TAG,
132
127
  WORKER_HEARTBEAT_STALE_MS,
133
128
  WorkerAlreadyRunningError,
134
- anchoredMarkerPattern,
135
129
  applyIndexingPriority,
136
130
  auditClaudeMd,
137
131
  buildExtractiveCompact,
@@ -140,14 +134,11 @@ import {
140
134
  buildProjectMap,
141
135
  buildStats,
142
136
  capJsonRows,
137
+ commandHash,
143
138
  compactDoc,
144
139
  compactPathFor,
145
140
  confirmAndApply,
146
141
  contentHash,
147
- copilotCliConfigPath,
148
- copilotCliMcpToolsDir,
149
- copilotCliScriptPath,
150
- copilotCliUserRoot,
151
142
  detectWalkMode,
152
143
  dirtyQueuePathFor,
153
144
  docxOutline,
@@ -171,13 +162,13 @@ import {
171
162
  findMemSuggestionCandidates,
172
163
  findMemoryMd,
173
164
  findOrphanedChunkPaths,
174
- findStrayClaudeMdBlocks,
175
165
  findSystemTempFiles,
176
166
  formatAge,
177
167
  formatCsvTable,
178
168
  formatMemSuggestions,
179
169
  formatProjectMap,
180
170
  formatXlsxRange,
171
+ getBashOutput,
181
172
  getContextPressure,
182
173
  getDirtyPathsFor,
183
174
  getEmbeddingCoverage,
@@ -188,35 +179,24 @@ import {
188
179
  getSessionFiles,
189
180
  getSessionId,
190
181
  getSkillFilePath,
191
- groupHasTokenGoat,
182
+ getTrackedFiles,
192
183
  headSheet,
193
184
  incrementSkillHit,
194
185
  indexFileEmbeddings,
195
186
  indexFileSync,
196
187
  indexedPathSpellingIsStale,
197
188
  injectionFencingEnabled,
198
- installClaudeMd,
199
- installCodex,
200
- installCopilotCli,
201
- installGrok,
202
- installHooks,
203
- installKimi,
204
- installSkill,
205
- installVscode,
206
189
  isAvailable,
207
- isCodexInstalled,
208
190
  isCompactFresh,
209
- isCopilotCliInstalled,
210
191
  isEmbedFresh,
211
192
  isEmbeddableDocument,
212
- isGrokInstalled,
213
193
  isHintCategory,
214
- isInstalled,
215
- isKimiInstalled,
216
194
  isParseSkipEligible,
195
+ isRecallCacheType,
217
196
  isWorkerRunning,
218
197
  listBlobs,
219
198
  listOutputs,
199
+ listRecentRecall,
220
200
  listSheets,
221
201
  listSkills,
222
202
  loadBlob,
@@ -224,7 +204,7 @@ import {
224
204
  mapLookupBytesSaved,
225
205
  markCategoryEffective,
226
206
  markCategoryIneffective,
227
- otherScopeHasManagedServer,
207
+ normalizeCommandForCacheKey,
228
208
  parseWhereSpecs,
229
209
  pptxNotesText,
230
210
  pptxOutline,
@@ -245,6 +225,7 @@ import {
245
225
  runContextStats,
246
226
  runDetachedWorkerDaemon,
247
227
  scanAndRecord,
228
+ searchRecall,
248
229
  shrinkImage,
249
230
  skillOutputsDir,
250
231
  startDetachedWorker,
@@ -253,33 +234,32 @@ import {
253
234
  storeOutput,
254
235
  tokenGoatHome,
255
236
  trimToBudget,
256
- uninstallClaudeMd,
257
- uninstallCodex,
258
- uninstallCopilotCli,
259
- uninstallGrok,
260
- uninstallHooks,
261
- uninstallKimi,
262
- uninstallSkill,
263
- uninstallVscode,
264
237
  urlPolicyDenialReason,
265
- vscodeDecoderConfigured,
238
+ visionTokensSavedByText,
266
239
  walkProject,
267
240
  writeCompact
268
- } from "./token-goat-chunk-DK4VLLYB.mjs";
241
+ } from "./token-goat-chunk-FDXINYK4.mjs";
269
242
  import {
270
243
  C,
271
244
  CONFIG_KEY_ENV_OVERRIDES,
272
245
  ENV_KEYS,
273
246
  FILTERS,
274
247
  LOCK_WAIT_MS_HARDENED,
248
+ MATERIALIZE_SHRUNK_IMAGE_JS,
275
249
  RESET,
276
250
  VERSION,
277
251
  _useRichStats,
252
+ anchoredMarkerPattern,
278
253
  atomicWriteBytes,
279
254
  atomicWriteText,
280
255
  canonicalize,
256
+ cappedSourceBytesSaved,
281
257
  colorStdout,
282
258
  configPath,
259
+ copilotCliConfigPath,
260
+ copilotCliMcpToolsDir,
261
+ copilotCliScriptPath,
262
+ copilotCliUserRoot,
283
263
  countNoun,
284
264
  dataDir,
285
265
  decodeSource,
@@ -294,6 +274,7 @@ import {
294
274
  fg,
295
275
  fileIsAbsent,
296
276
  findProject,
277
+ findStrayClaudeMdBlocks,
297
278
  fingerprintContent,
298
279
  fingerprintFile,
299
280
  foldPath,
@@ -304,9 +285,24 @@ import {
304
285
  getLastProjectConfigParseError,
305
286
  getProjectConfigInfo,
306
287
  globalDbPath,
288
+ groupHasTokenGoat,
289
+ installClaudeMd,
290
+ installCodex,
291
+ installCopilotCli,
292
+ installGrok,
293
+ installHooks,
294
+ installKimi,
307
295
  installSingleFilePlugin,
296
+ installSkill,
297
+ installVscode,
308
298
  invalidateConfigCache,
299
+ isCodexInstalled,
300
+ isCopilotCliInstalled,
309
301
  isDotenvPath,
302
+ isGrokInstalled,
303
+ isInstalled,
304
+ isKimiInstalled,
305
+ isTestFile,
310
306
  isUnderBlockedRoot,
311
307
  isWindows,
312
308
  lastProjectConfigLockedKeys,
@@ -314,6 +310,7 @@ import {
314
310
  loadPersistedConfig,
315
311
  normalizeDarwinSystemAlias,
316
312
  normalizePath,
313
+ otherScopeHasManagedServer,
317
314
  pad,
318
315
  parse,
319
316
  pushAll,
@@ -341,14 +338,23 @@ import {
341
338
  summarize,
342
339
  toDisplayPath,
343
340
  toKB,
341
+ uninstallClaudeMd,
342
+ uninstallCodex,
343
+ uninstallCopilotCli,
344
+ uninstallGrok,
345
+ uninstallHooks,
346
+ uninstallKimi,
344
347
  uninstallSingleFilePlugin,
348
+ uninstallSkill,
349
+ uninstallVscode,
345
350
  validateEnumField,
346
351
  validateNumericField,
352
+ vscodeDecoderConfigured,
347
353
  withExtension,
348
354
  withFileLock,
349
355
  withRetryOnLock,
350
356
  writeJsonSettings
351
- } from "./token-goat-chunk-2JZ66BBE.mjs";
357
+ } from "./token-goat-chunk-4ZDRTNGV.mjs";
352
358
  import {
353
359
  __export
354
360
  } from "./token-goat-chunk-AEX54RUZ.mjs";
@@ -3467,9 +3473,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3467
3473
  * @param {string} [path]
3468
3474
  * @return {(string|null|Command)}
3469
3475
  */
3470
- executableDir(path29) {
3471
- if (path29 === void 0) return this._executableDir;
3472
- this._executableDir = path29;
3476
+ executableDir(path30) {
3477
+ if (path30 === void 0) return this._executableDir;
3478
+ this._executableDir = path30;
3473
3479
  return this;
3474
3480
  }
3475
3481
  /**
@@ -3769,7 +3775,7 @@ function attemptedCommandName(argv) {
3769
3775
 
3770
3776
  // src/cli.ts
3771
3777
  import * as fs28 from "fs";
3772
- import * as path28 from "path";
3778
+ import * as path29 from "path";
3773
3779
  import { homedir as homedir13 } from "os";
3774
3780
 
3775
3781
  // src/walk_index.ts
@@ -5657,9 +5663,110 @@ function formatCues(cues) {
5657
5663
  return cues.map((cue) => `[${formatTimestamp(cue.startSeconds)}]${cue.speaker !== null ? ` ${cue.speaker}:` : ""} ${cue.text}`).join("\n");
5658
5664
  }
5659
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
+
5660
5767
  // src/purge.ts
5661
5768
  import * as fs10 from "node:fs";
5662
- import * as path9 from "node:path";
5769
+ import * as path10 from "node:path";
5663
5770
  function directorySize(root) {
5664
5771
  let total = 0;
5665
5772
  let entries;
@@ -5669,7 +5776,7 @@ function directorySize(root) {
5669
5776
  return 0;
5670
5777
  }
5671
5778
  for (const entry of entries) {
5672
- const full = path9.join(root, entry.name);
5779
+ const full = path10.join(root, entry.name);
5673
5780
  if (entry.isDirectory()) {
5674
5781
  total += directorySize(full);
5675
5782
  continue;
@@ -5712,7 +5819,7 @@ function formatBytes2(bytes) {
5712
5819
 
5713
5820
  // src/cli_stats.ts
5714
5821
  import * as fs11 from "node:fs";
5715
- import * as path10 from "node:path";
5822
+ import * as path11 from "node:path";
5716
5823
  function writeRaw(text) {
5717
5824
  const payload = colorStdout() ? text : stripAnsi(text);
5718
5825
  process.stdout.write(ensureNewline(payload));
@@ -5724,7 +5831,7 @@ function formatTopFiles(ranked) {
5724
5831
  if (ranked.length === 0) return "";
5725
5832
  const lines = ["Top files this session:"];
5726
5833
  for (const { path: filePath, count } of ranked) {
5727
- const basename8 = path10.basename(filePath);
5834
+ const basename8 = path11.basename(filePath);
5728
5835
  lines.push(` ${count.toString().padStart(3)}x ${basename8} (${filePath})`);
5729
5836
  }
5730
5837
  return lines.join("\n");
@@ -5741,12 +5848,12 @@ function renderTopSessionFiles(topN = 5) {
5741
5848
  }
5742
5849
  function renderTopSessionFilesFromDisk(topN = 5, overrideSessionsDir) {
5743
5850
  try {
5744
- const sessionsDir = overrideSessionsDir ?? path10.join(dataDir(), "sessions");
5851
+ const sessionsDir = overrideSessionsDir ?? path11.join(dataDir(), "sessions");
5745
5852
  if (!fs11.existsSync(sessionsDir)) return "";
5746
- 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);
5747
5854
  for (const { name } of files) {
5748
5855
  try {
5749
- const raw = fs11.readFileSync(path10.join(sessionsDir, name), "utf-8");
5856
+ const raw = fs11.readFileSync(path11.join(sessionsDir, name), "utf-8");
5750
5857
  const data = JSON.parse(raw);
5751
5858
  const filesList = data["files"];
5752
5859
  if (!Array.isArray(filesList)) continue;
@@ -5766,8 +5873,8 @@ function renderTopSessionFilesFromDisk(topN = 5, overrideSessionsDir) {
5766
5873
  var METHODOLOGY = {
5767
5874
  estimate_scope: "Local estimate of content avoided or reduced by token-goat.",
5768
5875
  billing: "They are not GitHub Copilot usage, provider-reported token consumption, or billing data.",
5769
- byte_derived_formula: "Most read, hook, and command entries go through savedTokensFromBytes in src/stats.ts, which is Math.round(bytes_saved / 4).",
5770
- 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.",
5771
5878
  advisory_events: "Zero-byte, zero-token advisory events show that guidance fired, not that an agent followed it.",
5772
5879
  audit: "Use stats --full or stats --json for source and command breakdowns; reconcile billing with provider-exported usage data."
5773
5880
  };
@@ -5835,12 +5942,12 @@ function runStats(opts = {}) {
5835
5942
  // src/cli_doctor.ts
5836
5943
  import * as fs12 from "fs";
5837
5944
  import * as os8 from "os";
5838
- import * as path11 from "path";
5945
+ import * as path12 from "path";
5839
5946
  import { execSync, spawnSync as spawnSync2 } from "child_process";
5840
5947
  function globalMcpConfigPath() {
5841
5948
  const copilotHome = process.env["COPILOT_HOME"];
5842
- const root = copilotHome !== void 0 && copilotHome.trim() !== "" ? path11.resolve(copilotHome) : path11.join(os8.homedir(), ".copilot");
5843
- 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");
5844
5951
  }
5845
5952
  function checkGlobalMcpConfig(configPath2 = globalMcpConfigPath()) {
5846
5953
  if (!fs12.existsSync(configPath2)) {
@@ -5957,7 +6064,7 @@ function checkWorkerRunning(dataDir2) {
5957
6064
  }
5958
6065
  var DB_SIZE_WARN_BYTES = 1024 * 1024 * 1024;
5959
6066
  function checkDbExists(dataDir2) {
5960
- const dbPath = path11.join(dataDir2, "global.db");
6067
+ const dbPath = path12.join(dataDir2, "global.db");
5961
6068
  if (!fs12.existsSync(dbPath)) {
5962
6069
  return {
5963
6070
  name: "Database",
@@ -6428,15 +6535,15 @@ function runDoctor(dataDir2, configPath2, rootDir, processes) {
6428
6535
  results.push(checkStrayClaudeMdBlocks());
6429
6536
  results.push(checkWorkerRunning(actualDataDir) ? { name: "Worker", status: "ok", message: "running" } : { name: "Worker", status: "warn", message: "not running" });
6430
6537
  results.push(checkDbExists(actualDataDir));
6431
- results.push(checkSymbolBodySize(path11.join(actualDataDir, "global.db")));
6432
- 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));
6433
6540
  results.push(checkDirtyQueueHealth(actualDataDir));
6434
- results.push(checkCompactionChannel(path11.join(actualDataDir, "global.db")));
6435
- 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")));
6436
6543
  const actualConfigPath = configPath2 || configPath();
6437
6544
  results.push(checkConfigValid(actualConfigPath));
6438
6545
  results.push(checkEmbeddings(loadConfig(rootDir)));
6439
- results.push(checkEmbeddingCoverage(path11.join(actualDataDir, "global.db"), rootDir));
6546
+ results.push(checkEmbeddingCoverage(path12.join(actualDataDir, "global.db"), rootDir));
6440
6547
  for (const result of checkSecurityPosture(loadConfig(rootDir), actualDataDir)) results.push(result);
6441
6548
  results.push(checkDiskSpace(actualDataDir));
6442
6549
  const copilotResult = checkCopilotCli(copilotCliConfigPath(), copilotCliScriptPath());
@@ -6477,7 +6584,7 @@ async function runDoctorAndExit(opts) {
6477
6584
  console.log();
6478
6585
  try {
6479
6586
  const dir = skillOutputsDir();
6480
- const pregenPath = path11.join(dir, "pregen.json");
6587
+ const pregenPath = path12.join(dir, "pregen.json");
6481
6588
  if (fs12.existsSync(pregenPath)) {
6482
6589
  const content = JSON.parse(fs12.readFileSync(pregenPath, "utf-8"));
6483
6590
  const pregenNames = new Set(content.names || []);
@@ -6487,7 +6594,7 @@ async function runDoctorAndExit(opts) {
6487
6594
  for (const entry of entries) {
6488
6595
  if (!entry.isFile() || !entry.name.endsWith(".meta")) continue;
6489
6596
  try {
6490
- 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"));
6491
6598
  if (meta.skillName && !pregenNames.has(meta.skillName)) {
6492
6599
  skillsSeen.add(meta.skillName);
6493
6600
  }
@@ -6514,7 +6621,7 @@ import { existsSync as existsSync7, readdirSync as readdirSync5, statSync as sta
6514
6621
  import * as http from "http";
6515
6622
  import * as https from "https";
6516
6623
  import { isIPv4, isIPv6 } from "net";
6517
- import { resolve as resolve4, join as join10 } from "path";
6624
+ import { resolve as resolve5, join as join10 } from "path";
6518
6625
  import { URL as URL2 } from "url";
6519
6626
  import { promisify } from "util";
6520
6627
  import { lookup as dnsLookup } from "dns";
@@ -6703,7 +6810,7 @@ function cleanupStaleDownloads() {
6703
6810
  const files = readdirSync5(cacheDir);
6704
6811
  for (const file of files) {
6705
6812
  if (file.endsWith(".tmp")) {
6706
- const filePath = resolve4(cacheDir, file);
6813
+ const filePath = resolve5(cacheDir, file);
6707
6814
  try {
6708
6815
  const stat = statSync4(filePath);
6709
6816
  if (Date.now() - stat.mtimeMs < STALE_DOWNLOAD_AGE_MS) continue;
@@ -6969,7 +7076,7 @@ async function getSectionContent(fileId, heading, opts = {}) {
6969
7076
 
6970
7077
  // src/pack.ts
6971
7078
  import * as fs13 from "node:fs";
6972
- import * as path13 from "node:path";
7079
+ import * as path14 from "node:path";
6973
7080
 
6974
7081
  // node_modules/minimatch/dist/esm/index.js
6975
7082
  var esm_exports = {};
@@ -8093,11 +8200,11 @@ var qmarksTestNoExtDot = ([$0]) => {
8093
8200
  return (f) => f.length === len && f !== "." && f !== "..";
8094
8201
  };
8095
8202
  var defaultPlatform = typeof process === "object" && process ? typeof process.env === "object" && process.env && process.env.__MINIMATCH_TESTING_PLATFORM__ || process.platform : "posix";
8096
- var path12 = {
8203
+ var path13 = {
8097
8204
  win32: { sep: "\\" },
8098
8205
  posix: { sep: "/" }
8099
8206
  };
8100
- var sep2 = defaultPlatform === "win32" ? path12.win32.sep : path12.posix.sep;
8207
+ var sep2 = defaultPlatform === "win32" ? path13.win32.sep : path13.posix.sep;
8101
8208
  minimatch.sep = sep2;
8102
8209
  var GLOBSTAR = /* @__PURE__ */ Symbol("globstar **");
8103
8210
  minimatch.GLOBSTAR = GLOBSTAR;
@@ -8882,7 +8989,7 @@ var LANG_MAP = {
8882
8989
  ".exs": "elixir"
8883
8990
  };
8884
8991
  function getLang(filePath) {
8885
- const ext2 = path13.extname(filePath).toLowerCase();
8992
+ const ext2 = path14.extname(filePath).toLowerCase();
8886
8993
  return LANG_MAP[ext2] ?? "";
8887
8994
  }
8888
8995
  function matches(rel, patterns) {
@@ -8892,8 +8999,8 @@ function matches(rel, patterns) {
8892
8999
  return patterns.some((pat) => mm.minimatch(norm, pat) || mm.minimatch(base, pat));
8893
9000
  }
8894
9001
  function isPathWithinRoot(rootReal, resolvedPath) {
8895
- const rel = path13.relative(rootReal, resolvedPath);
8896
- 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);
8897
9004
  }
8898
9005
  function openWithinRoot(rootReal, p) {
8899
9006
  let fd;
@@ -8933,11 +9040,11 @@ function openWithinRoot(rootReal, p) {
8933
9040
  }
8934
9041
  function* resolveOpenCandidates(projectRoot, patterns, rootReal, ignorePatterns, seen, skipped) {
8935
9042
  for (const pattern of patterns) {
8936
- const p = path13.isAbsolute(pattern) ? pattern : path13.join(projectRoot, pattern);
9043
+ const p = path14.isAbsolute(pattern) ? pattern : path14.join(projectRoot, pattern);
8937
9044
  if (seen.has(p)) continue;
8938
9045
  let rel;
8939
9046
  try {
8940
- rel = path13.relative(projectRoot, p).replace(/\\/g, "/");
9047
+ rel = path14.relative(projectRoot, p).replace(/\\/g, "/");
8941
9048
  } catch {
8942
9049
  skipped.push(`${p} (outside project root)`);
8943
9050
  continue;
@@ -9011,7 +9118,7 @@ function stripBlockComments(content, pattern, lineStates) {
9011
9118
  );
9012
9119
  }
9013
9120
  function stripComments(content, filePath) {
9014
- const ext2 = path13.extname(filePath).toLowerCase();
9121
+ const ext2 = path14.extname(filePath).toLowerCase();
9015
9122
  const lineStates = computeLineStartQuoteStates(content);
9016
9123
  if (ext2 === ".py") {
9017
9124
  return stripLineComments(content, PY_LINE_COMMENT_RE, lineStates);
@@ -9049,7 +9156,7 @@ var SAFE_EXTS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp
9049
9156
  function scanSecrets(files) {
9050
9157
  const hits = [];
9051
9158
  for (const pf of files) {
9052
- if (SAFE_EXTS.has(path13.extname(pf.path).toLowerCase())) {
9159
+ if (SAFE_EXTS.has(path14.extname(pf.path).toLowerCase())) {
9053
9160
  continue;
9054
9161
  }
9055
9162
  for (const [lineno, line] of pf.content.split("\n").entries()) {
@@ -9072,7 +9179,7 @@ function scanSecrets(files) {
9072
9179
  function collectFiles(projectRoot, patterns, opts = {}) {
9073
9180
  const result = { files: [], skipped: [], total_lines: 0, total_tokens: 0 };
9074
9181
  const seen = /* @__PURE__ */ new Set();
9075
- const rootResolved = path13.resolve(projectRoot);
9182
+ const rootResolved = path14.resolve(projectRoot);
9076
9183
  let rootReal;
9077
9184
  try {
9078
9185
  rootReal = fs13.realpathSync(rootResolved);
@@ -9247,7 +9354,7 @@ function formatPack(result, style, opts = {}) {
9247
9354
  function estimateBudget(projectRoot, patterns, opts = {}) {
9248
9355
  const result = { entries: [], skipped: [], total_lines: 0, total_tokens: 0 };
9249
9356
  const seen = /* @__PURE__ */ new Set();
9250
- const rootResolved = path13.resolve(projectRoot);
9357
+ const rootResolved = path14.resolve(projectRoot);
9251
9358
  let rootReal;
9252
9359
  try {
9253
9360
  rootReal = fs13.realpathSync(rootResolved);
@@ -9744,7 +9851,7 @@ function formatFailureDeltaJson(delta, runner) {
9744
9851
 
9745
9852
  // src/failures_state.ts
9746
9853
  import * as fs14 from "node:fs";
9747
- import * as path14 from "node:path";
9854
+ import * as path15 from "node:path";
9748
9855
  var KEY_RE = /^[A-Za-z0-9_-]{1,80}$/;
9749
9856
  var DEFAULT_FAILURES_STATE_KEY = "default";
9750
9857
  function validateKey(key) {
@@ -9756,7 +9863,7 @@ function validateKey(key) {
9756
9863
  }
9757
9864
  function failuresStatePath(projectHash, key) {
9758
9865
  validateKey(key);
9759
- return path14.join(dataDir(), "projects", `${projectHash}_failures_${key}.json`);
9866
+ return path15.join(dataDir(), "projects", `${projectHash}_failures_${key}.json`);
9760
9867
  }
9761
9868
  function loadFailureSnapshot(projectHash, key) {
9762
9869
  const p = failuresStatePath(projectHash, key);
@@ -9777,7 +9884,7 @@ function loadFailureSnapshot(projectHash, key) {
9777
9884
  }
9778
9885
  function saveFailureSnapshot(projectHash, key, snapshot) {
9779
9886
  const p = failuresStatePath(projectHash, key);
9780
- const dir = path14.dirname(p);
9887
+ const dir = path15.dirname(p);
9781
9888
  ensureDirSync(dir);
9782
9889
  const content = JSON.stringify(snapshot);
9783
9890
  const doSave = () => {
@@ -9789,18 +9896,18 @@ function saveFailureSnapshot(projectHash, key, snapshot) {
9789
9896
 
9790
9897
  // src/text_commands.ts
9791
9898
  import * as fs16 from "fs";
9792
- import * as path16 from "path";
9899
+ import * as path17 from "path";
9793
9900
 
9794
9901
  // src/project_memory.ts
9795
9902
  import * as fs15 from "node:fs";
9796
- import * as path15 from "node:path";
9903
+ import * as path16 from "node:path";
9797
9904
  var MAX_ENTRIES = 30;
9798
9905
  var KEY_RE2 = /^[A-Za-z0-9_-]{1,80}$/;
9799
9906
  function ordinal(a, b) {
9800
9907
  return a < b ? -1 : a > b ? 1 : 0;
9801
9908
  }
9802
9909
  function memoryPath(projectHash) {
9803
- return path15.join(dataDir(), "projects", `${projectHash}_memory.toml`);
9910
+ return path16.join(dataDir(), "projects", `${projectHash}_memory.toml`);
9804
9911
  }
9805
9912
  function validateKey2(key) {
9806
9913
  if (!KEY_RE2.test(key)) {
@@ -9859,7 +9966,7 @@ function save(filePath, entries) {
9859
9966
  lines.push(`${k} = "${escaped}"`);
9860
9967
  }
9861
9968
  const content = lines.length > 0 ? lines.join("\n") + "\n" : "";
9862
- const dir = path15.dirname(filePath);
9969
+ const dir = path16.dirname(filePath);
9863
9970
  ensureDirSync(dir);
9864
9971
  atomicWriteText(filePath, content);
9865
9972
  }
@@ -9869,7 +9976,7 @@ function loadEntries(projectHash) {
9869
9976
  function setEntry(projectHash, key, value) {
9870
9977
  validateKey2(key);
9871
9978
  const p = memoryPath(projectHash);
9872
- const dir = path15.dirname(p);
9979
+ const dir = path16.dirname(p);
9873
9980
  ensureDirSync(dir);
9874
9981
  const doSet = () => {
9875
9982
  const entries = loadRaw(p);
@@ -9967,7 +10074,7 @@ function collectTodoFiles(patterns) {
9967
10074
  if (patterns.length > 0) {
9968
10075
  const results = [];
9969
10076
  for (const p of patterns) {
9970
- const abs = path16.resolve(p);
10077
+ const abs = path17.resolve(p);
9971
10078
  try {
9972
10079
  const stat = fs16.statSync(abs);
9973
10080
  if (stat.isDirectory()) {
@@ -10238,7 +10345,7 @@ function isProjectFrame(framePath, cwd) {
10238
10345
  if (framePath.includes("/rustc/")) return false;
10239
10346
  if (framePath.includes(".cargo/registry") || framePath.includes(".cargo\\registry")) return false;
10240
10347
  if (/^<.+>$/.test(framePath)) return false;
10241
- if (!path16.isAbsolute(framePath) && !framePath.startsWith("..")) return true;
10348
+ if (!path17.isAbsolute(framePath) && !framePath.startsWith("..")) return true;
10242
10349
  return false;
10243
10350
  }
10244
10351
  function resolveFrameSymbol(frame, projectRoot) {
@@ -10451,17 +10558,17 @@ function findLockfile(startPath) {
10451
10558
  if (stat !== void 0 && stat.isFile()) {
10452
10559
  return { file: startPath, others: [] };
10453
10560
  }
10454
- const dir = stat !== void 0 && stat.isDirectory() ? startPath : path16.dirname(startPath);
10561
+ const dir = stat !== void 0 && stat.isDirectory() ? startPath : path17.dirname(startPath);
10455
10562
  const found = [];
10456
10563
  for (const name of LOCK_PRIORITY) {
10457
- const candidate = path16.join(dir, name);
10564
+ const candidate = path17.join(dir, name);
10458
10565
  if (fs16.existsSync(candidate)) found.push(candidate);
10459
10566
  }
10460
10567
  try {
10461
10568
  const entries = fs16.readdirSync(dir);
10462
10569
  for (const e of entries) {
10463
10570
  if (/^requirements.*\.txt$/.test(e)) {
10464
- const p = path16.join(dir, e);
10571
+ const p = path17.join(dir, e);
10465
10572
  if (!found.includes(p)) found.push(p);
10466
10573
  }
10467
10574
  }
@@ -10658,7 +10765,7 @@ function parsePnpmLock(content) {
10658
10765
  return deps;
10659
10766
  }
10660
10767
  function parseLockFile(filePath) {
10661
- const base = path16.basename(filePath);
10768
+ const base = path17.basename(filePath);
10662
10769
  const content = fs16.readFileSync(filePath, "utf8");
10663
10770
  if (base === "package-lock.json") return { format: "npm", deps: parsePackageLockJson(content) };
10664
10771
  if (base === "yarn.lock") return { format: "yarn", deps: parseYarnLock(content) };
@@ -10774,7 +10881,7 @@ Depended on by direct/top-level deps (${dependedOnBy.length}):
10774
10881
  `);
10775
10882
  }
10776
10883
  function cmdLockdeps(filePath, opts) {
10777
- const target = filePath !== void 0 ? path16.resolve(filePath) : process.cwd();
10884
+ const target = filePath !== void 0 ? path17.resolve(filePath) : process.cwd();
10778
10885
  const found = findLockfile(target);
10779
10886
  if (found === null) {
10780
10887
  throw new Error("No lockfile found. Expected one of: " + LOCK_PRIORITY.join(", ") + ", requirements*.txt");
@@ -10862,7 +10969,7 @@ function cmdNote(action, key, value, opts) {
10862
10969
  throw new Error(`Unknown note action: ${action}. Use: set, get, unset, list, clear`);
10863
10970
  }
10864
10971
  function loadAllSessionReadCounts() {
10865
- const sessionsDir = path16.join(tokenGoatHome(), "sessions");
10972
+ const sessionsDir = path17.join(tokenGoatHome(), "sessions");
10866
10973
  const totals = /* @__PURE__ */ new Map();
10867
10974
  let entries;
10868
10975
  try {
@@ -10872,7 +10979,7 @@ function loadAllSessionReadCounts() {
10872
10979
  }
10873
10980
  for (const entry of entries) {
10874
10981
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
10875
- const filePath = path16.join(sessionsDir, entry.name);
10982
+ const filePath = path17.join(sessionsDir, entry.name);
10876
10983
  let raw;
10877
10984
  try {
10878
10985
  raw = JSON.parse(fs16.readFileSync(filePath, "utf8"));
@@ -11008,7 +11115,7 @@ function cmdIgnores(opts) {
11008
11115
  }
11009
11116
  function countPackIgnorePatterns(root) {
11010
11117
  try {
11011
- 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;
11012
11119
  } catch {
11013
11120
  return 0;
11014
11121
  }
@@ -11017,7 +11124,7 @@ function countPackIgnorePatterns(root) {
11017
11124
  // src/dep_docs.ts
11018
11125
  import { createRequire } from "node:module";
11019
11126
  import * as fs17 from "node:fs";
11020
- import * as path17 from "node:path";
11127
+ import * as path18 from "node:path";
11021
11128
  var _require = createRequire(import.meta.url);
11022
11129
  var _ts = null;
11023
11130
  var _tsLoadAttempted = false;
@@ -11068,7 +11175,7 @@ function listInstalledPackageNames(nodeModulesDir) {
11068
11175
  if (entry.name.startsWith("@")) {
11069
11176
  let scoped;
11070
11177
  try {
11071
- scoped = fs17.readdirSync(path17.join(nodeModulesDir, entry.name), { withFileTypes: true });
11178
+ scoped = fs17.readdirSync(path18.join(nodeModulesDir, entry.name), { withFileTypes: true });
11072
11179
  } catch {
11073
11180
  continue;
11074
11181
  }
@@ -11085,7 +11192,7 @@ var README_NAME_RE = /^readme(\.(md|markdown|txt))?$/i;
11085
11192
  function findReadmeFile(pkgDir) {
11086
11193
  const direct = findReadmeIn(pkgDir);
11087
11194
  if (direct !== null) return direct;
11088
- return findReadmeIn(path17.join(pkgDir, "docs"));
11195
+ return findReadmeIn(path18.join(pkgDir, "docs"));
11089
11196
  }
11090
11197
  function findReadmeIn(dir) {
11091
11198
  let entries;
@@ -11102,7 +11209,7 @@ function findReadmeIn(dir) {
11102
11209
  return 1;
11103
11210
  };
11104
11211
  matches2.sort((a, b) => rank(a.name) - rank(b.name));
11105
- return path17.join(dir, matches2[0].name);
11212
+ return path18.join(dir, matches2[0].name);
11106
11213
  }
11107
11214
  function typesPackageDirName(pkgName) {
11108
11215
  if (pkgName.startsWith("@")) {
@@ -11129,30 +11236,30 @@ function resolveTypesLocation(pkgDir, pkgJson, nodeModulesDir, pkgName) {
11129
11236
  if (declared !== void 0) {
11130
11237
  const candidates = [declared, declared.endsWith(".d.ts") ? declared : `${declared}.d.ts`, declared.replace(/\.[cm]?[jt]s$/, ".d.ts")];
11131
11238
  for (const c of candidates) {
11132
- const p = path17.join(pkgDir, c);
11239
+ const p = path18.join(pkgDir, c);
11133
11240
  if (fileExists(p)) return { path: p, source: "bundled" };
11134
11241
  }
11135
11242
  }
11136
11243
  const main = typeof pkgJson["main"] === "string" ? pkgJson["main"] : "index.js";
11137
- const mainDts = path17.join(pkgDir, main.replace(/\.[cm]?js$/, ".d.ts"));
11244
+ const mainDts = path18.join(pkgDir, main.replace(/\.[cm]?js$/, ".d.ts"));
11138
11245
  if (fileExists(mainDts)) return { path: mainDts, source: "bundled" };
11139
- const indexDts = path17.join(pkgDir, "index.d.ts");
11246
+ const indexDts = path18.join(pkgDir, "index.d.ts");
11140
11247
  if (fileExists(indexDts)) return { path: indexDts, source: "bundled" };
11141
11248
  const typesDirName = typesPackageDirName(pkgName);
11142
- const typesPkgDir = path17.join(nodeModulesDir, "@types", typesDirName);
11143
- 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"));
11144
11251
  if (typesPkgJsonRaw !== null) {
11145
11252
  try {
11146
11253
  const typesPkgJson = JSON.parse(typesPkgJsonRaw);
11147
11254
  const entry = (typeof typesPkgJson["types"] === "string" ? typesPkgJson["types"] : void 0) ?? (typeof typesPkgJson["main"] === "string" ? typesPkgJson["main"] : void 0) ?? "index.d.ts";
11148
11255
  const entryCandidates = [entry, entry.endsWith(".d.ts") ? entry : `${entry}.d.ts`, entry.replace(/\.[cm]?[jt]s$/, ".d.ts")];
11149
11256
  for (const c of entryCandidates) {
11150
- const entryPath = path17.join(typesPkgDir, c);
11257
+ const entryPath = path18.join(typesPkgDir, c);
11151
11258
  if (fileExists(entryPath)) return { path: entryPath, source: "@types" };
11152
11259
  }
11153
11260
  } catch {
11154
11261
  }
11155
- const fallback = path17.join(typesPkgDir, "index.d.ts");
11262
+ const fallback = path18.join(typesPkgDir, "index.d.ts");
11156
11263
  if (fileExists(fallback)) return { path: fallback, source: "@types" };
11157
11264
  }
11158
11265
  return null;
@@ -11219,9 +11326,9 @@ function recordDepDocsStat(fullSourceBytes, emittedText, packageName) {
11219
11326
  }
11220
11327
  function runDepDocs(opts) {
11221
11328
  const root = resolveProjectRoot({ project: opts.projectRoot ?? process.cwd() });
11222
- const nodeModulesDir = path17.join(root, "node_modules");
11223
- const pkgDir = path17.join(nodeModulesDir, opts.packageName);
11224
- 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");
11225
11332
  if (!dirExists(pkgDir) || !fileExists(pkgJsonPath)) {
11226
11333
  const known = listInstalledPackageNames(nodeModulesDir);
11227
11334
  const suggestions = suggestPackageNames(opts.packageName, known);
@@ -11384,13 +11491,13 @@ async function buildResumePacket(sessionId) {
11384
11491
  }
11385
11492
 
11386
11493
  // src/cache_session_commands.ts
11387
- function emitErr(text) {
11494
+ function emitErr2(text) {
11388
11495
  process.stderr.write(ensureNewline(text));
11389
11496
  }
11390
11497
  function capAndNote(rows, limit) {
11391
11498
  const shown = rows.slice(0, limit);
11392
11499
  if (shown.length < rows.length) {
11393
- emitErr(`Showing ${shown.length} of ${rows.length} entries (raise --limit to see the rest).`);
11500
+ emitErr2(`Showing ${shown.length} of ${rows.length} entries (raise --limit to see the rest).`);
11394
11501
  }
11395
11502
  return shown;
11396
11503
  }
@@ -11400,11 +11507,11 @@ function parseLimitOpt(cmdName, limitStr, dflt = 30) {
11400
11507
  try {
11401
11508
  n = requireNonNegativeStrictInt("--limit", limitStr);
11402
11509
  } catch (e) {
11403
- emitErr(`${cmdName}: --limit must be a positive number, got: "${limitStr}"`);
11510
+ emitErr2(`${cmdName}: --limit must be a positive number, got: "${limitStr}"`);
11404
11511
  throw new Error(`invalid --limit: ${limitStr}`, { cause: e });
11405
11512
  }
11406
11513
  if (n === 0) {
11407
- emitErr(`${cmdName}: --limit must be a positive number, got: "${limitStr}"`);
11514
+ emitErr2(`${cmdName}: --limit must be a positive number, got: "${limitStr}"`);
11408
11515
  throw new Error(`invalid --limit: ${limitStr}`);
11409
11516
  }
11410
11517
  return n;
@@ -11738,7 +11845,7 @@ function cmdBaseline(opts) {
11738
11845
 
11739
11846
  // src/index_reclaim.ts
11740
11847
  import * as fs18 from "node:fs";
11741
- import * as path18 from "node:path";
11848
+ import * as path19 from "node:path";
11742
11849
  var DERIVED_TABLES = ["chunk_vectors", "chunks", "refs", "symbols", "files"];
11743
11850
  function indexSizeBytes(dbPath) {
11744
11851
  let total = 0;
@@ -11822,7 +11929,7 @@ function mb(bytes) {
11822
11929
  }
11823
11930
  function cmdReclaimIndex(opts) {
11824
11931
  const dbPath = opts.dbPath ?? globalDbPath();
11825
- if (opts.force !== true && isWorkerRunning(path18.dirname(dbPath))) {
11932
+ if (opts.force !== true && isWorkerRunning(path19.dirname(dbPath))) {
11826
11933
  throw new Error(
11827
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)."
11828
11935
  );
@@ -11856,7 +11963,7 @@ function cmdReclaimIndex(opts) {
11856
11963
  }
11857
11964
  if (result.checkpointBusy) {
11858
11965
  process.stdout.write(
11859
- ` 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
11860
11967
  `
11861
11968
  );
11862
11969
  }
@@ -11865,16 +11972,16 @@ function cmdReclaimIndex(opts) {
11865
11972
  // src/config_commands.ts
11866
11973
  import * as fs19 from "node:fs";
11867
11974
  import * as os9 from "node:os";
11868
- import * as path19 from "node:path";
11869
- function emit(text) {
11975
+ import * as path20 from "node:path";
11976
+ function emit2(text) {
11870
11977
  const payload = colorStdout() ? text : stripAnsi(text);
11871
11978
  process.stdout.write(ensureNewline(payload));
11872
11979
  }
11873
- function emitErr2(text) {
11980
+ function emitErr3(text) {
11874
11981
  process.stderr.write(ensureNewline(text));
11875
11982
  }
11876
11983
  function saveConfigSafe(cfg) {
11877
- ensureDirSync(path19.dirname(configPath()));
11984
+ ensureDirSync(path20.dirname(configPath()));
11878
11985
  saveConfig(cfg);
11879
11986
  }
11880
11987
  function levenshtein(a, b, cap = 3) {
@@ -12036,16 +12143,16 @@ function cmdConfig(opts) {
12036
12143
  if (state.layer !== "global" && state.layer !== "project-unparsed") sources[k] = layerJson(state);
12037
12144
  }
12038
12145
  if (Object.keys(sources).length > 0) payload["_sources"] = sources;
12039
- emit(JSON.stringify(payload, null, 2));
12146
+ emit2(JSON.stringify(payload, null, 2));
12040
12147
  return;
12041
12148
  }
12042
12149
  for (const [k, v] of pairs) {
12043
12150
  const state = resolveConfigKeyLayer(k, v, cfg, projectInfo);
12044
- emit(`${k} = ${JSON.stringify(v)}${state.layer === "project-unparsed" ? "" : layerAnnotation(state)}`);
12151
+ emit2(`${k} = ${JSON.stringify(v)}${state.layer === "project-unparsed" ? "" : layerAnnotation(state)}`);
12045
12152
  }
12046
12153
  if (projectInfo !== null) {
12047
- emit("");
12048
- emit(
12154
+ emit2("");
12155
+ emit2(
12049
12156
  projectInfo.parseError !== null ? `# project override ${projectInfo.path} failed to parse (${projectInfo.parseError}); ignored` : `# project override: ${projectInfo.path}`
12050
12157
  );
12051
12158
  }
@@ -12063,11 +12170,11 @@ function cmdConfig(opts) {
12063
12170
  }
12064
12171
  const getState = resolveConfigKeyLayer(opts.key, result.value, cfg, getProjectConfigInfo());
12065
12172
  if (opts.json === true) {
12066
- 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));
12067
12174
  return;
12068
12175
  }
12069
12176
  const rendered = typeof result.value === "string" ? result.value : JSON.stringify(result.value);
12070
- emit(`${rendered}${layerAnnotation(getState)}`);
12177
+ emit2(`${rendered}${layerAnnotation(getState)}`);
12071
12178
  return;
12072
12179
  }
12073
12180
  if (action === "set") {
@@ -12086,7 +12193,7 @@ function cmdConfig(opts) {
12086
12193
  if (parseErrAtLoad !== null) {
12087
12194
  try {
12088
12195
  fs19.copyFileSync(configPath(), `${configPath()}.bak`);
12089
- 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`);
12090
12197
  } catch {
12091
12198
  }
12092
12199
  }
@@ -12121,8 +12228,8 @@ function cmdConfig(opts) {
12121
12228
  saveConfigSafe(cfg);
12122
12229
  return coercedValue;
12123
12230
  };
12124
- ensureDirSync(path19.dirname(configPath()));
12125
- const lockPath = path19.join(path19.dirname(configPath()), ".config.lock");
12231
+ ensureDirSync(path20.dirname(configPath()));
12232
+ const lockPath = path20.join(path20.dirname(configPath()), ".config.lock");
12126
12233
  const lockResult = withFileLock(lockPath, applySet, { waitMs: LOCK_WAIT_MS_HARDENED });
12127
12234
  const coerced = lockResult === void 0 ? applySet() : lockResult;
12128
12235
  invalidateConfigCache();
@@ -12131,19 +12238,19 @@ function cmdConfig(opts) {
12131
12238
  const setState = resolveConfigKeyLayer(key, setEffective.found ? setEffective.value : void 0, effectiveCfg, getProjectConfigInfo());
12132
12239
  const shadowed = setEffective.found && setEffective.value !== coerced;
12133
12240
  if (shadowed && setState.layer === "env") {
12134
- 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`);
12135
12242
  } else if (shadowed && setState.layer === "env-invalid") {
12136
- 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`);
12137
12244
  } else if (setState.layer === "project") {
12138
- 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`);
12139
12246
  } else if (setState.layer === "project-invalid") {
12140
- 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`);
12141
12248
  }
12142
12249
  if (opts.json === true) {
12143
- emit(JSON.stringify({ key, value: coerced }, null, 2));
12250
+ emit2(JSON.stringify({ key, value: coerced }, null, 2));
12144
12251
  return;
12145
12252
  }
12146
- emit(`${key} = ${JSON.stringify(coerced)}`);
12253
+ emit2(`${key} = ${JSON.stringify(coerced)}`);
12147
12254
  return;
12148
12255
  }
12149
12256
  if (action === "validate") {
@@ -12211,19 +12318,19 @@ function cmdConfig(opts) {
12211
12318
  }
12212
12319
  if (findings.length > 0) process.exitCode = 1;
12213
12320
  if (opts.json === true) {
12214
- emit(JSON.stringify({ findings, ok: findings.length === 0 }, null, 2));
12321
+ emit2(JSON.stringify({ findings, ok: findings.length === 0 }, null, 2));
12215
12322
  return;
12216
12323
  }
12217
12324
  if (findings.length === 0) {
12218
- emit("config validate: no issues found");
12325
+ emit2("config validate: no issues found");
12219
12326
  return;
12220
12327
  }
12221
12328
  for (const f of findings) {
12222
12329
  const isNearMiss = f.kind === "unknown_section" || f.kind === "unknown_key";
12223
12330
  const hint = f.suggestion === void 0 ? "" : isNearMiss ? ` (did you mean: ${f.suggestion}?)` : ` (${f.suggestion})`;
12224
- emit(`[${f.kind}] ${f.key}${hint}`);
12331
+ emit2(`[${f.kind}] ${f.key}${hint}`);
12225
12332
  }
12226
- emit(`config validate: ${findings.length} issue(s) found`);
12333
+ emit2(`config validate: ${findings.length} issue(s) found`);
12227
12334
  return;
12228
12335
  }
12229
12336
  throw new Error(`config: unknown action '${action}'. Use list, get, set, or validate.`);
@@ -12235,33 +12342,33 @@ function cmdProject(opts) {
12235
12342
  const active = findProject(process.cwd());
12236
12343
  const blocked = cfg.worker.blocked_roots;
12237
12344
  if (opts.json === true) {
12238
- 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));
12239
12346
  return;
12240
12347
  }
12241
12348
  if (active) {
12242
- emit(`Active project: ${active.root}`);
12243
- emit(` marker: ${active.marker}`);
12349
+ emit2(`Active project: ${active.root}`);
12350
+ emit2(` marker: ${active.marker}`);
12244
12351
  } else {
12245
- emit("Active project: (none \u2014 not inside a recognized project root)");
12352
+ emit2("Active project: (none \u2014 not inside a recognized project root)");
12246
12353
  }
12247
12354
  if (blocked.length === 0) {
12248
- emit("Blocked roots: (none)");
12355
+ emit2("Blocked roots: (none)");
12249
12356
  } else {
12250
- emit("Blocked roots:");
12251
- for (const r of blocked) emit(` ${r}`);
12357
+ emit2("Blocked roots:");
12358
+ for (const r of blocked) emit2(` ${r}`);
12252
12359
  }
12253
12360
  return;
12254
12361
  }
12255
12362
  if (action === "exclude") {
12256
12363
  if (!opts.pathArg) {
12257
- emitErr2("project exclude requires a path argument");
12364
+ emitErr3("project exclude requires a path argument");
12258
12365
  throw new Error("missing path");
12259
12366
  }
12260
- const target = path19.resolve(opts.pathArg);
12367
+ const target = path20.resolve(opts.pathArg);
12261
12368
  const cfg = loadPersistedConfig();
12262
12369
  const targetFolded = foldPath(normalizePath(target));
12263
12370
  if (cfg.worker.blocked_roots.some((r) => foldPath(normalizePath(r)) === targetFolded)) {
12264
- emit(`Already excluded: ${target}`);
12371
+ emit2(`Already excluded: ${target}`);
12265
12372
  return;
12266
12373
  }
12267
12374
  cfg.worker.blocked_roots = [...cfg.worker.blocked_roots, target];
@@ -12269,12 +12376,12 @@ function cmdProject(opts) {
12269
12376
  invalidateConfigCache();
12270
12377
  const purged = pruneBlockedRoot(target);
12271
12378
  if (opts.json === true) {
12272
- 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));
12273
12380
  return;
12274
12381
  }
12275
- emit(`Excluded: ${target}`);
12382
+ emit2(`Excluded: ${target}`);
12276
12383
  if (purged.length > 0) {
12277
- 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.`);
12278
12385
  }
12279
12386
  return;
12280
12387
  }
@@ -12294,26 +12401,26 @@ function cmdProject(opts) {
12294
12401
  const orphanChunkPaths = findOrphanedChunkPaths();
12295
12402
  if (opts.dryRun === true) {
12296
12403
  if (opts.json === true) {
12297
- 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));
12298
12405
  return;
12299
12406
  }
12300
12407
  if (removed === 0) {
12301
- emit("Would prune 0 stale root(s). Nothing to do.");
12408
+ emit2("Would prune 0 stale root(s). Nothing to do.");
12302
12409
  } else {
12303
- emit(`Would prune ${removed} stale root(s):`);
12304
- for (const r of stale) emit(` ${r}`);
12410
+ emit2(`Would prune ${removed} stale root(s):`);
12411
+ for (const r of stale) emit2(` ${r}`);
12305
12412
  }
12306
12413
  if (staleTempFiles.length === 0) {
12307
- 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.");
12308
12415
  } else {
12309
- emit(`Would prune ${staleTempFiles.length} stale indexed temp-dir file(s):`);
12310
- 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}`);
12311
12418
  }
12312
12419
  if (orphanChunkPaths.length === 0) {
12313
- 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.");
12314
12421
  } else {
12315
- emit(`Would prune orphaned embedding chunks for ${orphanChunkPaths.length} file(s):`);
12316
- 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}`);
12317
12424
  }
12318
12425
  return;
12319
12426
  }
@@ -12323,37 +12430,37 @@ function cmdProject(opts) {
12323
12430
  const prunedTempFiles = pruneSystemTempFiles();
12324
12431
  const prunedOrphanChunks = pruneOrphanedChunks();
12325
12432
  if (opts.json === true) {
12326
- 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));
12327
12434
  return;
12328
12435
  }
12329
- emit(`Pruned ${removed} stale root(s). Remaining: ${after.length}`);
12330
- emit(`Pruned ${prunedTempFiles.length} stale indexed temp-dir file(s).`);
12331
- 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).`);
12332
12439
  return;
12333
12440
  }
12334
- emitErr2(`project: unknown action '${action}'. Use list, exclude, or prune.`);
12441
+ emitErr3(`project: unknown action '${action}'. Use list, exclude, or prune.`);
12335
12442
  throw new Error(`unknown project action: ${action}`);
12336
12443
  }
12337
12444
  function recordCompactDocStat(fullSourceBytes, emittedText, detail) {
12338
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(emittedText, "utf8"));
12445
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(emittedText, "utf8"));
12339
12446
  recordStat("compact_doc", bytesSaved, savedTokensFromBytes(bytesSaved), void 0, detail);
12340
12447
  }
12341
12448
  function cmdCompactDoc(opts) {
12342
- const resolved = path19.resolve(opts.filePath);
12449
+ const resolved = path20.resolve(opts.filePath);
12343
12450
  if (opts.heading !== void 0) {
12344
12451
  const result = compactDoc(resolved, opts.heading);
12345
12452
  if (result === null) {
12346
- emitErr2(`compact-doc: could not read or compact '${resolved}'`);
12453
+ emitErr3(`compact-doc: could not read or compact '${resolved}'`);
12347
12454
  throw new Error(`could not compact: ${resolved}`);
12348
12455
  }
12349
12456
  const legacyFullBytes = fs19.statSync(resolved).size;
12350
12457
  if (opts.json === true) {
12351
12458
  const jsonText = JSON.stringify({ path: resolved, compact: result }, null, 2);
12352
- emit(jsonText);
12459
+ emit2(jsonText);
12353
12460
  recordCompactDocStat(legacyFullBytes, jsonText, resolved);
12354
12461
  return;
12355
12462
  }
12356
- emit(result);
12463
+ emit2(result);
12357
12464
  recordCompactDocStat(legacyFullBytes, result, resolved);
12358
12465
  return;
12359
12466
  }
@@ -12362,7 +12469,7 @@ function cmdCompactDoc(opts) {
12362
12469
  try {
12363
12470
  sentences = requirePositiveStrictInt("--sentences", opts.sentences);
12364
12471
  } catch (e) {
12365
- 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}"`);
12366
12473
  throw new Error(`invalid --sentences: ${opts.sentences}`, { cause: e });
12367
12474
  }
12368
12475
  }
@@ -12375,7 +12482,7 @@ function cmdCompactDoc(opts) {
12375
12482
  try {
12376
12483
  sourceText = fs19.readFileSync(resolved, "utf-8");
12377
12484
  } catch {
12378
- emitErr2(`compact-doc: could not read or compact '${resolved}'`);
12485
+ emitErr3(`compact-doc: could not read or compact '${resolved}'`);
12379
12486
  throw new Error(`could not compact: ${resolved}`);
12380
12487
  }
12381
12488
  body = buildExtractiveCompact(sourceText, sentences);
@@ -12384,7 +12491,7 @@ function cmdCompactDoc(opts) {
12384
12491
  } else {
12385
12492
  const existing = readCompactBody(compactPath);
12386
12493
  if (existing === null) {
12387
- emitErr2(`compact-doc: could not read or compact '${resolved}'`);
12494
+ emitErr3(`compact-doc: could not read or compact '${resolved}'`);
12388
12495
  throw new Error(`could not compact: ${resolved}`);
12389
12496
  }
12390
12497
  body = existing;
@@ -12392,16 +12499,16 @@ function cmdCompactDoc(opts) {
12392
12499
  const extractiveFullBytes = fs19.statSync(resolved).size;
12393
12500
  if (opts.json === true) {
12394
12501
  const jsonText = JSON.stringify({ path: resolved, compactPath, rebuilt, compact: body }, null, 2);
12395
- emit(jsonText);
12502
+ emit2(jsonText);
12396
12503
  recordCompactDocStat(extractiveFullBytes, jsonText, resolved);
12397
12504
  return;
12398
12505
  }
12399
12506
  if (opts.show === true) {
12400
- emit(body);
12507
+ emit2(body);
12401
12508
  recordCompactDocStat(extractiveFullBytes, body, resolved);
12402
12509
  return;
12403
12510
  }
12404
- emit(
12511
+ emit2(
12405
12512
  `Compact sidecar ${rebuilt ? "built" : "already fresh"} at ${compactPath} (source: ${resolved}). Use --show to print it, --force to rebuild.`
12406
12513
  );
12407
12514
  }
@@ -12442,11 +12549,11 @@ async function cmdFetchImage(opts) {
12442
12549
  try {
12443
12550
  fetched = await fetchBuffer(opts.url);
12444
12551
  } catch (e) {
12445
- emitErr2(`fetch-image: network error \u2014 ${extractErrorMessage(e)}`);
12552
+ emitErr3(`fetch-image: network error \u2014 ${extractErrorMessage(e)}`);
12446
12553
  throw new Error(`fetch failed: ${opts.url}`, { cause: e });
12447
12554
  }
12448
12555
  const buf = fetched.body;
12449
- 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)}`);
12450
12557
  const originalBytes = buf.length;
12451
12558
  let shrunkBytes;
12452
12559
  let outData;
@@ -12469,11 +12576,11 @@ async function cmdFetchImage(opts) {
12469
12576
  }
12470
12577
  atomicWriteBytes(finalPath, outData);
12471
12578
  if (opts.json === true) {
12472
- 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));
12473
12580
  return;
12474
12581
  }
12475
12582
  const savings = wasShrunk ? ` (saved ${originalBytes - shrunkBytes} bytes)` : " (not shrunk \u2014 already small or unsupported format)";
12476
- emit(`Fetched ${originalBytes} bytes \u2192 ${finalPath}${savings}`);
12583
+ emit2(`Fetched ${originalBytes} bytes \u2192 ${finalPath}${savings}`);
12477
12584
  }
12478
12585
  function cmdHistory(opts) {
12479
12586
  let limit = 30;
@@ -12481,11 +12588,11 @@ function cmdHistory(opts) {
12481
12588
  try {
12482
12589
  limit = requireNonNegativeStrictInt("--limit", opts.limit);
12483
12590
  } catch (e) {
12484
- 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}"`);
12485
12592
  throw new Error(`invalid --limit: ${opts.limit}`, { cause: e });
12486
12593
  }
12487
12594
  if (limit === 0) {
12488
- emitErr2(`history: --limit must be a positive number, got: "${opts.limit}"`);
12595
+ emitErr3(`history: --limit must be a positive number, got: "${opts.limit}"`);
12489
12596
  throw new Error(`invalid --limit: ${opts.limit}`);
12490
12597
  }
12491
12598
  }
@@ -12520,21 +12627,21 @@ function cmdHistory(opts) {
12520
12627
  return;
12521
12628
  }
12522
12629
  if (items.length === 0) {
12523
- 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).");
12524
12631
  return;
12525
12632
  }
12526
12633
  const pad2 = (s, n) => s.length >= n ? s : s + " ".repeat(n - s.length);
12527
- emit(`${pad2("type", 5)} ${pad2("id", 18)} summary`);
12634
+ emit2(`${pad2("type", 5)} ${pad2("id", 18)} summary`);
12528
12635
  for (const item of items) {
12529
12636
  const preview = item.summary.length > 80 ? item.summary.slice(0, 77) + "..." : item.summary;
12530
- emit(`${pad2(item.type, 5)} ${pad2(item.id, 18)} ${preview}`);
12637
+ emit2(`${pad2(item.type, 5)} ${pad2(item.id, 18)} ${preview}`);
12531
12638
  }
12532
12639
  }
12533
12640
 
12534
12641
  // src/cli_bootstrap_audit.ts
12535
12642
  import * as fs20 from "node:fs";
12536
12643
  import * as os10 from "node:os";
12537
- import * as path20 from "node:path";
12644
+ import * as path21 from "node:path";
12538
12645
  import * as readline from "node:readline";
12539
12646
  var FRONTMATTER_KEYS = /* @__PURE__ */ new Set(["description", "tools", "allowed-tools", "allowed_tools"]);
12540
12647
  function parseBudget(name, value) {
@@ -12593,17 +12700,17 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
12593
12700
  logicalRoot = await fs20.promises.realpath(root);
12594
12701
  } catch (error) {
12595
12702
  if (error.code === "ENOENT") return entries;
12596
- diagnostics.push({ path: path20.resolve(root), reason: "unreadable root" });
12703
+ diagnostics.push({ path: path21.resolve(root), reason: "unreadable root" });
12597
12704
  return entries;
12598
12705
  }
12599
12706
  if (rootIsLink && !followLinks) {
12600
- 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)" });
12601
12708
  return entries;
12602
12709
  }
12603
12710
  const canonicalKey = (candidate) => process.platform === "win32" ? candidate.toLocaleLowerCase() : candidate;
12604
12711
  const isContained = (candidate, allowedRoot) => {
12605
- const relative3 = path20.relative(canonicalKey(allowedRoot), canonicalKey(candidate));
12606
- 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);
12607
12714
  };
12608
12715
  async function visit(candidate, allowedRoot, topLevel = false) {
12609
12716
  let real;
@@ -12612,13 +12719,13 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
12612
12719
  candidateIsLink = (await fs20.promises.lstat(candidate)).isSymbolicLink();
12613
12720
  real = await fs20.promises.realpath(candidate);
12614
12721
  } catch {
12615
- diagnostics.push({ path: path20.resolve(candidate), reason: "broken or unreadable link" });
12722
+ diagnostics.push({ path: path21.resolve(candidate), reason: "broken or unreadable link" });
12616
12723
  return;
12617
12724
  }
12618
12725
  const withinAllowedRoot = allowedRoot === void 0 ? isContained(real, logicalRoot) : isContained(real, allowedRoot);
12619
12726
  const canFollowExternal = followLinks && topLevel;
12620
12727
  if (!withinAllowedRoot && !canFollowExternal) {
12621
- 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" });
12622
12729
  return;
12623
12730
  }
12624
12731
  const trustedRoot = !withinAllowedRoot && canFollowExternal ? real : allowedRoot ?? real;
@@ -12626,7 +12733,7 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
12626
12733
  try {
12627
12734
  stat = await fs20.promises.stat(real);
12628
12735
  } catch {
12629
- diagnostics.push({ path: path20.resolve(candidate), reason: "unreadable" });
12736
+ diagnostics.push({ path: path21.resolve(candidate), reason: "unreadable" });
12630
12737
  return;
12631
12738
  }
12632
12739
  const canonical = process.platform === "win32" ? real.toLocaleLowerCase() : real;
@@ -12637,14 +12744,14 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
12637
12744
  try {
12638
12745
  children = await fs20.promises.readdir(real, { withFileTypes: true });
12639
12746
  } catch {
12640
- diagnostics.push({ path: path20.resolve(candidate), reason: "unreadable directory" });
12747
+ diagnostics.push({ path: path21.resolve(candidate), reason: "unreadable directory" });
12641
12748
  return;
12642
12749
  }
12643
12750
  children.sort((a, b) => a.name.localeCompare(b.name));
12644
- 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);
12645
12752
  return;
12646
12753
  }
12647
- if (!stat.isFile() || path20.extname(real).toLowerCase() !== ".md") return;
12754
+ if (!stat.isFile() || path21.extname(real).toLowerCase() !== ".md") return;
12648
12755
  if (seenFiles.has(canonical)) return;
12649
12756
  seenFiles.add(canonical);
12650
12757
  try {
@@ -12654,35 +12761,35 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
12654
12761
  const toolsBytes = Buffer.byteLength(metadata.tools, "utf8");
12655
12762
  entries.push({
12656
12763
  kind,
12657
- path: path20.resolve(real),
12764
+ path: path21.resolve(real),
12658
12765
  description_bytes: descriptionBytes,
12659
12766
  tools_bytes: toolsBytes,
12660
12767
  metadata_bytes: descriptionBytes + toolsBytes,
12661
12768
  estimated_tokens: Math.floor((descriptionBytes + toolsBytes) / 4)
12662
12769
  });
12663
12770
  } catch {
12664
- diagnostics.push({ path: path20.resolve(candidate), reason: "unreadable file" });
12771
+ diagnostics.push({ path: path21.resolve(candidate), reason: "unreadable file" });
12665
12772
  }
12666
12773
  }
12667
12774
  try {
12668
12775
  const children = await fs20.promises.readdir(logicalRoot, { withFileTypes: true });
12669
12776
  children.sort((a, b) => a.name.localeCompare(b.name));
12670
- 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);
12671
12778
  } catch {
12672
- diagnostics.push({ path: path20.resolve(root), reason: "unreadable root" });
12779
+ diagnostics.push({ path: path21.resolve(root), reason: "unreadable root" });
12673
12780
  }
12674
12781
  return entries;
12675
12782
  }
12676
12783
  async function buildBootstrapAudit(opts = {}) {
12677
12784
  const project = resolveProjectRoot(opts.project === void 0 ? {} : { project: opts.project });
12678
- const home = path20.resolve(opts.home ?? os10.homedir());
12785
+ const home = path21.resolve(opts.home ?? os10.homedir());
12679
12786
  const context = buildStats(project, home, opts.project === void 0 ? [] : [opts.project]);
12680
12787
  const diagnostics = [];
12681
12788
  const top = opts.top === void 0 ? 10 : parseBudget("--top", opts.top) ?? 10;
12682
12789
  const visitedDirs = /* @__PURE__ */ new Set();
12683
12790
  const seenFiles = /* @__PURE__ */ new Set();
12684
- const agents = await scanMetadataRoot(path20.join(home, ".claude", "agents"), "agent", diagnostics, visitedDirs, seenFiles, opts.followLinks === true);
12685
- const skills = await scanMetadataRoot(path20.join(home, ".claude", "skills"), "skill", diagnostics, visitedDirs, seenFiles, opts.followLinks === true);
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);
12686
12793
  const rankedEntries = [...agents, ...skills].sort((a, b) => b.metadata_bytes - a.metadata_bytes || a.path.localeCompare(b.path));
12687
12794
  const largestTotal = rankedEntries.length;
12688
12795
  const largest = rankedEntries.slice(0, top);
@@ -12749,7 +12856,7 @@ async function runBootstrapAudit(opts = {}) {
12749
12856
 
12750
12857
  // src/cli_memory.ts
12751
12858
  import * as fs21 from "node:fs";
12752
- import * as path21 from "node:path";
12859
+ import * as path22 from "node:path";
12753
12860
  function removeExactDupLines(text, report) {
12754
12861
  if (report.exactDupLines.length === 0) return text;
12755
12862
  const toRemove = new Set(report.exactDupLines.map(([, dupIdx]) => dupIdx));
@@ -12807,7 +12914,7 @@ function printReport(reports, clusters) {
12807
12914
  } else {
12808
12915
  for (const cluster of clusters) {
12809
12916
  w(
12810
- ` [${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(", ")}
12811
12918
  `
12812
12919
  );
12813
12920
  }
@@ -12822,7 +12929,7 @@ async function runMemoryCommand(opts = {}) {
12822
12929
  const claudeMds = findClaudeMdFiles(projectRoot);
12823
12930
  const reports = auditClaudeMd(claudeMds);
12824
12931
  const memoryMdPath = findMemoryMd(projectRoot);
12825
- const clusters = memoryMdPath !== null ? await findContentDuplicates(path21.dirname(memoryMdPath)) : [];
12932
+ const clusters = memoryMdPath !== null ? await findContentDuplicates(path22.dirname(memoryMdPath)) : [];
12826
12933
  printReport(reports, clusters);
12827
12934
  if (opts.fix !== true) return;
12828
12935
  const changes = [];
@@ -12868,16 +12975,16 @@ async function runMemoryCommand(opts = {}) {
12868
12975
 
12869
12976
  // src/cli_waste.ts
12870
12977
  import * as fs24 from "node:fs";
12871
- import * as path24 from "node:path";
12978
+ import * as path25 from "node:path";
12872
12979
 
12873
12980
  // src/waste.ts
12874
12981
  import * as fs22 from "node:fs";
12875
12982
  import * as os11 from "node:os";
12876
- import * as path22 from "node:path";
12983
+ import * as path23 from "node:path";
12877
12984
  function projectTranscriptsDir(projectRoot) {
12878
- const rootStr = path22.resolve(projectRoot);
12985
+ const rootStr = path23.resolve(projectRoot);
12879
12986
  const slug = rootStr.replace(/[^A-Za-z0-9]/g, "-");
12880
- return path22.join(os11.homedir(), ".claude", "projects", slug);
12987
+ return path23.join(os11.homedir(), ".claude", "projects", slug);
12881
12988
  }
12882
12989
  function findLatestTranscript(projectRoot) {
12883
12990
  const dir = projectTranscriptsDir(projectRoot);
@@ -12889,7 +12996,7 @@ function findLatestTranscript(projectRoot) {
12889
12996
  }
12890
12997
  let best = null;
12891
12998
  for (const name of entries) {
12892
- const full = path22.join(dir, name);
12999
+ const full = path23.join(dir, name);
12893
13000
  let stat;
12894
13001
  try {
12895
13002
  stat = fs22.statSync(full);
@@ -13119,7 +13226,7 @@ async function buildWasteReport(transcriptPath, opts = {}) {
13119
13226
 
13120
13227
  // src/copilot_waste.ts
13121
13228
  import * as fs23 from "node:fs";
13122
- import * as path23 from "node:path";
13229
+ import * as path24 from "node:path";
13123
13230
  var HOOK_RECORD_TYPES = /* @__PURE__ */ new Set(["hook.start", "hook.end"]);
13124
13231
  function splitInjectedBlocks(transformed) {
13125
13232
  const out2 = [];
@@ -13138,7 +13245,7 @@ function splitInjectedBlocks(transformed) {
13138
13245
  return out2;
13139
13246
  }
13140
13247
  function findLatestCopilotSession() {
13141
- const root = path23.join(copilotCliUserRoot(), "session-state");
13248
+ const root = path24.join(copilotCliUserRoot(), "session-state");
13142
13249
  let entries;
13143
13250
  try {
13144
13251
  entries = fs23.readdirSync(root);
@@ -13148,7 +13255,7 @@ function findLatestCopilotSession() {
13148
13255
  let best = null;
13149
13256
  let bestMtime = -Infinity;
13150
13257
  for (const entry of entries) {
13151
- const candidate = path23.join(root, entry, "events.jsonl");
13258
+ const candidate = path24.join(root, entry, "events.jsonl");
13152
13259
  try {
13153
13260
  const st = fs23.statSync(candidate);
13154
13261
  if (!st.isFile()) continue;
@@ -13169,7 +13276,7 @@ function buildCopilotWasteReport(eventsPath) {
13169
13276
  const raw = fs23.readFileSync(eventsPath, "utf-8");
13170
13277
  const report = {
13171
13278
  sessionPath: eventsPath,
13172
- sessionId: path23.basename(path23.dirname(eventsPath)),
13279
+ sessionId: path24.basename(path24.dirname(eventsPath)),
13173
13280
  turns: 0,
13174
13281
  tokens: null,
13175
13282
  blocks: [],
@@ -13448,7 +13555,7 @@ function printResidentContext(resident, w) {
13448
13555
  }
13449
13556
  async function runWasteCommand(opts = {}) {
13450
13557
  if (opts.copilot === true) {
13451
- const eventsPath = opts.transcript !== void 0 ? path24.resolve(opts.transcript) : findLatestCopilotSession();
13558
+ const eventsPath = opts.transcript !== void 0 ? path25.resolve(opts.transcript) : findLatestCopilotSession();
13452
13559
  if (eventsPath === null || !fs24.existsSync(eventsPath)) {
13453
13560
  const detail = eventsPath === null ? "no Copilot CLI session found under <copilot-home>/session-state" : `Copilot session event log not found: ${eventsPath}`;
13454
13561
  if (opts.json === true) {
@@ -13472,7 +13579,7 @@ async function runWasteCommand(opts = {}) {
13472
13579
  return;
13473
13580
  }
13474
13581
  const projectRoot = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
13475
- 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);
13476
13583
  if (transcriptPath === null) {
13477
13584
  if (opts.json === true) {
13478
13585
  process.stdout.write(`${JSON.stringify({ error: "no session transcript found", project: projectRoot })}
@@ -13503,7 +13610,7 @@ async function runWasteCommand(opts = {}) {
13503
13610
 
13504
13611
  // src/session_read.ts
13505
13612
  import * as fs25 from "node:fs";
13506
- import * as path25 from "node:path";
13613
+ import * as path26 from "node:path";
13507
13614
  import * as readline2 from "node:readline";
13508
13615
  function resolveSessionTranscript(arg, opts = {}) {
13509
13616
  if (arg !== void 0 && arg !== "") {
@@ -13511,7 +13618,7 @@ function resolveSessionTranscript(arg, opts = {}) {
13511
13618
  const projectRoot2 = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
13512
13619
  const dir = projectTranscriptsDir(projectRoot2);
13513
13620
  const byId = arg.endsWith(".jsonl") ? arg : `${arg}.jsonl`;
13514
- const candidate = path25.join(dir, byId);
13621
+ const candidate = path26.join(dir, byId);
13515
13622
  if (fs25.existsSync(candidate) && fs25.statSync(candidate).isFile()) return candidate;
13516
13623
  return null;
13517
13624
  }
@@ -13670,7 +13777,7 @@ function formatSessionSlice(turns) {
13670
13777
  // src/session_audit.ts
13671
13778
  import * as fs26 from "node:fs";
13672
13779
  import * as os12 from "node:os";
13673
- import * as path26 from "node:path";
13780
+ import * as path27 from "node:path";
13674
13781
  import * as readline3 from "node:readline";
13675
13782
  function emptyMeasured() {
13676
13783
  return { apiCalls: 0, inputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, outputTokens: 0 };
@@ -13717,6 +13824,64 @@ var CACHE_READ_MULTIPLIER = 0.1;
13717
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)/;
13718
13825
  var READ_DIVERT_MAX_BYTES = 2500;
13719
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
+ }
13720
13885
  var BASH_FILTER_MARKER_RE = /\[token-goat[:\]]/;
13721
13886
  var BASH_SMALL_RESULT_MAX_BYTES = 1024;
13722
13887
  function commandHead(raw) {
@@ -13771,7 +13936,7 @@ function listCorpusTranscripts(corpusDir) {
13771
13936
  const found = [];
13772
13937
  const walk = (dir, entries) => {
13773
13938
  for (const entry of entries) {
13774
- const full = path26.join(dir, entry.name);
13939
+ const full = path27.join(dir, entry.name);
13775
13940
  if (entry.name.endsWith(".jsonl")) {
13776
13941
  found.push(full);
13777
13942
  } else if (entry.isDirectory()) {
@@ -13789,9 +13954,9 @@ function listCorpusTranscripts(corpusDir) {
13789
13954
  return found.sort();
13790
13955
  }
13791
13956
  function defaultCorpusDir() {
13792
- return path26.join(os12.homedir(), ".claude", "projects");
13957
+ return path27.join(os12.homedir(), ".claude", "projects");
13793
13958
  }
13794
- async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneObservations, bashHeadMap) {
13959
+ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneObservations, bashHeadMap, denyRows) {
13795
13960
  const isLane = filePath.split(/[\\/]/).includes("subagents");
13796
13961
  let laneFirstPrefix = null;
13797
13962
  let laneBriefBytes = -1;
@@ -13799,8 +13964,12 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
13799
13964
  const bashHeadById = /* @__PURE__ */ new Map();
13800
13965
  let pendingBashReread = [];
13801
13966
  const readCallById = /* @__PURE__ */ new Map();
13967
+ const readPathById = /* @__PURE__ */ new Map();
13968
+ const editPathById = /* @__PURE__ */ new Map();
13969
+ const editErrorById = /* @__PURE__ */ new Map();
13802
13970
  const readPathEpoch = /* @__PURE__ */ new Map();
13803
13971
  let compactEpoch = 0;
13972
+ const openDenies = [];
13804
13973
  let sawTokenGoatHook = false;
13805
13974
  let fileRepeats = 0;
13806
13975
  let fileRepeatsFullNoRange = 0;
@@ -13895,13 +14064,38 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
13895
14064
  toolNameById.set(block["id"], name);
13896
14065
  toolRollup(toolMap, name).calls += 1;
13897
14066
  const input = block["input"] !== null && typeof block["input"] === "object" ? block["input"] : {};
14067
+ let readNorm;
14068
+ let editNorm;
13898
14069
  if (name === "Bash" && typeof input["command"] === "string") {
13899
14070
  bashHeadById.set(block["id"], commandHead(input["command"]));
13900
14071
  } else if (name === "Read" && typeof input["file_path"] === "string") {
13901
- const norm = normalizeReadPath(input["file_path"]);
13902
- const priorEpoch = readPathEpoch.get(norm);
14072
+ readNorm = normalizeReadPath(input["file_path"]);
14073
+ const priorEpoch = readPathEpoch.get(readNorm);
13903
14074
  readCallById.set(block["id"], { wasSeenBefore: priorEpoch !== void 0, hasRange: input["offset"] !== void 0 || input["limit"] !== void 0, afterCompaction: priorEpoch !== void 0 && compactEpoch > priorEpoch });
13904
- 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
+ }
13905
14099
  }
13906
14100
  }
13907
14101
  let inputBytes;
@@ -13930,8 +14124,27 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
13930
14124
  if (!toolNameById.has(id)) roll.calls += 1;
13931
14125
  roll.resultBytes += bytes;
13932
14126
  roll.resultEstTokens += estimateTokensFromLength(bytes);
14127
+ if (name === "Edit" && block["is_error"] === true) {
14128
+ s.editErrorBaseline.totalErrors += 1;
14129
+ editErrorById.set(id, true);
14130
+ }
13933
14131
  if (name === "Read") {
13934
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
+ }
13935
14148
  if (bytes < READ_DIVERT_MAX_BYTES && READ_DIVERT_MARKER_RE.test(text)) {
13936
14149
  s.readInterception.divertedByMarker += 1;
13937
14150
  s.readInterception.divertedBytes += bytes;
@@ -14068,6 +14281,42 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
14068
14281
  }
14069
14282
  flushLane(0);
14070
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
+ }
14071
14320
  if (sawTokenGoatHook) {
14072
14321
  s.readInterception.repeatInHookedSessions += fileRepeats;
14073
14322
  s.readInterception.repeatFullNoRangeInHookedSessions += fileRepeatsFullNoRange;
@@ -14117,8 +14366,47 @@ function hookRollup(hookMap, origin, event) {
14117
14366
  }
14118
14367
  return roll;
14119
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
+ }
14120
14408
  async function auditSessionCorpus(opts = {}) {
14121
- const corpusDir = path26.resolve(opts.dir ?? defaultCorpusDir());
14409
+ const corpusDir = path27.resolve(opts.dir ?? defaultCorpusDir());
14122
14410
  if (!fs26.existsSync(corpusDir)) {
14123
14411
  throw new Error(`session corpus directory not found: ${corpusDir}`);
14124
14412
  }
@@ -14156,6 +14444,8 @@ async function auditSessionCorpus(opts = {}) {
14156
14444
  laneAgentTypes: [],
14157
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 },
14158
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 },
14159
14449
  lineTypes: {},
14160
14450
  omissionMarkers: { fires: 0, linesOmitted: 0 }
14161
14451
  };
@@ -14164,9 +14454,10 @@ async function auditSessionCorpus(opts = {}) {
14164
14454
  const hookMap = /* @__PURE__ */ new Map();
14165
14455
  const laneObservations = [];
14166
14456
  const bashHeadMap = /* @__PURE__ */ new Map();
14457
+ const denyRows = [];
14167
14458
  for (const file of files) {
14168
14459
  try {
14169
- await auditOneFile(file, summary, toolMap, attachmentMap, hookMap, laneObservations, bashHeadMap);
14460
+ await auditOneFile(file, summary, toolMap, attachmentMap, hookMap, laneObservations, bashHeadMap, denyRows);
14170
14461
  summary.filesScanned += 1;
14171
14462
  } catch {
14172
14463
  summary.filesFailed += 1;
@@ -14203,6 +14494,8 @@ async function auditSessionCorpus(opts = {}) {
14203
14494
  }
14204
14495
  summary.attachmentKinds = [...attachmentMap.values()].sort((a, b) => b.billedEquivTokens - a.billedEquivTokens || b.injections - a.injections || a.kind.localeCompare(b.kind));
14205
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;
14206
14499
  summary.runtimeMs = Date.now() - started;
14207
14500
  return summary;
14208
14501
  }
@@ -14288,6 +14581,15 @@ function formatSessionAudit(s) {
14288
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)}`);
14289
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)})`);
14290
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("");
14291
14593
  lines.push("## Bash filter fire-rate (token-goat in-band markers inside Bash tool results)");
14292
14594
  const bi = s.bashInterception;
14293
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)`);
@@ -14306,7 +14608,7 @@ function formatSessionAudit(s) {
14306
14608
  // src/cli_mcp_audit.ts
14307
14609
  import * as fs27 from "node:fs";
14308
14610
  import * as os13 from "node:os";
14309
- import * as path27 from "node:path";
14611
+ import * as path28 from "node:path";
14310
14612
  function readMcpJsonFile(configPath2) {
14311
14613
  try {
14312
14614
  if (!fs27.existsSync(configPath2)) return null;
@@ -14345,8 +14647,8 @@ function readClaudeJsonConfig(claudeJsonPath, projectRoot) {
14345
14647
  }
14346
14648
  }
14347
14649
  function discoverMcpConfig(projectRoot, home) {
14348
- const mcpJsonPath = path27.join(projectRoot, ".mcp.json");
14349
- const claudeJsonPath = path27.join(home, ".claude.json");
14650
+ const mcpJsonPath = path28.join(projectRoot, ".mcp.json");
14651
+ const claudeJsonPath = path28.join(home, ".claude.json");
14350
14652
  const sourcesChecked = [mcpJsonPath, claudeJsonPath];
14351
14653
  const fromMcpJson = readMcpJsonFile(mcpJsonPath);
14352
14654
  if (fromMcpJson !== null) return { servers: fromMcpJson, sourcePath: mcpJsonPath, sourcesChecked };
@@ -14906,7 +15208,7 @@ async function cmdMcpServe() {
14906
15208
  let StdioServerTransport;
14907
15209
  try {
14908
15210
  ;
14909
- ({ createMcpServer } = await import("./token-goat-chunk-ZIPBLIUZ.mjs"));
15211
+ ({ createMcpServer } = await import("./token-goat-chunk-CD6U5OD6.mjs"));
14910
15212
  ({ StdioServerTransport } = await import("./token-goat-chunk-324QOJYZ.mjs"));
14911
15213
  } catch (err2) {
14912
15214
  process.stderr.write(
@@ -14919,8 +15221,8 @@ async function cmdMcpServe() {
14919
15221
  const server = await createMcpServer();
14920
15222
  const transport = new StdioServerTransport();
14921
15223
  await server.connect(transport);
14922
- await new Promise((resolve13) => {
14923
- server.onclose = resolve13;
15224
+ await new Promise((resolve14) => {
15225
+ server.onclose = resolve14;
14924
15226
  });
14925
15227
  }
14926
15228
  function printBridgeVerificationNotice(harness) {
@@ -14931,7 +15233,7 @@ async function cmdHook(event, opts) {
14931
15233
  if (typeof opts.harness === "string" && opts.harness.length > 0) {
14932
15234
  process.env[ENV_KEYS.HARNESS_OVERRIDE] = opts.harness;
14933
15235
  }
14934
- const { relay } = await import("./token-goat-chunk-LJEETTER.mjs");
15236
+ const { relay } = await import("./token-goat-chunk-AUM22UQ2.mjs");
14935
15237
  await relay(event);
14936
15238
  }
14937
15239
  async function cmdInstall(opts) {
@@ -15044,14 +15346,14 @@ async function cmdInstall(opts) {
15044
15346
  );
15045
15347
  }
15046
15348
  try {
15047
- const skillDir = path28.join(homedir13(), ".claude", "skills");
15349
+ const skillDir = path29.join(homedir13(), ".claude", "skills");
15048
15350
  if (fs28.existsSync(skillDir)) {
15049
15351
  const entries = fs28.readdirSync(skillDir, { withFileTypes: true });
15050
15352
  const skillNames = [];
15051
15353
  const sessionId = getSessionId();
15052
15354
  for (const entry of entries) {
15053
15355
  if (!entry.isDirectory()) continue;
15054
- const skillFile = path28.join(skillDir, entry.name, "SKILL.md");
15356
+ const skillFile = path29.join(skillDir, entry.name, "SKILL.md");
15055
15357
  if (fs28.existsSync(skillFile)) {
15056
15358
  const body = fs28.readFileSync(skillFile, "utf-8");
15057
15359
  const compact = extractCompactFromMarker(body);
@@ -15064,7 +15366,7 @@ async function cmdInstall(opts) {
15064
15366
  if (skillNames.length > 0) {
15065
15367
  const dir = skillOutputsDir();
15066
15368
  await fs28.promises.mkdir(dir, { recursive: true });
15067
- const pregenPath = path28.join(dir, "pregen.json");
15369
+ const pregenPath = path29.join(dir, "pregen.json");
15068
15370
  const pregenData = { ts: Date.now(), names: skillNames };
15069
15371
  await fs28.promises.writeFile(pregenPath, JSON.stringify(pregenData, null, 2));
15070
15372
  out(`Pre-generated ${skillNames.length} skill compacts.`);
@@ -15265,7 +15567,7 @@ async function cmdSessionOutline(sessionIdOrPath, opts = {}) {
15265
15567
  ${formatSessionOutline(turns)}`;
15266
15568
  out(text);
15267
15569
  const fullSourceBytes = sessionTranscriptSize(transcriptPath);
15268
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
15570
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
15269
15571
  recordStat("session_outline", bytesSaved, savedTokensFromBytes(bytesSaved));
15270
15572
  }
15271
15573
  function sessionTranscriptSize(transcriptPath) {
@@ -15287,7 +15589,7 @@ async function cmdSessionSlice(sessionIdOrPath, opts) {
15287
15589
  const text = opts.json === true ? JSON.stringify({ transcriptPath, turns }) : formatSessionSlice(turns);
15288
15590
  out(text);
15289
15591
  const fullSourceBytes = sessionTranscriptSize(transcriptPath);
15290
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
15592
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
15291
15593
  recordStat("session_slice", bytesSaved, savedTokensFromBytes(bytesSaved));
15292
15594
  }
15293
15595
  function cmdMcpAudit(opts = {}) {
@@ -15328,7 +15630,7 @@ function cmdHintStats(opts = {}) {
15328
15630
  });
15329
15631
  }
15330
15632
  function _applyFiltersAndPrint(content, opts, fenceByProvenance = false, fenceTag = UNTRUSTED_WEB_TAG) {
15331
- const emit2 = (text) => {
15633
+ const emit3 = (text) => {
15332
15634
  if (!fenceByProvenance || text === "") {
15333
15635
  out(text);
15334
15636
  return text;
@@ -15365,7 +15667,7 @@ function _applyFiltersAndPrint(content, opts, fenceByProvenance = false, fenceTa
15365
15667
  }
15366
15668
  const rawLines = content.split(/\r?\n/);
15367
15669
  if (opts.full === true) {
15368
- return emit2(rawLines.join("\n"));
15670
+ return emit3(rawLines.join("\n"));
15369
15671
  }
15370
15672
  const lines = rawLines.length > 1 && rawLines[rawLines.length - 1] === "" ? rawLines.slice(0, -1) : rawLines;
15371
15673
  const headN = opts.head !== void 0 ? requireNonNegativeInt("--head", opts.head) : 30;
@@ -15388,7 +15690,7 @@ function _applyFiltersAndPrint(content, opts, fenceByProvenance = false, fenceTa
15388
15690
  result = lines.slice(Math.max(0, lines.length - tailN));
15389
15691
  noteLineCap("last", "tail", result.length, lines.length);
15390
15692
  }
15391
- return emit2(result.join("\n"));
15693
+ return emit3(result.join("\n"));
15392
15694
  }
15393
15695
  function fenceFileText(text) {
15394
15696
  return fenceUntrusted(text, UNTRUSTED_FILE_TAG);
@@ -15463,7 +15765,7 @@ async function cmdPdfExtract(file, opts) {
15463
15765
  const text = await runPdfExtractText(file, opts.pages, opts.layout === true);
15464
15766
  const printed = _applyFiltersAndPrint(text, opts, true, UNTRUSTED_FILE_TAG);
15465
15767
  const fullSourceBytes = fileSizeOrZero(file);
15466
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(printed, "utf8"));
15768
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(printed, "utf8"));
15467
15769
  recordStat("pdf_extract", bytesSaved, savedTokensFromBytes(bytesSaved));
15468
15770
  }
15469
15771
  async function cmdPdfLocate(file, pattern, opts) {
@@ -15493,7 +15795,7 @@ ${countNoun(matches2.length, "match", "matches")} across ${countNoun(pages.lengt
15493
15795
  out(printed);
15494
15796
  }
15495
15797
  const fullSourceBytes = fileSizeOrZero(file);
15496
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(printed, "utf8"));
15798
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(printed, "utf8"));
15497
15799
  recordStat("pdf_locate", bytesSaved, savedTokensFromBytes(bytesSaved));
15498
15800
  }
15499
15801
  async function cmdPdfOutline(file, opts) {
@@ -15509,7 +15811,7 @@ async function cmdPdfOutline(file, opts) {
15509
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"));
15510
15812
  out(text);
15511
15813
  const fullSourceBytes = fileSizeOrZero(file);
15512
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
15814
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
15513
15815
  recordStat("pdf_outline", bytesSaved, savedTokensFromBytes(bytesSaved));
15514
15816
  }
15515
15817
  async function cmdPdfMeta(file, opts = {}) {
@@ -15523,7 +15825,7 @@ async function cmdPdfMeta(file, opts = {}) {
15523
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");
15524
15826
  out(text);
15525
15827
  const fullSourceBytes = fileSizeOrZero(file);
15526
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
15828
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
15527
15829
  recordStat("pdf_meta", bytesSaved, savedTokensFromBytes(bytesSaved));
15528
15830
  }
15529
15831
  async function cmdImageMeta(file, opts = {}) {
@@ -15545,8 +15847,8 @@ ${msg}`;
15545
15847
  const text = opts.json === true ? JSON.stringify(meta, null, 2) : lines.join("\n");
15546
15848
  out(text);
15547
15849
  const fullSourceBytes = fileSizeOrZero(file);
15548
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
15549
- 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));
15550
15852
  }
15551
15853
  function fenceOcrText(text) {
15552
15854
  if (!injectionFencingEnabled()) return text;
@@ -15577,8 +15879,8 @@ ${fenceOcrText(result.text)}` : `${lines.join("\n")}
15577
15879
  }
15578
15880
  out(text);
15579
15881
  const fullSourceBytes = fileSizeOrZero(file);
15580
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
15581
- 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));
15582
15884
  }
15583
15885
  function cmdVideoChapters(file) {
15584
15886
  const { chapters, subtitleStreams } = extractVideoChapters(file);
@@ -15603,7 +15905,7 @@ function cmdVideoChapters(file) {
15603
15905
  const text = lines.join("\n");
15604
15906
  out(text);
15605
15907
  const fullSourceBytes = fileSizeOrZero(file);
15606
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
15908
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
15607
15909
  recordStat("video_chapters", bytesSaved, savedTokensFromBytes(bytesSaved));
15608
15910
  }
15609
15911
  function formatVideoTimestamp(totalSeconds) {
@@ -15630,7 +15932,7 @@ function cmdSharepointResolve(url) {
15630
15932
  }
15631
15933
  function recordXlsxStat(kind, file, emitted) {
15632
15934
  const fullSourceBytes = fileSizeOrZero(file);
15633
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(emitted, "utf8"));
15935
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(emitted, "utf8"));
15634
15936
  recordStat(kind, bytesSaved, savedTokensFromBytes(bytesSaved));
15635
15937
  }
15636
15938
  async function cmdXlsxSheets(file, opts = {}) {
@@ -15665,7 +15967,7 @@ async function cmdXlsxQuery(file, opts) {
15665
15967
  }
15666
15968
  function recordDocStat(kind, file, emitted) {
15667
15969
  const fullSourceBytes = fileSizeOrZero(file);
15668
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(emitted, "utf8"));
15970
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(emitted, "utf8"));
15669
15971
  recordStat(kind, bytesSaved, savedTokensFromBytes(bytesSaved));
15670
15972
  }
15671
15973
  async function cmdPptxOutline(file, opts) {
@@ -15823,6 +16125,18 @@ function cmdConflicts(targetPath, opts) {
15823
16125
  async function cmdScreenshot(url, destPath, opts) {
15824
16126
  out(await runScreenshot(url, destPath, opts));
15825
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
+ }
15826
16140
  function runExit(fn) {
15827
16141
  try {
15828
16142
  process.exitCode = fn();
@@ -15853,7 +16167,7 @@ function emitExtraFileArgsNote(command, first, extras, opts = {}) {
15853
16167
  }
15854
16168
  async function cmdCompress(opts) {
15855
16169
  try {
15856
- const bashRunner = await import("./token-goat-chunk-L2XHDICZ.mjs");
16170
+ const bashRunner = await import("./token-goat-chunk-4KD2N46D.mjs");
15857
16171
  if (opts.compress === false) {
15858
16172
  process.exitCode = bashRunner.runRaw(opts.cmd, parseTimeout(opts.timeout, bashRunner.DEFAULT_TIMEOUT_SECONDS));
15859
16173
  return;
@@ -15951,8 +16265,8 @@ async function cmdSkillCompact(name, opts) {
15951
16265
  }
15952
16266
  throw new CliError(`failed to read skill file '${opts.path}': ${extractErrorMessage(e)}`);
15953
16267
  }
15954
- cacheName = name ?? path28.basename(path28.dirname(path28.resolve(opts.path)));
15955
- sourcePath = path28.resolve(opts.path);
16268
+ cacheName = name ?? path29.basename(path29.dirname(path29.resolve(opts.path)));
16269
+ sourcePath = path29.resolve(opts.path);
15956
16270
  } else {
15957
16271
  if (name === void 0 || !name.trim()) {
15958
16272
  throw new CliError("skill-compact requires a <name> or --path <file>");
@@ -16093,8 +16407,8 @@ async function cmdSkillDiff(name) {
16093
16407
  }
16094
16408
  const newer = versions[0];
16095
16409
  const older = versions[1];
16096
- const newerBody = await fs28.promises.readFile(path28.resolve(dir, `${newer.outputId}.txt`), "utf-8").catch(() => null);
16097
- 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);
16098
16412
  if (newerBody === null || olderBody === null) {
16099
16413
  out(`a cached version of '${name}' was evicted while diffing -- try again`);
16100
16414
  return;
@@ -16146,7 +16460,7 @@ function atomicWriteBuffer(dest, data) {
16146
16460
  if (e.code !== "ENOENT") throw e;
16147
16461
  }
16148
16462
  const rnd = randomBytes(4).toString("hex");
16149
- 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}`);
16150
16464
  try {
16151
16465
  fs28.writeFileSync(tmp, data, { mode: 384 });
16152
16466
  try {
@@ -16184,9 +16498,9 @@ function mapFsError(e, src, dest, srcLabel = "source") {
16184
16498
  const fe = e;
16185
16499
  if (fe.code === "ENOENT") {
16186
16500
  const errPath = fe.path ?? "";
16187
- const isSource = src !== void 0 && path28.resolve(errPath) === path28.resolve(src);
16501
+ const isSource = src !== void 0 && path29.resolve(errPath) === path29.resolve(src);
16188
16502
  if (isSource) throw new CliError(`${/\bfile$/i.test(srcLabel) ? srcLabel : `${srcLabel} file`} not found: ${src}`);
16189
- 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 || "."));
16190
16504
  throw new CliError(`destination directory does not exist: ${destDir}`);
16191
16505
  }
16192
16506
  if (fe.code === "ENOTDIR") {
@@ -16197,7 +16511,7 @@ function mapFsError(e, src, dest, srcLabel = "source") {
16197
16511
  }
16198
16512
  if (fe.code === "EISDIR") {
16199
16513
  const errPath = fe.path ?? "";
16200
- 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));
16201
16515
  if (isSource) throw new CliError(`source is a directory, not a file: ${src}`);
16202
16516
  throw new CliError(`destination is a directory, not a file: ${dest ?? (errPath || "(unknown)")}`);
16203
16517
  }
@@ -16266,7 +16580,7 @@ function validateWritablePath(dest, label) {
16266
16580
  throw new CliError(`${label} path contains a null byte`);
16267
16581
  }
16268
16582
  if (isWindows()) {
16269
- const base = path28.basename(dest);
16583
+ const base = path29.basename(dest);
16270
16584
  const stem = base.replace(/\.[^.]*$/, "").toUpperCase();
16271
16585
  if (WIN_RESERVED.has(stem)) {
16272
16586
  throw new CliError(`${label} '${base}' is a reserved Windows device name`);
@@ -16407,7 +16721,7 @@ function cmdWriteFile(dest, opts) {
16407
16721
  throw new CliError(`TOKEN_GOAT_MAX_STDIN_MB must be a positive integer; got '${process.env["TOKEN_GOAT_MAX_STDIN_MB"] ?? ""}'`);
16408
16722
  }
16409
16723
  const maxBytes = maxMB * 1024 * 1024;
16410
- return new Promise((resolve13, reject) => {
16724
+ return new Promise((resolve14, reject) => {
16411
16725
  const chunks = [];
16412
16726
  let totalBytes = 0;
16413
16727
  let settled = false;
@@ -16431,7 +16745,7 @@ function cmdWriteFile(dest, opts) {
16431
16745
  try {
16432
16746
  atomicWriteBuffer(dest, Buffer.concat(chunks));
16433
16747
  enqueueDirtyPathSafe(dest);
16434
- resolve13();
16748
+ resolve14();
16435
16749
  } catch (e) {
16436
16750
  try {
16437
16751
  mapFsError(e, void 0, dest);
@@ -16783,7 +17097,7 @@ ${content}`;
16783
17097
  }
16784
17098
  const toEmit = fenceUntrusted(emitted, UNTRUSTED_WEB_TAG);
16785
17099
  out(toEmit);
16786
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(toEmit, "utf8"));
17100
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(toEmit, "utf8"));
16787
17101
  recordStat("gdrive_sections", bytesSaved, savedTokensFromBytes(bytesSaved));
16788
17102
  }
16789
17103
  function expandGlobs(root, patterns, globFnOverride) {
@@ -16793,17 +17107,17 @@ function expandGlobs(root, patterns, globFnOverride) {
16793
17107
  if (globFn !== void 0 && (p.includes("*") || p.includes("?") || p.includes("{"))) {
16794
17108
  try {
16795
17109
  const hits = globFn(p, { cwd: root });
16796
- 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));
16797
17111
  continue;
16798
17112
  } catch {
16799
17113
  }
16800
17114
  }
16801
- out2.push(path28.isAbsolute(p) ? p : path28.join(root, p));
17115
+ out2.push(path29.isAbsolute(p) ? p : path29.join(root, p));
16802
17116
  }
16803
17117
  return out2;
16804
17118
  }
16805
17119
  function readIgnoreFile(root) {
16806
- const ignorePath = path28.join(root, ".tokengoatignore");
17120
+ const ignorePath = path29.join(root, ".tokengoatignore");
16807
17121
  let raw;
16808
17122
  try {
16809
17123
  raw = fs28.readFileSync(ignorePath, "utf8");
@@ -16874,7 +17188,7 @@ function cmdTokens(patterns, opts) {
16874
17188
  if (opts.tree === true) {
16875
17189
  const dirs = /* @__PURE__ */ new Map();
16876
17190
  for (const e of entries) {
16877
- const dir = path28.dirname(e.rel_path);
17191
+ const dir = path29.dirname(e.rel_path);
16878
17192
  if (!dirs.has(dir)) dirs.set(dir, []);
16879
17193
  dirs.get(dir).push(e);
16880
17194
  }
@@ -16884,7 +17198,7 @@ function cmdTokens(patterns, opts) {
16884
17198
  const pct2 = result.total_tokens > 0 ? Math.round(dirTokens / result.total_tokens * 100) : 0;
16885
17199
  lines2.push(`${dir}/ (${dirTokens} tokens, ${pct2}%)`);
16886
17200
  for (const e of dirEntries) {
16887
- 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`);
16888
17202
  }
16889
17203
  }
16890
17204
  out(lines2.join("\n"));
@@ -16935,6 +17249,63 @@ function cmdFailures(src, opts) {
16935
17249
  saveFailureSnapshot(project.hash, key, { signatures, runner: result.runner, storedAt: Date.now() });
16936
17250
  out(opts.json === true ? formatFailureDeltaJson(delta, result.runner) : formatFailureDeltaText(delta, result.runner));
16937
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
+ }
16938
17309
  function buildProgram() {
16939
17310
  const program2 = new Command();
16940
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)");
@@ -17113,6 +17484,15 @@ function buildProgram() {
17113
17484
  worker.command("stop").description("stop the background indexer").action(guard(cmdWorkerStop));
17114
17485
  worker.command("status").description("check if the indexer is running").action(guard(cmdWorkerStatus));
17115
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
+ );
17116
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));
17117
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));
17118
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));
@@ -17263,10 +17643,22 @@ function buildProgram() {
17263
17643
  })
17264
17644
  )
17265
17645
  );
17266
- 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(
17267
17647
  (opts) => runExit(
17268
17648
  () => runArch({
17269
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 } : {},
17270
17662
  ...opts.json === true ? { json: true } : {}
17271
17663
  })
17272
17664
  )
@@ -17491,8 +17883,24 @@ function buildProgram() {
17491
17883
  out(VERSION);
17492
17884
  })
17493
17885
  );
17494
- 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";
17495
- 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();
17496
17904
  return program2;
17497
17905
  }
17498
17906
  function applyExitOverride(command) {