token-goat 2.8.6 → 2.9.2

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-FXAPKVRG.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-VCNW7BGU.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,35 @@ 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-LJ3CHCTT.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,
249
+ PROJECT_LOCKED_KEYS,
250
+ PROJECT_LOCKED_SECTIONS,
275
251
  RESET,
276
252
  VERSION,
277
253
  _useRichStats,
254
+ anchoredMarkerPattern,
278
255
  atomicWriteBytes,
279
256
  atomicWriteText,
280
257
  canonicalize,
258
+ cappedSourceBytesSaved,
281
259
  colorStdout,
260
+ compileCustomPatterns,
282
261
  configPath,
262
+ copilotCliConfigPath,
263
+ copilotCliMcpToolsDir,
264
+ copilotCliScriptPath,
265
+ copilotCliUserRoot,
283
266
  countNoun,
284
267
  dataDir,
285
268
  decodeSource,
@@ -289,11 +272,13 @@ import {
289
272
  encodeSource,
290
273
  ensureDirSync,
291
274
  ensureNewline,
275
+ envBool,
292
276
  escapeRegExp,
293
277
  extractErrorMessage,
294
278
  fg,
295
279
  fileIsAbsent,
296
280
  findProject,
281
+ findStrayClaudeMdBlocks,
297
282
  fingerprintContent,
298
283
  fingerprintFile,
299
284
  foldPath,
@@ -304,9 +289,24 @@ import {
304
289
  getLastProjectConfigParseError,
305
290
  getProjectConfigInfo,
306
291
  globalDbPath,
292
+ groupHasTokenGoat,
293
+ installClaudeMd,
294
+ installCodex,
295
+ installCopilotCli,
296
+ installGrok,
297
+ installHooks,
298
+ installKimi,
307
299
  installSingleFilePlugin,
300
+ installSkill,
301
+ installVscode,
308
302
  invalidateConfigCache,
303
+ isCodexInstalled,
304
+ isCopilotCliInstalled,
309
305
  isDotenvPath,
306
+ isGrokInstalled,
307
+ isInstalled,
308
+ isKimiInstalled,
309
+ isTestFile,
310
310
  isUnderBlockedRoot,
311
311
  isWindows,
312
312
  lastProjectConfigLockedKeys,
@@ -314,6 +314,7 @@ import {
314
314
  loadPersistedConfig,
315
315
  normalizeDarwinSystemAlias,
316
316
  normalizePath,
317
+ otherScopeHasManagedServer,
317
318
  pad,
318
319
  parse,
319
320
  pushAll,
@@ -341,14 +342,23 @@ import {
341
342
  summarize,
342
343
  toDisplayPath,
343
344
  toKB,
345
+ uninstallClaudeMd,
346
+ uninstallCodex,
347
+ uninstallCopilotCli,
348
+ uninstallGrok,
349
+ uninstallHooks,
350
+ uninstallKimi,
344
351
  uninstallSingleFilePlugin,
352
+ uninstallSkill,
353
+ uninstallVscode,
345
354
  validateEnumField,
346
355
  validateNumericField,
356
+ vscodeDecoderConfigured,
347
357
  withExtension,
348
358
  withFileLock,
349
359
  withRetryOnLock,
350
360
  writeJsonSettings
351
- } from "./token-goat-chunk-2JZ66BBE.mjs";
361
+ } from "./token-goat-chunk-UZ2NFOOZ.mjs";
352
362
  import {
353
363
  __export
354
364
  } from "./token-goat-chunk-AEX54RUZ.mjs";
@@ -3467,9 +3477,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3467
3477
  * @param {string} [path]
3468
3478
  * @return {(string|null|Command)}
3469
3479
  */
3470
- executableDir(path29) {
3471
- if (path29 === void 0) return this._executableDir;
3472
- this._executableDir = path29;
3480
+ executableDir(path30) {
3481
+ if (path30 === void 0) return this._executableDir;
3482
+ this._executableDir = path30;
3473
3483
  return this;
3474
3484
  }
3475
3485
  /**
@@ -3769,7 +3779,7 @@ function attemptedCommandName(argv) {
3769
3779
 
3770
3780
  // src/cli.ts
3771
3781
  import * as fs28 from "fs";
3772
- import * as path28 from "path";
3782
+ import * as path29 from "path";
3773
3783
  import { homedir as homedir13 } from "os";
3774
3784
 
3775
3785
  // src/walk_index.ts
@@ -4311,7 +4321,7 @@ function materializeShrunkImage(context: string | undefined): string | undefined
4311
4321
  try {
4312
4322
  pruneMaterializedShrinks();
4313
4323
  const buf = Buffer.from(match[2], "base64");
4314
- const name = \`token-goat-shrink-\${process.pid}-\${Date.now()}-\${Math.random().toString(36).slice(2)}.\${match[1]}\`;
4324
+ const name = \`token-goat-shrink-\${process.pid}-\${Date.now()}-\${globalThis.crypto.randomUUID()}.\${match[1]}\`;
4315
4325
  const file = path.join(os.tmpdir(), name);
4316
4326
  fs.writeFileSync(file, buf);
4317
4327
  return file;
@@ -5657,9 +5667,110 @@ function formatCues(cues) {
5657
5667
  return cues.map((cue) => `[${formatTimestamp(cue.startSeconds)}]${cue.speaker !== null ? ` ${cue.speaker}:` : ""} ${cue.text}`).join("\n");
5658
5668
  }
5659
5669
 
5670
+ // src/affected.ts
5671
+ import * as path9 from "node:path";
5672
+ var DEFAULT_AFFECTED_DEPTH = 5;
5673
+ function emit(text) {
5674
+ const payload = colorStdout() ? text : stripAnsi(text);
5675
+ process.stdout.write(ensureNewline(payload));
5676
+ }
5677
+ function emitErr(text) {
5678
+ process.stderr.write(ensureNewline(text));
5679
+ }
5680
+ function computeAffected(opts) {
5681
+ const cwd = opts.cwd ?? process.cwd();
5682
+ const depth = opts.depth ?? DEFAULT_AFFECTED_DEPTH;
5683
+ const { files, importedBy } = buildImportGraph(cwd);
5684
+ const trackedByFolded = /* @__PURE__ */ new Map();
5685
+ for (const f of files) trackedByFolded.set(foldPath(normalizePath(f)), f);
5686
+ const unknownSeeds = [];
5687
+ let frontier = [];
5688
+ const seen = /* @__PURE__ */ new Set();
5689
+ for (const raw of opts.files) {
5690
+ const abs = normalizePath(path9.resolve(cwd, raw));
5691
+ const tracked = trackedByFolded.get(foldPath(abs));
5692
+ if (tracked === void 0) {
5693
+ unknownSeeds.push(raw);
5694
+ continue;
5695
+ }
5696
+ if (seen.has(tracked)) continue;
5697
+ seen.add(tracked);
5698
+ frontier.push(tracked);
5699
+ }
5700
+ let hops = 0;
5701
+ while (frontier.length > 0 && hops < depth) {
5702
+ const next = [];
5703
+ for (const file of frontier) {
5704
+ for (const importer of importedBy.get(file) ?? []) {
5705
+ if (seen.has(importer)) continue;
5706
+ seen.add(importer);
5707
+ next.push(importer);
5708
+ }
5709
+ }
5710
+ frontier = next;
5711
+ hops++;
5712
+ }
5713
+ const depthLimited = frontier.length > 0;
5714
+ const matcher = opts.filter === void 0 ? null : new RegExp(opts.filter);
5715
+ const isAffectedTest = (f) => matcher === null ? isTestFile(f) : matcher.test(f);
5716
+ const root = getDisplayRoot(cwd);
5717
+ const testFiles = [...seen].filter(isAffectedTest).map((f) => toDisplayPath(root, f)).sort();
5718
+ return {
5719
+ testFiles,
5720
+ reachedCount: seen.size,
5721
+ unknownSeeds,
5722
+ depthLimited,
5723
+ unexploredAtFrontier: frontier.length,
5724
+ depth
5725
+ };
5726
+ }
5727
+ function runAffected(opts) {
5728
+ if (opts.depth !== void 0 && opts.depth <= 0) {
5729
+ emitErr(`--depth must be a positive number, got: ${opts.depth}`);
5730
+ return 1;
5731
+ }
5732
+ if (opts.files.length === 0) {
5733
+ emitErr("No changed files given. Pass paths as arguments, or pipe them in with --stdin.");
5734
+ return 1;
5735
+ }
5736
+ if (opts.filter !== void 0) {
5737
+ try {
5738
+ new RegExp(opts.filter);
5739
+ } catch (err2) {
5740
+ emitErr(`--filter is not a valid regular expression: ${err2 instanceof Error ? err2.message : String(err2)}`);
5741
+ return 1;
5742
+ }
5743
+ }
5744
+ const result = computeAffected(opts);
5745
+ if (opts.json === true) {
5746
+ emit(JSON.stringify(result, null, 2));
5747
+ return 0;
5748
+ }
5749
+ if (opts.quiet === true) {
5750
+ for (const f of result.testFiles) emit(f);
5751
+ } else if (result.testFiles.length === 0) {
5752
+ const traced = opts.files.length - result.unknownSeeds.length;
5753
+ if (traced === 0) {
5754
+ 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.`);
5755
+ } else {
5756
+ emit(`No test files import ${countNoun(traced, "changed file")} (within ${countNoun(result.depth, "hop")}).`);
5757
+ }
5758
+ } else {
5759
+ emit(`${countNoun(result.testFiles.length, "affected test file")} (${countNoun(result.reachedCount, "file")} reached within ${countNoun(result.depth, "hop")}):`);
5760
+ for (const f of result.testFiles) emit(` ${f}`);
5761
+ }
5762
+ if (result.unknownSeeds.length > 0) {
5763
+ emitErr(`Not tracked in this project, so nothing was traced from ${result.unknownSeeds.length === 1 ? "it" : "them"}: ${result.unknownSeeds.join(", ")}`);
5764
+ }
5765
+ if (result.depthLimited) {
5766
+ emitErr(`Stopped at --depth ${result.depth} with ${countNoun(result.unexploredAtFrontier, "file")} still unexplored; more tests may be affected (raise --depth).`);
5767
+ }
5768
+ return 0;
5769
+ }
5770
+
5660
5771
  // src/purge.ts
5661
5772
  import * as fs10 from "node:fs";
5662
- import * as path9 from "node:path";
5773
+ import * as path10 from "node:path";
5663
5774
  function directorySize(root) {
5664
5775
  let total = 0;
5665
5776
  let entries;
@@ -5669,7 +5780,7 @@ function directorySize(root) {
5669
5780
  return 0;
5670
5781
  }
5671
5782
  for (const entry of entries) {
5672
- const full = path9.join(root, entry.name);
5783
+ const full = path10.join(root, entry.name);
5673
5784
  if (entry.isDirectory()) {
5674
5785
  total += directorySize(full);
5675
5786
  continue;
@@ -5712,7 +5823,7 @@ function formatBytes2(bytes) {
5712
5823
 
5713
5824
  // src/cli_stats.ts
5714
5825
  import * as fs11 from "node:fs";
5715
- import * as path10 from "node:path";
5826
+ import * as path11 from "node:path";
5716
5827
  function writeRaw(text) {
5717
5828
  const payload = colorStdout() ? text : stripAnsi(text);
5718
5829
  process.stdout.write(ensureNewline(payload));
@@ -5724,7 +5835,7 @@ function formatTopFiles(ranked) {
5724
5835
  if (ranked.length === 0) return "";
5725
5836
  const lines = ["Top files this session:"];
5726
5837
  for (const { path: filePath, count } of ranked) {
5727
- const basename8 = path10.basename(filePath);
5838
+ const basename8 = path11.basename(filePath);
5728
5839
  lines.push(` ${count.toString().padStart(3)}x ${basename8} (${filePath})`);
5729
5840
  }
5730
5841
  return lines.join("\n");
@@ -5741,12 +5852,12 @@ function renderTopSessionFiles(topN = 5) {
5741
5852
  }
5742
5853
  function renderTopSessionFilesFromDisk(topN = 5, overrideSessionsDir) {
5743
5854
  try {
5744
- const sessionsDir = overrideSessionsDir ?? path10.join(dataDir(), "sessions");
5855
+ const sessionsDir = overrideSessionsDir ?? path11.join(dataDir(), "sessions");
5745
5856
  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);
5857
+ 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
5858
  for (const { name } of files) {
5748
5859
  try {
5749
- const raw = fs11.readFileSync(path10.join(sessionsDir, name), "utf-8");
5860
+ const raw = fs11.readFileSync(path11.join(sessionsDir, name), "utf-8");
5750
5861
  const data = JSON.parse(raw);
5751
5862
  const filesList = data["files"];
5752
5863
  if (!Array.isArray(filesList)) continue;
@@ -5766,8 +5877,8 @@ function renderTopSessionFilesFromDisk(topN = 5, overrideSessionsDir) {
5766
5877
  var METHODOLOGY = {
5767
5878
  estimate_scope: "Local estimate of content avoided or reduced by token-goat.",
5768
5879
  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.",
5880
+ 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.",
5881
+ 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
5882
  advisory_events: "Zero-byte, zero-token advisory events show that guidance fired, not that an agent followed it.",
5772
5883
  audit: "Use stats --full or stats --json for source and command breakdowns; reconcile billing with provider-exported usage data."
5773
5884
  };
@@ -5835,12 +5946,12 @@ function runStats(opts = {}) {
5835
5946
  // src/cli_doctor.ts
5836
5947
  import * as fs12 from "fs";
5837
5948
  import * as os8 from "os";
5838
- import * as path11 from "path";
5949
+ import * as path12 from "path";
5839
5950
  import { execSync, spawnSync as spawnSync2 } from "child_process";
5840
5951
  function globalMcpConfigPath() {
5841
5952
  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");
5953
+ const root = copilotHome !== void 0 && copilotHome.trim() !== "" ? path12.resolve(copilotHome) : path12.join(os8.homedir(), ".copilot");
5954
+ return path12.join(root, "mcp-config.json");
5844
5955
  }
5845
5956
  function checkGlobalMcpConfig(configPath2 = globalMcpConfigPath()) {
5846
5957
  if (!fs12.existsSync(configPath2)) {
@@ -5957,7 +6068,7 @@ function checkWorkerRunning(dataDir2) {
5957
6068
  }
5958
6069
  var DB_SIZE_WARN_BYTES = 1024 * 1024 * 1024;
5959
6070
  function checkDbExists(dataDir2) {
5960
- const dbPath = path11.join(dataDir2, "global.db");
6071
+ const dbPath = path12.join(dataDir2, "global.db");
5961
6072
  if (!fs12.existsSync(dbPath)) {
5962
6073
  return {
5963
6074
  name: "Database",
@@ -6295,6 +6406,40 @@ function checkStrayClaudeMdBlocks(searchRoot) {
6295
6406
  message: `${strays.length} stray cop${strays.length === 1 ? "y" : "ies"} outside CLAUDE.md (never refreshed by install, never removed by uninstall, will go stale): ${strays.join(", ")}`
6296
6407
  };
6297
6408
  }
6409
+ var LOCKED_BOOLEAN_SAFE_VALUE = {
6410
+ "gdrive.enabled": false,
6411
+ "injection.enabled": true,
6412
+ "redaction.strict": true,
6413
+ "mcp.confine_reads_to_project_root": true,
6414
+ "screenshot.block_private_targets": true,
6415
+ "network.offline": true,
6416
+ "indexing.cross_project_symbols": false,
6417
+ "webfetch.compress_bodies": true
6418
+ };
6419
+ function lockedEnvOverridableKeys() {
6420
+ const out2 = [];
6421
+ for (const key of Object.keys(CONFIG_KEY_ENV_OVERRIDES)) {
6422
+ const section = key.split(".")[0] ?? "";
6423
+ const locked = PROJECT_LOCKED_SECTIONS.includes(section) || PROJECT_LOCKED_KEYS.includes(key);
6424
+ if (locked && (CONFIG_KEY_ENV_OVERRIDES[key] ?? []).length > 0) out2.push(key);
6425
+ }
6426
+ return out2.sort();
6427
+ }
6428
+ function envOverriddenSecuritySettings() {
6429
+ const out2 = [];
6430
+ for (const setting of lockedEnvOverridableKeys()) {
6431
+ const safe = LOCKED_BOOLEAN_SAFE_VALUE[setting];
6432
+ for (const envVar of CONFIG_KEY_ENV_OVERRIDES[setting] ?? []) {
6433
+ if (safe === void 0) {
6434
+ const raw = process.env[envVar];
6435
+ if (raw !== void 0 && raw.trim() !== "") out2.push({ setting, envVar, kind: "replaced" });
6436
+ } else if (envBool(envVar, safe) !== safe) {
6437
+ out2.push({ setting, envVar, kind: "weakened" });
6438
+ }
6439
+ }
6440
+ }
6441
+ return out2;
6442
+ }
6298
6443
  function checkSecurityPosture(cfg, dataDirPath) {
6299
6444
  const results = [];
6300
6445
  results.push({
@@ -6317,6 +6462,19 @@ function checkSecurityPosture(cfg, dataDirPath) {
6317
6462
  status: "ok",
6318
6463
  message: allow > 0 ? `${allow} allowed host pattern${allow === 1 ? "" : "s"}, ${deny} denied: nothing outside the allow list is fetched` : `no allow list, ${deny} denied pattern${deny === 1 ? "" : "s"}: any host not denied can be fetched`
6319
6464
  });
6465
+ const custom = compileCustomPatterns(cfg.redaction.custom_patterns);
6466
+ const strictNote = cfg.redaction.strict ? "strict mode on" : "strict mode off (redaction.strict)";
6467
+ const patternNote = custom.patterns.length === 0 ? "built-in patterns only" : `${custom.patterns.length} custom pattern${custom.patterns.length === 1 ? "" : "s"} plus the built-in ones`;
6468
+ results.push(
6469
+ custom.problems.length > 0 ? {
6470
+ name: "Security redaction",
6471
+ // A pattern the operator believes is redacting, but which never compiled, is the one
6472
+ // failure here that is invisible from the output itself -- so it warns rather than
6473
+ // being reported as a healthy count that happens to be short.
6474
+ status: "warn",
6475
+ message: `${custom.problems.length} custom redaction pattern${custom.problems.length === 1 ? "" : "s"} could not be used and ${custom.problems.length === 1 ? "is" : "are"} not redacting anything: ` + custom.problems.map((p) => `${p.pattern} (${p.reason})`).join("; ")
6476
+ } : { name: "Security redaction", status: "ok", message: `${patternNote}, ${strictNote}` }
6477
+ );
6320
6478
  const extraRoots = cfg.mcp.allowed_roots.length;
6321
6479
  results.push(
6322
6480
  cfg.mcp.confine_reads_to_project_root ? {
@@ -6325,6 +6483,27 @@ function checkSecurityPosture(cfg, dataDirPath) {
6325
6483
  message: extraRoots === 0 ? "reads are confined to the project root" : `reads are confined to the project root plus ${extraRoots} configured root${extraRoots === 1 ? "" : "s"}`
6326
6484
  } : { name: "Security mcp roots", status: "warn", message: "confinement is off (mcp.confine_reads_to_project_root): a read can leave the project" }
6327
6485
  );
6486
+ const overridden = envOverriddenSecuritySettings();
6487
+ const weakened = overridden.filter((o) => o.kind === "weakened");
6488
+ const replaced = overridden.filter((o) => o.kind === "replaced");
6489
+ const describe = (o) => `${o.setting} (${o.envVar})`;
6490
+ const parts = [];
6491
+ if (weakened.length > 0) parts.push(`held open: ${weakened.map(describe).join(", ")}`);
6492
+ if (replaced.length > 0) parts.push(`set from the environment: ${replaced.map(describe).join(", ")}`);
6493
+ results.push(
6494
+ overridden.length === 0 ? {
6495
+ name: "Security config overrides",
6496
+ status: "ok",
6497
+ // Naming the count keeps this from reading as a guarantee about settings it never looked
6498
+ // at, which is what the first version of this line did while nine locked keys went
6499
+ // unchecked behind it.
6500
+ message: `no environment variable is overriding any of the ${lockedEnvOverridableKeys().length} project-locked settings`
6501
+ } : {
6502
+ name: "Security config overrides",
6503
+ status: "warn",
6504
+ message: `the environment, not the config file, is deciding ${overridden.length === 1 ? "a" : "these"} project-locked setting${overridden.length === 1 ? "" : "s"}. ${parts.join("; ")}. A project config cannot change these; an environment variable can, and one can be set by a file in a cloned repository.`
6505
+ }
6506
+ );
6328
6507
  results.push(dataDirPermissionResult(dataDirPath));
6329
6508
  return results;
6330
6509
  }
@@ -6428,15 +6607,15 @@ function runDoctor(dataDir2, configPath2, rootDir, processes) {
6428
6607
  results.push(checkStrayClaudeMdBlocks());
6429
6608
  results.push(checkWorkerRunning(actualDataDir) ? { name: "Worker", status: "ok", message: "running" } : { name: "Worker", status: "warn", message: "not running" });
6430
6609
  results.push(checkDbExists(actualDataDir));
6431
- results.push(checkSymbolBodySize(path11.join(actualDataDir, "global.db")));
6432
- results.push(checkSymbolCount(path11.join(actualDataDir, "global.db"), rootDir));
6610
+ results.push(checkSymbolBodySize(path12.join(actualDataDir, "global.db")));
6611
+ results.push(checkSymbolCount(path12.join(actualDataDir, "global.db"), rootDir));
6433
6612
  results.push(checkDirtyQueueHealth(actualDataDir));
6434
- results.push(checkCompactionChannel(path11.join(actualDataDir, "global.db")));
6435
- results.push(checkUnmappedTools(path11.join(actualDataDir, "global.db")));
6613
+ results.push(checkCompactionChannel(path12.join(actualDataDir, "global.db")));
6614
+ results.push(checkUnmappedTools(path12.join(actualDataDir, "global.db")));
6436
6615
  const actualConfigPath = configPath2 || configPath();
6437
6616
  results.push(checkConfigValid(actualConfigPath));
6438
6617
  results.push(checkEmbeddings(loadConfig(rootDir)));
6439
- results.push(checkEmbeddingCoverage(path11.join(actualDataDir, "global.db"), rootDir));
6618
+ results.push(checkEmbeddingCoverage(path12.join(actualDataDir, "global.db"), rootDir));
6440
6619
  for (const result of checkSecurityPosture(loadConfig(rootDir), actualDataDir)) results.push(result);
6441
6620
  results.push(checkDiskSpace(actualDataDir));
6442
6621
  const copilotResult = checkCopilotCli(copilotCliConfigPath(), copilotCliScriptPath());
@@ -6477,7 +6656,7 @@ async function runDoctorAndExit(opts) {
6477
6656
  console.log();
6478
6657
  try {
6479
6658
  const dir = skillOutputsDir();
6480
- const pregenPath = path11.join(dir, "pregen.json");
6659
+ const pregenPath = path12.join(dir, "pregen.json");
6481
6660
  if (fs12.existsSync(pregenPath)) {
6482
6661
  const content = JSON.parse(fs12.readFileSync(pregenPath, "utf-8"));
6483
6662
  const pregenNames = new Set(content.names || []);
@@ -6487,7 +6666,7 @@ async function runDoctorAndExit(opts) {
6487
6666
  for (const entry of entries) {
6488
6667
  if (!entry.isFile() || !entry.name.endsWith(".meta")) continue;
6489
6668
  try {
6490
- const meta = JSON.parse(fs12.readFileSync(path11.join(dir, entry.name), "utf-8"));
6669
+ const meta = JSON.parse(fs12.readFileSync(path12.join(dir, entry.name), "utf-8"));
6491
6670
  if (meta.skillName && !pregenNames.has(meta.skillName)) {
6492
6671
  skillsSeen.add(meta.skillName);
6493
6672
  }
@@ -6509,12 +6688,96 @@ async function runDoctorAndExit(opts) {
6509
6688
  return results.some((r) => r.status === "fail") ? 1 : 0;
6510
6689
  }
6511
6690
 
6691
+ // src/capabilities.ts
6692
+ function collectCapabilities(config = loadConfig()) {
6693
+ const online = !config.network.offline;
6694
+ return [
6695
+ {
6696
+ id: "network.http_fetch",
6697
+ kind: "egress",
6698
+ what: "Fetches a URL when a command asks for one, including page text and remote images.",
6699
+ enabled: online,
6700
+ controlledBy: "network.offline",
6701
+ enforcedAt: "src/webfetch.ts::performHttpFetch"
6702
+ },
6703
+ {
6704
+ id: "network.google_drive",
6705
+ kind: "egress",
6706
+ what: "Exports a Google Doc so a section of it can be read.",
6707
+ // Two independent gates, so this reports the conjunction: turning either one off is enough.
6708
+ enabled: online && config.gdrive.enabled,
6709
+ controlledBy: "gdrive.enabled, network.offline",
6710
+ enforcedAt: "src/gdrive.ts::fetchDoc (network gate in src/webfetch.ts::performHttpFetch)"
6711
+ },
6712
+ {
6713
+ id: "network.embedding_model_download",
6714
+ kind: "egress",
6715
+ what: "Downloads the local embedding model once, so semantic search can run without a server.",
6716
+ enabled: online,
6717
+ controlledBy: "network.offline",
6718
+ enforcedAt: "src/embed_model.ts"
6719
+ },
6720
+ {
6721
+ id: "network.ocr_data_download",
6722
+ kind: "egress",
6723
+ what: "Downloads OCR language data once, so text can be read out of an image.",
6724
+ enabled: online,
6725
+ controlledBy: "network.offline",
6726
+ enforcedAt: "src/image_ocr.ts"
6727
+ },
6728
+ {
6729
+ id: "network.screenshot",
6730
+ kind: "egress",
6731
+ what: "Loads a page in a browser to capture it.",
6732
+ enabled: online,
6733
+ controlledBy: "network.offline",
6734
+ enforcedAt: "src/screenshot.ts"
6735
+ },
6736
+ {
6737
+ id: "at_rest.symbol_index",
6738
+ kind: "at-rest",
6739
+ what: "Stores the text of indexed source files in a local database, so a symbol can be returned without re-reading the file.",
6740
+ // Always on: it is the product. Reported anyway, because a reviewer deciding about data at
6741
+ // rest needs the always-on items more than the optional ones.
6742
+ enabled: true,
6743
+ controlledBy: "always on (this is what the tool does)",
6744
+ enforcedAt: "src/db.ts"
6745
+ },
6746
+ {
6747
+ id: "at_rest.command_output_cache",
6748
+ kind: "at-rest",
6749
+ what: "Keeps recent command and web output on disk so it can be recalled instead of re-run. Expires after 24 hours.",
6750
+ enabled: true,
6751
+ controlledBy: "always on, 24-hour expiry",
6752
+ enforcedAt: "src/disk_cache.ts"
6753
+ }
6754
+ ];
6755
+ }
6756
+ function renderCapabilities(caps) {
6757
+ const lines = [];
6758
+ for (const kind of ["egress", "at-rest"]) {
6759
+ const group = caps.filter((c) => c.kind === kind);
6760
+ if (group.length === 0) continue;
6761
+ lines.push(kind === "egress" ? "Can send data off this machine:" : "Leaves data on this machine:");
6762
+ for (const c of group) {
6763
+ lines.push(` [${c.enabled ? "ON " : "OFF"}] ${c.id}`);
6764
+ lines.push(` ${c.what}`);
6765
+ lines.push(` controlled by: ${c.controlledBy}`);
6766
+ lines.push(` enforced at: ${c.enforcedAt}`);
6767
+ }
6768
+ lines.push("");
6769
+ }
6770
+ lines.push("Verify rather than trust: `token-goat capabilities --json` reports the state of the");
6771
+ lines.push('binary you have installed, and each "enforced at" names the code that decides it.');
6772
+ return lines.join("\n");
6773
+ }
6774
+
6512
6775
  // src/webfetch.ts
6513
6776
  import { existsSync as existsSync7, readdirSync as readdirSync5, statSync as statSync4, unlinkSync as unlinkSync2 } from "fs";
6514
6777
  import * as http from "http";
6515
6778
  import * as https from "https";
6516
6779
  import { isIPv4, isIPv6 } from "net";
6517
- import { resolve as resolve4, join as join10 } from "path";
6780
+ import { resolve as resolve5, join as join10 } from "path";
6518
6781
  import { URL as URL2 } from "url";
6519
6782
  import { promisify } from "util";
6520
6783
  import { lookup as dnsLookup } from "dns";
@@ -6703,7 +6966,7 @@ function cleanupStaleDownloads() {
6703
6966
  const files = readdirSync5(cacheDir);
6704
6967
  for (const file of files) {
6705
6968
  if (file.endsWith(".tmp")) {
6706
- const filePath = resolve4(cacheDir, file);
6969
+ const filePath = resolve5(cacheDir, file);
6707
6970
  try {
6708
6971
  const stat = statSync4(filePath);
6709
6972
  if (Date.now() - stat.mtimeMs < STALE_DOWNLOAD_AGE_MS) continue;
@@ -6969,7 +7232,7 @@ async function getSectionContent(fileId, heading, opts = {}) {
6969
7232
 
6970
7233
  // src/pack.ts
6971
7234
  import * as fs13 from "node:fs";
6972
- import * as path13 from "node:path";
7235
+ import * as path14 from "node:path";
6973
7236
 
6974
7237
  // node_modules/minimatch/dist/esm/index.js
6975
7238
  var esm_exports = {};
@@ -8093,11 +8356,11 @@ var qmarksTestNoExtDot = ([$0]) => {
8093
8356
  return (f) => f.length === len && f !== "." && f !== "..";
8094
8357
  };
8095
8358
  var defaultPlatform = typeof process === "object" && process ? typeof process.env === "object" && process.env && process.env.__MINIMATCH_TESTING_PLATFORM__ || process.platform : "posix";
8096
- var path12 = {
8359
+ var path13 = {
8097
8360
  win32: { sep: "\\" },
8098
8361
  posix: { sep: "/" }
8099
8362
  };
8100
- var sep2 = defaultPlatform === "win32" ? path12.win32.sep : path12.posix.sep;
8363
+ var sep2 = defaultPlatform === "win32" ? path13.win32.sep : path13.posix.sep;
8101
8364
  minimatch.sep = sep2;
8102
8365
  var GLOBSTAR = /* @__PURE__ */ Symbol("globstar **");
8103
8366
  minimatch.GLOBSTAR = GLOBSTAR;
@@ -8882,7 +9145,7 @@ var LANG_MAP = {
8882
9145
  ".exs": "elixir"
8883
9146
  };
8884
9147
  function getLang(filePath) {
8885
- const ext2 = path13.extname(filePath).toLowerCase();
9148
+ const ext2 = path14.extname(filePath).toLowerCase();
8886
9149
  return LANG_MAP[ext2] ?? "";
8887
9150
  }
8888
9151
  function matches(rel, patterns) {
@@ -8892,8 +9155,8 @@ function matches(rel, patterns) {
8892
9155
  return patterns.some((pat) => mm.minimatch(norm, pat) || mm.minimatch(base, pat));
8893
9156
  }
8894
9157
  function isPathWithinRoot(rootReal, resolvedPath) {
8895
- const rel = path13.relative(rootReal, resolvedPath);
8896
- return rel !== "" && rel !== ".." && !rel.startsWith(".." + path13.sep) && !path13.isAbsolute(rel);
9158
+ const rel = path14.relative(rootReal, resolvedPath);
9159
+ return rel !== "" && rel !== ".." && !rel.startsWith(".." + path14.sep) && !path14.isAbsolute(rel);
8897
9160
  }
8898
9161
  function openWithinRoot(rootReal, p) {
8899
9162
  let fd;
@@ -8933,11 +9196,11 @@ function openWithinRoot(rootReal, p) {
8933
9196
  }
8934
9197
  function* resolveOpenCandidates(projectRoot, patterns, rootReal, ignorePatterns, seen, skipped) {
8935
9198
  for (const pattern of patterns) {
8936
- const p = path13.isAbsolute(pattern) ? pattern : path13.join(projectRoot, pattern);
9199
+ const p = path14.isAbsolute(pattern) ? pattern : path14.join(projectRoot, pattern);
8937
9200
  if (seen.has(p)) continue;
8938
9201
  let rel;
8939
9202
  try {
8940
- rel = path13.relative(projectRoot, p).replace(/\\/g, "/");
9203
+ rel = path14.relative(projectRoot, p).replace(/\\/g, "/");
8941
9204
  } catch {
8942
9205
  skipped.push(`${p} (outside project root)`);
8943
9206
  continue;
@@ -9011,7 +9274,7 @@ function stripBlockComments(content, pattern, lineStates) {
9011
9274
  );
9012
9275
  }
9013
9276
  function stripComments(content, filePath) {
9014
- const ext2 = path13.extname(filePath).toLowerCase();
9277
+ const ext2 = path14.extname(filePath).toLowerCase();
9015
9278
  const lineStates = computeLineStartQuoteStates(content);
9016
9279
  if (ext2 === ".py") {
9017
9280
  return stripLineComments(content, PY_LINE_COMMENT_RE, lineStates);
@@ -9049,7 +9312,7 @@ var SAFE_EXTS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp
9049
9312
  function scanSecrets(files) {
9050
9313
  const hits = [];
9051
9314
  for (const pf of files) {
9052
- if (SAFE_EXTS.has(path13.extname(pf.path).toLowerCase())) {
9315
+ if (SAFE_EXTS.has(path14.extname(pf.path).toLowerCase())) {
9053
9316
  continue;
9054
9317
  }
9055
9318
  for (const [lineno, line] of pf.content.split("\n").entries()) {
@@ -9072,7 +9335,7 @@ function scanSecrets(files) {
9072
9335
  function collectFiles(projectRoot, patterns, opts = {}) {
9073
9336
  const result = { files: [], skipped: [], total_lines: 0, total_tokens: 0 };
9074
9337
  const seen = /* @__PURE__ */ new Set();
9075
- const rootResolved = path13.resolve(projectRoot);
9338
+ const rootResolved = path14.resolve(projectRoot);
9076
9339
  let rootReal;
9077
9340
  try {
9078
9341
  rootReal = fs13.realpathSync(rootResolved);
@@ -9247,7 +9510,7 @@ function formatPack(result, style, opts = {}) {
9247
9510
  function estimateBudget(projectRoot, patterns, opts = {}) {
9248
9511
  const result = { entries: [], skipped: [], total_lines: 0, total_tokens: 0 };
9249
9512
  const seen = /* @__PURE__ */ new Set();
9250
- const rootResolved = path13.resolve(projectRoot);
9513
+ const rootResolved = path14.resolve(projectRoot);
9251
9514
  let rootReal;
9252
9515
  try {
9253
9516
  rootReal = fs13.realpathSync(rootResolved);
@@ -9744,7 +10007,7 @@ function formatFailureDeltaJson(delta, runner) {
9744
10007
 
9745
10008
  // src/failures_state.ts
9746
10009
  import * as fs14 from "node:fs";
9747
- import * as path14 from "node:path";
10010
+ import * as path15 from "node:path";
9748
10011
  var KEY_RE = /^[A-Za-z0-9_-]{1,80}$/;
9749
10012
  var DEFAULT_FAILURES_STATE_KEY = "default";
9750
10013
  function validateKey(key) {
@@ -9756,7 +10019,7 @@ function validateKey(key) {
9756
10019
  }
9757
10020
  function failuresStatePath(projectHash, key) {
9758
10021
  validateKey(key);
9759
- return path14.join(dataDir(), "projects", `${projectHash}_failures_${key}.json`);
10022
+ return path15.join(dataDir(), "projects", `${projectHash}_failures_${key}.json`);
9760
10023
  }
9761
10024
  function loadFailureSnapshot(projectHash, key) {
9762
10025
  const p = failuresStatePath(projectHash, key);
@@ -9777,7 +10040,7 @@ function loadFailureSnapshot(projectHash, key) {
9777
10040
  }
9778
10041
  function saveFailureSnapshot(projectHash, key, snapshot) {
9779
10042
  const p = failuresStatePath(projectHash, key);
9780
- const dir = path14.dirname(p);
10043
+ const dir = path15.dirname(p);
9781
10044
  ensureDirSync(dir);
9782
10045
  const content = JSON.stringify(snapshot);
9783
10046
  const doSave = () => {
@@ -9789,18 +10052,18 @@ function saveFailureSnapshot(projectHash, key, snapshot) {
9789
10052
 
9790
10053
  // src/text_commands.ts
9791
10054
  import * as fs16 from "fs";
9792
- import * as path16 from "path";
10055
+ import * as path17 from "path";
9793
10056
 
9794
10057
  // src/project_memory.ts
9795
10058
  import * as fs15 from "node:fs";
9796
- import * as path15 from "node:path";
10059
+ import * as path16 from "node:path";
9797
10060
  var MAX_ENTRIES = 30;
9798
10061
  var KEY_RE2 = /^[A-Za-z0-9_-]{1,80}$/;
9799
10062
  function ordinal(a, b) {
9800
10063
  return a < b ? -1 : a > b ? 1 : 0;
9801
10064
  }
9802
10065
  function memoryPath(projectHash) {
9803
- return path15.join(dataDir(), "projects", `${projectHash}_memory.toml`);
10066
+ return path16.join(dataDir(), "projects", `${projectHash}_memory.toml`);
9804
10067
  }
9805
10068
  function validateKey2(key) {
9806
10069
  if (!KEY_RE2.test(key)) {
@@ -9859,7 +10122,7 @@ function save(filePath, entries) {
9859
10122
  lines.push(`${k} = "${escaped}"`);
9860
10123
  }
9861
10124
  const content = lines.length > 0 ? lines.join("\n") + "\n" : "";
9862
- const dir = path15.dirname(filePath);
10125
+ const dir = path16.dirname(filePath);
9863
10126
  ensureDirSync(dir);
9864
10127
  atomicWriteText(filePath, content);
9865
10128
  }
@@ -9869,7 +10132,7 @@ function loadEntries(projectHash) {
9869
10132
  function setEntry(projectHash, key, value) {
9870
10133
  validateKey2(key);
9871
10134
  const p = memoryPath(projectHash);
9872
- const dir = path15.dirname(p);
10135
+ const dir = path16.dirname(p);
9873
10136
  ensureDirSync(dir);
9874
10137
  const doSet = () => {
9875
10138
  const entries = loadRaw(p);
@@ -9967,7 +10230,7 @@ function collectTodoFiles(patterns) {
9967
10230
  if (patterns.length > 0) {
9968
10231
  const results = [];
9969
10232
  for (const p of patterns) {
9970
- const abs = path16.resolve(p);
10233
+ const abs = path17.resolve(p);
9971
10234
  try {
9972
10235
  const stat = fs16.statSync(abs);
9973
10236
  if (stat.isDirectory()) {
@@ -10238,7 +10501,7 @@ function isProjectFrame(framePath, cwd) {
10238
10501
  if (framePath.includes("/rustc/")) return false;
10239
10502
  if (framePath.includes(".cargo/registry") || framePath.includes(".cargo\\registry")) return false;
10240
10503
  if (/^<.+>$/.test(framePath)) return false;
10241
- if (!path16.isAbsolute(framePath) && !framePath.startsWith("..")) return true;
10504
+ if (!path17.isAbsolute(framePath) && !framePath.startsWith("..")) return true;
10242
10505
  return false;
10243
10506
  }
10244
10507
  function resolveFrameSymbol(frame, projectRoot) {
@@ -10451,17 +10714,17 @@ function findLockfile(startPath) {
10451
10714
  if (stat !== void 0 && stat.isFile()) {
10452
10715
  return { file: startPath, others: [] };
10453
10716
  }
10454
- const dir = stat !== void 0 && stat.isDirectory() ? startPath : path16.dirname(startPath);
10717
+ const dir = stat !== void 0 && stat.isDirectory() ? startPath : path17.dirname(startPath);
10455
10718
  const found = [];
10456
10719
  for (const name of LOCK_PRIORITY) {
10457
- const candidate = path16.join(dir, name);
10720
+ const candidate = path17.join(dir, name);
10458
10721
  if (fs16.existsSync(candidate)) found.push(candidate);
10459
10722
  }
10460
10723
  try {
10461
10724
  const entries = fs16.readdirSync(dir);
10462
10725
  for (const e of entries) {
10463
10726
  if (/^requirements.*\.txt$/.test(e)) {
10464
- const p = path16.join(dir, e);
10727
+ const p = path17.join(dir, e);
10465
10728
  if (!found.includes(p)) found.push(p);
10466
10729
  }
10467
10730
  }
@@ -10658,7 +10921,7 @@ function parsePnpmLock(content) {
10658
10921
  return deps;
10659
10922
  }
10660
10923
  function parseLockFile(filePath) {
10661
- const base = path16.basename(filePath);
10924
+ const base = path17.basename(filePath);
10662
10925
  const content = fs16.readFileSync(filePath, "utf8");
10663
10926
  if (base === "package-lock.json") return { format: "npm", deps: parsePackageLockJson(content) };
10664
10927
  if (base === "yarn.lock") return { format: "yarn", deps: parseYarnLock(content) };
@@ -10774,7 +11037,7 @@ Depended on by direct/top-level deps (${dependedOnBy.length}):
10774
11037
  `);
10775
11038
  }
10776
11039
  function cmdLockdeps(filePath, opts) {
10777
- const target = filePath !== void 0 ? path16.resolve(filePath) : process.cwd();
11040
+ const target = filePath !== void 0 ? path17.resolve(filePath) : process.cwd();
10778
11041
  const found = findLockfile(target);
10779
11042
  if (found === null) {
10780
11043
  throw new Error("No lockfile found. Expected one of: " + LOCK_PRIORITY.join(", ") + ", requirements*.txt");
@@ -10862,7 +11125,7 @@ function cmdNote(action, key, value, opts) {
10862
11125
  throw new Error(`Unknown note action: ${action}. Use: set, get, unset, list, clear`);
10863
11126
  }
10864
11127
  function loadAllSessionReadCounts() {
10865
- const sessionsDir = path16.join(tokenGoatHome(), "sessions");
11128
+ const sessionsDir = path17.join(tokenGoatHome(), "sessions");
10866
11129
  const totals = /* @__PURE__ */ new Map();
10867
11130
  let entries;
10868
11131
  try {
@@ -10872,7 +11135,7 @@ function loadAllSessionReadCounts() {
10872
11135
  }
10873
11136
  for (const entry of entries) {
10874
11137
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
10875
- const filePath = path16.join(sessionsDir, entry.name);
11138
+ const filePath = path17.join(sessionsDir, entry.name);
10876
11139
  let raw;
10877
11140
  try {
10878
11141
  raw = JSON.parse(fs16.readFileSync(filePath, "utf8"));
@@ -11008,7 +11271,7 @@ function cmdIgnores(opts) {
11008
11271
  }
11009
11272
  function countPackIgnorePatterns(root) {
11010
11273
  try {
11011
- return fs16.readFileSync(path16.join(root, ".tokengoatignore"), "utf8").split("\n").map((ln) => ln.trim()).filter((ln) => ln.length > 0 && !ln.startsWith("#")).length;
11274
+ return fs16.readFileSync(path17.join(root, ".tokengoatignore"), "utf8").split("\n").map((ln) => ln.trim()).filter((ln) => ln.length > 0 && !ln.startsWith("#")).length;
11012
11275
  } catch {
11013
11276
  return 0;
11014
11277
  }
@@ -11017,7 +11280,7 @@ function countPackIgnorePatterns(root) {
11017
11280
  // src/dep_docs.ts
11018
11281
  import { createRequire } from "node:module";
11019
11282
  import * as fs17 from "node:fs";
11020
- import * as path17 from "node:path";
11283
+ import * as path18 from "node:path";
11021
11284
  var _require = createRequire(import.meta.url);
11022
11285
  var _ts = null;
11023
11286
  var _tsLoadAttempted = false;
@@ -11068,7 +11331,7 @@ function listInstalledPackageNames(nodeModulesDir) {
11068
11331
  if (entry.name.startsWith("@")) {
11069
11332
  let scoped;
11070
11333
  try {
11071
- scoped = fs17.readdirSync(path17.join(nodeModulesDir, entry.name), { withFileTypes: true });
11334
+ scoped = fs17.readdirSync(path18.join(nodeModulesDir, entry.name), { withFileTypes: true });
11072
11335
  } catch {
11073
11336
  continue;
11074
11337
  }
@@ -11085,7 +11348,7 @@ var README_NAME_RE = /^readme(\.(md|markdown|txt))?$/i;
11085
11348
  function findReadmeFile(pkgDir) {
11086
11349
  const direct = findReadmeIn(pkgDir);
11087
11350
  if (direct !== null) return direct;
11088
- return findReadmeIn(path17.join(pkgDir, "docs"));
11351
+ return findReadmeIn(path18.join(pkgDir, "docs"));
11089
11352
  }
11090
11353
  function findReadmeIn(dir) {
11091
11354
  let entries;
@@ -11102,7 +11365,7 @@ function findReadmeIn(dir) {
11102
11365
  return 1;
11103
11366
  };
11104
11367
  matches2.sort((a, b) => rank(a.name) - rank(b.name));
11105
- return path17.join(dir, matches2[0].name);
11368
+ return path18.join(dir, matches2[0].name);
11106
11369
  }
11107
11370
  function typesPackageDirName(pkgName) {
11108
11371
  if (pkgName.startsWith("@")) {
@@ -11129,30 +11392,30 @@ function resolveTypesLocation(pkgDir, pkgJson, nodeModulesDir, pkgName) {
11129
11392
  if (declared !== void 0) {
11130
11393
  const candidates = [declared, declared.endsWith(".d.ts") ? declared : `${declared}.d.ts`, declared.replace(/\.[cm]?[jt]s$/, ".d.ts")];
11131
11394
  for (const c of candidates) {
11132
- const p = path17.join(pkgDir, c);
11395
+ const p = path18.join(pkgDir, c);
11133
11396
  if (fileExists(p)) return { path: p, source: "bundled" };
11134
11397
  }
11135
11398
  }
11136
11399
  const main = typeof pkgJson["main"] === "string" ? pkgJson["main"] : "index.js";
11137
- const mainDts = path17.join(pkgDir, main.replace(/\.[cm]?js$/, ".d.ts"));
11400
+ const mainDts = path18.join(pkgDir, main.replace(/\.[cm]?js$/, ".d.ts"));
11138
11401
  if (fileExists(mainDts)) return { path: mainDts, source: "bundled" };
11139
- const indexDts = path17.join(pkgDir, "index.d.ts");
11402
+ const indexDts = path18.join(pkgDir, "index.d.ts");
11140
11403
  if (fileExists(indexDts)) return { path: indexDts, source: "bundled" };
11141
11404
  const typesDirName = typesPackageDirName(pkgName);
11142
- const typesPkgDir = path17.join(nodeModulesDir, "@types", typesDirName);
11143
- const typesPkgJsonRaw = readFileTextOrNull(path17.join(typesPkgDir, "package.json"));
11405
+ const typesPkgDir = path18.join(nodeModulesDir, "@types", typesDirName);
11406
+ const typesPkgJsonRaw = readFileTextOrNull(path18.join(typesPkgDir, "package.json"));
11144
11407
  if (typesPkgJsonRaw !== null) {
11145
11408
  try {
11146
11409
  const typesPkgJson = JSON.parse(typesPkgJsonRaw);
11147
11410
  const entry = (typeof typesPkgJson["types"] === "string" ? typesPkgJson["types"] : void 0) ?? (typeof typesPkgJson["main"] === "string" ? typesPkgJson["main"] : void 0) ?? "index.d.ts";
11148
11411
  const entryCandidates = [entry, entry.endsWith(".d.ts") ? entry : `${entry}.d.ts`, entry.replace(/\.[cm]?[jt]s$/, ".d.ts")];
11149
11412
  for (const c of entryCandidates) {
11150
- const entryPath = path17.join(typesPkgDir, c);
11413
+ const entryPath = path18.join(typesPkgDir, c);
11151
11414
  if (fileExists(entryPath)) return { path: entryPath, source: "@types" };
11152
11415
  }
11153
11416
  } catch {
11154
11417
  }
11155
- const fallback = path17.join(typesPkgDir, "index.d.ts");
11418
+ const fallback = path18.join(typesPkgDir, "index.d.ts");
11156
11419
  if (fileExists(fallback)) return { path: fallback, source: "@types" };
11157
11420
  }
11158
11421
  return null;
@@ -11219,9 +11482,9 @@ function recordDepDocsStat(fullSourceBytes, emittedText, packageName) {
11219
11482
  }
11220
11483
  function runDepDocs(opts) {
11221
11484
  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");
11485
+ const nodeModulesDir = path18.join(root, "node_modules");
11486
+ const pkgDir = path18.join(nodeModulesDir, opts.packageName);
11487
+ const pkgJsonPath = path18.join(pkgDir, "package.json");
11225
11488
  if (!dirExists(pkgDir) || !fileExists(pkgJsonPath)) {
11226
11489
  const known = listInstalledPackageNames(nodeModulesDir);
11227
11490
  const suggestions = suggestPackageNames(opts.packageName, known);
@@ -11384,13 +11647,13 @@ async function buildResumePacket(sessionId) {
11384
11647
  }
11385
11648
 
11386
11649
  // src/cache_session_commands.ts
11387
- function emitErr(text) {
11650
+ function emitErr2(text) {
11388
11651
  process.stderr.write(ensureNewline(text));
11389
11652
  }
11390
11653
  function capAndNote(rows, limit) {
11391
11654
  const shown = rows.slice(0, limit);
11392
11655
  if (shown.length < rows.length) {
11393
- emitErr(`Showing ${shown.length} of ${rows.length} entries (raise --limit to see the rest).`);
11656
+ emitErr2(`Showing ${shown.length} of ${rows.length} entries (raise --limit to see the rest).`);
11394
11657
  }
11395
11658
  return shown;
11396
11659
  }
@@ -11400,11 +11663,11 @@ function parseLimitOpt(cmdName, limitStr, dflt = 30) {
11400
11663
  try {
11401
11664
  n = requireNonNegativeStrictInt("--limit", limitStr);
11402
11665
  } catch (e) {
11403
- emitErr(`${cmdName}: --limit must be a positive number, got: "${limitStr}"`);
11666
+ emitErr2(`${cmdName}: --limit must be a positive number, got: "${limitStr}"`);
11404
11667
  throw new Error(`invalid --limit: ${limitStr}`, { cause: e });
11405
11668
  }
11406
11669
  if (n === 0) {
11407
- emitErr(`${cmdName}: --limit must be a positive number, got: "${limitStr}"`);
11670
+ emitErr2(`${cmdName}: --limit must be a positive number, got: "${limitStr}"`);
11408
11671
  throw new Error(`invalid --limit: ${limitStr}`);
11409
11672
  }
11410
11673
  return n;
@@ -11738,7 +12001,7 @@ function cmdBaseline(opts) {
11738
12001
 
11739
12002
  // src/index_reclaim.ts
11740
12003
  import * as fs18 from "node:fs";
11741
- import * as path18 from "node:path";
12004
+ import * as path19 from "node:path";
11742
12005
  var DERIVED_TABLES = ["chunk_vectors", "chunks", "refs", "symbols", "files"];
11743
12006
  function indexSizeBytes(dbPath) {
11744
12007
  let total = 0;
@@ -11822,7 +12085,7 @@ function mb(bytes) {
11822
12085
  }
11823
12086
  function cmdReclaimIndex(opts) {
11824
12087
  const dbPath = opts.dbPath ?? globalDbPath();
11825
- if (opts.force !== true && isWorkerRunning(path18.dirname(dbPath))) {
12088
+ if (opts.force !== true && isWorkerRunning(path19.dirname(dbPath))) {
11826
12089
  throw new Error(
11827
12090
  "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
12091
  );
@@ -11856,7 +12119,7 @@ function cmdReclaimIndex(opts) {
11856
12119
  }
11857
12120
  if (result.checkpointBusy) {
11858
12121
  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
12122
+ ` 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
12123
  `
11861
12124
  );
11862
12125
  }
@@ -11865,16 +12128,16 @@ function cmdReclaimIndex(opts) {
11865
12128
  // src/config_commands.ts
11866
12129
  import * as fs19 from "node:fs";
11867
12130
  import * as os9 from "node:os";
11868
- import * as path19 from "node:path";
11869
- function emit(text) {
12131
+ import * as path20 from "node:path";
12132
+ function emit2(text) {
11870
12133
  const payload = colorStdout() ? text : stripAnsi(text);
11871
12134
  process.stdout.write(ensureNewline(payload));
11872
12135
  }
11873
- function emitErr2(text) {
12136
+ function emitErr3(text) {
11874
12137
  process.stderr.write(ensureNewline(text));
11875
12138
  }
11876
12139
  function saveConfigSafe(cfg) {
11877
- ensureDirSync(path19.dirname(configPath()));
12140
+ ensureDirSync(path20.dirname(configPath()));
11878
12141
  saveConfig(cfg);
11879
12142
  }
11880
12143
  function levenshtein(a, b, cap = 3) {
@@ -12036,16 +12299,16 @@ function cmdConfig(opts) {
12036
12299
  if (state.layer !== "global" && state.layer !== "project-unparsed") sources[k] = layerJson(state);
12037
12300
  }
12038
12301
  if (Object.keys(sources).length > 0) payload["_sources"] = sources;
12039
- emit(JSON.stringify(payload, null, 2));
12302
+ emit2(JSON.stringify(payload, null, 2));
12040
12303
  return;
12041
12304
  }
12042
12305
  for (const [k, v] of pairs) {
12043
12306
  const state = resolveConfigKeyLayer(k, v, cfg, projectInfo);
12044
- emit(`${k} = ${JSON.stringify(v)}${state.layer === "project-unparsed" ? "" : layerAnnotation(state)}`);
12307
+ emit2(`${k} = ${JSON.stringify(v)}${state.layer === "project-unparsed" ? "" : layerAnnotation(state)}`);
12045
12308
  }
12046
12309
  if (projectInfo !== null) {
12047
- emit("");
12048
- emit(
12310
+ emit2("");
12311
+ emit2(
12049
12312
  projectInfo.parseError !== null ? `# project override ${projectInfo.path} failed to parse (${projectInfo.parseError}); ignored` : `# project override: ${projectInfo.path}`
12050
12313
  );
12051
12314
  }
@@ -12063,11 +12326,11 @@ function cmdConfig(opts) {
12063
12326
  }
12064
12327
  const getState = resolveConfigKeyLayer(opts.key, result.value, cfg, getProjectConfigInfo());
12065
12328
  if (opts.json === true) {
12066
- emit(JSON.stringify({ key: opts.key, value: result.value, ...layerJson(getState) }, null, 2));
12329
+ emit2(JSON.stringify({ key: opts.key, value: result.value, ...layerJson(getState) }, null, 2));
12067
12330
  return;
12068
12331
  }
12069
12332
  const rendered = typeof result.value === "string" ? result.value : JSON.stringify(result.value);
12070
- emit(`${rendered}${layerAnnotation(getState)}`);
12333
+ emit2(`${rendered}${layerAnnotation(getState)}`);
12071
12334
  return;
12072
12335
  }
12073
12336
  if (action === "set") {
@@ -12086,7 +12349,7 @@ function cmdConfig(opts) {
12086
12349
  if (parseErrAtLoad !== null) {
12087
12350
  try {
12088
12351
  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`);
12352
+ emitErr3(`config set: warning: config.toml failed to parse (${parseErrAtLoad}); backed up the original to config.toml.bak and rewriting it from defaults`);
12090
12353
  } catch {
12091
12354
  }
12092
12355
  }
@@ -12121,8 +12384,8 @@ function cmdConfig(opts) {
12121
12384
  saveConfigSafe(cfg);
12122
12385
  return coercedValue;
12123
12386
  };
12124
- ensureDirSync(path19.dirname(configPath()));
12125
- const lockPath = path19.join(path19.dirname(configPath()), ".config.lock");
12387
+ ensureDirSync(path20.dirname(configPath()));
12388
+ const lockPath = path20.join(path20.dirname(configPath()), ".config.lock");
12126
12389
  const lockResult = withFileLock(lockPath, applySet, { waitMs: LOCK_WAIT_MS_HARDENED });
12127
12390
  const coerced = lockResult === void 0 ? applySet() : lockResult;
12128
12391
  invalidateConfigCache();
@@ -12131,19 +12394,19 @@ function cmdConfig(opts) {
12131
12394
  const setState = resolveConfigKeyLayer(key, setEffective.found ? setEffective.value : void 0, effectiveCfg, getProjectConfigInfo());
12132
12395
  const shadowed = setEffective.found && setEffective.value !== coerced;
12133
12396
  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`);
12397
+ 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
12398
  } 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`);
12399
+ 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
12400
  } 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`);
12401
+ 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
12402
  } 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`);
12403
+ 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
12404
  }
12142
12405
  if (opts.json === true) {
12143
- emit(JSON.stringify({ key, value: coerced }, null, 2));
12406
+ emit2(JSON.stringify({ key, value: coerced }, null, 2));
12144
12407
  return;
12145
12408
  }
12146
- emit(`${key} = ${JSON.stringify(coerced)}`);
12409
+ emit2(`${key} = ${JSON.stringify(coerced)}`);
12147
12410
  return;
12148
12411
  }
12149
12412
  if (action === "validate") {
@@ -12211,19 +12474,19 @@ function cmdConfig(opts) {
12211
12474
  }
12212
12475
  if (findings.length > 0) process.exitCode = 1;
12213
12476
  if (opts.json === true) {
12214
- emit(JSON.stringify({ findings, ok: findings.length === 0 }, null, 2));
12477
+ emit2(JSON.stringify({ findings, ok: findings.length === 0 }, null, 2));
12215
12478
  return;
12216
12479
  }
12217
12480
  if (findings.length === 0) {
12218
- emit("config validate: no issues found");
12481
+ emit2("config validate: no issues found");
12219
12482
  return;
12220
12483
  }
12221
12484
  for (const f of findings) {
12222
12485
  const isNearMiss = f.kind === "unknown_section" || f.kind === "unknown_key";
12223
12486
  const hint = f.suggestion === void 0 ? "" : isNearMiss ? ` (did you mean: ${f.suggestion}?)` : ` (${f.suggestion})`;
12224
- emit(`[${f.kind}] ${f.key}${hint}`);
12487
+ emit2(`[${f.kind}] ${f.key}${hint}`);
12225
12488
  }
12226
- emit(`config validate: ${findings.length} issue(s) found`);
12489
+ emit2(`config validate: ${findings.length} issue(s) found`);
12227
12490
  return;
12228
12491
  }
12229
12492
  throw new Error(`config: unknown action '${action}'. Use list, get, set, or validate.`);
@@ -12235,33 +12498,33 @@ function cmdProject(opts) {
12235
12498
  const active = findProject(process.cwd());
12236
12499
  const blocked = cfg.worker.blocked_roots;
12237
12500
  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));
12501
+ emit2(JSON.stringify({ active: active ? { root: active.root, hash: active.hash, marker: active.marker } : null, blocked_roots: blocked }, null, 2));
12239
12502
  return;
12240
12503
  }
12241
12504
  if (active) {
12242
- emit(`Active project: ${active.root}`);
12243
- emit(` marker: ${active.marker}`);
12505
+ emit2(`Active project: ${active.root}`);
12506
+ emit2(` marker: ${active.marker}`);
12244
12507
  } else {
12245
- emit("Active project: (none \u2014 not inside a recognized project root)");
12508
+ emit2("Active project: (none \u2014 not inside a recognized project root)");
12246
12509
  }
12247
12510
  if (blocked.length === 0) {
12248
- emit("Blocked roots: (none)");
12511
+ emit2("Blocked roots: (none)");
12249
12512
  } else {
12250
- emit("Blocked roots:");
12251
- for (const r of blocked) emit(` ${r}`);
12513
+ emit2("Blocked roots:");
12514
+ for (const r of blocked) emit2(` ${r}`);
12252
12515
  }
12253
12516
  return;
12254
12517
  }
12255
12518
  if (action === "exclude") {
12256
12519
  if (!opts.pathArg) {
12257
- emitErr2("project exclude requires a path argument");
12520
+ emitErr3("project exclude requires a path argument");
12258
12521
  throw new Error("missing path");
12259
12522
  }
12260
- const target = path19.resolve(opts.pathArg);
12523
+ const target = path20.resolve(opts.pathArg);
12261
12524
  const cfg = loadPersistedConfig();
12262
12525
  const targetFolded = foldPath(normalizePath(target));
12263
12526
  if (cfg.worker.blocked_roots.some((r) => foldPath(normalizePath(r)) === targetFolded)) {
12264
- emit(`Already excluded: ${target}`);
12527
+ emit2(`Already excluded: ${target}`);
12265
12528
  return;
12266
12529
  }
12267
12530
  cfg.worker.blocked_roots = [...cfg.worker.blocked_roots, target];
@@ -12269,12 +12532,12 @@ function cmdProject(opts) {
12269
12532
  invalidateConfigCache();
12270
12533
  const purged = pruneBlockedRoot(target);
12271
12534
  if (opts.json === true) {
12272
- emit(JSON.stringify({ excluded: target, purgedFromIndex: purged.length, blocked_roots: cfg.worker.blocked_roots }, null, 2));
12535
+ emit2(JSON.stringify({ excluded: target, purgedFromIndex: purged.length, blocked_roots: cfg.worker.blocked_roots }, null, 2));
12273
12536
  return;
12274
12537
  }
12275
- emit(`Excluded: ${target}`);
12538
+ emit2(`Excluded: ${target}`);
12276
12539
  if (purged.length > 0) {
12277
- emit(`Removed ${purged.length} already-indexed file${purged.length === 1 ? "" : "s"} under it from the index.`);
12540
+ emit2(`Removed ${purged.length} already-indexed file${purged.length === 1 ? "" : "s"} under it from the index.`);
12278
12541
  }
12279
12542
  return;
12280
12543
  }
@@ -12294,26 +12557,26 @@ function cmdProject(opts) {
12294
12557
  const orphanChunkPaths = findOrphanedChunkPaths();
12295
12558
  if (opts.dryRun === true) {
12296
12559
  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));
12560
+ emit2(JSON.stringify({ dryRun: true, wouldPrune: removed, stale, wouldPruneTempFiles: staleTempFiles.length, staleTempFiles, wouldPruneOrphanChunkFiles: orphanChunkPaths.length, orphanChunkPaths, blocked_roots: before }, null, 2));
12298
12561
  return;
12299
12562
  }
12300
12563
  if (removed === 0) {
12301
- emit("Would prune 0 stale root(s). Nothing to do.");
12564
+ emit2("Would prune 0 stale root(s). Nothing to do.");
12302
12565
  } else {
12303
- emit(`Would prune ${removed} stale root(s):`);
12304
- for (const r of stale) emit(` ${r}`);
12566
+ emit2(`Would prune ${removed} stale root(s):`);
12567
+ for (const r of stale) emit2(` ${r}`);
12305
12568
  }
12306
12569
  if (staleTempFiles.length === 0) {
12307
- emit("Would prune 0 stale indexed temp-dir file(s). Nothing to do.");
12570
+ emit2("Would prune 0 stale indexed temp-dir file(s). Nothing to do.");
12308
12571
  } else {
12309
- emit(`Would prune ${staleTempFiles.length} stale indexed temp-dir file(s):`);
12310
- for (const p of staleTempFiles) emit(` ${p}`);
12572
+ emit2(`Would prune ${staleTempFiles.length} stale indexed temp-dir file(s):`);
12573
+ for (const p of staleTempFiles) emit2(` ${p}`);
12311
12574
  }
12312
12575
  if (orphanChunkPaths.length === 0) {
12313
- emit("Would prune 0 orphaned embedding chunk file(s). Nothing to do.");
12576
+ emit2("Would prune 0 orphaned embedding chunk file(s). Nothing to do.");
12314
12577
  } else {
12315
- emit(`Would prune orphaned embedding chunks for ${orphanChunkPaths.length} file(s):`);
12316
- for (const p of orphanChunkPaths) emit(` ${p}`);
12578
+ emit2(`Would prune orphaned embedding chunks for ${orphanChunkPaths.length} file(s):`);
12579
+ for (const p of orphanChunkPaths) emit2(` ${p}`);
12317
12580
  }
12318
12581
  return;
12319
12582
  }
@@ -12323,37 +12586,37 @@ function cmdProject(opts) {
12323
12586
  const prunedTempFiles = pruneSystemTempFiles();
12324
12587
  const prunedOrphanChunks = pruneOrphanedChunks();
12325
12588
  if (opts.json === true) {
12326
- emit(JSON.stringify({ pruned: removed, blocked_roots: after, prunedTempFiles: prunedTempFiles.length, prunedOrphanChunkFiles: prunedOrphanChunks.length }, null, 2));
12589
+ emit2(JSON.stringify({ pruned: removed, blocked_roots: after, prunedTempFiles: prunedTempFiles.length, prunedOrphanChunkFiles: prunedOrphanChunks.length }, null, 2));
12327
12590
  return;
12328
12591
  }
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).`);
12592
+ emit2(`Pruned ${removed} stale root(s). Remaining: ${after.length}`);
12593
+ emit2(`Pruned ${prunedTempFiles.length} stale indexed temp-dir file(s).`);
12594
+ emit2(`Pruned orphaned embedding chunks for ${prunedOrphanChunks.length} file(s).`);
12332
12595
  return;
12333
12596
  }
12334
- emitErr2(`project: unknown action '${action}'. Use list, exclude, or prune.`);
12597
+ emitErr3(`project: unknown action '${action}'. Use list, exclude, or prune.`);
12335
12598
  throw new Error(`unknown project action: ${action}`);
12336
12599
  }
12337
12600
  function recordCompactDocStat(fullSourceBytes, emittedText, detail) {
12338
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(emittedText, "utf8"));
12601
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(emittedText, "utf8"));
12339
12602
  recordStat("compact_doc", bytesSaved, savedTokensFromBytes(bytesSaved), void 0, detail);
12340
12603
  }
12341
12604
  function cmdCompactDoc(opts) {
12342
- const resolved = path19.resolve(opts.filePath);
12605
+ const resolved = path20.resolve(opts.filePath);
12343
12606
  if (opts.heading !== void 0) {
12344
12607
  const result = compactDoc(resolved, opts.heading);
12345
12608
  if (result === null) {
12346
- emitErr2(`compact-doc: could not read or compact '${resolved}'`);
12609
+ emitErr3(`compact-doc: could not read or compact '${resolved}'`);
12347
12610
  throw new Error(`could not compact: ${resolved}`);
12348
12611
  }
12349
12612
  const legacyFullBytes = fs19.statSync(resolved).size;
12350
12613
  if (opts.json === true) {
12351
12614
  const jsonText = JSON.stringify({ path: resolved, compact: result }, null, 2);
12352
- emit(jsonText);
12615
+ emit2(jsonText);
12353
12616
  recordCompactDocStat(legacyFullBytes, jsonText, resolved);
12354
12617
  return;
12355
12618
  }
12356
- emit(result);
12619
+ emit2(result);
12357
12620
  recordCompactDocStat(legacyFullBytes, result, resolved);
12358
12621
  return;
12359
12622
  }
@@ -12362,7 +12625,7 @@ function cmdCompactDoc(opts) {
12362
12625
  try {
12363
12626
  sentences = requirePositiveStrictInt("--sentences", opts.sentences);
12364
12627
  } catch (e) {
12365
- emitErr2(`compact-doc: --sentences must be a positive number, got: "${opts.sentences}"`);
12628
+ emitErr3(`compact-doc: --sentences must be a positive number, got: "${opts.sentences}"`);
12366
12629
  throw new Error(`invalid --sentences: ${opts.sentences}`, { cause: e });
12367
12630
  }
12368
12631
  }
@@ -12375,7 +12638,7 @@ function cmdCompactDoc(opts) {
12375
12638
  try {
12376
12639
  sourceText = fs19.readFileSync(resolved, "utf-8");
12377
12640
  } catch {
12378
- emitErr2(`compact-doc: could not read or compact '${resolved}'`);
12641
+ emitErr3(`compact-doc: could not read or compact '${resolved}'`);
12379
12642
  throw new Error(`could not compact: ${resolved}`);
12380
12643
  }
12381
12644
  body = buildExtractiveCompact(sourceText, sentences);
@@ -12384,7 +12647,7 @@ function cmdCompactDoc(opts) {
12384
12647
  } else {
12385
12648
  const existing = readCompactBody(compactPath);
12386
12649
  if (existing === null) {
12387
- emitErr2(`compact-doc: could not read or compact '${resolved}'`);
12650
+ emitErr3(`compact-doc: could not read or compact '${resolved}'`);
12388
12651
  throw new Error(`could not compact: ${resolved}`);
12389
12652
  }
12390
12653
  body = existing;
@@ -12392,16 +12655,16 @@ function cmdCompactDoc(opts) {
12392
12655
  const extractiveFullBytes = fs19.statSync(resolved).size;
12393
12656
  if (opts.json === true) {
12394
12657
  const jsonText = JSON.stringify({ path: resolved, compactPath, rebuilt, compact: body }, null, 2);
12395
- emit(jsonText);
12658
+ emit2(jsonText);
12396
12659
  recordCompactDocStat(extractiveFullBytes, jsonText, resolved);
12397
12660
  return;
12398
12661
  }
12399
12662
  if (opts.show === true) {
12400
- emit(body);
12663
+ emit2(body);
12401
12664
  recordCompactDocStat(extractiveFullBytes, body, resolved);
12402
12665
  return;
12403
12666
  }
12404
- emit(
12667
+ emit2(
12405
12668
  `Compact sidecar ${rebuilt ? "built" : "already fresh"} at ${compactPath} (source: ${resolved}). Use --show to print it, --force to rebuild.`
12406
12669
  );
12407
12670
  }
@@ -12442,11 +12705,11 @@ async function cmdFetchImage(opts) {
12442
12705
  try {
12443
12706
  fetched = await fetchBuffer(opts.url);
12444
12707
  } catch (e) {
12445
- emitErr2(`fetch-image: network error \u2014 ${extractErrorMessage(e)}`);
12708
+ emitErr3(`fetch-image: network error \u2014 ${extractErrorMessage(e)}`);
12446
12709
  throw new Error(`fetch failed: ${opts.url}`, { cause: e });
12447
12710
  }
12448
12711
  const buf = fetched.body;
12449
- const outPath = opts.out ?? path19.join(os9.tmpdir(), `tg-fetch-${Date.now()}${extensionForContentType(fetched.contentType)}`);
12712
+ const outPath = opts.out ?? path20.join(os9.tmpdir(), `tg-fetch-${Date.now()}${extensionForContentType(fetched.contentType)}`);
12450
12713
  const originalBytes = buf.length;
12451
12714
  let shrunkBytes;
12452
12715
  let outData;
@@ -12469,11 +12732,11 @@ async function cmdFetchImage(opts) {
12469
12732
  }
12470
12733
  atomicWriteBytes(finalPath, outData);
12471
12734
  if (opts.json === true) {
12472
- emit(JSON.stringify({ url: opts.url, out: finalPath, originalBytes, shrunkBytes, wasShrunk }, null, 2));
12735
+ emit2(JSON.stringify({ url: opts.url, out: finalPath, originalBytes, shrunkBytes, wasShrunk }, null, 2));
12473
12736
  return;
12474
12737
  }
12475
12738
  const savings = wasShrunk ? ` (saved ${originalBytes - shrunkBytes} bytes)` : " (not shrunk \u2014 already small or unsupported format)";
12476
- emit(`Fetched ${originalBytes} bytes \u2192 ${finalPath}${savings}`);
12739
+ emit2(`Fetched ${originalBytes} bytes \u2192 ${finalPath}${savings}`);
12477
12740
  }
12478
12741
  function cmdHistory(opts) {
12479
12742
  let limit = 30;
@@ -12481,11 +12744,11 @@ function cmdHistory(opts) {
12481
12744
  try {
12482
12745
  limit = requireNonNegativeStrictInt("--limit", opts.limit);
12483
12746
  } catch (e) {
12484
- emitErr2(`history: --limit must be a non-negative number, got: "${opts.limit}"`);
12747
+ emitErr3(`history: --limit must be a non-negative number, got: "${opts.limit}"`);
12485
12748
  throw new Error(`invalid --limit: ${opts.limit}`, { cause: e });
12486
12749
  }
12487
12750
  if (limit === 0) {
12488
- emitErr2(`history: --limit must be a positive number, got: "${opts.limit}"`);
12751
+ emitErr3(`history: --limit must be a positive number, got: "${opts.limit}"`);
12489
12752
  throw new Error(`invalid --limit: ${opts.limit}`);
12490
12753
  }
12491
12754
  }
@@ -12520,21 +12783,21 @@ function cmdHistory(opts) {
12520
12783
  return;
12521
12784
  }
12522
12785
  if (items.length === 0) {
12523
- emit("No history entries found (no cached bash outputs or web fetches).");
12786
+ emit2("No history entries found (no cached bash outputs or web fetches).");
12524
12787
  return;
12525
12788
  }
12526
12789
  const pad2 = (s, n) => s.length >= n ? s : s + " ".repeat(n - s.length);
12527
- emit(`${pad2("type", 5)} ${pad2("id", 18)} summary`);
12790
+ emit2(`${pad2("type", 5)} ${pad2("id", 18)} summary`);
12528
12791
  for (const item of items) {
12529
12792
  const preview = item.summary.length > 80 ? item.summary.slice(0, 77) + "..." : item.summary;
12530
- emit(`${pad2(item.type, 5)} ${pad2(item.id, 18)} ${preview}`);
12793
+ emit2(`${pad2(item.type, 5)} ${pad2(item.id, 18)} ${preview}`);
12531
12794
  }
12532
12795
  }
12533
12796
 
12534
12797
  // src/cli_bootstrap_audit.ts
12535
12798
  import * as fs20 from "node:fs";
12536
12799
  import * as os10 from "node:os";
12537
- import * as path20 from "node:path";
12800
+ import * as path21 from "node:path";
12538
12801
  import * as readline from "node:readline";
12539
12802
  var FRONTMATTER_KEYS = /* @__PURE__ */ new Set(["description", "tools", "allowed-tools", "allowed_tools"]);
12540
12803
  function parseBudget(name, value) {
@@ -12593,17 +12856,17 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
12593
12856
  logicalRoot = await fs20.promises.realpath(root);
12594
12857
  } catch (error) {
12595
12858
  if (error.code === "ENOENT") return entries;
12596
- diagnostics.push({ path: path20.resolve(root), reason: "unreadable root" });
12859
+ diagnostics.push({ path: path21.resolve(root), reason: "unreadable root" });
12597
12860
  return entries;
12598
12861
  }
12599
12862
  if (rootIsLink && !followLinks) {
12600
- diagnostics.push({ path: path20.resolve(root), reason: "external root link skipped (use --follow-links)" });
12863
+ diagnostics.push({ path: path21.resolve(root), reason: "external root link skipped (use --follow-links)" });
12601
12864
  return entries;
12602
12865
  }
12603
12866
  const canonicalKey = (candidate) => process.platform === "win32" ? candidate.toLocaleLowerCase() : candidate;
12604
12867
  const isContained = (candidate, allowedRoot) => {
12605
- const relative3 = path20.relative(canonicalKey(allowedRoot), canonicalKey(candidate));
12606
- return relative3 === "" || relative3 !== ".." && !relative3.startsWith(`..${path20.sep}`) && !path20.isAbsolute(relative3);
12868
+ const relative3 = path21.relative(canonicalKey(allowedRoot), canonicalKey(candidate));
12869
+ return relative3 === "" || relative3 !== ".." && !relative3.startsWith(`..${path21.sep}`) && !path21.isAbsolute(relative3);
12607
12870
  };
12608
12871
  async function visit(candidate, allowedRoot, topLevel = false) {
12609
12872
  let real;
@@ -12612,13 +12875,13 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
12612
12875
  candidateIsLink = (await fs20.promises.lstat(candidate)).isSymbolicLink();
12613
12876
  real = await fs20.promises.realpath(candidate);
12614
12877
  } catch {
12615
- diagnostics.push({ path: path20.resolve(candidate), reason: "broken or unreadable link" });
12878
+ diagnostics.push({ path: path21.resolve(candidate), reason: "broken or unreadable link" });
12616
12879
  return;
12617
12880
  }
12618
12881
  const withinAllowedRoot = allowedRoot === void 0 ? isContained(real, logicalRoot) : isContained(real, allowedRoot);
12619
12882
  const canFollowExternal = followLinks && topLevel;
12620
12883
  if (!withinAllowedRoot && !canFollowExternal) {
12621
- diagnostics.push({ path: path20.resolve(candidate), reason: candidateIsLink ? "external link skipped (use --follow-links)" : "canonical target outside scan root" });
12884
+ diagnostics.push({ path: path21.resolve(candidate), reason: candidateIsLink ? "external link skipped (use --follow-links)" : "canonical target outside scan root" });
12622
12885
  return;
12623
12886
  }
12624
12887
  const trustedRoot = !withinAllowedRoot && canFollowExternal ? real : allowedRoot ?? real;
@@ -12626,7 +12889,7 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
12626
12889
  try {
12627
12890
  stat = await fs20.promises.stat(real);
12628
12891
  } catch {
12629
- diagnostics.push({ path: path20.resolve(candidate), reason: "unreadable" });
12892
+ diagnostics.push({ path: path21.resolve(candidate), reason: "unreadable" });
12630
12893
  return;
12631
12894
  }
12632
12895
  const canonical = process.platform === "win32" ? real.toLocaleLowerCase() : real;
@@ -12637,14 +12900,14 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
12637
12900
  try {
12638
12901
  children = await fs20.promises.readdir(real, { withFileTypes: true });
12639
12902
  } catch {
12640
- diagnostics.push({ path: path20.resolve(candidate), reason: "unreadable directory" });
12903
+ diagnostics.push({ path: path21.resolve(candidate), reason: "unreadable directory" });
12641
12904
  return;
12642
12905
  }
12643
12906
  children.sort((a, b) => a.name.localeCompare(b.name));
12644
- for (const child of children) await visit(path20.join(real, child.name), trustedRoot);
12907
+ for (const child of children) await visit(path21.join(real, child.name), trustedRoot);
12645
12908
  return;
12646
12909
  }
12647
- if (!stat.isFile() || path20.extname(real).toLowerCase() !== ".md") return;
12910
+ if (!stat.isFile() || path21.extname(real).toLowerCase() !== ".md") return;
12648
12911
  if (seenFiles.has(canonical)) return;
12649
12912
  seenFiles.add(canonical);
12650
12913
  try {
@@ -12654,35 +12917,35 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
12654
12917
  const toolsBytes = Buffer.byteLength(metadata.tools, "utf8");
12655
12918
  entries.push({
12656
12919
  kind,
12657
- path: path20.resolve(real),
12920
+ path: path21.resolve(real),
12658
12921
  description_bytes: descriptionBytes,
12659
12922
  tools_bytes: toolsBytes,
12660
12923
  metadata_bytes: descriptionBytes + toolsBytes,
12661
12924
  estimated_tokens: Math.floor((descriptionBytes + toolsBytes) / 4)
12662
12925
  });
12663
12926
  } catch {
12664
- diagnostics.push({ path: path20.resolve(candidate), reason: "unreadable file" });
12927
+ diagnostics.push({ path: path21.resolve(candidate), reason: "unreadable file" });
12665
12928
  }
12666
12929
  }
12667
12930
  try {
12668
12931
  const children = await fs20.promises.readdir(logicalRoot, { withFileTypes: true });
12669
12932
  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);
12933
+ for (const child of children) await visit(path21.join(logicalRoot, child.name), void 0, true);
12671
12934
  } catch {
12672
- diagnostics.push({ path: path20.resolve(root), reason: "unreadable root" });
12935
+ diagnostics.push({ path: path21.resolve(root), reason: "unreadable root" });
12673
12936
  }
12674
12937
  return entries;
12675
12938
  }
12676
12939
  async function buildBootstrapAudit(opts = {}) {
12677
12940
  const project = resolveProjectRoot(opts.project === void 0 ? {} : { project: opts.project });
12678
- const home = path20.resolve(opts.home ?? os10.homedir());
12941
+ const home = path21.resolve(opts.home ?? os10.homedir());
12679
12942
  const context = buildStats(project, home, opts.project === void 0 ? [] : [opts.project]);
12680
12943
  const diagnostics = [];
12681
12944
  const top = opts.top === void 0 ? 10 : parseBudget("--top", opts.top) ?? 10;
12682
12945
  const visitedDirs = /* @__PURE__ */ new Set();
12683
12946
  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);
12947
+ const agents = await scanMetadataRoot(path21.join(home, ".claude", "agents"), "agent", diagnostics, visitedDirs, seenFiles, opts.followLinks === true);
12948
+ const skills = await scanMetadataRoot(path21.join(home, ".claude", "skills"), "skill", diagnostics, visitedDirs, seenFiles, opts.followLinks === true);
12686
12949
  const rankedEntries = [...agents, ...skills].sort((a, b) => b.metadata_bytes - a.metadata_bytes || a.path.localeCompare(b.path));
12687
12950
  const largestTotal = rankedEntries.length;
12688
12951
  const largest = rankedEntries.slice(0, top);
@@ -12749,7 +13012,7 @@ async function runBootstrapAudit(opts = {}) {
12749
13012
 
12750
13013
  // src/cli_memory.ts
12751
13014
  import * as fs21 from "node:fs";
12752
- import * as path21 from "node:path";
13015
+ import * as path22 from "node:path";
12753
13016
  function removeExactDupLines(text, report) {
12754
13017
  if (report.exactDupLines.length === 0) return text;
12755
13018
  const toRemove = new Set(report.exactDupLines.map(([, dupIdx]) => dupIdx));
@@ -12807,7 +13070,7 @@ function printReport(reports, clusters) {
12807
13070
  } else {
12808
13071
  for (const cluster of clusters) {
12809
13072
  w(
12810
- ` [${cluster.method}, similarity ${cluster.similarity}, ~${cluster.tokens} tok] ${cluster.members.map((m) => path21.basename(m)).join(", ")}
13073
+ ` [${cluster.method}, similarity ${cluster.similarity}, ~${cluster.tokens} tok] ${cluster.members.map((m) => path22.basename(m)).join(", ")}
12811
13074
  `
12812
13075
  );
12813
13076
  }
@@ -12822,7 +13085,7 @@ async function runMemoryCommand(opts = {}) {
12822
13085
  const claudeMds = findClaudeMdFiles(projectRoot);
12823
13086
  const reports = auditClaudeMd(claudeMds);
12824
13087
  const memoryMdPath = findMemoryMd(projectRoot);
12825
- const clusters = memoryMdPath !== null ? await findContentDuplicates(path21.dirname(memoryMdPath)) : [];
13088
+ const clusters = memoryMdPath !== null ? await findContentDuplicates(path22.dirname(memoryMdPath)) : [];
12826
13089
  printReport(reports, clusters);
12827
13090
  if (opts.fix !== true) return;
12828
13091
  const changes = [];
@@ -12868,16 +13131,16 @@ async function runMemoryCommand(opts = {}) {
12868
13131
 
12869
13132
  // src/cli_waste.ts
12870
13133
  import * as fs24 from "node:fs";
12871
- import * as path24 from "node:path";
13134
+ import * as path25 from "node:path";
12872
13135
 
12873
13136
  // src/waste.ts
12874
13137
  import * as fs22 from "node:fs";
12875
13138
  import * as os11 from "node:os";
12876
- import * as path22 from "node:path";
13139
+ import * as path23 from "node:path";
12877
13140
  function projectTranscriptsDir(projectRoot) {
12878
- const rootStr = path22.resolve(projectRoot);
13141
+ const rootStr = path23.resolve(projectRoot);
12879
13142
  const slug = rootStr.replace(/[^A-Za-z0-9]/g, "-");
12880
- return path22.join(os11.homedir(), ".claude", "projects", slug);
13143
+ return path23.join(os11.homedir(), ".claude", "projects", slug);
12881
13144
  }
12882
13145
  function findLatestTranscript(projectRoot) {
12883
13146
  const dir = projectTranscriptsDir(projectRoot);
@@ -12889,7 +13152,7 @@ function findLatestTranscript(projectRoot) {
12889
13152
  }
12890
13153
  let best = null;
12891
13154
  for (const name of entries) {
12892
- const full = path22.join(dir, name);
13155
+ const full = path23.join(dir, name);
12893
13156
  let stat;
12894
13157
  try {
12895
13158
  stat = fs22.statSync(full);
@@ -13119,7 +13382,7 @@ async function buildWasteReport(transcriptPath, opts = {}) {
13119
13382
 
13120
13383
  // src/copilot_waste.ts
13121
13384
  import * as fs23 from "node:fs";
13122
- import * as path23 from "node:path";
13385
+ import * as path24 from "node:path";
13123
13386
  var HOOK_RECORD_TYPES = /* @__PURE__ */ new Set(["hook.start", "hook.end"]);
13124
13387
  function splitInjectedBlocks(transformed) {
13125
13388
  const out2 = [];
@@ -13138,7 +13401,7 @@ function splitInjectedBlocks(transformed) {
13138
13401
  return out2;
13139
13402
  }
13140
13403
  function findLatestCopilotSession() {
13141
- const root = path23.join(copilotCliUserRoot(), "session-state");
13404
+ const root = path24.join(copilotCliUserRoot(), "session-state");
13142
13405
  let entries;
13143
13406
  try {
13144
13407
  entries = fs23.readdirSync(root);
@@ -13148,7 +13411,7 @@ function findLatestCopilotSession() {
13148
13411
  let best = null;
13149
13412
  let bestMtime = -Infinity;
13150
13413
  for (const entry of entries) {
13151
- const candidate = path23.join(root, entry, "events.jsonl");
13414
+ const candidate = path24.join(root, entry, "events.jsonl");
13152
13415
  try {
13153
13416
  const st = fs23.statSync(candidate);
13154
13417
  if (!st.isFile()) continue;
@@ -13169,7 +13432,7 @@ function buildCopilotWasteReport(eventsPath) {
13169
13432
  const raw = fs23.readFileSync(eventsPath, "utf-8");
13170
13433
  const report = {
13171
13434
  sessionPath: eventsPath,
13172
- sessionId: path23.basename(path23.dirname(eventsPath)),
13435
+ sessionId: path24.basename(path24.dirname(eventsPath)),
13173
13436
  turns: 0,
13174
13437
  tokens: null,
13175
13438
  blocks: [],
@@ -13448,7 +13711,7 @@ function printResidentContext(resident, w) {
13448
13711
  }
13449
13712
  async function runWasteCommand(opts = {}) {
13450
13713
  if (opts.copilot === true) {
13451
- const eventsPath = opts.transcript !== void 0 ? path24.resolve(opts.transcript) : findLatestCopilotSession();
13714
+ const eventsPath = opts.transcript !== void 0 ? path25.resolve(opts.transcript) : findLatestCopilotSession();
13452
13715
  if (eventsPath === null || !fs24.existsSync(eventsPath)) {
13453
13716
  const detail = eventsPath === null ? "no Copilot CLI session found under <copilot-home>/session-state" : `Copilot session event log not found: ${eventsPath}`;
13454
13717
  if (opts.json === true) {
@@ -13472,7 +13735,7 @@ async function runWasteCommand(opts = {}) {
13472
13735
  return;
13473
13736
  }
13474
13737
  const projectRoot = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
13475
- const transcriptPath = opts.transcript !== void 0 ? path24.resolve(opts.transcript) : findLatestTranscript(projectRoot);
13738
+ const transcriptPath = opts.transcript !== void 0 ? path25.resolve(opts.transcript) : findLatestTranscript(projectRoot);
13476
13739
  if (transcriptPath === null) {
13477
13740
  if (opts.json === true) {
13478
13741
  process.stdout.write(`${JSON.stringify({ error: "no session transcript found", project: projectRoot })}
@@ -13503,7 +13766,7 @@ async function runWasteCommand(opts = {}) {
13503
13766
 
13504
13767
  // src/session_read.ts
13505
13768
  import * as fs25 from "node:fs";
13506
- import * as path25 from "node:path";
13769
+ import * as path26 from "node:path";
13507
13770
  import * as readline2 from "node:readline";
13508
13771
  function resolveSessionTranscript(arg, opts = {}) {
13509
13772
  if (arg !== void 0 && arg !== "") {
@@ -13511,7 +13774,7 @@ function resolveSessionTranscript(arg, opts = {}) {
13511
13774
  const projectRoot2 = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
13512
13775
  const dir = projectTranscriptsDir(projectRoot2);
13513
13776
  const byId = arg.endsWith(".jsonl") ? arg : `${arg}.jsonl`;
13514
- const candidate = path25.join(dir, byId);
13777
+ const candidate = path26.join(dir, byId);
13515
13778
  if (fs25.existsSync(candidate) && fs25.statSync(candidate).isFile()) return candidate;
13516
13779
  return null;
13517
13780
  }
@@ -13670,7 +13933,7 @@ function formatSessionSlice(turns) {
13670
13933
  // src/session_audit.ts
13671
13934
  import * as fs26 from "node:fs";
13672
13935
  import * as os12 from "node:os";
13673
- import * as path26 from "node:path";
13936
+ import * as path27 from "node:path";
13674
13937
  import * as readline3 from "node:readline";
13675
13938
  function emptyMeasured() {
13676
13939
  return { apiCalls: 0, inputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, outputTokens: 0 };
@@ -13717,6 +13980,64 @@ var CACHE_READ_MULTIPLIER = 0.1;
13717
13980
  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
13981
  var READ_DIVERT_MAX_BYTES = 2500;
13719
13982
  var READ_FULL_SERVE_MIN_BYTES = 10240;
13983
+ var DENY_TEMPLATES = [
13984
+ { kind: "node_modules_deny", re: /node_modules is typically noise/ },
13985
+ { kind: "lock_file_deny", re: /Lock files are rarely useful to read in full/ },
13986
+ { kind: "tsbuildinfo_deny", re: /TypeScript incremental build cache file/ },
13987
+ { kind: "generated_build_deny", re: /Generated\/build artifact — read the source file instead\./ },
13988
+ { kind: "compact_sidecar_served", re: /Serving the extractive compact sidecar in place of the full file/ },
13989
+ { kind: "notebook_sidecar_served", re: /Serving the output-stripped notebook in place of the full file/ },
13990
+ // 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.
13991
+ { kind: "subagent_markdown_first_read_deny", re: /Subagent first read of a large markdown file/ },
13992
+ { kind: "markdown_heading_tree_deny", re: /Large markdown file \(\d+ headings\)/ },
13993
+ // The first alternative is a wording hooks_read.ts no longer emits (it claimed a compact-manifest
13994
+ // section that never existed, reworded in 3d044feb). It stays because this table classifies a
13995
+ // historical corpus, not just today's output: transcripts written before the rewording still
13996
+ // carry the old text, and dropping the alternative silently shrank this kind from 51 events to
13997
+ // 30 -- 41% of its history -- with a green suite. A superseded alternative is only removable
13998
+ // once no transcript contains it, which source code cannot tell you. See the superseded-wording
13999
+ // test in tests/deny_outcomes.test.ts.
14000
+ { 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/ },
14001
+ { kind: "improve_state_reread_deny", re: /Orchestrator state already read this session/ },
14002
+ { kind: "env_reread_deny", re: /Environment files rarely change mid-session/ },
14003
+ { kind: "session_artifact_truncated_deny", re: /File was truncated on last read\. Use `token-goat bash-output/ },
14004
+ { kind: "session_artifact_unchanged_deny", re: /is unchanged since last read\. Use `token-goat bash-output/ },
14005
+ { kind: "session_artifact_diff_deny", re: /Content changed since last read of [\s\S]*?bash-output --file/ },
14006
+ { kind: "session_artifact_large_deny", re: /(?:Session transcript|Tool-result file) is large \(/ },
14007
+ { kind: "session_artifact_generic_reread_deny", re: /already read this session\. Use `token-goat bash-output --file/ },
14008
+ { kind: "truncated_read_deny", re: /File was truncated on last read \(>33K tokens\)/ },
14009
+ { kind: "doc_unchanged_deny", re: /is unchanged since last read\. Use `token-goat (?:section|read)/ },
14010
+ { kind: "doc_diff_deny", re: /Content changed since last read of [\s\S]*?Use `token-goat (?:section|read)/ },
14011
+ { kind: "read_served_deny", re: /was already served in this session, byte for byte/ },
14012
+ { kind: "markdown_already_read_deny", re: /Markdown file already read this session\. Use `token-goat section/ },
14013
+ { kind: "read_count_deny", re: /Read this file \d+ times already/ },
14014
+ { kind: "generic_reread_deny", re: /was already read this session \(\d+ read/ },
14015
+ { kind: "large_file_deny", re: /is very large \(\d+(?:\.\d+)?KB\)\./ },
14016
+ { 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/ }
14017
+ ];
14018
+ var DENY_KINDS_WITH_SIZE = /* @__PURE__ */ new Set(["large_file_deny", "session_artifact_large_deny", "file_type_handler_deny"]);
14019
+ var DENY_SIZE_RE = /(\d+(?:\.\d+)?)\s*(B|KB|MB|GB|TB)\b/;
14020
+ function parseWithheldBytes(kind, text) {
14021
+ if (!DENY_KINDS_WITH_SIZE.has(kind)) return null;
14022
+ const m = DENY_SIZE_RE.exec(text);
14023
+ if (m === null) return null;
14024
+ const n = Number.parseFloat(m[1]);
14025
+ const unit = m[2].toUpperCase();
14026
+ const mult = unit === "B" ? 1 : unit === "KB" ? 1024 : unit === "MB" ? 1024 * 1024 : unit === "GB" ? 1024 ** 3 : 1024 ** 4;
14027
+ return Math.round(n * mult);
14028
+ }
14029
+ 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/;
14030
+ var SHELL_READER_TOKEN_RE = /\b(?:sed|grep|cat|head|tail|awk|rg|less|nl|python3?|node)\b/;
14031
+ function escapeRegExp2(s) {
14032
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14033
+ }
14034
+ function shellReadMatch(command, basename8) {
14035
+ if (basename8 === "" || !SHELL_READER_TOKEN_RE.test(command)) return "none";
14036
+ if (!command.includes(basename8)) return "none";
14037
+ const escaped = escapeRegExp2(basename8);
14038
+ const strict = new RegExp(`(?:^|["'\`/\\\\\\s])${escaped}(?:$|["'\`/\\\\\\s,;:)])`);
14039
+ return strict.test(command) ? "match" : "ambiguous";
14040
+ }
13720
14041
  var BASH_FILTER_MARKER_RE = /\[token-goat[:\]]/;
13721
14042
  var BASH_SMALL_RESULT_MAX_BYTES = 1024;
13722
14043
  function commandHead(raw) {
@@ -13771,7 +14092,7 @@ function listCorpusTranscripts(corpusDir) {
13771
14092
  const found = [];
13772
14093
  const walk = (dir, entries) => {
13773
14094
  for (const entry of entries) {
13774
- const full = path26.join(dir, entry.name);
14095
+ const full = path27.join(dir, entry.name);
13775
14096
  if (entry.name.endsWith(".jsonl")) {
13776
14097
  found.push(full);
13777
14098
  } else if (entry.isDirectory()) {
@@ -13789,9 +14110,9 @@ function listCorpusTranscripts(corpusDir) {
13789
14110
  return found.sort();
13790
14111
  }
13791
14112
  function defaultCorpusDir() {
13792
- return path26.join(os12.homedir(), ".claude", "projects");
14113
+ return path27.join(os12.homedir(), ".claude", "projects");
13793
14114
  }
13794
- async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneObservations, bashHeadMap) {
14115
+ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneObservations, bashHeadMap, denyRows) {
13795
14116
  const isLane = filePath.split(/[\\/]/).includes("subagents");
13796
14117
  let laneFirstPrefix = null;
13797
14118
  let laneBriefBytes = -1;
@@ -13799,8 +14120,12 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
13799
14120
  const bashHeadById = /* @__PURE__ */ new Map();
13800
14121
  let pendingBashReread = [];
13801
14122
  const readCallById = /* @__PURE__ */ new Map();
14123
+ const readPathById = /* @__PURE__ */ new Map();
14124
+ const editPathById = /* @__PURE__ */ new Map();
14125
+ const editErrorById = /* @__PURE__ */ new Map();
13802
14126
  const readPathEpoch = /* @__PURE__ */ new Map();
13803
14127
  let compactEpoch = 0;
14128
+ const openDenies = [];
13804
14129
  let sawTokenGoatHook = false;
13805
14130
  let fileRepeats = 0;
13806
14131
  let fileRepeatsFullNoRange = 0;
@@ -13895,13 +14220,38 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
13895
14220
  toolNameById.set(block["id"], name);
13896
14221
  toolRollup(toolMap, name).calls += 1;
13897
14222
  const input = block["input"] !== null && typeof block["input"] === "object" ? block["input"] : {};
14223
+ let readNorm;
14224
+ let editNorm;
13898
14225
  if (name === "Bash" && typeof input["command"] === "string") {
13899
14226
  bashHeadById.set(block["id"], commandHead(input["command"]));
13900
14227
  } else if (name === "Read" && typeof input["file_path"] === "string") {
13901
- const norm = normalizeReadPath(input["file_path"]);
13902
- const priorEpoch = readPathEpoch.get(norm);
14228
+ readNorm = normalizeReadPath(input["file_path"]);
14229
+ const priorEpoch = readPathEpoch.get(readNorm);
13903
14230
  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);
14231
+ readPathById.set(block["id"], readNorm);
14232
+ readPathEpoch.set(readNorm, compactEpoch);
14233
+ } else if (name === "Edit" && typeof input["file_path"] === "string") {
14234
+ editNorm = normalizeReadPath(input["file_path"]);
14235
+ editPathById.set(block["id"], editNorm);
14236
+ s.editErrorBaseline.totalEdits += 1;
14237
+ }
14238
+ if (openDenies.length > 0) {
14239
+ const bashCommand = name === "Bash" && typeof input["command"] === "string" ? input["command"] : void 0;
14240
+ for (const o of openDenies) {
14241
+ if (o.toolCalls.length >= 10) continue;
14242
+ const isReadSamePath = readNorm !== void 0 && o.path !== "" && readNorm === o.path;
14243
+ const isSurgicalSamePath = bashCommand !== void 0 && o.basename !== "" && SURGICAL_COMMAND_RE.test(bashCommand) && bashCommand.includes(o.basename);
14244
+ const shellRead = bashCommand !== void 0 && o.basename !== "" ? shellReadMatch(bashCommand, o.basename) : "none";
14245
+ const isEditSamePath = editNorm !== void 0 && o.path !== "" && editNorm === o.path;
14246
+ o.toolCalls.push({
14247
+ isReadSamePath,
14248
+ isSurgicalSamePath,
14249
+ isShellReadSamePath: shellRead === "match",
14250
+ isShellReadAmbiguous: shellRead === "ambiguous",
14251
+ editCallId: isEditSamePath ? block["id"] : void 0
14252
+ });
14253
+ if (o.toolCalls.length === 3) o.compactEpochAtWindow = compactEpoch;
14254
+ }
13905
14255
  }
13906
14256
  }
13907
14257
  let inputBytes;
@@ -13930,8 +14280,27 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
13930
14280
  if (!toolNameById.has(id)) roll.calls += 1;
13931
14281
  roll.resultBytes += bytes;
13932
14282
  roll.resultEstTokens += estimateTokensFromLength(bytes);
14283
+ if (name === "Edit" && block["is_error"] === true) {
14284
+ s.editErrorBaseline.totalErrors += 1;
14285
+ editErrorById.set(id, true);
14286
+ }
13933
14287
  if (name === "Read") {
13934
14288
  s.readInterception.readResults += 1;
14289
+ const denyTemplate = DENY_TEMPLATES.find((t) => t.re.test(text));
14290
+ if (denyTemplate !== void 0) {
14291
+ const path30 = readPathById.get(id) ?? "";
14292
+ const lastSlash = path30.lastIndexOf("/");
14293
+ openDenies.push({
14294
+ kind: denyTemplate.kind,
14295
+ withheldBytes: parseWithheldBytes(denyTemplate.kind, text),
14296
+ path: path30,
14297
+ basename: lastSlash === -1 ? path30 : path30.slice(lastSlash + 1),
14298
+ callIndexAtDeny: perCall.length,
14299
+ compactEpochAtOpen: compactEpoch,
14300
+ compactEpochAtWindow: null,
14301
+ toolCalls: []
14302
+ });
14303
+ }
13935
14304
  if (bytes < READ_DIVERT_MAX_BYTES && READ_DIVERT_MARKER_RE.test(text)) {
13936
14305
  s.readInterception.divertedByMarker += 1;
13937
14306
  s.readInterception.divertedBytes += bytes;
@@ -14068,6 +14437,42 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
14068
14437
  }
14069
14438
  flushLane(0);
14070
14439
  flushLane(1);
14440
+ for (const o of openDenies) {
14441
+ if (o.compactEpochAtWindow === null) o.compactEpochAtWindow = compactEpoch;
14442
+ const windowCalls = o.toolCalls.slice(0, 3);
14443
+ let outcome;
14444
+ if (o.compactEpochAtWindow > o.compactEpochAtOpen) {
14445
+ outcome = "compacted";
14446
+ } else if (windowCalls.some((c) => c.isReadSamePath)) {
14447
+ outcome = "retried";
14448
+ } else if (windowCalls.some((c) => c.isSurgicalSamePath)) {
14449
+ outcome = "substituted";
14450
+ } else if (windowCalls.some((c) => c.isShellReadSamePath)) {
14451
+ outcome = "shell_read";
14452
+ } else if (o.toolCalls.length < 3) {
14453
+ outcome = "unresolved";
14454
+ } else {
14455
+ outcome = "abandoned";
14456
+ }
14457
+ const retriedWithin10 = o.toolCalls.some((c) => c.isReadSamePath);
14458
+ const shellReadAmbiguous = windowCalls.some((c) => c.isShellReadAmbiguous);
14459
+ const editWithin10 = o.toolCalls.some((c) => c.editCallId !== void 0);
14460
+ const editErrorWithin10 = o.toolCalls.some((c) => c.editCallId !== void 0 && editErrorById.get(c.editCallId) === true);
14461
+ const R = perCall.length - o.callIndexAtDeny;
14462
+ const nextCall = perCall[o.callIndexAtDeny];
14463
+ denyRows.push({
14464
+ kind: o.kind,
14465
+ withheldBytes: o.withheldBytes,
14466
+ R,
14467
+ nextCallInputTotal: nextCall?.inputTotal ?? 0,
14468
+ nextCallCacheRead: nextCall?.cacheRead ?? 0,
14469
+ outcome,
14470
+ retriedWithin10,
14471
+ shellReadAmbiguous,
14472
+ editWithin10,
14473
+ editErrorWithin10
14474
+ });
14475
+ }
14071
14476
  if (sawTokenGoatHook) {
14072
14477
  s.readInterception.repeatInHookedSessions += fileRepeats;
14073
14478
  s.readInterception.repeatFullNoRangeInHookedSessions += fileRepeatsFullNoRange;
@@ -14117,8 +14522,47 @@ function hookRollup(hookMap, origin, event) {
14117
14522
  }
14118
14523
  return roll;
14119
14524
  }
14525
+ function median(arr) {
14526
+ if (arr.length === 0) return 0;
14527
+ const sorted = [...arr].sort((a, b) => a - b);
14528
+ return sorted[Math.floor(sorted.length / 2)];
14529
+ }
14530
+ function aggregateDenyOutcomes(rows) {
14531
+ const byKind = /* @__PURE__ */ new Map();
14532
+ for (const r of rows) {
14533
+ const bucket = byKind.get(r.kind);
14534
+ if (bucket === void 0) byKind.set(r.kind, [r]);
14535
+ else bucket.push(r);
14536
+ }
14537
+ const result = [];
14538
+ for (const [kind, kindRows] of byKind) {
14539
+ const count = kindRows.length;
14540
+ const rateOf = (outcome) => kindRows.filter((r) => r.outcome === outcome).length / count;
14541
+ const knownBytes = kindRows.map((r) => r.withheldBytes).filter((b) => b !== null);
14542
+ result.push({
14543
+ kind,
14544
+ count,
14545
+ compactedRate: rateOf("compacted"),
14546
+ retriedRate: rateOf("retried"),
14547
+ substitutedRate: rateOf("substituted"),
14548
+ shellReadRate: rateOf("shell_read"),
14549
+ unresolvedRate: rateOf("unresolved"),
14550
+ abandonedRate: rateOf("abandoned"),
14551
+ retriedWithin10Rate: kindRows.filter((r) => r.retriedWithin10).length / count,
14552
+ medianWithheldBytes: knownBytes.length === 0 ? null : median(knownBytes),
14553
+ withheldBytesUnknownFraction: (count - knownBytes.length) / count,
14554
+ medianR: median(kindRows.map((r) => r.R)),
14555
+ medianNextCallInputTotal: median(kindRows.map((r) => r.nextCallInputTotal)),
14556
+ medianNextCallCacheRead: median(kindRows.map((r) => r.nextCallCacheRead)),
14557
+ shellReadAmbiguousCount: kindRows.filter((r) => r.shellReadAmbiguous).length,
14558
+ editWithin10Count: kindRows.filter((r) => r.editWithin10).length,
14559
+ editErrorWithin10Count: kindRows.filter((r) => r.editErrorWithin10).length
14560
+ });
14561
+ }
14562
+ return result.sort((a, b) => b.count - a.count || a.kind.localeCompare(b.kind));
14563
+ }
14120
14564
  async function auditSessionCorpus(opts = {}) {
14121
- const corpusDir = path26.resolve(opts.dir ?? defaultCorpusDir());
14565
+ const corpusDir = path27.resolve(opts.dir ?? defaultCorpusDir());
14122
14566
  if (!fs26.existsSync(corpusDir)) {
14123
14567
  throw new Error(`session corpus directory not found: ${corpusDir}`);
14124
14568
  }
@@ -14156,6 +14600,8 @@ async function auditSessionCorpus(opts = {}) {
14156
14600
  laneAgentTypes: [],
14157
14601
  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
14602
  bashInterception: { bashResults: 0, markedByFilter: 0, markedBytes: 0, smallUntouched: 0, smallUntouchedBytes: 0, untouched: 0, untouchedBytes: 0, untouchedEstTokens: 0, untouchedRereadTokens: 0, untouchedBilledEquivTokens: 0, untouchedHeads: [] },
14603
+ denyOutcomes: [],
14604
+ editErrorBaseline: { totalEdits: 0, totalErrors: 0, rate: 0 },
14159
14605
  lineTypes: {},
14160
14606
  omissionMarkers: { fires: 0, linesOmitted: 0 }
14161
14607
  };
@@ -14164,9 +14610,10 @@ async function auditSessionCorpus(opts = {}) {
14164
14610
  const hookMap = /* @__PURE__ */ new Map();
14165
14611
  const laneObservations = [];
14166
14612
  const bashHeadMap = /* @__PURE__ */ new Map();
14613
+ const denyRows = [];
14167
14614
  for (const file of files) {
14168
14615
  try {
14169
- await auditOneFile(file, summary, toolMap, attachmentMap, hookMap, laneObservations, bashHeadMap);
14616
+ await auditOneFile(file, summary, toolMap, attachmentMap, hookMap, laneObservations, bashHeadMap, denyRows);
14170
14617
  summary.filesScanned += 1;
14171
14618
  } catch {
14172
14619
  summary.filesFailed += 1;
@@ -14203,6 +14650,8 @@ async function auditSessionCorpus(opts = {}) {
14203
14650
  }
14204
14651
  summary.attachmentKinds = [...attachmentMap.values()].sort((a, b) => b.billedEquivTokens - a.billedEquivTokens || b.injections - a.injections || a.kind.localeCompare(b.kind));
14205
14652
  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));
14653
+ summary.denyOutcomes = aggregateDenyOutcomes(denyRows);
14654
+ summary.editErrorBaseline.rate = summary.editErrorBaseline.totalEdits === 0 ? 0 : summary.editErrorBaseline.totalErrors / summary.editErrorBaseline.totalEdits;
14206
14655
  summary.runtimeMs = Date.now() - started;
14207
14656
  return summary;
14208
14657
  }
@@ -14288,6 +14737,15 @@ function formatSessionAudit(s) {
14288
14737
  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
14738
  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
14739
  lines.push("");
14740
+ lines.push("## Deny outcomes (what actually happened after a token-goat Read deny; raw tokens, not billed units)");
14741
+ const eb = s.editErrorBaseline;
14742
+ 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.`);
14743
+ for (const d of s.denyOutcomes) {
14744
+ const editErrorRatio = d.editWithin10Count > 0 ? `${fmt(d.editErrorWithin10Count)}/${fmt(d.editWithin10Count)}` : "n/a";
14745
+ 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}`);
14746
+ }
14747
+ if (s.denyOutcomes.length === 0) lines.push("(no denies matched a known template)");
14748
+ lines.push("");
14291
14749
  lines.push("## Bash filter fire-rate (token-goat in-band markers inside Bash tool results)");
14292
14750
  const bi = s.bashInterception;
14293
14751
  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 +14764,7 @@ function formatSessionAudit(s) {
14306
14764
  // src/cli_mcp_audit.ts
14307
14765
  import * as fs27 from "node:fs";
14308
14766
  import * as os13 from "node:os";
14309
- import * as path27 from "node:path";
14767
+ import * as path28 from "node:path";
14310
14768
  function readMcpJsonFile(configPath2) {
14311
14769
  try {
14312
14770
  if (!fs27.existsSync(configPath2)) return null;
@@ -14345,8 +14803,8 @@ function readClaudeJsonConfig(claudeJsonPath, projectRoot) {
14345
14803
  }
14346
14804
  }
14347
14805
  function discoverMcpConfig(projectRoot, home) {
14348
- const mcpJsonPath = path27.join(projectRoot, ".mcp.json");
14349
- const claudeJsonPath = path27.join(home, ".claude.json");
14806
+ const mcpJsonPath = path28.join(projectRoot, ".mcp.json");
14807
+ const claudeJsonPath = path28.join(home, ".claude.json");
14350
14808
  const sourcesChecked = [mcpJsonPath, claudeJsonPath];
14351
14809
  const fromMcpJson = readMcpJsonFile(mcpJsonPath);
14352
14810
  if (fromMcpJson !== null) return { servers: fromMcpJson, sourcePath: mcpJsonPath, sourcesChecked };
@@ -14906,7 +15364,7 @@ async function cmdMcpServe() {
14906
15364
  let StdioServerTransport;
14907
15365
  try {
14908
15366
  ;
14909
- ({ createMcpServer } = await import("./token-goat-chunk-ZIPBLIUZ.mjs"));
15367
+ ({ createMcpServer } = await import("./token-goat-chunk-LEBYARO3.mjs"));
14910
15368
  ({ StdioServerTransport } = await import("./token-goat-chunk-324QOJYZ.mjs"));
14911
15369
  } catch (err2) {
14912
15370
  process.stderr.write(
@@ -14919,8 +15377,8 @@ async function cmdMcpServe() {
14919
15377
  const server = await createMcpServer();
14920
15378
  const transport = new StdioServerTransport();
14921
15379
  await server.connect(transport);
14922
- await new Promise((resolve13) => {
14923
- server.onclose = resolve13;
15380
+ await new Promise((resolve14) => {
15381
+ server.onclose = resolve14;
14924
15382
  });
14925
15383
  }
14926
15384
  function printBridgeVerificationNotice(harness) {
@@ -14931,7 +15389,7 @@ async function cmdHook(event, opts) {
14931
15389
  if (typeof opts.harness === "string" && opts.harness.length > 0) {
14932
15390
  process.env[ENV_KEYS.HARNESS_OVERRIDE] = opts.harness;
14933
15391
  }
14934
- const { relay } = await import("./token-goat-chunk-LJEETTER.mjs");
15392
+ const { relay } = await import("./token-goat-chunk-ZVN5WHTO.mjs");
14935
15393
  await relay(event);
14936
15394
  }
14937
15395
  async function cmdInstall(opts) {
@@ -15044,14 +15502,14 @@ async function cmdInstall(opts) {
15044
15502
  );
15045
15503
  }
15046
15504
  try {
15047
- const skillDir = path28.join(homedir13(), ".claude", "skills");
15505
+ const skillDir = path29.join(homedir13(), ".claude", "skills");
15048
15506
  if (fs28.existsSync(skillDir)) {
15049
15507
  const entries = fs28.readdirSync(skillDir, { withFileTypes: true });
15050
15508
  const skillNames = [];
15051
15509
  const sessionId = getSessionId();
15052
15510
  for (const entry of entries) {
15053
15511
  if (!entry.isDirectory()) continue;
15054
- const skillFile = path28.join(skillDir, entry.name, "SKILL.md");
15512
+ const skillFile = path29.join(skillDir, entry.name, "SKILL.md");
15055
15513
  if (fs28.existsSync(skillFile)) {
15056
15514
  const body = fs28.readFileSync(skillFile, "utf-8");
15057
15515
  const compact = extractCompactFromMarker(body);
@@ -15064,7 +15522,7 @@ async function cmdInstall(opts) {
15064
15522
  if (skillNames.length > 0) {
15065
15523
  const dir = skillOutputsDir();
15066
15524
  await fs28.promises.mkdir(dir, { recursive: true });
15067
- const pregenPath = path28.join(dir, "pregen.json");
15525
+ const pregenPath = path29.join(dir, "pregen.json");
15068
15526
  const pregenData = { ts: Date.now(), names: skillNames };
15069
15527
  await fs28.promises.writeFile(pregenPath, JSON.stringify(pregenData, null, 2));
15070
15528
  out(`Pre-generated ${skillNames.length} skill compacts.`);
@@ -15265,7 +15723,7 @@ async function cmdSessionOutline(sessionIdOrPath, opts = {}) {
15265
15723
  ${formatSessionOutline(turns)}`;
15266
15724
  out(text);
15267
15725
  const fullSourceBytes = sessionTranscriptSize(transcriptPath);
15268
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
15726
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
15269
15727
  recordStat("session_outline", bytesSaved, savedTokensFromBytes(bytesSaved));
15270
15728
  }
15271
15729
  function sessionTranscriptSize(transcriptPath) {
@@ -15287,7 +15745,7 @@ async function cmdSessionSlice(sessionIdOrPath, opts) {
15287
15745
  const text = opts.json === true ? JSON.stringify({ transcriptPath, turns }) : formatSessionSlice(turns);
15288
15746
  out(text);
15289
15747
  const fullSourceBytes = sessionTranscriptSize(transcriptPath);
15290
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
15748
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
15291
15749
  recordStat("session_slice", bytesSaved, savedTokensFromBytes(bytesSaved));
15292
15750
  }
15293
15751
  function cmdMcpAudit(opts = {}) {
@@ -15328,7 +15786,7 @@ function cmdHintStats(opts = {}) {
15328
15786
  });
15329
15787
  }
15330
15788
  function _applyFiltersAndPrint(content, opts, fenceByProvenance = false, fenceTag = UNTRUSTED_WEB_TAG) {
15331
- const emit2 = (text) => {
15789
+ const emit3 = (text) => {
15332
15790
  if (!fenceByProvenance || text === "") {
15333
15791
  out(text);
15334
15792
  return text;
@@ -15365,7 +15823,7 @@ function _applyFiltersAndPrint(content, opts, fenceByProvenance = false, fenceTa
15365
15823
  }
15366
15824
  const rawLines = content.split(/\r?\n/);
15367
15825
  if (opts.full === true) {
15368
- return emit2(rawLines.join("\n"));
15826
+ return emit3(rawLines.join("\n"));
15369
15827
  }
15370
15828
  const lines = rawLines.length > 1 && rawLines[rawLines.length - 1] === "" ? rawLines.slice(0, -1) : rawLines;
15371
15829
  const headN = opts.head !== void 0 ? requireNonNegativeInt("--head", opts.head) : 30;
@@ -15388,7 +15846,7 @@ function _applyFiltersAndPrint(content, opts, fenceByProvenance = false, fenceTa
15388
15846
  result = lines.slice(Math.max(0, lines.length - tailN));
15389
15847
  noteLineCap("last", "tail", result.length, lines.length);
15390
15848
  }
15391
- return emit2(result.join("\n"));
15849
+ return emit3(result.join("\n"));
15392
15850
  }
15393
15851
  function fenceFileText(text) {
15394
15852
  return fenceUntrusted(text, UNTRUSTED_FILE_TAG);
@@ -15463,7 +15921,7 @@ async function cmdPdfExtract(file, opts) {
15463
15921
  const text = await runPdfExtractText(file, opts.pages, opts.layout === true);
15464
15922
  const printed = _applyFiltersAndPrint(text, opts, true, UNTRUSTED_FILE_TAG);
15465
15923
  const fullSourceBytes = fileSizeOrZero(file);
15466
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(printed, "utf8"));
15924
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(printed, "utf8"));
15467
15925
  recordStat("pdf_extract", bytesSaved, savedTokensFromBytes(bytesSaved));
15468
15926
  }
15469
15927
  async function cmdPdfLocate(file, pattern, opts) {
@@ -15493,7 +15951,7 @@ ${countNoun(matches2.length, "match", "matches")} across ${countNoun(pages.lengt
15493
15951
  out(printed);
15494
15952
  }
15495
15953
  const fullSourceBytes = fileSizeOrZero(file);
15496
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(printed, "utf8"));
15954
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(printed, "utf8"));
15497
15955
  recordStat("pdf_locate", bytesSaved, savedTokensFromBytes(bytesSaved));
15498
15956
  }
15499
15957
  async function cmdPdfOutline(file, opts) {
@@ -15509,7 +15967,7 @@ async function cmdPdfOutline(file, opts) {
15509
15967
  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
15968
  out(text);
15511
15969
  const fullSourceBytes = fileSizeOrZero(file);
15512
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
15970
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
15513
15971
  recordStat("pdf_outline", bytesSaved, savedTokensFromBytes(bytesSaved));
15514
15972
  }
15515
15973
  async function cmdPdfMeta(file, opts = {}) {
@@ -15523,7 +15981,7 @@ async function cmdPdfMeta(file, opts = {}) {
15523
15981
  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
15982
  out(text);
15525
15983
  const fullSourceBytes = fileSizeOrZero(file);
15526
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
15984
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
15527
15985
  recordStat("pdf_meta", bytesSaved, savedTokensFromBytes(bytesSaved));
15528
15986
  }
15529
15987
  async function cmdImageMeta(file, opts = {}) {
@@ -15545,8 +16003,8 @@ ${msg}`;
15545
16003
  const text = opts.json === true ? JSON.stringify(meta, null, 2) : lines.join("\n");
15546
16004
  out(text);
15547
16005
  const fullSourceBytes = fileSizeOrZero(file);
15548
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
15549
- recordStat("image_meta", bytesSaved, savedTokensFromBytes(bytesSaved));
16006
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
16007
+ recordStat("image_meta", bytesSaved, visionTokensSavedByText(meta.width, meta.height, Buffer.byteLength(text, "utf8"), loadConfig().image_shrink.vision_tier));
15550
16008
  }
15551
16009
  function fenceOcrText(text) {
15552
16010
  if (!injectionFencingEnabled()) return text;
@@ -15577,8 +16035,8 @@ ${fenceOcrText(result.text)}` : `${lines.join("\n")}
15577
16035
  }
15578
16036
  out(text);
15579
16037
  const fullSourceBytes = fileSizeOrZero(file);
15580
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
15581
- recordStat("image_text", bytesSaved, savedTokensFromBytes(bytesSaved));
16038
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
16039
+ recordStat("image_text", bytesSaved, visionTokensSavedByText(null, null, Buffer.byteLength(text, "utf8"), loadConfig().image_shrink.vision_tier));
15582
16040
  }
15583
16041
  function cmdVideoChapters(file) {
15584
16042
  const { chapters, subtitleStreams } = extractVideoChapters(file);
@@ -15603,7 +16061,7 @@ function cmdVideoChapters(file) {
15603
16061
  const text = lines.join("\n");
15604
16062
  out(text);
15605
16063
  const fullSourceBytes = fileSizeOrZero(file);
15606
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
16064
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
15607
16065
  recordStat("video_chapters", bytesSaved, savedTokensFromBytes(bytesSaved));
15608
16066
  }
15609
16067
  function formatVideoTimestamp(totalSeconds) {
@@ -15630,7 +16088,7 @@ function cmdSharepointResolve(url) {
15630
16088
  }
15631
16089
  function recordXlsxStat(kind, file, emitted) {
15632
16090
  const fullSourceBytes = fileSizeOrZero(file);
15633
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(emitted, "utf8"));
16091
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(emitted, "utf8"));
15634
16092
  recordStat(kind, bytesSaved, savedTokensFromBytes(bytesSaved));
15635
16093
  }
15636
16094
  async function cmdXlsxSheets(file, opts = {}) {
@@ -15665,7 +16123,7 @@ async function cmdXlsxQuery(file, opts) {
15665
16123
  }
15666
16124
  function recordDocStat(kind, file, emitted) {
15667
16125
  const fullSourceBytes = fileSizeOrZero(file);
15668
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(emitted, "utf8"));
16126
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(emitted, "utf8"));
15669
16127
  recordStat(kind, bytesSaved, savedTokensFromBytes(bytesSaved));
15670
16128
  }
15671
16129
  async function cmdPptxOutline(file, opts) {
@@ -15823,6 +16281,18 @@ function cmdConflicts(targetPath, opts) {
15823
16281
  async function cmdScreenshot(url, destPath, opts) {
15824
16282
  out(await runScreenshot(url, destPath, opts));
15825
16283
  }
16284
+ function readStdinPaths() {
16285
+ if (process.stdin.isTTY) {
16286
+ throw new CliError("--stdin requires piped input, e.g. `git diff --name-only | token-goat affected --stdin`");
16287
+ }
16288
+ let raw;
16289
+ try {
16290
+ raw = fs28.readFileSync(0, "utf8");
16291
+ } catch (e) {
16292
+ throw new CliError(`could not read the file list from stdin: ${extractErrorMessage(e)}`);
16293
+ }
16294
+ return raw.split("\n").map((line) => line.trim()).filter((line) => line !== "");
16295
+ }
15826
16296
  function runExit(fn) {
15827
16297
  try {
15828
16298
  process.exitCode = fn();
@@ -15853,7 +16323,7 @@ function emitExtraFileArgsNote(command, first, extras, opts = {}) {
15853
16323
  }
15854
16324
  async function cmdCompress(opts) {
15855
16325
  try {
15856
- const bashRunner = await import("./token-goat-chunk-L2XHDICZ.mjs");
16326
+ const bashRunner = await import("./token-goat-chunk-NY4HYRSK.mjs");
15857
16327
  if (opts.compress === false) {
15858
16328
  process.exitCode = bashRunner.runRaw(opts.cmd, parseTimeout(opts.timeout, bashRunner.DEFAULT_TIMEOUT_SECONDS));
15859
16329
  return;
@@ -15951,8 +16421,8 @@ async function cmdSkillCompact(name, opts) {
15951
16421
  }
15952
16422
  throw new CliError(`failed to read skill file '${opts.path}': ${extractErrorMessage(e)}`);
15953
16423
  }
15954
- cacheName = name ?? path28.basename(path28.dirname(path28.resolve(opts.path)));
15955
- sourcePath = path28.resolve(opts.path);
16424
+ cacheName = name ?? path29.basename(path29.dirname(path29.resolve(opts.path)));
16425
+ sourcePath = path29.resolve(opts.path);
15956
16426
  } else {
15957
16427
  if (name === void 0 || !name.trim()) {
15958
16428
  throw new CliError("skill-compact requires a <name> or --path <file>");
@@ -16093,8 +16563,8 @@ async function cmdSkillDiff(name) {
16093
16563
  }
16094
16564
  const newer = versions[0];
16095
16565
  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);
16566
+ const newerBody = await fs28.promises.readFile(path29.resolve(dir, `${newer.outputId}.txt`), "utf-8").catch(() => null);
16567
+ const olderBody = await fs28.promises.readFile(path29.resolve(dir, `${older.outputId}.txt`), "utf-8").catch(() => null);
16098
16568
  if (newerBody === null || olderBody === null) {
16099
16569
  out(`a cached version of '${name}' was evicted while diffing -- try again`);
16100
16570
  return;
@@ -16146,7 +16616,7 @@ function atomicWriteBuffer(dest, data) {
16146
16616
  if (e.code !== "ENOENT") throw e;
16147
16617
  }
16148
16618
  const rnd = randomBytes(4).toString("hex");
16149
- const tmp = path28.join(path28.dirname(path28.resolve(dest)), `.tmp.${process.pid}.${rnd}`);
16619
+ const tmp = path29.join(path29.dirname(path29.resolve(dest)), `.tmp.${process.pid}.${rnd}`);
16150
16620
  try {
16151
16621
  fs28.writeFileSync(tmp, data, { mode: 384 });
16152
16622
  try {
@@ -16184,9 +16654,9 @@ function mapFsError(e, src, dest, srcLabel = "source") {
16184
16654
  const fe = e;
16185
16655
  if (fe.code === "ENOENT") {
16186
16656
  const errPath = fe.path ?? "";
16187
- const isSource = src !== void 0 && path28.resolve(errPath) === path28.resolve(src);
16657
+ const isSource = src !== void 0 && path29.resolve(errPath) === path29.resolve(src);
16188
16658
  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 || "."));
16659
+ const destDir = dest ? path29.dirname(path29.resolve(dest)) : path29.dirname(path29.resolve(errPath || "."));
16190
16660
  throw new CliError(`destination directory does not exist: ${destDir}`);
16191
16661
  }
16192
16662
  if (fe.code === "ENOTDIR") {
@@ -16197,7 +16667,7 @@ function mapFsError(e, src, dest, srcLabel = "source") {
16197
16667
  }
16198
16668
  if (fe.code === "EISDIR") {
16199
16669
  const errPath = fe.path ?? "";
16200
- const isSource = src !== void 0 && (errPath === "" || path28.resolve(errPath) === path28.resolve(src));
16670
+ const isSource = src !== void 0 && (errPath === "" || path29.resolve(errPath) === path29.resolve(src));
16201
16671
  if (isSource) throw new CliError(`source is a directory, not a file: ${src}`);
16202
16672
  throw new CliError(`destination is a directory, not a file: ${dest ?? (errPath || "(unknown)")}`);
16203
16673
  }
@@ -16266,7 +16736,7 @@ function validateWritablePath(dest, label) {
16266
16736
  throw new CliError(`${label} path contains a null byte`);
16267
16737
  }
16268
16738
  if (isWindows()) {
16269
- const base = path28.basename(dest);
16739
+ const base = path29.basename(dest);
16270
16740
  const stem = base.replace(/\.[^.]*$/, "").toUpperCase();
16271
16741
  if (WIN_RESERVED.has(stem)) {
16272
16742
  throw new CliError(`${label} '${base}' is a reserved Windows device name`);
@@ -16407,7 +16877,7 @@ function cmdWriteFile(dest, opts) {
16407
16877
  throw new CliError(`TOKEN_GOAT_MAX_STDIN_MB must be a positive integer; got '${process.env["TOKEN_GOAT_MAX_STDIN_MB"] ?? ""}'`);
16408
16878
  }
16409
16879
  const maxBytes = maxMB * 1024 * 1024;
16410
- return new Promise((resolve13, reject) => {
16880
+ return new Promise((resolve14, reject) => {
16411
16881
  const chunks = [];
16412
16882
  let totalBytes = 0;
16413
16883
  let settled = false;
@@ -16431,7 +16901,7 @@ function cmdWriteFile(dest, opts) {
16431
16901
  try {
16432
16902
  atomicWriteBuffer(dest, Buffer.concat(chunks));
16433
16903
  enqueueDirtyPathSafe(dest);
16434
- resolve13();
16904
+ resolve14();
16435
16905
  } catch (e) {
16436
16906
  try {
16437
16907
  mapFsError(e, void 0, dest);
@@ -16783,7 +17253,7 @@ ${content}`;
16783
17253
  }
16784
17254
  const toEmit = fenceUntrusted(emitted, UNTRUSTED_WEB_TAG);
16785
17255
  out(toEmit);
16786
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(toEmit, "utf8"));
17256
+ const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(toEmit, "utf8"));
16787
17257
  recordStat("gdrive_sections", bytesSaved, savedTokensFromBytes(bytesSaved));
16788
17258
  }
16789
17259
  function expandGlobs(root, patterns, globFnOverride) {
@@ -16793,17 +17263,17 @@ function expandGlobs(root, patterns, globFnOverride) {
16793
17263
  if (globFn !== void 0 && (p.includes("*") || p.includes("?") || p.includes("{"))) {
16794
17264
  try {
16795
17265
  const hits = globFn(p, { cwd: root });
16796
- for (const h of hits) out2.push(path28.isAbsolute(h) ? h : path28.join(root, h));
17266
+ for (const h of hits) out2.push(path29.isAbsolute(h) ? h : path29.join(root, h));
16797
17267
  continue;
16798
17268
  } catch {
16799
17269
  }
16800
17270
  }
16801
- out2.push(path28.isAbsolute(p) ? p : path28.join(root, p));
17271
+ out2.push(path29.isAbsolute(p) ? p : path29.join(root, p));
16802
17272
  }
16803
17273
  return out2;
16804
17274
  }
16805
17275
  function readIgnoreFile(root) {
16806
- const ignorePath = path28.join(root, ".tokengoatignore");
17276
+ const ignorePath = path29.join(root, ".tokengoatignore");
16807
17277
  let raw;
16808
17278
  try {
16809
17279
  raw = fs28.readFileSync(ignorePath, "utf8");
@@ -16874,7 +17344,7 @@ function cmdTokens(patterns, opts) {
16874
17344
  if (opts.tree === true) {
16875
17345
  const dirs = /* @__PURE__ */ new Map();
16876
17346
  for (const e of entries) {
16877
- const dir = path28.dirname(e.rel_path);
17347
+ const dir = path29.dirname(e.rel_path);
16878
17348
  if (!dirs.has(dir)) dirs.set(dir, []);
16879
17349
  dirs.get(dir).push(e);
16880
17350
  }
@@ -16884,7 +17354,7 @@ function cmdTokens(patterns, opts) {
16884
17354
  const pct2 = result.total_tokens > 0 ? Math.round(dirTokens / result.total_tokens * 100) : 0;
16885
17355
  lines2.push(`${dir}/ (${dirTokens} tokens, ${pct2}%)`);
16886
17356
  for (const e of dirEntries) {
16887
- lines2.push(` ${path28.basename(e.rel_path).padEnd(30)} ${String(e.tokens).padStart(8)} tokens`);
17357
+ lines2.push(` ${path29.basename(e.rel_path).padEnd(30)} ${String(e.tokens).padStart(8)} tokens`);
16888
17358
  }
16889
17359
  }
16890
17360
  out(lines2.join("\n"));
@@ -16935,6 +17405,64 @@ function cmdFailures(src, opts) {
16935
17405
  saveFailureSnapshot(project.hash, key, { signatures, runner: result.runner, storedAt: Date.now() });
16936
17406
  out(opts.json === true ? formatFailureDeltaJson(delta, result.runner) : formatFailureDeltaText(delta, result.runner));
16937
17407
  }
17408
+ var originalHelpInformation = null;
17409
+ function generateCompactHelp() {
17410
+ const lines = [
17411
+ "Usage: token-goat [options] [command]",
17412
+ "",
17413
+ "Surgical token-reduction companion for AI coding agents",
17414
+ "",
17415
+ "Options:",
17416
+ " -v, --version print the token-goat version",
17417
+ " --cwd <path> run as if invoked from this directory",
17418
+ " -h, --help display help for command",
17419
+ "",
17420
+ "Reads: symbol, read, brief, section, semantic, skeleton, outline, refs, scope,",
17421
+ " similar, context-for, call-chain, impact, callers, dead, test-for",
17422
+ "",
17423
+ "Analysis: arch, affected, blame, deps, types, coverage-gaps, changed, diff, log,",
17424
+ " exports, imports, find, grep",
17425
+ "",
17426
+ "File Formats: pdf-meta, pdf-outline, pdf-extract, pdf-locate, xlsx-sheets,",
17427
+ " xlsx-head, xlsx-query, xlsx-range, yaml-outline, yaml-query, json-outline,",
17428
+ " json-query, xml-outline, xml-query, docx-outline, docx-text, pptx-outline,",
17429
+ " pptx-slide, pptx-text, pptx-notes",
17430
+ "",
17431
+ "Index & Search: index, map, reconcile, doctor, commands, ask, pack, tokens,",
17432
+ " budget, failures, todo, trace, logfold, lockdeps, dep-docs, recent, hot,",
17433
+ " snapshot-snapshot, baseline, cost, coverage-report-gaps, clean-cache,",
17434
+ " cache-audit, reclaim-index, project",
17435
+ "",
17436
+ "Session: stats, waste, session-outline, session-audit, session-slice,",
17437
+ " session-summary, context-stats, bootstrap-audit, memory, hint-stats,",
17438
+ " mcp-audit",
17439
+ "",
17440
+ "Skills: skill-body, skill-compact, skill-list, skill-size, skill-history,",
17441
+ " skill-diff, skill-section",
17442
+ "",
17443
+ "Compression: compress-text, retrieve, handoff-create, handoff-resolve,",
17444
+ " compress, recall, bash-output, web-output, mcp-output, compress-text,",
17445
+ " compress",
17446
+ "",
17447
+ "Config & Integration: install, uninstall, mcp-serve, mcp-status, hook,",
17448
+ " bridges-status, worker, statusline, version, config, config-get,",
17449
+ " capabilities, help",
17450
+ "",
17451
+ "Data: note, note-add, note-get, note-list, sqlite-query, sqlite-schema,",
17452
+ " csv-profile, csv-query",
17453
+ "",
17454
+ "Utils: ignores, bash-history, web-history, openapi-op, openapi-outline,",
17455
+ " gdrive-sections, screenshot, opencode-*, fetch-image, image-meta,",
17456
+ " image-text, start, stop, resume, replace, insert-section, pr-slice,",
17457
+ " sharepoint-resolve, transcript, transcript-outline, video-chapters,",
17458
+ " write-file, zip-list, zip-read, mcp-history, compact-doc, compact-hint,",
17459
+ " conflicts, prune-cache",
17460
+ "",
17461
+ "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).",
17462
+ ""
17463
+ ];
17464
+ return lines.join("\n");
17465
+ }
16938
17466
  function buildProgram() {
16939
17467
  const program2 = new Command();
16940
17468
  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 +17641,21 @@ function buildProgram() {
17113
17641
  worker.command("stop").description("stop the background indexer").action(guard(cmdWorkerStop));
17114
17642
  worker.command("status").description("check if the indexer is running").action(guard(cmdWorkerStatus));
17115
17643
  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));
17644
+ 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(
17645
+ (opts) => runExit(
17646
+ () => runReconcile({
17647
+ ...opts.dryRun === true ? { dryRun: true } : {},
17648
+ ...opts.budgetMs !== void 0 ? { budgetMs: requireNonNegativeInt("--budget-ms", opts.budgetMs) } : {},
17649
+ ...opts.json === true ? { json: true } : {}
17650
+ })
17651
+ )
17652
+ );
17653
+ program2.command("capabilities").description("report every capability that can send data off this machine, and its current state").option("-j, --json", "emit as JSON, for asserting on in a pipeline").action(
17654
+ guard((opts) => {
17655
+ const caps = collectCapabilities();
17656
+ console.log(opts.json ? JSON.stringify({ version: VERSION, capabilities: caps }, null, 2) : renderCapabilities(caps));
17657
+ })
17658
+ );
17116
17659
  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
17660
  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
17661
  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 +17806,22 @@ function buildProgram() {
17263
17806
  })
17264
17807
  )
17265
17808
  );
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(
17809
+ 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
17810
  (opts) => runExit(
17268
17811
  () => runArch({
17269
17812
  ...opts.top !== void 0 ? { top: requireNonNegativeInt("--top", opts.top) } : {},
17813
+ ...opts.json === true ? { json: true } : {},
17814
+ ...opts.modules === true ? { modules: true } : {}
17815
+ })
17816
+ )
17817
+ );
17818
+ 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(
17819
+ (files, opts) => runExit(
17820
+ () => runAffected({
17821
+ files: opts.stdin === true ? [...files, ...readStdinPaths()] : files,
17822
+ ...opts.depth !== void 0 ? { depth: requireNonNegativeInt("--depth", opts.depth) } : {},
17823
+ ...opts.filter !== void 0 ? { filter: opts.filter } : {},
17824
+ ...opts.quiet === true ? { quiet: true } : {},
17270
17825
  ...opts.json === true ? { json: true } : {}
17271
17826
  })
17272
17827
  )
@@ -17491,8 +18046,24 @@ function buildProgram() {
17491
18046
  out(VERSION);
17492
18047
  })
17493
18048
  );
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);
18049
+ program2.command("help [command]").description("show help for a command").option("--full", "show full original help instead of compact summary").action(
18050
+ guard((cmd, opts) => {
18051
+ const options = opts;
18052
+ if (options?.["full"] && !cmd) {
18053
+ out(originalHelpInformation ? originalHelpInformation() : generateCompactHelp());
18054
+ return;
18055
+ }
18056
+ if (cmd) {
18057
+ const argv = [process.argv[0] ?? "node", process.argv[1] ?? "token-goat", cmd, "--help"];
18058
+ program2.parse(argv);
18059
+ } else {
18060
+ out(generateCompactHelp());
18061
+ }
18062
+ })
18063
+ );
18064
+ program2.helpOption("-h, --help", "display help for command");
18065
+ originalHelpInformation = program2.helpInformation.bind(program2);
18066
+ program2.helpInformation = () => generateCompactHelp();
17496
18067
  return program2;
17497
18068
  }
17498
18069
  function applyExitOverride(command) {