token-goat 2.9.18 → 2.9.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +2 -0
  2. package/dist/{token-goat-chunk-V3ZWBEHM.mjs → token-goat-chunk-2GTB5RBZ.mjs} +365 -26
  3. package/dist/{token-goat-chunk-ARNRFR6M.mjs → token-goat-chunk-3W5P673V.mjs} +1 -1
  4. package/dist/{token-goat-chunk-YQZE5NS6.mjs → token-goat-chunk-62VJ47WN.mjs} +6 -6
  5. package/dist/{token-goat-chunk-CTOYJ232.mjs → token-goat-chunk-7DGTVBWP.mjs} +45 -8
  6. package/dist/{token-goat-chunk-5MMJAJF3.mjs → token-goat-chunk-AVRD7SBO.mjs} +60 -8
  7. package/dist/{token-goat-chunk-7VLWNQDQ.mjs → token-goat-chunk-BTKDZW7F.mjs} +1 -1
  8. package/dist/{token-goat-chunk-LMFO66YD.mjs → token-goat-chunk-DA43OE7L.mjs} +1 -1
  9. package/dist/{token-goat-chunk-6LGVCCMY.mjs → token-goat-chunk-DA6TIDLI.mjs} +21 -16
  10. package/dist/{token-goat-chunk-UENEL6KX.mjs → token-goat-chunk-DHIXE6GV.mjs} +2 -2
  11. package/dist/{token-goat-chunk-W3E65QOW.mjs → token-goat-chunk-EP7TCWHM.mjs} +304 -46
  12. package/dist/{token-goat-chunk-HIZXQVNU.mjs → token-goat-chunk-EXLLLIC3.mjs} +4 -4
  13. package/dist/{token-goat-chunk-H3FONFBO.mjs → token-goat-chunk-F24NE3FO.mjs} +3 -3
  14. package/dist/{token-goat-chunk-LA3O4JGD.mjs → token-goat-chunk-FJZ5MZK7.mjs} +3 -3
  15. package/dist/{token-goat-chunk-BFEWOMA3.mjs → token-goat-chunk-FQYMZASI.mjs} +632 -352
  16. package/dist/{token-goat-chunk-OWSJJY5R.mjs → token-goat-chunk-FX5DHKGF.mjs} +5 -5
  17. package/dist/{token-goat-chunk-K4DG6EEF.mjs → token-goat-chunk-GYC34EGE.mjs} +7 -8
  18. package/dist/{token-goat-chunk-WFX2V3OF.mjs → token-goat-chunk-HPMMLV37.mjs} +358 -116
  19. package/dist/{token-goat-chunk-I67CPTDD.mjs → token-goat-chunk-IBTFGZBX.mjs} +19 -14
  20. package/dist/{token-goat-chunk-L7VCX33I.mjs → token-goat-chunk-JNIFSVWM.mjs} +36 -10
  21. package/dist/{token-goat-chunk-EPXNOKIV.mjs → token-goat-chunk-KWM6KBPA.mjs} +2 -2
  22. package/dist/{token-goat-chunk-BWCRC23L.mjs → token-goat-chunk-L2UOJBV4.mjs} +21 -20
  23. package/dist/{token-goat-chunk-52IMXJ2L.mjs → token-goat-chunk-LACH3WKS.mjs} +4 -4
  24. package/dist/token-goat-chunk-MU6I4IDI.mjs +337 -0
  25. package/dist/{token-goat-chunk-GRI6HWX3.mjs → token-goat-chunk-NJKNAC37.mjs} +1 -1
  26. package/dist/{token-goat-chunk-ERTXEKB6.mjs → token-goat-chunk-NMTKNYGF.mjs} +2 -0
  27. package/dist/{token-goat-chunk-34OUJ2IE.mjs → token-goat-chunk-NYFYIRG3.mjs} +12 -12
  28. package/dist/{token-goat-chunk-4EXFN2AW.mjs → token-goat-chunk-OZRSREKR.mjs} +2 -2
  29. package/dist/token-goat-chunk-P46RTOAQ.mjs +27 -0
  30. package/dist/{token-goat-chunk-QML7ITHJ.mjs → token-goat-chunk-PIAESKVU.mjs} +139 -23
  31. package/dist/{token-goat-chunk-HPOFR6QX.mjs → token-goat-chunk-S7RFIX4Y.mjs} +180 -50
  32. package/dist/{token-goat-chunk-CA6V36KW.mjs → token-goat-chunk-SOKAPOOC.mjs} +23 -4
  33. package/dist/token-goat-chunk-VJQBYSXG.mjs +20 -0
  34. package/dist/{token-goat-chunk-LTYWLKAG.mjs → token-goat-chunk-WKO5TVNA.mjs} +922 -611
  35. package/dist/{token-goat-chunk-QIDLJGXZ.mjs → token-goat-chunk-ZGQXRYKE.mjs} +29 -5
  36. package/dist/token-goat-hook.mjs +12 -12
  37. package/dist/token-goat.core.mjs +21 -20
  38. package/docs/cli.md +2 -1
  39. package/docs/security.md +1 -1
  40. package/package.json +1 -1
  41. package/dist/token-goat-chunk-2KTZ6J7T.mjs +0 -173
  42. package/dist/token-goat-chunk-RHHFEXKF.mjs +0 -93
@@ -1,5 +1,8 @@
1
1
  import { createRequire as __cjsRequire } from 'node:module';
2
2
  const require = __cjsRequire(import.meta.url);
3
+ import {
4
+ compareSemver
5
+ } from "./token-goat-chunk-GYC34EGE.mjs";
3
6
  import {
4
7
  ALL_SYMBOLS_IN_FILE_LIMIT,
5
8
  AMBIGUOUS_HEADING_LIMIT,
@@ -20,8 +23,10 @@ import {
20
23
  isAvailable as isAvailable2,
21
24
  load,
22
25
  loadError,
26
+ namedSpecsMergeable,
23
27
  queryJson,
24
28
  rankSimilarNames,
29
+ readSpecsMergeable,
25
30
  resolveBody,
26
31
  resolveHandoff,
27
32
  resolveSymbolMatch,
@@ -91,24 +96,31 @@ import {
91
96
  symbolNamesInFile,
92
97
  upsertNote,
93
98
  warnIfFilesStale
94
- } from "./token-goat-chunk-QML7ITHJ.mjs";
99
+ } from "./token-goat-chunk-PIAESKVU.mjs";
95
100
  import {
96
101
  DEFAULT_RECONCILE_BUDGET_MS,
97
102
  HOOK_EVENTS,
103
+ MCP_TOOL_PATTERN,
98
104
  WEB_OUTPUT_SUBDIR,
99
105
  buildCommandManifest,
106
+ buildWasteReport,
100
107
  checkSymbolBodySize,
101
108
  cmdDescribe,
102
109
  cmdSessionSchema,
110
+ extractResultText,
103
111
  filterCommandManifest,
112
+ findLatestTranscript,
104
113
  formatCommandManifest,
105
114
  getWebOutput,
106
115
  getWebOutputByUrlFromDisk,
107
116
  getWebOutputRaw,
117
+ projectTranscriptsDir,
108
118
  readStdinJson,
119
+ runRecallCommand,
109
120
  runReconcile,
121
+ safeStringify,
110
122
  storeWebOutput
111
- } from "./token-goat-chunk-V3ZWBEHM.mjs";
123
+ } from "./token-goat-chunk-2GTB5RBZ.mjs";
112
124
  import {
113
125
  AGENT_SALT_MARKER,
114
126
  CONTEXT_AUTOCOMPACT_TOKENS,
@@ -121,7 +133,6 @@ import {
121
133
  SKIP_DIRS,
122
134
  WORKER_HEARTBEAT_STALE_MS,
123
135
  WorkerAlreadyRunningError,
124
- accumulateResidentLine,
125
136
  applyIndexingPriority,
126
137
  auditClaudeMd,
127
138
  buildExtractiveCompact,
@@ -133,6 +144,7 @@ import {
133
144
  buildStats,
134
145
  bundledCliPath,
135
146
  capJsonRows,
147
+ claudeConfigDir,
136
148
  cmdReclaimIndex,
137
149
  compactDoc,
138
150
  compactPathFor,
@@ -143,7 +155,6 @@ import {
143
155
  copilotCliScriptPath,
144
156
  copilotCliUserRoot,
145
157
  copilotHooksFilePaths,
146
- createResidentContextStats,
147
158
  detectWalkMode,
148
159
  dirtyQueuePathFor,
149
160
  drainHeartbeatPathFor,
@@ -163,6 +174,7 @@ import {
163
174
  findMemoryMd,
164
175
  findOrphanedChunkPaths,
165
176
  findSystemTempFiles,
177
+ findTopIndexedProjects,
166
178
  formatAge,
167
179
  formatBytes,
168
180
  formatMemSuggestions,
@@ -189,9 +201,11 @@ import {
189
201
  isCompactFresh,
190
202
  isCopilotCliInstalled,
191
203
  isHintCategory,
204
+ isIgnoredIndexPath,
192
205
  isManagedServer,
193
206
  isPrivateIpv4Octets,
194
207
  isPrivateIpv6Groups,
208
+ isResidueServersJson,
195
209
  isSuppressionCategory,
196
210
  isVisualStudioInstalled,
197
211
  isWorkerRunning,
@@ -208,6 +222,7 @@ import {
208
222
  pruneOrphanedChunks,
209
223
  pruneSkillOutputs,
210
224
  pruneSystemTempFiles,
225
+ pruneUnembeddableChunks,
211
226
  readCompactBody,
212
227
  readCopilotHooksOwners,
213
228
  readCopilotMcpTools,
@@ -219,6 +234,7 @@ import {
219
234
  runDetachedWorkerDaemon,
220
235
  scanAndRecord,
221
236
  serversOf,
237
+ sessionsDir,
222
238
  setTokenGoatServer,
223
239
  shrinkImage,
224
240
  skillDescriptionLine,
@@ -227,7 +243,6 @@ import {
227
243
  stopWorker,
228
244
  storeCompact,
229
245
  storeOutput,
230
- summarizeResidentContext,
231
246
  syncVisualStudioProjectGuidance,
232
247
  toolMatcherFor,
233
248
  trimToBudget,
@@ -244,59 +259,59 @@ import {
244
259
  visualStudioUserMcpPath,
245
260
  walkProject,
246
261
  writeCompact
247
- } from "./token-goat-chunk-BFEWOMA3.mjs";
262
+ } from "./token-goat-chunk-FQYMZASI.mjs";
248
263
  import {
249
264
  BASH_OUTPUT_SUBDIR,
250
265
  CompressedOutput,
251
266
  FILTERS,
252
- RECALL_DEFAULT_LIMIT,
253
267
  TOOL_FILTERS,
254
268
  ToolFilter,
255
269
  clipLongMatchLine,
256
270
  combineStreams,
257
- commandHash,
258
271
  deliverCompressed,
259
272
  detectFromCommand,
260
273
  getBashOutput,
261
- isRecallCacheType,
262
- listRecentRecall,
263
- normalizeCommandForCacheKey,
264
- searchRecall
265
- } from "./token-goat-chunk-CTOYJ232.mjs";
274
+ isRecallCacheType
275
+ } from "./token-goat-chunk-7DGTVBWP.mjs";
266
276
  import {
267
277
  C,
268
278
  DEFAULT_MAX_AGE_MS,
269
279
  DEFAULT_MAX_COUNT,
280
+ HOOK_STATS_RETENTION_DAYS,
270
281
  RESET,
271
282
  _useRichStats,
272
283
  colorStdout,
273
284
  compileCustomPatterns,
274
285
  fg,
275
286
  formatLocalTimestamp,
287
+ getGlobalDb,
276
288
  listBlobs,
277
289
  loadBlob,
278
290
  pruneBlobs,
291
+ pruneStalePatternCoveredUnmappedTools,
279
292
  readUnmappedTools,
280
293
  recordStat,
281
294
  redactSecrets,
282
295
  renderShortStats,
283
296
  renderStats,
284
297
  savedTokensFromBytes,
298
+ statsHasDurationColumn,
299
+ statsHasHarnessColumn,
285
300
  stripAnsi,
286
301
  summarize
287
- } from "./token-goat-chunk-HPOFR6QX.mjs";
302
+ } from "./token-goat-chunk-S7RFIX4Y.mjs";
288
303
  import {
289
304
  docxOutline,
290
305
  docxTables,
291
306
  docxText,
292
307
  formatDocxTables
293
- } from "./token-goat-chunk-UENEL6KX.mjs";
308
+ } from "./token-goat-chunk-DHIXE6GV.mjs";
294
309
  import {
295
310
  pptxNotesText,
296
311
  pptxOutline,
297
312
  pptxSlideText,
298
313
  pptxTextGrep
299
- } from "./token-goat-chunk-ARNRFR6M.mjs";
314
+ } from "./token-goat-chunk-3W5P673V.mjs";
300
315
  import {
301
316
  formatXlsxColumns,
302
317
  formatXlsxRange,
@@ -305,13 +320,13 @@ import {
305
320
  querySheet,
306
321
  rangeSheet,
307
322
  xlsxColumns
308
- } from "./token-goat-chunk-LA3O4JGD.mjs";
323
+ } from "./token-goat-chunk-FJZ5MZK7.mjs";
309
324
  import {
310
325
  formatCsvTable,
311
326
  parseWhereSpecs
312
- } from "./token-goat-chunk-EPXNOKIV.mjs";
327
+ } from "./token-goat-chunk-KWM6KBPA.mjs";
313
328
  import {
314
- PARSER_FINGERPRINT,
329
+ deleteFileEmbeddings,
315
330
  embeddingBackendLoadError,
316
331
  embeddingsDepsAvailable,
317
332
  ensureEmbeddingProvenance,
@@ -329,14 +344,16 @@ import {
329
344
  loadRegexExtractors,
330
345
  missingTreeSitterGrammarPackages,
331
346
  modelFilesPresent,
347
+ parserFingerprintForLanguage,
332
348
  readSection,
333
349
  treeSitterCoreAvailable,
334
350
  treeSitterCoreLoadError
335
- } from "./token-goat-chunk-W3E65QOW.mjs";
351
+ } from "./token-goat-chunk-EP7TCWHM.mjs";
336
352
  import {
337
353
  getFileEntry,
354
+ getProjectFileEntries,
338
355
  querySymbols
339
- } from "./token-goat-chunk-I67CPTDD.mjs";
356
+ } from "./token-goat-chunk-IBTFGZBX.mjs";
340
357
  import {
341
358
  TREE_SITTER_LANGUAGES,
342
359
  detectLanguage,
@@ -347,7 +364,7 @@ import {
347
364
  matchGroovySlashy,
348
365
  nonTreeSitterLanguageCount,
349
366
  redactIfDotenv
350
- } from "./token-goat-chunk-L7VCX33I.mjs";
367
+ } from "./token-goat-chunk-JNIFSVWM.mjs";
351
368
  import {
352
369
  CONFIG_KEY_ENV_OVERRIDES,
353
370
  PROJECT_LOCKED_KEYS,
@@ -376,7 +393,7 @@ import {
376
393
  stringify,
377
394
  validateEnumField,
378
395
  validateNumericField
379
- } from "./token-goat-chunk-CA6V36KW.mjs";
396
+ } from "./token-goat-chunk-SOKAPOOC.mjs";
380
397
  import {
381
398
  ENV_KEYS,
382
399
  LOCK_WAIT_MS_HARDENED,
@@ -402,6 +419,7 @@ import {
402
419
  foldPath,
403
420
  globalDbPath,
404
421
  hookCommandFor,
422
+ hookExecPartsFor,
405
423
  installSingleFilePlugin,
406
424
  isTestFile,
407
425
  isUnderBlockedRoot,
@@ -414,6 +432,7 @@ import {
414
432
  recordCreatedConfig,
415
433
  redactUrlQuery,
416
434
  removeCreatedBackups,
435
+ removeEmptyDirInScope,
417
436
  removeFileInScope,
418
437
  requireNonNegativeStrictInt,
419
438
  requirePositiveStrictInt,
@@ -436,7 +455,7 @@ import {
436
455
  withRetryOnLock,
437
456
  writeIfDifferent,
438
457
  writeJsonSettings
439
- } from "./token-goat-chunk-QIDLJGXZ.mjs";
458
+ } from "./token-goat-chunk-ZGQXRYKE.mjs";
440
459
  import {
441
460
  UNTRUSTED_FILE_TAG,
442
461
  UNTRUSTED_TOOL_TAG,
@@ -450,7 +469,7 @@ import {
450
469
  normalizePath,
451
470
  resolveIndexPath,
452
471
  toDisplayPath
453
- } from "./token-goat-chunk-ERTXEKB6.mjs";
472
+ } from "./token-goat-chunk-NMTKNYGF.mjs";
454
473
  import {
455
474
  compileGuardedRegex
456
475
  } from "./token-goat-chunk-GMOUBOX4.mjs";
@@ -3584,9 +3603,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3584
3603
  * @param {string} [path]
3585
3604
  * @return {(string|null|Command)}
3586
3605
  */
3587
- executableDir(path44) {
3588
- if (path44 === void 0) return this._executableDir;
3589
- this._executableDir = path44;
3606
+ executableDir(path43) {
3607
+ if (path43 === void 0) return this._executableDir;
3608
+ this._executableDir = path43;
3590
3609
  return this;
3591
3610
  }
3592
3611
  /**
@@ -3886,9 +3905,8 @@ function attemptedCommandName(argv) {
3886
3905
  }
3887
3906
 
3888
3907
  // src/cli.ts
3889
- import * as fs47 from "fs";
3890
- import * as path43 from "path";
3891
- import { homedir as homedir20 } from "os";
3908
+ import * as fs46 from "fs";
3909
+ import * as path42 from "path";
3892
3910
 
3893
3911
  // src/walk_index.ts
3894
3912
  init_define_import_meta_env();
@@ -3941,8 +3959,8 @@ function collectWalkIndexFiles(root, opts = {}) {
3941
3959
 
3942
3960
  // src/install.ts
3943
3961
  init_define_import_meta_env();
3962
+ import { spawnSync } from "node:child_process";
3944
3963
  import * as fs3 from "node:fs";
3945
- import * as os2 from "node:os";
3946
3964
  import * as path3 from "node:path";
3947
3965
 
3948
3966
  // src/bridges/claudecode.ts
@@ -3974,15 +3992,18 @@ var SHIM_TRY_IN_PROCESS = `// Attempts the in-process hook call: import()s dist/
3974
3992
  // loaded) and calls its exported relayInProcess() directly, avoiding a second node
3975
3993
  // process spawn entirely. Returns undefined (triggering the spawnSync fallback below)
3976
3994
  // when entryPath is absent, the sibling file doesn't exist (an older install predating
3977
- // this file), or anything else goes wrong -- this must never throw.
3978
- async function tryInProcess(entryPath, eventName, input) {
3995
+ // this file), or anything else goes wrong -- this must never throw. harnessWaitMs, when
3996
+ // the caller has one (Claude Code's async-detach marker time -- see SHIM_ASYNC_DETACH),
3997
+ // is forwarded straight through: this runs in the same process as the caller, so its
3998
+ // performance.now() reading shares the same origin and needs no conversion.
3999
+ async function tryInProcess(entryPath, eventName, input, harnessWaitMs) {
3979
4000
  if (!entryPath) return undefined
3980
4001
  try {
3981
4002
  const hookLibPath = path.join(path.dirname(entryPath), 'token-goat-hook.mjs')
3982
4003
  if (!require('node:fs').existsSync(hookLibPath)) return undefined
3983
4004
  const mod = await import(pathToFileURL(hookLibPath).href)
3984
4005
  const payload = JSON.parse(input)
3985
- return await mod.relayInProcess(eventName, payload)
4006
+ return await mod.relayInProcess(eventName, payload, harnessWaitMs)
3986
4007
  } catch {
3987
4008
  return undefined
3988
4009
  }
@@ -3996,6 +4017,52 @@ var SHIM_SPAWN_LADDER = ` const res = entryPath
3996
4017
  maxBuffer: SHIM_MAX_BUFFER_BYTES,
3997
4018
  })`;
3998
4019
  var SHIM_MAX_BUFFER_CONST = `const SHIM_MAX_BUFFER_BYTES = 32 * 1024 * 1024`;
4020
+ var SHIM_ASYNC_DETACH = `const ASYNC_DETACH_TOOLS = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit'])
4021
+ const ASYNC_DETACH_SKIP_EXT_RE = /\\.(md|mdx|markdown|rst)$/i
4022
+ const ASYNC_DETACH_BASH_MAX_BYTES = 200
4023
+ // Same key order as OUTPUT_FIRST_TOOL_RESPONSE_KEYS in hooks_common.ts.
4024
+ const ASYNC_DETACH_BASH_TEXT_KEYS = ['output', 'content', 'text', 'body', 'stdout', 'stderr']
4025
+ function bashResultByteLength(resp) {
4026
+ if (typeof resp === 'string') return Buffer.byteLength(resp, 'utf8')
4027
+ if (resp && typeof resp === 'object') {
4028
+ if (typeof resp['persistedOutputSize'] === 'number') return resp['persistedOutputSize']
4029
+ for (const key of ASYNC_DETACH_BASH_TEXT_KEYS) {
4030
+ if (typeof resp[key] === 'string' && resp[key] !== '') return Buffer.byteLength(resp[key], 'utf8')
4031
+ }
4032
+ return Buffer.byteLength(JSON.stringify(resp), 'utf8')
4033
+ }
4034
+ return 0
4035
+ }
4036
+ function isAsyncDetachEligible(eventName, input) {
4037
+ if (eventName === 'subagent_stop') return true
4038
+ if (eventName !== 'post_tool_use') return false
4039
+ try {
4040
+ const payload = JSON.parse(input)
4041
+ const toolName = payload['tool_name']
4042
+ if (ASYNC_DETACH_TOOLS.has(toolName)) {
4043
+ const toolInput = payload['tool_input'] || {}
4044
+ const filePath =
4045
+ typeof toolInput['file_path'] === 'string'
4046
+ ? toolInput['file_path']
4047
+ : typeof toolInput['notebook_path'] === 'string'
4048
+ ? toolInput['notebook_path']
4049
+ : ''
4050
+ return !ASYNC_DETACH_SKIP_EXT_RE.test(filePath)
4051
+ }
4052
+ if (toolName === 'Bash') return bashResultByteLength(payload['tool_response']) < ASYNC_DETACH_BASH_MAX_BYTES
4053
+ return false
4054
+ } catch {
4055
+ return false
4056
+ }
4057
+ }`;
4058
+ var SHIM_OWN_COMMAND_BYPASS = `const TG_CD_PREFIX_RE = /^(?:cd\\s+(?:"[^"]*"|'[^']*'|\\S+)[ \\t]*(?:&&|;|\\r?\\n)\\s*)+/
4059
+ const TG_OWN_COMMAND_RE = /^(?:[A-Za-z_][A-Za-z0-9_]*=\\S+\\s+)*(?:token-goat\\b|node(?:\\.exe)?\\s+["']?(?:\\S*[\\\\/])?token-goat(?:\\.mjs)?["']?\\b)/i
4060
+ function isOwnTokenGoatCommand(cmd) {
4061
+ if (typeof cmd !== 'string' || cmd.trim() === '') return false
4062
+ const body = (cmd.replace(TG_CD_PREFIX_RE, '') || cmd).trim()
4063
+ if (/[;&|\`]|\\$\\(/.test(body)) return false
4064
+ return TG_OWN_COMMAND_RE.test(body)
4065
+ }`;
3999
4066
 
4000
4067
  // src/bridges/claudecode.ts
4001
4068
  var CLAUDECODE_HOOK_SCRIPT = `#!/usr/bin/env node
@@ -4025,6 +4092,10 @@ ${SHIM_TRY_IN_PROCESS}
4025
4092
 
4026
4093
  ${SHIM_MAX_BUFFER_CONST}
4027
4094
 
4095
+ ${SHIM_ASYNC_DETACH}
4096
+
4097
+ ${SHIM_OWN_COMMAND_BYPASS}
4098
+
4028
4099
  async function main() {
4029
4100
  const eventName = process.argv[2] || ''
4030
4101
  if (!VALID_HOOK_EVENTS.has(eventName)) {
@@ -4038,6 +4109,32 @@ async function main() {
4038
4109
  process.stdout.write('{}')
4039
4110
  return
4040
4111
  }
4112
+ // A pre_tool_use Bash call that is token-goat's own CLI has nothing for the pipeline below to
4113
+ // say, so this skips the round trip entirely rather than backgrounding it -- see
4114
+ // isOwnTokenGoatCommand's docstring in shim_common.ts for the measured emission rate this
4115
+ // forfeits.
4116
+ if (eventName === 'pre_tool_use') {
4117
+ try {
4118
+ const payload = JSON.parse(input)
4119
+ if (payload['tool_name'] === 'Bash' && isOwnTokenGoatCommand(payload['tool_input'] && payload['tool_input']['command'])) {
4120
+ process.stdout.write('{}')
4121
+ return
4122
+ }
4123
+ } catch {
4124
+ // Fall through to normal handling on unparseable stdin.
4125
+ }
4126
+ }
4127
+ // Print this before the in-process/spawn round trip below, not after: the harness detaches on
4128
+ // the first stdout line it sees, and the whole point is not waiting out that round trip for a
4129
+ // call that was always going to answer '{}'. asyncDetachAtMs, taken at this exact print, is the
4130
+ // last instant this process's elapsed time and the harness's own wait are the same number --
4131
+ // record it now so the finally block in relayInProcess (relay.ts) can report what Claude Code
4132
+ // actually waited on instead of this call's full lifetime.
4133
+ let asyncDetachAtMs
4134
+ if (isAsyncDetachEligible(eventName, input)) {
4135
+ asyncDetachAtMs = performance.now()
4136
+ process.stdout.write('{"async":true}\\n')
4137
+ }
4041
4138
  // process.argv[3], when present, is the absolute path to the running token-goat CLI
4042
4139
  // entry (mirrors the same argv[3]-baked-entry convention used by the Codex/Copilot
4043
4140
  // CLI shims). Try the in-process hook lib first (tryInProcess above), which avoids
@@ -4048,8 +4145,15 @@ async function main() {
4048
4145
  // timeout/killSignal on both spawnSync fallbacks means token-goat degrades to its own
4049
4146
  // fail-open '{}' rather than being force-killed by Claude Code's own hook timeout.
4050
4147
  const entryPath = process.argv[3]
4051
- let stdout = await tryInProcess(entryPath, eventName, input)
4148
+ let stdout = await tryInProcess(entryPath, eventName, input, asyncDetachAtMs)
4052
4149
  if (stdout === undefined) {
4150
+ // Reaching here with asyncDetachAtMs set means tryInProcess failed after
4151
+ // isAsyncDetachEligible had already parsed this same input successfully, so the cause is an
4152
+ // install missing its dist/token-goat-hook.mjs sibling or a broken import -- never a bad
4153
+ // payload. asyncDetachAtMs is this process's own elapsed time and has no meaning in the
4154
+ // spawned child's clock, so it is deliberately not threaded across that boundary; the child
4155
+ // falls back to timing its own lifetime, the same already-approximate number every hook got
4156
+ // on this fallback path before async-detach existed.
4053
4157
  ${SHIM_SPAWN_LADDER}
4054
4158
  : spawnSync('token-goat hook ' + eventName, {
4055
4159
  input,
@@ -4091,18 +4195,56 @@ function anchoredMarkerPattern(marker) {
4091
4195
  return new RegExp(`(?<![a-zA-Z0-9_-])${escaped}(?![a-zA-Z0-9_-])`);
4092
4196
  }
4093
4197
  var HOOK_MARKER_PATTERNS = [SHIM_COMMAND_MARKER, COMMAND_MARKER, ...LEGACY_COMMAND_MARKERS].map(anchoredMarkerPattern);
4094
- function isTokenGoatHookCommand(command) {
4095
- return HOOK_MARKER_PATTERNS.some((pattern) => pattern.test(command));
4198
+ function isTokenGoatHookCommand(command, args) {
4199
+ const haystack = args !== void 0 && args.length > 0 ? `${command} ${args.join(" ")}` : command;
4200
+ return HOOK_MARKER_PATTERNS.some((pattern) => pattern.test(haystack));
4201
+ }
4202
+ var CLAUDE_EXEC_FORM_MIN_VERSION = "2.1.139";
4203
+ var _claudeExecFormHooksSupported;
4204
+ function claudeExecFormHooksSupported() {
4205
+ const override = process.env["TOKEN_GOAT_CLAUDE_EXEC_FORM_HOOKS"];
4206
+ if (override === "1") return true;
4207
+ if (override === "0") return false;
4208
+ if (_claudeExecFormHooksSupported !== void 0) return _claudeExecFormHooksSupported;
4209
+ _claudeExecFormHooksSupported = probeClaudeExecFormHooksSupported();
4210
+ return _claudeExecFormHooksSupported;
4211
+ }
4212
+ function probeClaudeExecFormHooksSupported() {
4213
+ const resolved = resolveOnPath("claude");
4214
+ if (resolved === null) return false;
4215
+ try {
4216
+ const isBatch = /\.(?:cmd|bat)$/i.test(resolved);
4217
+ const comspec = path3.join(process.env["SystemRoot"] ?? process.env["windir"] ?? "C:\\Windows", "System32", "cmd.exe");
4218
+ const result = isBatch ? spawnSync(fs3.existsSync(comspec) ? comspec : "cmd.exe", ["/d", "/s", "/c", resolved, "--version"], { encoding: "utf-8", timeout: 5e3, windowsHide: true }) : spawnSync(resolved, ["--version"], { encoding: "utf-8", timeout: 5e3, windowsHide: true });
4219
+ if (result.status !== 0) return false;
4220
+ return execFormSupportedForVersionOutput(result.stdout ?? "");
4221
+ } catch {
4222
+ return false;
4223
+ }
4224
+ }
4225
+ function execFormSupportedForVersionOutput(stdout) {
4226
+ const m = /(\d+\.\d+\.\d+)/.exec(stdout);
4227
+ if (!m) return false;
4228
+ return compareSemver(m[1], CLAUDE_EXEC_FORM_MIN_VERSION) >= 0;
4229
+ }
4230
+ function expectedHookEntryFor(scriptPath, event) {
4231
+ return claudeExecFormHooksSupported() ? hookExecPartsFor(scriptPath, event) : { command: hookCommandFor(scriptPath, event) };
4232
+ }
4233
+ function hookEntryMatches(command, args, expected) {
4234
+ if (command !== expected.command) return false;
4235
+ const a = args ?? [];
4236
+ const b = expected.args ?? [];
4237
+ return a.length === b.length && a.every((v, i) => v === b[i]);
4096
4238
  }
4097
4239
  function claudeHookScriptPath() {
4098
- return path3.join(os2.homedir(), ".claude", "hooks", "token-goat-shim.js");
4240
+ return path3.join(claudeConfigDir(), "hooks", "token-goat-shim.js");
4099
4241
  }
4100
4242
  function anyScopeReferencesShim(scriptPath, currentScope, alreadyStripped) {
4101
4243
  const referencesIn = (map) => {
4102
4244
  for (const groups of Object.values(map ?? {})) {
4103
4245
  for (const group of groups) {
4104
4246
  for (const h of group.hooks ?? []) {
4105
- if (h.command.includes(scriptPath)) return true;
4247
+ if (h.command.includes(scriptPath) || (h.args ?? []).some((a) => a.includes(scriptPath))) return true;
4106
4248
  }
4107
4249
  }
4108
4250
  }
@@ -4116,8 +4258,7 @@ function anyScopeReferencesShim(scriptPath, currentScope, alreadyStripped) {
4116
4258
  return false;
4117
4259
  }
4118
4260
  function settingsPath(scope) {
4119
- const root = scope === "user" ? os2.homedir() : normalizeDarwinSystemAlias(process.cwd());
4120
- const base = path3.join(root, ".claude");
4261
+ const base = scope === "user" ? claudeConfigDir() : path3.join(normalizeDarwinSystemAlias(process.cwd()), ".claude");
4121
4262
  return path3.join(base, "settings.json");
4122
4263
  }
4123
4264
  var SettingsParseError = class extends Error {
@@ -4154,7 +4295,7 @@ function groupHasTokenGoat(groups, predicate) {
4154
4295
  if (groups === void 0) return false;
4155
4296
  for (const group of groups) {
4156
4297
  for (const h of group.hooks ?? []) {
4157
- if (predicate(h.command)) return true;
4298
+ if (predicate(h.command, h.args)) return true;
4158
4299
  }
4159
4300
  }
4160
4301
  return false;
@@ -4176,13 +4317,13 @@ function installHooksScoped(scope) {
4176
4317
  });
4177
4318
  let settingsChanged = false;
4178
4319
  for (const [eventKey, eventArg] of HOOK_EVENT_MAP) {
4179
- const expectedCommand = hookCommandFor(scriptPath, eventArg);
4320
+ const expected = expectedHookEntryFor(scriptPath, eventArg);
4180
4321
  const existingGroups = hooks[eventKey] ?? [];
4181
4322
  const groups = [];
4182
4323
  let strippedStale = false;
4183
4324
  for (const group of existingGroups) {
4184
4325
  const keptHooks = (group.hooks ?? []).filter((h) => {
4185
- const isStale = isTokenGoatHookCommand(h.command) && h.command !== expectedCommand;
4326
+ const isStale = isTokenGoatHookCommand(h.command, h.args) && !hookEntryMatches(h.command, h.args, expected);
4186
4327
  if (isStale) strippedStale = true;
4187
4328
  return !isStale;
4188
4329
  });
@@ -4192,7 +4333,7 @@ function installHooksScoped(scope) {
4192
4333
  groups.push(group);
4193
4334
  }
4194
4335
  }
4195
- const isOurs = (command) => command === expectedCommand;
4336
+ const isOurs = (command, args) => hookEntryMatches(command, args, expected);
4196
4337
  if (groupHasTokenGoat(groups, isOurs)) {
4197
4338
  const narrowed = toolMatcherFor(eventArg);
4198
4339
  let renarrowed = false;
@@ -4201,7 +4342,7 @@ function installHooksScoped(scope) {
4201
4342
  const group = groups[i];
4202
4343
  if (group === void 0) continue;
4203
4344
  const ownHooks = group.hooks ?? [];
4204
- const isOwnGroup = ownHooks.length > 0 && ownHooks.every((h) => isOurs(h.command));
4345
+ const isOwnGroup = ownHooks.length > 0 && ownHooks.every((h) => isOurs(h.command, h.args));
4205
4346
  if (isOwnGroup && group.matcher !== narrowed) {
4206
4347
  groups[i] = { ...group, matcher: narrowed };
4207
4348
  renarrowed = true;
@@ -4215,7 +4356,7 @@ function installHooksScoped(scope) {
4215
4356
  continue;
4216
4357
  }
4217
4358
  const matcher = toolMatcherFor(eventArg) ?? "";
4218
- groups.push({ matcher, hooks: [{ type: "command", command: expectedCommand }] });
4359
+ groups.push({ matcher, hooks: [{ type: "command", command: expected.command, ...expected.args !== void 0 ? { args: expected.args } : {} }] });
4219
4360
  hooks[eventKey] = groups;
4220
4361
  settingsChanged = true;
4221
4362
  }
@@ -4259,15 +4400,15 @@ function isInstalled(scope = "user") {
4259
4400
  const scriptPath = claudeHookScriptPath();
4260
4401
  if (!fs3.existsSync(scriptPath)) return false;
4261
4402
  for (const [eventKey, eventArg] of HOOK_EVENT_MAP) {
4262
- const expectedCommand = hookCommandFor(scriptPath, eventArg);
4263
- if (!groupHasTokenGoat(hooks[eventKey], (c) => c === expectedCommand)) return false;
4403
+ const expected = expectedHookEntryFor(scriptPath, eventArg);
4404
+ if (!groupHasTokenGoat(hooks[eventKey], (c, a) => hookEntryMatches(c, a, expected))) return false;
4264
4405
  }
4265
4406
  return true;
4266
4407
  }
4267
4408
  var CLAUDE_MD_BEGIN = "<!-- token-goat-begin -->";
4268
4409
  var CLAUDE_MD_END = "<!-- token-goat-end -->";
4269
4410
  function claudeMdPath() {
4270
- return path3.join(os2.homedir(), ".claude", "CLAUDE.md");
4411
+ return path3.join(claudeConfigDir(), "CLAUDE.md");
4271
4412
  }
4272
4413
  function buildClaudeMdBlock() {
4273
4414
  return buildGuidanceBlock({
@@ -4292,7 +4433,7 @@ function uninstallClaudeMd() {
4292
4433
  return stripClaudeMdBlock(claudeMdPath());
4293
4434
  }
4294
4435
  function findStrayClaudeMdBlocks(searchRoot) {
4295
- const root = searchRoot ?? path3.join(os2.homedir(), ".claude");
4436
+ const root = searchRoot ?? claudeConfigDir();
4296
4437
  const canonical = path3.resolve(claudeMdPath());
4297
4438
  const found = [];
4298
4439
  if (!fs3.existsSync(root)) return found;
@@ -4354,7 +4495,7 @@ ${buildGuidanceBody("Claude Code's own Read, Grep, and Glob preference rules", {
4354
4495
  `;
4355
4496
  }
4356
4497
  function skillDir() {
4357
- return path3.join(os2.homedir(), ".claude", "skills", "token-goat");
4498
+ return path3.join(claudeConfigDir(), "skills", "token-goat");
4358
4499
  }
4359
4500
  function skillPath() {
4360
4501
  return path3.join(skillDir(), "SKILL.md");
@@ -4386,7 +4527,7 @@ function uninstallSkill() {
4386
4527
  init_define_import_meta_env();
4387
4528
  import * as crypto from "node:crypto";
4388
4529
  import * as fs4 from "node:fs";
4389
- import * as os3 from "node:os";
4530
+ import * as os2 from "node:os";
4390
4531
  import * as path4 from "node:path";
4391
4532
 
4392
4533
  // src/bridges/codex.ts
@@ -4541,13 +4682,13 @@ var CODEX_GLOBAL_EVENT_ARG = {
4541
4682
  var CodexConfigParseError = class extends Error {
4542
4683
  };
4543
4684
  function codexConfigPath() {
4544
- return path4.join(os3.homedir(), ".codex", "config.toml");
4685
+ return path4.join(os2.homedir(), ".codex", "config.toml");
4545
4686
  }
4546
4687
  function codexAgentsPath() {
4547
- return path4.join(os3.homedir(), ".codex", "AGENTS.md");
4688
+ return path4.join(os2.homedir(), ".codex", "AGENTS.md");
4548
4689
  }
4549
4690
  function codexHookScriptPath() {
4550
- return path4.join(os3.homedir(), ".codex", "hooks", "token-goat-shim.js");
4691
+ return path4.join(os2.homedir(), ".codex", "hooks", "token-goat-shim.js");
4551
4692
  }
4552
4693
  function readCodexConfig(p, opts = {}) {
4553
4694
  let raw;
@@ -4823,7 +4964,7 @@ function stripAgentsBlock(p) {
4823
4964
  // src/bridges/gemini_install.ts
4824
4965
  init_define_import_meta_env();
4825
4966
  import * as fs5 from "node:fs";
4826
- import * as os4 from "node:os";
4967
+ import * as os3 from "node:os";
4827
4968
  import * as path5 from "node:path";
4828
4969
  var GEMINI_LEGACY_COMMAND_MARKER = "token-goat hook";
4829
4970
  var GEMINI_HOOK_EVENTS = ["BeforeTool", "AfterTool", "PreCompress"];
@@ -4845,7 +4986,7 @@ function registeredInternalTools(eventName) {
4845
4986
  var GeminiSettingsParseError = class extends Error {
4846
4987
  };
4847
4988
  function geminiSettingsPath() {
4848
- return path5.join(os4.homedir(), ".gemini", "settings.json");
4989
+ return path5.join(os3.homedir(), ".gemini", "settings.json");
4849
4990
  }
4850
4991
  function readGeminiSettings(p, opts = {}) {
4851
4992
  let raw;
@@ -4976,7 +5117,7 @@ function isGeminiInstalled() {
4976
5117
  // src/bridges/qwen_install.ts
4977
5118
  init_define_import_meta_env();
4978
5119
  import * as fs6 from "node:fs";
4979
- import * as os5 from "node:os";
5120
+ import * as os4 from "node:os";
4980
5121
  import * as path6 from "node:path";
4981
5122
  var QWEN_HOOK_EVENTS = ["PreToolUse", "PostToolUse", "PreCompact", "UserPromptSubmit", "SubagentStop"];
4982
5123
  var QWEN_EVENT_ARG = {
@@ -4989,7 +5130,7 @@ var QWEN_EVENT_ARG = {
4989
5130
  var QwenSettingsParseError = class extends Error {
4990
5131
  };
4991
5132
  function qwenSettingsPath() {
4992
- return path6.join(os5.homedir(), ".qwen", "settings.json");
5133
+ return path6.join(os4.homedir(), ".qwen", "settings.json");
4993
5134
  }
4994
5135
  function readQwenSettings(p, opts = {}) {
4995
5136
  let raw;
@@ -5093,7 +5234,7 @@ function isQwenInstalled() {
5093
5234
  // src/bridges/kimi_install.ts
5094
5235
  init_define_import_meta_env();
5095
5236
  import * as fs7 from "node:fs";
5096
- import * as os6 from "node:os";
5237
+ import * as os5 from "node:os";
5097
5238
  import * as path7 from "node:path";
5098
5239
 
5099
5240
  // src/bridges/kimi.ts
@@ -5202,7 +5343,7 @@ var KimiConfigParseError = class extends Error {
5202
5343
  function kimiHome() {
5203
5344
  const override = process.env["KIMI_CODE_HOME"];
5204
5345
  if (override !== void 0 && override.trim() !== "") return path7.resolve(override);
5205
- return path7.join(os6.homedir(), ".kimi-code");
5346
+ return path7.join(os5.homedir(), ".kimi-code");
5206
5347
  }
5207
5348
  function kimiConfigPath() {
5208
5349
  return path7.join(kimiHome(), "config.toml");
@@ -5356,7 +5497,7 @@ function isKimiInstalled() {
5356
5497
  // src/bridges/pi_install.ts
5357
5498
  init_define_import_meta_env();
5358
5499
  import * as fs8 from "node:fs";
5359
- import * as os7 from "node:os";
5500
+ import * as os6 from "node:os";
5360
5501
  import * as path8 from "node:path";
5361
5502
 
5362
5503
  // src/bridges/pi.ts
@@ -5722,7 +5863,7 @@ export default function (pi: ExtensionAPI) {
5722
5863
 
5723
5864
  // src/bridges/pi_install.ts
5724
5865
  function piGlobalExtensionPath() {
5725
- return path8.join(os7.homedir(), ".pi", "agent", "extensions", "token-goat.ts");
5866
+ return path8.join(os6.homedir(), ".pi", "agent", "extensions", "token-goat.ts");
5726
5867
  }
5727
5868
  function piLocalExtensionPath() {
5728
5869
  return path8.join(process.cwd(), ".pi", "extensions", "token-goat.ts");
@@ -5758,7 +5899,7 @@ function isPiInstalled(opts = {}) {
5758
5899
  // src/bridges/opencode_install.ts
5759
5900
  init_define_import_meta_env();
5760
5901
  import * as fs9 from "node:fs";
5761
- import * as os8 from "node:os";
5902
+ import * as os7 from "node:os";
5762
5903
  import * as path9 from "node:path";
5763
5904
 
5764
5905
  // src/bridges/opencode.ts
@@ -6032,11 +6173,11 @@ function opencodeGlobalConfigDir() {
6032
6173
  if (process.platform === "win32") {
6033
6174
  const appData = process.env["APPDATA"];
6034
6175
  if (appData !== void 0 && appData.trim() !== "") return appData;
6035
- return path9.join(os8.homedir(), "AppData", "Roaming");
6176
+ return path9.join(os7.homedir(), "AppData", "Roaming");
6036
6177
  }
6037
6178
  const xdgConfigHome = process.env["XDG_CONFIG_HOME"];
6038
6179
  if (xdgConfigHome !== void 0 && xdgConfigHome.trim() !== "") return xdgConfigHome;
6039
- return path9.join(os8.homedir(), ".config");
6180
+ return path9.join(os7.homedir(), ".config");
6040
6181
  }
6041
6182
  function opencodePluginPath() {
6042
6183
  return path9.join(opencodeGlobalConfigDir(), "opencode", "plugins", "token-goat.ts");
@@ -6059,7 +6200,7 @@ function isOpencodeInstalled() {
6059
6200
  // src/bridges/openclaw_install.ts
6060
6201
  init_define_import_meta_env();
6061
6202
  import * as fs10 from "node:fs";
6062
- import * as os9 from "node:os";
6203
+ import * as os8 from "node:os";
6063
6204
  import * as path10 from "node:path";
6064
6205
 
6065
6206
  // src/bridges/openclaw.ts
@@ -6221,7 +6362,7 @@ var OpenclawConfigParseError = class extends Error {
6221
6362
  };
6222
6363
  var OPENCLAW_PLUGIN_ID = "token-goat";
6223
6364
  function openclawHomeDir() {
6224
- return path10.join(os9.homedir(), ".openclaw");
6365
+ return path10.join(os8.homedir(), ".openclaw");
6225
6366
  }
6226
6367
  function openclawConfigPath() {
6227
6368
  return path10.join(openclawHomeDir(), "openclaw.json");
@@ -6373,7 +6514,7 @@ function isOpenclawInstalled() {
6373
6514
  // src/bridges/grok_install.ts
6374
6515
  init_define_import_meta_env();
6375
6516
  import * as fs11 from "node:fs";
6376
- import * as os10 from "node:os";
6517
+ import * as os9 from "node:os";
6377
6518
  import * as path11 from "node:path";
6378
6519
 
6379
6520
  // src/bridges/grok.ts
@@ -6485,7 +6626,7 @@ var GROK_EVENT_ARG = {
6485
6626
  SubagentStop: "subagent_stop"
6486
6627
  };
6487
6628
  function grokHooksDir() {
6488
- return path11.join(os10.homedir(), ".grok", "hooks");
6629
+ return path11.join(os9.homedir(), ".grok", "hooks");
6489
6630
  }
6490
6631
  function grokConfigPath() {
6491
6632
  return path11.join(grokHooksDir(), "token-goat.json");
@@ -6535,7 +6676,7 @@ function isGrokInstalled() {
6535
6676
  // src/bridges/vscode_install.ts
6536
6677
  init_define_import_meta_env();
6537
6678
  import * as fs12 from "node:fs";
6538
- import * as os11 from "node:os";
6679
+ import * as os10 from "node:os";
6539
6680
  import * as path12 from "node:path";
6540
6681
  var VSCODE_GUIDANCE_BEGIN = "<!-- token-goat-vscode-begin -->";
6541
6682
  var VSCODE_GUIDANCE_END = "<!-- token-goat-vscode-end -->";
@@ -6544,13 +6685,13 @@ var END = VSCODE_GUIDANCE_END;
6544
6685
  function vscodeUserConfigDir() {
6545
6686
  if (process.platform === "win32") {
6546
6687
  const appData = process.env["APPDATA"];
6547
- const base = appData !== void 0 && appData.trim() !== "" ? appData : path12.join(os11.homedir(), "AppData", "Roaming");
6688
+ const base = appData !== void 0 && appData.trim() !== "" ? appData : path12.join(os10.homedir(), "AppData", "Roaming");
6548
6689
  return path12.join(base, "Code", "User");
6549
6690
  }
6550
6691
  if (process.platform === "darwin") {
6551
- return path12.join(os11.homedir(), "Library", "Application Support", "Code", "User");
6692
+ return path12.join(os10.homedir(), "Library", "Application Support", "Code", "User");
6552
6693
  }
6553
- return path12.join(os11.homedir(), ".config", "Code", "User");
6694
+ return path12.join(os10.homedir(), ".config", "Code", "User");
6554
6695
  }
6555
6696
  function vscodeUserMcpPath() {
6556
6697
  return path12.join(vscodeUserConfigDir(), "mcp.json");
@@ -6565,11 +6706,11 @@ function otherScopeMcpPath(opts) {
6565
6706
  return opts.project === true ? vscodeUserMcpPath() : vscodeProjectMcpPath(opts.projectRoot);
6566
6707
  }
6567
6708
  function vscodeInstructionsPath(opts = {}) {
6568
- return opts.project === true ? path12.join(path12.resolve(opts.projectRoot ?? process.cwd()), ".github", "copilot-instructions.md") : path12.join(os11.homedir(), ".copilot", "instructions", "token-goat.instructions.md");
6709
+ return opts.project === true ? path12.join(path12.resolve(opts.projectRoot ?? process.cwd()), ".github", "copilot-instructions.md") : path12.join(os10.homedir(), ".copilot", "instructions", "token-goat.instructions.md");
6569
6710
  }
6570
6711
  var USER_INSTRUCTIONS_FRONTMATTER = "---\ndescription: 'token-goat: when to use its MCP tools and CLI instead of reading whole files'\napplyTo: '**'\n---\n";
6571
6712
  function vscodeHooksDir(opts = {}) {
6572
- return opts.project === true ? path12.join(path12.resolve(opts.projectRoot ?? process.cwd()), ".github", "hooks") : path12.join(os11.homedir(), ".copilot", "hooks");
6713
+ return opts.project === true ? path12.join(path12.resolve(opts.projectRoot ?? process.cwd()), ".github", "hooks") : path12.join(os10.homedir(), ".copilot", "hooks");
6573
6714
  }
6574
6715
  function vscodeScopeFromFlags(flags) {
6575
6716
  return { project: flags.user !== true };
@@ -6715,8 +6856,12 @@ function uninstallVscodeScoped(opts) {
6715
6856
  }
6716
6857
  if (servers && isManagedServer(servers["token-goat"])) {
6717
6858
  const next = dropEmptyServers(updateConfig(config.text, void 0));
6718
- if (/^\s*\{\s*\}\s*$/.test(next) && takeCreatedConfig(mcpPath)) removeFileInScope(mcpPath);
6719
- else {
6859
+ if (/^\s*\{\s*\}\s*$/.test(next) && takeCreatedConfig(mcpPath)) {
6860
+ removeFileInScope(mcpPath);
6861
+ if (opts.project === true) {
6862
+ removeEmptyDirInScope(path12.dirname(mcpPath));
6863
+ }
6864
+ } else {
6720
6865
  backupFile(mcpPath);
6721
6866
  atomicWriteText(mcpPath, next);
6722
6867
  }
@@ -6733,16 +6878,31 @@ function uninstallVscodeScoped(opts) {
6733
6878
  if (releaseCopilotHooksFile(vscodeHooksDir(opts), "vscode", opts.keepBackups === true)) removed = true;
6734
6879
  return removed;
6735
6880
  }
6881
+ function cleanupDeprecatedVscodeProjectMcp(projectRoot) {
6882
+ const root = path12.resolve(projectRoot ?? process.cwd());
6883
+ return withInstallScope(root, () => {
6884
+ const projectMcp = path12.join(root, ".vscode", "mcp.json");
6885
+ if (!fs12.existsSync(projectMcp)) return false;
6886
+ const text = fs12.readFileSync(projectMcp, "utf8");
6887
+ if (isResidueServersJson(text) && takeCreatedConfig(projectMcp)) {
6888
+ removeFileInScope(projectMcp);
6889
+ removeEmptyDirInScope(path12.dirname(projectMcp));
6890
+ removeCreatedBackups(projectMcp);
6891
+ return true;
6892
+ }
6893
+ return false;
6894
+ });
6895
+ }
6736
6896
 
6737
6897
  // src/bridges/cursor_install.ts
6738
6898
  init_define_import_meta_env();
6739
6899
  import * as fs13 from "node:fs";
6740
- import * as os12 from "node:os";
6900
+ import * as os11 from "node:os";
6741
6901
  import * as path13 from "node:path";
6742
6902
  var MCP_SERVERS_KEY = "mcpServers";
6743
6903
  var TOKEN_GOAT_ENTRY_KEY = "token-goat";
6744
6904
  function cursorUserConfigDir() {
6745
- return path13.join(os12.homedir(), ".cursor");
6905
+ return path13.join(os11.homedir(), ".cursor");
6746
6906
  }
6747
6907
  function cursorMcpPath(opts = {}) {
6748
6908
  if (opts.project === true) {
@@ -6817,7 +6977,7 @@ function cursorManagedEntry(mcpPath) {
6817
6977
  // src/bridges/zed_install.ts
6818
6978
  init_define_import_meta_env();
6819
6979
  import * as fs14 from "node:fs";
6820
- import * as os13 from "node:os";
6980
+ import * as os12 from "node:os";
6821
6981
  import * as path14 from "node:path";
6822
6982
  var CONTEXT_SERVERS_KEY = "context_servers";
6823
6983
  var TOKEN_GOAT_ENTRY_KEY2 = "token-goat";
@@ -6825,11 +6985,11 @@ var ZED_TIMEOUT_MS = 3e4;
6825
6985
  function zedConfigDir() {
6826
6986
  if (process.platform === "win32") {
6827
6987
  const appData = process.env["APPDATA"];
6828
- const base2 = appData !== void 0 && appData.trim() !== "" ? appData : path14.join(os13.homedir(), "AppData", "Roaming");
6988
+ const base2 = appData !== void 0 && appData.trim() !== "" ? appData : path14.join(os12.homedir(), "AppData", "Roaming");
6829
6989
  return path14.join(base2, "Zed");
6830
6990
  }
6831
6991
  const xdgConfig = process.env["XDG_CONFIG_HOME"];
6832
- const base = xdgConfig !== void 0 && xdgConfig.trim() !== "" ? xdgConfig : path14.join(os13.homedir(), ".config");
6992
+ const base = xdgConfig !== void 0 && xdgConfig.trim() !== "" ? xdgConfig : path14.join(os12.homedir(), ".config");
6833
6993
  return path14.join(base, "zed");
6834
6994
  }
6835
6995
  function zedSettingsPath() {
@@ -6919,13 +7079,88 @@ function zedManagedEntry(settingsPath2) {
6919
7079
  init_define_import_meta_env();
6920
7080
  import * as fs18 from "fs";
6921
7081
  import * as path17 from "path";
6922
- import { spawnSync as spawnSync2 } from "child_process";
7082
+ import { spawnSync as spawnSync3 } from "child_process";
7083
+
7084
+ // src/hook_latency.ts
7085
+ init_define_import_meta_env();
7086
+ function percentile(sorted, p) {
7087
+ if (sorted.length === 0) return 0;
7088
+ const idx = Math.min(sorted.length - 1, Math.max(0, Math.ceil(p / 100 * sorted.length) - 1));
7089
+ return sorted[idx];
7090
+ }
7091
+ function formatAgeSeconds(seconds) {
7092
+ if (seconds < 60) return `${seconds}s ago`;
7093
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
7094
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
7095
+ return `${Math.floor(seconds / 86400)}d ago`;
7096
+ }
7097
+ function hookLatencyBreakdown(testDb, homeDir) {
7098
+ try {
7099
+ const db = testDb ?? getGlobalDb(homeDir);
7100
+ if (!statsHasDurationColumn(db)) return [];
7101
+ const harnessExpr = statsHasHarnessColumn(db) ? "COALESCE(harness, '')" : "''";
7102
+ const rows = db.prepare(
7103
+ `SELECT kind, ${harnessExpr} AS harness, duration_ms, ts FROM stats
7104
+ WHERE kind LIKE 'hook:%' AND duration_ms IS NOT NULL ORDER BY ts DESC`
7105
+ ).all();
7106
+ const grouped = /* @__PURE__ */ new Map();
7107
+ for (const row of rows) {
7108
+ const event = row.kind.slice("hook:".length);
7109
+ const key = `${event}::${row.harness}`;
7110
+ let g = grouped.get(key);
7111
+ if (g === void 0) {
7112
+ g = { event, harness: row.harness, durations: [], newest_ts: row.ts };
7113
+ grouped.set(key, g);
7114
+ }
7115
+ g.durations.push(row.duration_ms);
7116
+ if (row.ts > g.newest_ts) g.newest_ts = row.ts;
7117
+ }
7118
+ return [...grouped.values()].map((g) => {
7119
+ const sorted = [...g.durations].sort((a, b) => a - b);
7120
+ return {
7121
+ event: g.event,
7122
+ harness: g.harness,
7123
+ count: sorted.length,
7124
+ median_ms: percentile(sorted, 50),
7125
+ p95_ms: percentile(sorted, 95),
7126
+ max_ms: sorted[sorted.length - 1],
7127
+ newest_ts: g.newest_ts
7128
+ };
7129
+ }).sort((a, b) => b.p95_ms - a.p95_ms);
7130
+ } catch {
7131
+ return [];
7132
+ }
7133
+ }
7134
+ function renderHookLatencyStats(testDb, homeDir) {
7135
+ const rows = hookLatencyBreakdown(testDb, homeDir);
7136
+ if (rows.length === 0) {
7137
+ console.log(`No hook latency recorded in the last ${HOOK_STATS_RETENTION_DAYS} day(s) (or this database predates the duration_ms column).`);
7138
+ return;
7139
+ }
7140
+ const nowTs = Math.floor(Date.now() / 1e3);
7141
+ console.log(`Hook latency, last ${HOOK_STATS_RETENTION_DAYS} day(s) (worst p95 first):`);
7142
+ for (const r of rows) {
7143
+ const age = formatAgeSeconds(Math.max(0, nowTs - r.newest_ts));
7144
+ console.log(
7145
+ ` ${r.event.padEnd(22)} ${(r.harness || "unrecorded").padEnd(12)} n=${String(r.count).padEnd(5)} median ${String(r.median_ms).padStart(5)}ms p95 ${String(r.p95_ms).padStart(5)}ms max ${String(r.max_ms).padStart(6)}ms last seen ${age}`
7146
+ );
7147
+ }
7148
+ }
6923
7149
 
6924
7150
  // src/cli_doctor_process.ts
6925
7151
  init_define_import_meta_env();
6926
- import { spawnSync } from "node:child_process";
7152
+ import { spawnSync as spawnSync2 } from "node:child_process";
6927
7153
  import * as fs15 from "node:fs";
6928
7154
  import * as path15 from "node:path";
7155
+ var INTERPRETER_BASENAMES = /* @__PURE__ */ new Set(["node.exe", "python.exe", "python3.exe", "pwsh.exe", "powershell.exe", "cmd.exe"]);
7156
+ function describeProcess(commandLine) {
7157
+ for (const m of commandLine.matchAll(/[^\s"]+\.(?:js|mjs|cjs|ts|py|exe)\b/gi)) {
7158
+ const basename11 = m[0].replace(/\\/g, "/").split("/").pop() ?? m[0];
7159
+ if (!INTERPRETER_BASENAMES.has(basename11.toLowerCase())) return basename11;
7160
+ }
7161
+ const trimmed = commandLine.trim();
7162
+ return trimmed.length > 0 ? trimmed.length > 60 ? trimmed.slice(0, 60) + "\u2026" : trimmed : "(no command line)";
7163
+ }
6929
7164
  function checkMcpProcessHealth(processes) {
6930
7165
  if (processes === null) {
6931
7166
  return {
@@ -6947,13 +7182,13 @@ function checkMcpProcessHealth(processes) {
6947
7182
  if (chromeLaunchers.length > 1) details.push(`${chromeLaunchers.length} Chrome DevTools MCP launchers (PIDs: ${chromeLaunchers.map((p) => p.processId).join(", ")})`);
6948
7183
  if (playwrightLaunchers.length > 1) details.push(`${playwrightLaunchers.length} Playwright MCP launchers (PIDs: ${playwrightLaunchers.map((p) => p.processId).join(", ")})`);
6949
7184
  if (orphanedNodeProcesses.length > 0) {
6950
- const pids = orphanedNodeProcesses.map((p) => p.processId).join(", ");
6951
- details.push(`${orphanedNodeProcesses.length} orphaned Node process${orphanedNodeProcesses.length === 1 ? "" : "es"} (PIDs: ${pids})`);
7185
+ const described = orphanedNodeProcesses.map((p) => `${p.processId} (${describeProcess(p.commandLine)})`).join(", ");
7186
+ details.push(`${orphanedNodeProcesses.length} orphaned Node process${orphanedNodeProcesses.length === 1 ? "" : "es"}: ${described}`);
6952
7187
  }
6953
7188
  return {
6954
7189
  name: "MCP process health",
6955
7190
  status: "warn",
6956
- message: `${details.join("; ")} detected. These are host-managed processes left by closed terminal/CLI sessions; close stale sessions or terminate specific confirmed orphan PIDs with Stop-Process -Id <PID>.`
7191
+ message: `${details.join("; ")} detected. None of these are token-goat's own processes -- they are host-managed Node/MCP processes left by closed terminal or CLI sessions; confirm what each one is (the name shown above is a starting point) before stopping it with Stop-Process -Id <PID>.`
6957
7192
  };
6958
7193
  }
6959
7194
  return { name: "MCP process health", status: "ok", message: "no duplicate MCP launchers or orphaned Node processes detected" };
@@ -6962,7 +7197,7 @@ function runProcessListCommand() {
6962
7197
  const command = "Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,Name,CommandLine | ConvertTo-Json -Compress";
6963
7198
  const systemRoot = process.env["SystemRoot"] ?? process.env["windir"] ?? "C:\\Windows";
6964
7199
  const shell = path15.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
6965
- const result = spawnSync(fs15.existsSync(shell) ? shell : "powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", command], {
7200
+ const result = spawnSync2(fs15.existsSync(shell) ? shell : "powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", command], {
6966
7201
  encoding: "utf8",
6967
7202
  timeout: 2e4,
6968
7203
  maxBuffer: 10 * 1024 * 1024,
@@ -7000,11 +7235,11 @@ function checkWorkerRunning(dataDir2) {
7000
7235
  // src/cli_doctor_platforms.ts
7001
7236
  init_define_import_meta_env();
7002
7237
  import * as fs16 from "node:fs";
7003
- import * as os14 from "node:os";
7238
+ import * as os13 from "node:os";
7004
7239
  import * as path16 from "node:path";
7005
7240
  function globalMcpConfigPath() {
7006
7241
  const copilotHome = process.env["COPILOT_HOME"];
7007
- const root = copilotHome !== void 0 && copilotHome.trim() !== "" ? path16.resolve(copilotHome) : path16.join(os14.homedir(), ".copilot");
7242
+ const root = copilotHome !== void 0 && copilotHome.trim() !== "" ? path16.resolve(copilotHome) : path16.join(os13.homedir(), ".copilot");
7008
7243
  return path16.join(root, "mcp-config.json");
7009
7244
  }
7010
7245
  function checkGlobalMcpConfig(configPath2 = globalMcpConfigPath()) {
@@ -7161,6 +7396,28 @@ function checkStrayClaudeMdBlocks(searchRoot) {
7161
7396
  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(", ")}`
7162
7397
  };
7163
7398
  }
7399
+ function checkVscodeProjectMcp(projectRoot = process.cwd()) {
7400
+ const mcpPath = path16.join(path16.resolve(projectRoot), ".vscode", "mcp.json");
7401
+ if (!fs16.existsSync(mcpPath)) return null;
7402
+ if (hasManagedServer(mcpPath, "VS Code")) return null;
7403
+ try {
7404
+ const content = fs16.readFileSync(mcpPath, "utf8");
7405
+ if (isResidueServersJson(content)) {
7406
+ return {
7407
+ name: "VS Code project MCP",
7408
+ status: "warn",
7409
+ message: `empty residue file at ${displaySafeText(mcpPath)} triggers Copilot CLI deprecation warnings; delete it or run 'token-goat uninstall --vscode'`
7410
+ };
7411
+ }
7412
+ return {
7413
+ name: "VS Code project MCP",
7414
+ status: "warn",
7415
+ message: `${displaySafeText(mcpPath)} is deprecated by Copilot CLI v1.0.84+; migrate configured servers to .mcp.json or .github/mcp.json (https://gh.io/copilotcli-mcpmigrate)`
7416
+ };
7417
+ } catch {
7418
+ return null;
7419
+ }
7420
+ }
7164
7421
 
7165
7422
  // src/cli_doctor_security.ts
7166
7423
  init_define_import_meta_env();
@@ -7291,14 +7548,50 @@ function freelistBytes(header) {
7291
7548
  const rawPageSize = header.readUInt16BE(16);
7292
7549
  return (rawPageSize === 1 ? 65536 : rawPageSize) * header.readUInt32BE(36);
7293
7550
  }
7294
- function oversizeDbMessage(dbPath, sizeBytes, freeBytes, tempRows) {
7551
+ var CATEGORY_COLUMNS = [
7552
+ { name: "symbol bodies", table: "symbols", column: "body", command: "'token-goat reclaim-index --rebuild' drops and re-derives them under the current parser's size cap" },
7553
+ { name: "refs", table: "refs", column: "context", command: "'token-goat reclaim-index --rebuild' drops and re-derives them" },
7554
+ { name: "chunk text", table: "chunks", column: "text", command: "'token-goat reclaim-index --rebuild' drops and re-derives them" },
7555
+ { name: "stats detail", table: "stats", column: "detail", command: "ages out on its own (180-day retention) -- no manual command needed" }
7556
+ ];
7557
+ var VECTOR_BYTES_PER_ROW = 384 * 4;
7558
+ function dbCategoryBreakdown(dbPath) {
7559
+ const db = getDb(dbPath);
7560
+ const shares = [];
7561
+ for (const cat of CATEGORY_COLUMNS) {
7562
+ try {
7563
+ const row = db.prepare(`SELECT COALESCE(SUM(LENGTH(${cat.column})), 0) as bytes FROM ${cat.table}`).get();
7564
+ if (row.bytes > 0) shares.push({ name: cat.name, bytes: row.bytes, command: cat.command });
7565
+ } catch {
7566
+ }
7567
+ }
7568
+ try {
7569
+ const row = db.prepare("SELECT COUNT(*) as n FROM chunk_vectors").get();
7570
+ if (row.n > 0) {
7571
+ shares.push({
7572
+ name: "embedding vectors",
7573
+ bytes: row.n * VECTOR_BYTES_PER_ROW,
7574
+ command: "'token-goat reclaim-index --rebuild' drops them (set indexing.embeddings_enabled = false first so they don't regrow)"
7575
+ });
7576
+ }
7577
+ } catch {
7578
+ }
7579
+ return shares.sort((a, b) => b.bytes - a.bytes);
7580
+ }
7581
+ function oversizeDbMessage(dbPath, sizeBytes, freeBytes, tempRows, categories = [], topConsumers = []) {
7295
7582
  const mb = (bytes) => Math.round(bytes / (1024 * 1024));
7296
7583
  const advice = [];
7297
7584
  if (freeBytes >= sizeBytes / 10) advice.push(`'token-goat reclaim-index' returns the ${mb(freeBytes)} MB of it that is free pages`);
7298
7585
  if (tempRows > 0) advice.push(`'token-goat project prune' removes ${countNoun(tempRows, "scratch file")} indexed under the OS temp dir`);
7299
7586
  const head = `global.db is ${mb(sizeBytes)} MB at ${displaySafeText(dbPath)} (larger than recommended). `;
7300
- if (advice.length > 0) return `${head}${advice.join("; ")}.`;
7301
- return `${head}Only ${mb(freeBytes)} MB of it is free pages and none of it is temp-dir scratch, so it is live index data that neither 'reclaim-index' nor 'project prune' will shrink.`;
7587
+ const totalCategoryBytes = categories.reduce((sum, c) => sum + c.bytes, 0);
7588
+ const breakdown = totalCategoryBytes > 0 ? ` Where it went: ${categories.slice(0, 3).map((c) => `${c.name} ${mb(c.bytes)} MB (${Math.round(c.bytes / totalCategoryBytes * 100)}%) -- ${c.command}`).join("; ")}.` : "";
7589
+ let base = advice.length > 0 ? `${head}${advice.join("; ")}.${breakdown}` : `${head}Only ${mb(freeBytes)} MB of it is free pages and none of it is temp-dir scratch, so it is live index data that neither 'reclaim-index' nor 'project prune' will shrink.${breakdown}`;
7590
+ if (topConsumers.length > 0) {
7591
+ const list = topConsumers.map((c) => `${path17.basename(c.root) || c.root} (${countNoun(c.fileCount, "file")})`).join(", ");
7592
+ base += ` Top index consumers: ${list}.`;
7593
+ }
7594
+ return base;
7302
7595
  }
7303
7596
  function checkDbExists(dataDir2) {
7304
7597
  const dbPath = path17.join(dataDir2, "global.db");
@@ -7338,7 +7631,13 @@ function checkDbExists(dataDir2) {
7338
7631
  tempRows = findSystemTempFiles(dbPath).length;
7339
7632
  } catch {
7340
7633
  }
7341
- return { name: "Database", status: "warn", message: oversizeDbMessage(dbPath, sizeBytes, freelistBytes(headerBytes), tempRows) };
7634
+ let categories = [];
7635
+ try {
7636
+ categories = dbCategoryBreakdown(dbPath);
7637
+ } catch {
7638
+ }
7639
+ const topConsumers = findTopIndexedProjects(dbPath, 3);
7640
+ return { name: "Database", status: "warn", message: oversizeDbMessage(dbPath, sizeBytes, freelistBytes(headerBytes), tempRows, categories, topConsumers) };
7342
7641
  }
7343
7642
  return {
7344
7643
  name: "Database",
@@ -7421,7 +7720,7 @@ function checkParserFreshness(dbPath, rootDir) {
7421
7720
  return { name: "Parser freshness", status: "ok", message: "no database yet" };
7422
7721
  }
7423
7722
  try {
7424
- const { indexedFiles, currentFiles } = getParserFreshness(dbPath, PARSER_FINGERPRINT, rootDir);
7723
+ const { indexedFiles, currentFiles } = getParserFreshness(dbPath, parserFingerprintForLanguage, rootDir);
7425
7724
  if (indexedFiles === 0) {
7426
7725
  return { name: "Parser freshness", status: "ok", message: "no indexed files yet" };
7427
7726
  }
@@ -7484,7 +7783,7 @@ function checkInstall() {
7484
7783
  if (resolved !== null) {
7485
7784
  const isBatch = /\.(?:cmd|bat)$/i.test(resolved);
7486
7785
  const comspec = path17.join(process.env["SystemRoot"] ?? process.env["windir"] ?? "C:\\Windows", "System32", "cmd.exe");
7487
- const result = isBatch ? spawnSync2(fs18.existsSync(comspec) ? comspec : "cmd.exe", ["/d", "/s", "/c", resolved, "--version"], { encoding: "utf-8", timeout: 15e3, windowsHide: true }) : spawnSync2(resolved, ["--version"], { encoding: "utf-8", timeout: 15e3, windowsHide: true });
7786
+ const result = isBatch ? spawnSync3(fs18.existsSync(comspec) ? comspec : "cmd.exe", ["/d", "/s", "/c", resolved, "--version"], { encoding: "utf-8", timeout: 15e3, windowsHide: true }) : spawnSync3(resolved, ["--version"], { encoding: "utf-8", timeout: 15e3, windowsHide: true });
7488
7787
  if (result.status === 0) return { name: "Installation", status: "ok", message: (result.stdout ?? "").trim() };
7489
7788
  }
7490
7789
  return { name: "Installation", status: "fail", message: `token-goat command not found; run: npm install -g ${PACKAGE_NAME}` };
@@ -7633,7 +7932,7 @@ function checkDiskSpace(dataDir2) {
7633
7932
  }
7634
7933
  if (process.platform !== "win32") {
7635
7934
  try {
7636
- const result = spawnSync2("df", ["-Pk", dataDir2], { encoding: "utf-8" });
7935
+ const result = spawnSync3("df", ["-Pk", dataDir2], { encoding: "utf-8" });
7637
7936
  const stdout = typeof result.stdout === "string" ? result.stdout : "";
7638
7937
  if (result.error === void 0 && result.status === 0 && stdout) {
7639
7938
  const lines = stdout.trim().split("\n");
@@ -7689,7 +7988,7 @@ function checkCopilotCli(configPath2, scriptPath) {
7689
7988
  toolName: "view",
7690
7989
  toolArgs: { path: "doctor-check.txt" }
7691
7990
  });
7692
- const res = spawnSync2(preToolUseCommand, {
7991
+ const res = spawnSync3(preToolUseCommand, {
7693
7992
  input: synthetic,
7694
7993
  encoding: "utf-8",
7695
7994
  shell: true,
@@ -7724,6 +8023,7 @@ function checkCopilotCli(configPath2, scriptPath) {
7724
8023
  var COMPACTION_CHANNEL_WINDOW = 5;
7725
8024
  var COMPACTION_STATS_BATCH_SIZE = 50;
7726
8025
  var COMPACTION_STATS_SCAN_CEILING = 2e3;
8026
+ var COMPACTION_MANIFEST_FIX_VERSION = "2.9.18";
7727
8027
  function checkCompactionChannel(dbPath) {
7728
8028
  const name = "Compaction channel";
7729
8029
  if (!fs18.existsSync(dbPath)) {
@@ -7738,8 +8038,9 @@ function checkCompactionChannel(dbPath) {
7738
8038
  const conclusive = [];
7739
8039
  let offset = 0;
7740
8040
  let scanned = 0;
8041
+ let preFixConclusive = 0;
7741
8042
  for (; ; ) {
7742
- const batch = db.prepare("SELECT detail FROM stats WHERE kind = 'compact_summary' ORDER BY rowid DESC LIMIT ? OFFSET ?").all(COMPACTION_STATS_BATCH_SIZE, offset);
8043
+ const batch = db.prepare("SELECT detail, tg_version FROM stats WHERE kind = 'compact_summary' ORDER BY rowid DESC LIMIT ? OFFSET ?").all(COMPACTION_STATS_BATCH_SIZE, offset);
7743
8044
  if (batch.length === 0) break;
7744
8045
  offset += batch.length;
7745
8046
  scanned += batch.length;
@@ -7748,6 +8049,10 @@ function checkCompactionChannel(dbPath) {
7748
8049
  if (m === null) continue;
7749
8050
  const sampled = Number(m[2]);
7750
8051
  if (sampled === 0) continue;
8052
+ if (row.tg_version === null || compareSemver(row.tg_version, COMPACTION_MANIFEST_FIX_VERSION) < 0) {
8053
+ preFixConclusive++;
8054
+ continue;
8055
+ }
7751
8056
  conclusive.push({ survived: Number(m[1]), sampled });
7752
8057
  if (conclusive.length >= COMPACTION_CHANNEL_WINDOW) break;
7753
8058
  }
@@ -7755,6 +8060,9 @@ function checkCompactionChannel(dbPath) {
7755
8060
  if (scanned >= COMPACTION_STATS_SCAN_CEILING) break;
7756
8061
  }
7757
8062
  if (conclusive.length === 0) {
8063
+ if (preFixConclusive > 0) {
8064
+ return { name, status: "ok", message: `no post-fix compaction evidence yet -- ${preFixConclusive} compaction(s) found predate ${COMPACTION_MANIFEST_FIX_VERSION}'s manifest-survival fix and are not counted` };
8065
+ }
7758
8066
  return { name, status: "ok", message: "no compaction has been measured yet" };
7759
8067
  }
7760
8068
  const dead = conclusive.filter((c) => c.survived === 0).length;
@@ -7772,20 +8080,58 @@ function checkCompactionChannel(dbPath) {
7772
8080
  return { name, status: "warn", message: `could not read compaction stats: ${extractErrorMessage(e)}` };
7773
8081
  }
7774
8082
  }
8083
+ function checkHookLatency(dbPath) {
8084
+ const name = "Hook latency";
8085
+ if (!fs18.existsSync(dbPath)) {
8086
+ return { name, status: "ok", message: "no database yet" };
8087
+ }
8088
+ try {
8089
+ const db = getDb(dbPath);
8090
+ const present = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'stats'").get();
8091
+ if (present === void 0) {
8092
+ return { name, status: "ok", message: "no hooks measured yet" };
8093
+ }
8094
+ const rows = hookLatencyBreakdown(db);
8095
+ if (rows.length === 0) {
8096
+ return { name, status: "ok", message: "no hook latency recorded yet (or this database predates the duration_ms column)" };
8097
+ }
8098
+ const worst = rows[0];
8099
+ const totalCount = rows.reduce((n, r) => n + r.count, 0);
8100
+ const worstLabel = `${worst.event} (${worst.harness || "unrecorded harness"})`;
8101
+ const budgetMs = loadConfig().hooks.latency_budget_ms;
8102
+ if (worst.p95_ms > budgetMs) {
8103
+ return {
8104
+ name,
8105
+ status: "warn",
8106
+ message: `${worstLabel} is running a p95 of ${worst.p95_ms}ms across ${totalCount} recent hook(s); see 'token-goat stats --hooks' for the full breakdown`
8107
+ };
8108
+ }
8109
+ return { name, status: "ok", message: `worst p95 ${worst.p95_ms}ms (${worstLabel}) across ${totalCount} recent hook(s)` };
8110
+ } catch (e) {
8111
+ return { name, status: "warn", message: `could not read hook latency stats: ${extractErrorMessage(e)}` };
8112
+ }
8113
+ }
7775
8114
  var UNMAPPED_TOOL_SAMPLE = 5;
7776
- function checkUnmappedTools(dbPath) {
8115
+ var UNMAPPED_TOOL_MAX_AGE_DAYS = 7;
8116
+ function checkUnmappedTools(dbPath, options) {
7777
8117
  const name = "Tool names";
7778
8118
  if (!fs18.existsSync(dbPath)) {
7779
8119
  return { name, status: "ok", message: "no database yet" };
7780
8120
  }
7781
8121
  try {
8122
+ pruneStalePatternCoveredUnmappedTools(getDb(dbPath), [MCP_TOOL_PATTERN]);
7782
8123
  const rows = readUnmappedTools(dbPath);
7783
8124
  if (rows.length === 0) {
7784
8125
  return { name, status: "ok", message: "every tool name seen so far reached a handler that wanted it" };
7785
8126
  }
7786
- const nearMisses = rows.filter(
7787
- (r) => r.near_miss !== null && r.near_miss !== void 0 && r.near_miss !== r.tool_name
7788
- );
8127
+ const maxAgeDays = options?.maxAgeDays ?? UNMAPPED_TOOL_MAX_AGE_DAYS;
8128
+ const nowSecs = options?.nowSecs ?? Math.floor(Date.now() / 1e3);
8129
+ const maxAgeSecs = maxAgeDays * 86400;
8130
+ const nearMisses = rows.filter((r) => {
8131
+ if (r.near_miss === null || r.near_miss === void 0 || r.near_miss === r.tool_name) return false;
8132
+ if (r.last_seen > 0 && nowSecs - r.last_seen > maxAgeSecs) return false;
8133
+ return true;
8134
+ });
7789
8135
  if (nearMisses.length > 0) {
7790
8136
  const shown2 = nearMisses.slice(0, UNMAPPED_TOOL_SAMPLE).map((r) => `${r.harness} sent "${r.tool_name}" where "${r.near_miss}" is handled (${r.event_name}, ${r.hits}x)`);
7791
8137
  const more2 = nearMisses.length > UNMAPPED_TOOL_SAMPLE ? ` (+${nearMisses.length - UNMAPPED_TOOL_SAMPLE} more)` : "";
@@ -7822,6 +8168,7 @@ function runDoctor(dataDir2, configPath2, rootDir, processes) {
7822
8168
  results.push(checkSymbolCount(path17.join(actualDataDir, "global.db"), rootDir));
7823
8169
  results.push(checkDirtyQueueHealth(actualDataDir));
7824
8170
  results.push(checkCompactionChannel(path17.join(actualDataDir, "global.db")));
8171
+ results.push(checkHookLatency(path17.join(actualDataDir, "global.db")));
7825
8172
  results.push(checkUnmappedTools(path17.join(actualDataDir, "global.db")));
7826
8173
  const actualConfigPath = configPath2 || configPath();
7827
8174
  results.push(checkConfigValid(actualConfigPath));
@@ -7841,6 +8188,8 @@ function runDoctor(dataDir2, configPath2, rootDir, processes) {
7841
8188
  if (vscodeHooksResult) results.push(vscodeHooksResult);
7842
8189
  const vscodeScopeResult = checkVscodeUserScopeHooks(vscodeHooksInstalled(), vscodeHooksInstalled({ project: true }));
7843
8190
  if (vscodeScopeResult) results.push(vscodeScopeResult);
8191
+ const vscodeProjectMcpResult = checkVscodeProjectMcp(rootDir);
8192
+ if (vscodeProjectMcpResult) results.push(vscodeProjectMcpResult);
7844
8193
  const visualStudioResult = checkVisualStudio([visualStudioUserMcpPath(), visualStudioProjectMcpPath()], [visualStudioSolutionVscodeMcpPath()]);
7845
8194
  if (visualStudioResult) results.push(visualStudioResult);
7846
8195
  const zedResult = checkZed(zedSettingsPath());
@@ -7897,6 +8246,15 @@ async function runDoctorRepair(opts) {
7897
8246
  errors.push(`Failed to download embedding model: ${extractErrorMessage(e)}`);
7898
8247
  }
7899
8248
  }
8249
+ const rootDir = path17.resolve(opts?.rootDir ?? process.cwd());
8250
+ const projectMcp = path17.join(rootDir, ".vscode", "mcp.json");
8251
+ try {
8252
+ if (cleanupDeprecatedVscodeProjectMcp(rootDir)) {
8253
+ repairs.push(`Removed empty deprecated VS Code MCP residue file at ${displaySafeText(projectMcp)}`);
8254
+ }
8255
+ } catch (e) {
8256
+ errors.push(`Failed to clean up ${displaySafeText(projectMcp)}: ${extractErrorMessage(e)}`);
8257
+ }
7900
8258
  return { repairs, errors };
7901
8259
  }
7902
8260
  function printDoctorResults(results) {
@@ -8058,7 +8416,7 @@ async function runDoctorAndExit(opts) {
8058
8416
  }
8059
8417
  }
8060
8418
  try {
8061
- const { checkUpdateStatus } = await import("./token-goat-chunk-K4DG6EEF.mjs");
8419
+ const { checkUpdateStatus } = await import("./token-goat-chunk-VJQBYSXG.mjs");
8062
8420
  const update = await checkUpdateStatus(1500);
8063
8421
  if (update.updateAvailable && update.latest) {
8064
8422
  console.log(`
@@ -8150,6 +8508,7 @@ function generateCompactHelp() {
8150
8508
  "Options:",
8151
8509
  " -v, --version print the token-goat version",
8152
8510
  " --cwd <path> run as if invoked from this directory",
8511
+ " --notice <text> print this line before the command's own output",
8153
8512
  " -h, --help display help for command",
8154
8513
  "",
8155
8514
  "Reads: symbol, read, brief, section, semantic, skeleton, outline, refs, scope,",
@@ -8164,7 +8523,7 @@ function generateCompactHelp() {
8164
8523
  " html-lint, docx-outline, docx-tables, docx-text, pptx-outline, pptx-slide, pptx-text,",
8165
8524
  " pptx-notes",
8166
8525
  "",
8167
- "Index & Search: index, map, reconcile, doctor, commands, ask, pack, tokens,",
8526
+ "Index & Search: index, map, reconcile, doctor, commands, ask, answer, pack, tokens,",
8168
8527
  " budget, failures, todo, trace, logfold, lockdeps, dep-docs, recent, hot,",
8169
8528
  " snapshot-snapshot, baseline, cost, coverage-report-gaps, clean-cache,",
8170
8529
  " cache-audit, reclaim-index, project",
@@ -8206,7 +8565,7 @@ init_define_import_meta_env();
8206
8565
  // src/config_commands.ts
8207
8566
  init_define_import_meta_env();
8208
8567
  import * as fs20 from "node:fs";
8209
- import * as os15 from "node:os";
8568
+ import * as os14 from "node:os";
8210
8569
  import * as path18 from "node:path";
8211
8570
 
8212
8571
  // src/webfetch.ts
@@ -8215,7 +8574,7 @@ import { existsSync as existsSync14, readdirSync as readdirSync3, statSync as st
8215
8574
  import * as http from "http";
8216
8575
  import * as https from "https";
8217
8576
  import { isIPv4, isIPv6 } from "net";
8218
- import { resolve as resolve6, join as join16 } from "path";
8577
+ import { resolve as resolve7, join as join16 } from "path";
8219
8578
  import { URL as URL2 } from "url";
8220
8579
  import { promisify } from "util";
8221
8580
  import { lookup as dnsLookup } from "dns";
@@ -8356,7 +8715,7 @@ function cleanupStaleDownloads() {
8356
8715
  const files = readdirSync3(cacheDir);
8357
8716
  for (const file of files) {
8358
8717
  if (file.endsWith(".tmp")) {
8359
- const filePath = resolve6(cacheDir, file);
8718
+ const filePath = resolve7(cacheDir, file);
8360
8719
  try {
8361
8720
  const stat = statSync3(filePath);
8362
8721
  if (Date.now() - stat.mtimeMs < STALE_DOWNLOAD_AGE_MS) continue;
@@ -9072,7 +9431,7 @@ async function cmdFetchImage(opts) {
9072
9431
  throw new Error(`fetch failed: ${opts.url}`, { cause: e });
9073
9432
  }
9074
9433
  const buf = fetched.body;
9075
- const outPath = opts.out ?? path18.join(os15.tmpdir(), `tg-fetch-${Date.now()}${extensionForContentType(fetched.contentType)}`);
9434
+ const outPath = opts.out ?? path18.join(os14.tmpdir(), `tg-fetch-${Date.now()}${extensionForContentType(fetched.contentType)}`);
9076
9435
  const originalBytes = buf.length;
9077
9436
  let shrunkBytes;
9078
9437
  let outData;
@@ -12190,7 +12549,7 @@ import * as fs26 from "node:fs";
12190
12549
  // src/sharepoint_resolve.ts
12191
12550
  init_define_import_meta_env();
12192
12551
  import * as fs24 from "node:fs";
12193
- import * as os16 from "node:os";
12552
+ import * as os15 from "node:os";
12194
12553
  import * as path23 from "node:path";
12195
12554
  var TENANT_HOST_RE = /^([a-z0-9-]+?)(-my)?\.sharepoint\.com$/i;
12196
12555
  function parseShareUrl(url) {
@@ -12263,7 +12622,7 @@ function tryLibraryPaths(root, libSegments, triedPaths) {
12263
12622
  }
12264
12623
  return null;
12265
12624
  }
12266
- function resolveLocalPath(parsed, env = process.env, home = os16.homedir()) {
12625
+ function resolveLocalPath(parsed, env = process.env, home = os15.homedir()) {
12267
12626
  const roots = candidateRoots(env, home);
12268
12627
  const libSegments = parsed.libraryPath.split(/[/\\]+/).filter((s) => s.length > 0 && s !== "." && s !== "..");
12269
12628
  const triedPaths = [];
@@ -12423,12 +12782,12 @@ function formatCues(cues) {
12423
12782
 
12424
12783
  // src/video_chapters.ts
12425
12784
  init_define_import_meta_env();
12426
- import { spawnSync as spawnSync3 } from "node:child_process";
12785
+ import { spawnSync as spawnSync4 } from "node:child_process";
12427
12786
  var _ffprobeAvailable;
12428
12787
  function isFfprobeAvailable() {
12429
12788
  if (_ffprobeAvailable !== void 0) return _ffprobeAvailable;
12430
12789
  try {
12431
- const res = spawnSync3("ffprobe", ["-version"], { stdio: "ignore" });
12790
+ const res = spawnSync4("ffprobe", ["-version"], { stdio: "ignore" });
12432
12791
  _ffprobeAvailable = res.status === 0;
12433
12792
  } catch {
12434
12793
  _ffprobeAvailable = false;
@@ -12439,7 +12798,7 @@ function extractVideoChapters(file) {
12439
12798
  if (!isFfprobeAvailable()) {
12440
12799
  throw new Error("ffprobe not found on PATH; video-chapters requires ffmpeg (install ffmpeg and ensure ffprobe is on PATH)");
12441
12800
  }
12442
- const res = spawnSync3(
12801
+ const res = spawnSync4(
12443
12802
  "ffprobe",
12444
12803
  ["-v", "error", "-print_format", "json", "-show_chapters", "-show_streams", file],
12445
12804
  { encoding: "utf8", maxBuffer: 64 * 1024 * 1024 }
@@ -13629,15 +13988,248 @@ function registerFormatCommands(program2, guard) {
13629
13988
  // src/cli_cmd_analysis.ts
13630
13989
  init_define_import_meta_env();
13631
13990
 
13991
+ // src/answer_router.ts
13992
+ init_define_import_meta_env();
13993
+ function emit2(text) {
13994
+ const payload = colorStdout() ? text : stripAnsi(text);
13995
+ process.stdout.write(ensureNewline(payload));
13996
+ }
13997
+ function emitErr2(text) {
13998
+ process.stderr.write(ensureNewline(text));
13999
+ }
14000
+ function refusal(why, suggestion) {
14001
+ return `cannot answer deterministically: ${why}; try: ${suggestion}`;
14002
+ }
14003
+ var JUDGEMENT_PATTERNS = [
14004
+ /\bwhy\b/i,
14005
+ /\bshould (?:i|we|it|this|that)\b/i,
14006
+ /^(?:is|are|does|do|did|can|could|would|will|has|have)\b/i,
14007
+ /^(?:explain|describe|summari[sz]e|review|assess|evaluate|compare|analy[sz]e)\b/i,
14008
+ /^how\b/i,
14009
+ /\bwhat(?:'s| is)? (?:the )?(?:bug|issue|problem|point|difference)\b/i,
14010
+ /\bwhat(?:'s| is) wrong\b/i,
14011
+ /\bat runtime\b/i,
14012
+ /\bwhat value\b/i,
14013
+ /\breturns? when\b/i,
14014
+ /\b(?:correct|incorrect|buggy|broken|safe|unsafe|better|worse|best)\b/i
14015
+ ];
14016
+ var INTENT_RULES = [
14017
+ { intent: "callers", re: /^who calls (.+)$/i },
14018
+ { intent: "callers", re: /^what calls (.+)$/i },
14019
+ { intent: "callers", re: /^callers of (.+)$/i },
14020
+ { intent: "callers", re: /^call ?-?sites of (.+)$/i },
14021
+ { intent: "callers", re: /^(?:all )?(.+) call ?-?sites$/i },
14022
+ { intent: "tests", re: /^wh(?:at|ich) tests? (?:covers?|exercises?|tests?|touch(?:es)?) (.+)$/i },
14023
+ { intent: "tests", re: /^tests? for (.+)$/i },
14024
+ { intent: "tests", re: /^test coverage (?:for|of) (.+)$/i },
14025
+ { intent: "exports", re: /^what does (.+) export$/i },
14026
+ { intent: "exports", re: /^exports of (.+)$/i },
14027
+ { intent: "exports", re: /^(.+) exports$/i },
14028
+ { intent: "imports", re: /^what does (.+) import$/i },
14029
+ { intent: "imports", re: /^imports of (.+)$/i },
14030
+ // The bare form is the commoner of the pair in the captured corpus (34 hits vs 15 for `X exports`). It is only safe alongside file-only subject resolution for this intent: with a symbol fallback, the corpus lines `Add imports` and `Update imports` resolve `Add`/`Update` to whatever same-named symbol sorts first.
14031
+ { intent: "imports", re: /^(.+) imports$/i },
14032
+ { intent: "impact", re: /^what breaks if (.+) changes?$/i },
14033
+ { intent: "impact", re: /^what breaks if (?:i|we|you) chang(?:e|ed) (.+)$/i },
14034
+ { intent: "impact", re: /^what breaks when (?:i|we|you) chang(?:e|ed) (.+)$/i },
14035
+ { intent: "impact", re: /^what breaks when (.+) chang(?:es|ed)$/i },
14036
+ { intent: "impact", re: /^what depends on (.+)$/i },
14037
+ { intent: "impact", re: /^what(?:'s| is) impacted by (?:changing )?(.+)$/i },
14038
+ { intent: "impact", re: /^blast radius of (.+)$/i },
14039
+ { intent: "impact", re: /^impact of (?:changing )?(.+)$/i },
14040
+ { intent: "where", re: /^where is (.+) (?:defined|declared)$/i },
14041
+ { intent: "where", re: /^where is (.+)$/i },
14042
+ { intent: "where", re: /^where does (.+) live$/i }
14043
+ ];
14044
+ function normalizeQuestion(question) {
14045
+ const collapsed = question.replace(/\s+/g, " ").trim().replace(/[?\s]+$/, "");
14046
+ return collapsed.replace(/^(?:check|show|list|find|get|print|inspect|read|locate|verify|measure|view|trace|identify) (?:the |a |an )?/i, "");
14047
+ }
14048
+ function isJudgementQuestion(rawQuestion) {
14049
+ const question = normalizeQuestion(rawQuestion);
14050
+ return JUDGEMENT_PATTERNS.some((p) => p.test(question));
14051
+ }
14052
+ function classify(rawQuestion) {
14053
+ const question = normalizeQuestion(rawQuestion);
14054
+ if (isJudgementQuestion(question)) return null;
14055
+ for (const rule of INTENT_RULES) {
14056
+ const m = rule.re.exec(question);
14057
+ if (m && m[1] !== void 0) {
14058
+ const subject = normalizeSubject(m[1]);
14059
+ if (subject.length > 0) return { intent: rule.intent, subject };
14060
+ }
14061
+ }
14062
+ return null;
14063
+ }
14064
+ function normalizeSubject(raw) {
14065
+ let s = raw.trim();
14066
+ for (; ; ) {
14067
+ const next = s.replace(/^[`'"]+/, "").replace(/[`'".,;:!]+$/, "").trim();
14068
+ if (next === s) break;
14069
+ s = next;
14070
+ }
14071
+ s = s.replace(/^(?:the|a|an) /i, "");
14072
+ return s.trim();
14073
+ }
14074
+ var SYMBOL_SCAN_PAGE = 200;
14075
+ function resolveSymbolHit(subject, rootDir) {
14076
+ for (let offset = 0; ; offset += SYMBOL_SCAN_PAGE) {
14077
+ const rows = querySymbols({ name: subject, rootDir, limit: SYMBOL_SCAN_PAGE, offset });
14078
+ if (rows.length === 0) return null;
14079
+ const hit = rows.find((r) => !isIgnoredIndexPath(r.filePath));
14080
+ if (hit) return { name: hit.name, file: hit.filePath };
14081
+ if (rows.length < SYMBOL_SCAN_PAGE) return null;
14082
+ }
14083
+ }
14084
+ function resolveFileHit(subject, rootDir) {
14085
+ const entry = getFileEntry(resolveIndexPath(subject));
14086
+ if (entry && !isIgnoredIndexPath(entry.filePath)) return { kind: "file", path: entry.filePath };
14087
+ if (subject.includes("/") || subject.includes("\\")) return null;
14088
+ const want = foldPath(subject);
14089
+ const matches2 = [];
14090
+ for (const [folded, indexed] of getProjectFileEntries(rootDir)) {
14091
+ if (isIgnoredIndexPath(folded)) continue;
14092
+ const base = folded.slice(Math.max(folded.lastIndexOf("/"), folded.lastIndexOf("\\")) + 1);
14093
+ const dot = base.lastIndexOf(".");
14094
+ if (base === want || dot > 0 && base.slice(0, dot) === want) matches2.push(indexed.filePath);
14095
+ }
14096
+ const paths = [...new Set(matches2)].sort();
14097
+ if (paths.length === 0) return null;
14098
+ if (paths.length === 1 && paths[0] !== void 0) return { kind: "file", path: paths[0] };
14099
+ return { kind: "ambiguous", candidates: paths };
14100
+ }
14101
+ function resolveSubject(subject, mode = "symbol-first") {
14102
+ const rootDir = resolveProjectRoot({ project: process.cwd() });
14103
+ const sep5 = subject.lastIndexOf("::");
14104
+ if (sep5 > 0 && sep5 + 2 < subject.length) {
14105
+ const file2 = resolveIndexPath(subject.slice(0, sep5));
14106
+ const hit2 = querySymbols({ filePath: file2, name: subject.slice(sep5 + 2), rootDir, limit: 1 })[0];
14107
+ if (hit2 && !isIgnoredIndexPath(hit2.filePath)) return { kind: "symbol", name: hit2.name, file: hit2.filePath };
14108
+ return null;
14109
+ }
14110
+ if (/\s/.test(subject)) return null;
14111
+ if (mode === "symbol-first") {
14112
+ const hit2 = resolveSymbolHit(subject, rootDir);
14113
+ if (hit2) return { kind: "symbol", name: hit2.name, file: hit2.file };
14114
+ return resolveFileHit(subject, rootDir);
14115
+ }
14116
+ const file = resolveFileHit(subject, rootDir);
14117
+ if (file) return file;
14118
+ const hit = resolveSymbolHit(subject, rootDir);
14119
+ if (!hit) return null;
14120
+ return mode === "file-first" ? { kind: "symbol", name: hit.name, file: hit.file } : { kind: "symbol-only", name: hit.name, file: hit.file };
14121
+ }
14122
+ var FILE_INTENTS = /* @__PURE__ */ new Set(["tests", "exports", "imports"]);
14123
+ var ANSWER_DELEGATE_LIMIT = 20;
14124
+ function subjectModeFor(intent) {
14125
+ if (intent === "exports" || intent === "imports") return "file-only";
14126
+ return intent === "tests" ? "file-first" : "symbol-first";
14127
+ }
14128
+ function runAnswer(opts) {
14129
+ const question = opts.question.trim();
14130
+ if (question.length === 0) {
14131
+ emitErr2(refusal("the question is empty", 'token-goat answer "who calls <symbol>"'));
14132
+ return 1;
14133
+ }
14134
+ if (isJudgementQuestion(question)) {
14135
+ emitErr2(
14136
+ refusal(
14137
+ "that asks for judgement, intent, or runtime behaviour, which the index cannot answer -- it would need the code read and reasoned over",
14138
+ `token-goat semantic "${question}"`
14139
+ )
14140
+ );
14141
+ return 1;
14142
+ }
14143
+ const cls = classify(question);
14144
+ if (cls === null) {
14145
+ emitErr2(
14146
+ refusal(
14147
+ "no intent matched -- this router only answers where/who-calls/what-tests-cover/what-exports/what-imports/what-breaks questions about a named symbol or file",
14148
+ `token-goat semantic "${question}"`
14149
+ )
14150
+ );
14151
+ return 1;
14152
+ }
14153
+ const resolved = resolveSubject(cls.subject, subjectModeFor(cls.intent));
14154
+ if (resolved === null) {
14155
+ emitErr2(
14156
+ refusal(
14157
+ `'${cls.subject}' is not an indexed symbol or file`,
14158
+ `token-goat semantic "${question}"`
14159
+ )
14160
+ );
14161
+ return 1;
14162
+ }
14163
+ const rootDir = resolveProjectRoot({ project: process.cwd() });
14164
+ if (resolved.kind === "ambiguous") {
14165
+ const shown = resolved.candidates.slice(0, 5).map((c) => toDisplayPath(rootDir, c));
14166
+ const more = resolved.candidates.length - shown.length;
14167
+ const first = shown[0] ?? "";
14168
+ const next = FILE_INTENTS.has(cls.intent) ? `token-goat answer "${cls.intent === "tests" ? "tests for" : cls.intent === "imports" ? "imports of" : "exports of"} ${first}"` : `token-goat outline ${first}`;
14169
+ emitErr2(
14170
+ refusal(
14171
+ `'${cls.subject}' names ${resolved.candidates.length} files in this project (${shown.join(", ")}${more > 0 ? `, +${more} more` : ""})`,
14172
+ next
14173
+ )
14174
+ );
14175
+ return 1;
14176
+ }
14177
+ if (resolved.kind === "symbol-only") {
14178
+ emitErr2(
14179
+ refusal(
14180
+ `'${cls.subject}' is a symbol; exports/imports are file-level`,
14181
+ `token-goat ${cls.intent} ${toDisplayPath(rootDir, resolved.file)}`
14182
+ )
14183
+ );
14184
+ return 1;
14185
+ }
14186
+ if (FILE_INTENTS.has(cls.intent)) {
14187
+ const file = resolved.kind === "symbol" ? resolved.file : resolved.path;
14188
+ const display = toDisplayPath(rootDir, file);
14189
+ if (cls.intent === "tests") {
14190
+ emit2(`via: token-goat test-for ${display}`);
14191
+ return runTestFor({ file });
14192
+ }
14193
+ if (cls.intent === "exports") {
14194
+ emit2(`via: token-goat exports ${display}`);
14195
+ return runExports({ file });
14196
+ }
14197
+ emit2(`via: token-goat imports ${display}`);
14198
+ return runImports({ file });
14199
+ }
14200
+ if (resolved.kind === "file") {
14201
+ const display = toDisplayPath(rootDir, resolved.path);
14202
+ emitErr2(
14203
+ refusal(
14204
+ `'${cls.subject}' is a file, and ${cls.intent === "where" ? "where" : cls.intent} needs a symbol`,
14205
+ `token-goat outline ${display}`
14206
+ )
14207
+ );
14208
+ return 1;
14209
+ }
14210
+ if (cls.intent === "callers") {
14211
+ emit2(`via: token-goat callers ${displaySafeText(resolved.name)} --limit ${ANSWER_DELEGATE_LIMIT}`);
14212
+ return runCallers({ symbol: resolved.name, limit: ANSWER_DELEGATE_LIMIT });
14213
+ }
14214
+ if (cls.intent === "impact") {
14215
+ emit2(`via: token-goat impact ${displaySafeText(resolved.name)} --top ${ANSWER_DELEGATE_LIMIT}`);
14216
+ return runImpact({ symbol: resolved.name, top: ANSWER_DELEGATE_LIMIT });
14217
+ }
14218
+ emit2(`via: token-goat symbol ${displaySafeText(resolved.name)} -p --exclude-vendored`);
14219
+ const r = runSymbol({ name: resolved.name, projectRoot: rootDir, limit: ANSWER_DELEGATE_LIMIT, excludeVendored: true });
14220
+ if (r.text.length > 0) emit2(r.text);
14221
+ return r.code;
14222
+ }
14223
+
13632
14224
  // src/affected.ts
13633
14225
  init_define_import_meta_env();
13634
14226
  import * as path25 from "node:path";
13635
14227
  var DEFAULT_AFFECTED_DEPTH = 5;
13636
- function emit2(text) {
14228
+ function emit3(text) {
13637
14229
  const payload = colorStdout() ? text : stripAnsi(text);
13638
14230
  process.stdout.write(ensureNewline(payload));
13639
14231
  }
13640
- function emitErr2(text) {
14232
+ function emitErr3(text) {
13641
14233
  process.stderr.write(ensureNewline(text));
13642
14234
  }
13643
14235
  function guardedFilter(filter2) {
@@ -13694,43 +14286,43 @@ function computeAffected(opts) {
13694
14286
  }
13695
14287
  function runAffected(opts) {
13696
14288
  if (opts.depth !== void 0 && opts.depth <= 0) {
13697
- emitErr2(`--depth must be a positive number, got: ${opts.depth}`);
14289
+ emitErr3(`--depth must be a positive number, got: ${opts.depth}`);
13698
14290
  return 1;
13699
14291
  }
13700
14292
  if (opts.files.length === 0) {
13701
- emitErr2("No changed files given. Pass paths as arguments, or pipe them in with --stdin.");
14293
+ emitErr3("No changed files given. Pass paths as arguments, or pipe them in with --stdin.");
13702
14294
  return 1;
13703
14295
  }
13704
14296
  if (opts.filter !== void 0) {
13705
14297
  const guarded = compileGuardedRegex(opts.filter);
13706
14298
  if (!guarded.ok) {
13707
- emitErr2(`--filter is not usable as a regular expression: ${guarded.reason}`);
14299
+ emitErr3(`--filter is not usable as a regular expression: ${guarded.reason}`);
13708
14300
  return 1;
13709
14301
  }
13710
14302
  }
13711
14303
  const result = computeAffected(opts);
13712
14304
  if (opts.json === true) {
13713
- emit2(displaySafeJson(result));
14305
+ emit3(displaySafeJson(result));
13714
14306
  return 0;
13715
14307
  }
13716
14308
  if (opts.quiet === true) {
13717
- for (const f of result.testFiles) emit2(f);
14309
+ for (const f of result.testFiles) emit3(f);
13718
14310
  } else if (result.testFiles.length === 0) {
13719
14311
  const traced = opts.files.length - result.unknownSeeds.length;
13720
14312
  if (traced === 0) {
13721
- emit2(`Nothing could be traced: ${result.unknownSeeds.length === 1 ? "the file given is not tracked" : "none of the files given are tracked"} in this project.`);
14313
+ emit3(`Nothing could be traced: ${result.unknownSeeds.length === 1 ? "the file given is not tracked" : "none of the files given are tracked"} in this project.`);
13722
14314
  } else {
13723
- emit2(`No test files import ${countNoun(traced, "changed file")} (within ${countNoun(result.depth, "hop")}).`);
14315
+ emit3(`No test files import ${countNoun(traced, "changed file")} (within ${countNoun(result.depth, "hop")}).`);
13724
14316
  }
13725
14317
  } else {
13726
- emit2(`${countNoun(result.testFiles.length, "affected test file")} (${countNoun(result.reachedCount, "file")} reached within ${countNoun(result.depth, "hop")}):`);
13727
- for (const f of result.testFiles) emit2(` ${f}`);
14318
+ emit3(`${countNoun(result.testFiles.length, "affected test file")} (${countNoun(result.reachedCount, "file")} reached within ${countNoun(result.depth, "hop")}):`);
14319
+ for (const f of result.testFiles) emit3(` ${f}`);
13728
14320
  }
13729
14321
  if (result.unknownSeeds.length > 0) {
13730
- emitErr2(`Not tracked in this project, so nothing was traced from ${result.unknownSeeds.length === 1 ? "it" : "them"}: ${result.unknownSeeds.join(", ")}`);
14322
+ emitErr3(`Not tracked in this project, so nothing was traced from ${result.unknownSeeds.length === 1 ? "it" : "them"}: ${result.unknownSeeds.join(", ")}`);
13731
14323
  }
13732
14324
  if (result.depthLimited) {
13733
- emitErr2(`Stopped at --depth ${result.depth} with ${countNoun(result.unexploredAtFrontier, "file")} still unexplored; more tests may be affected (raise --depth).`);
14325
+ emitErr3(`Stopped at --depth ${result.depth} with ${countNoun(result.unexploredAtFrontier, "file")} still unexplored; more tests may be affected (raise --depth).`);
13734
14326
  }
13735
14327
  return 0;
13736
14328
  }
@@ -14863,17 +15455,17 @@ function cmdNote(action, key, value, opts) {
14863
15455
  throw new Error(`Unknown note action: ${action}. Use: set, get, unset, list, clear`);
14864
15456
  }
14865
15457
  function loadAllSessionReadCounts() {
14866
- const sessionsDir = path29.join(tokenGoatHome(), "sessions");
15458
+ const sessionsDir2 = path29.join(tokenGoatHome(), "sessions");
14867
15459
  const totals = /* @__PURE__ */ new Map();
14868
15460
  let entries;
14869
15461
  try {
14870
- entries = fs32.readdirSync(sessionsDir, { withFileTypes: true });
15462
+ entries = fs32.readdirSync(sessionsDir2, { withFileTypes: true });
14871
15463
  } catch {
14872
15464
  return totals;
14873
15465
  }
14874
15466
  for (const entry of entries) {
14875
15467
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
14876
- const filePath = path29.join(sessionsDir, entry.name);
15468
+ const filePath = path29.join(sessionsDir2, entry.name);
14877
15469
  let raw;
14878
15470
  try {
14879
15471
  raw = JSON.parse(fs32.readFileSync(filePath, "utf8"));
@@ -15476,6 +16068,7 @@ function registerAnalysisCommands(program2, guard) {
15476
16068
  })
15477
16069
  )
15478
16070
  );
16071
+ program2.command("answer <question>").description("answer a plain-English question from the index alone, or refuse with a reason (no model call, no inference)").action((question) => runExit(() => runAnswer({ question })));
15479
16072
  program2.command("pack [patterns...]").description("bundle matched files into a single LLM-ready output (Markdown, XML, or plain text)").option("--format <style>", "output style: md (default), xml, or text", "md").option("--line-numbers", "prefix each line with its line number").option("--instruction-file <path>", "append a task prompt from a file").option("--output <path>", "write output to a file instead of stdout").option("--no-ignore", "bypass .tokengoatignore patterns").option("--strip-comments", "remove language-appropriate comments before packing").option("--scan-secrets", "scan for credentials; exit 2 if any are found").option("--budget <n>", "exit 3 if the estimated token count exceeds n").action(guard(cmdPack));
15480
16073
  program2.command("tokens [patterns...]").description("per-file token footprint table, sorted largest-first").option("--tree", "group by directory with subtotals and percentage of total").option("--top <n>", "limit to the N biggest files").option("--asc", "reverse order (ascending)").option("-j, --json", "output as JSON").action(guard(cmdTokens));
15481
16074
  program2.command("budget <patterns...>").description("estimate the total token cost of a file set").option("--context <n>", "context window in thousands of tokens (shows % fill)").option("-j, --json", "output as JSON").action(guard(cmdBudget));
@@ -15642,12 +16235,12 @@ function renderCapabilities(caps) {
15642
16235
 
15643
16236
  // src/cli_session.ts
15644
16237
  init_define_import_meta_env();
15645
- import * as fs42 from "node:fs";
16238
+ import * as fs41 from "node:fs";
15646
16239
 
15647
16240
  // src/session_audit.ts
15648
16241
  init_define_import_meta_env();
15649
16242
  import * as fs34 from "node:fs";
15650
- import * as os17 from "node:os";
16243
+ import * as os16 from "node:os";
15651
16244
  import * as path31 from "node:path";
15652
16245
  import * as readline from "node:readline";
15653
16246
  function emptyMeasured() {
@@ -15754,10 +16347,10 @@ var SHELL_READER_TOKEN_RE = /\b(?:sed|grep|cat|head|tail|awk|rg|less|nl|python3?
15754
16347
  function escapeRegExp2(s) {
15755
16348
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15756
16349
  }
15757
- function shellReadMatch(command, basename10) {
15758
- if (basename10 === "" || !SHELL_READER_TOKEN_RE.test(command)) return "none";
15759
- if (!command.includes(basename10)) return "none";
15760
- const escaped = escapeRegExp2(basename10);
16350
+ function shellReadMatch(command, basename11) {
16351
+ if (basename11 === "" || !SHELL_READER_TOKEN_RE.test(command)) return "none";
16352
+ if (!command.includes(basename11)) return "none";
16353
+ const escaped = escapeRegExp2(basename11);
15761
16354
  const strict = new RegExp(`(?:^|["'\`/\\\\\\s])${escaped}(?:$|["'\`/\\\\\\s,;:)])`);
15762
16355
  return strict.test(command) ? "match" : "ambiguous";
15763
16356
  }
@@ -15845,11 +16438,11 @@ function listCorpusTranscripts(corpusDir) {
15845
16438
  return found.sort();
15846
16439
  }
15847
16440
  function defaultCorpusDir() {
15848
- const claudeDir = path31.join(os17.homedir(), ".claude", "projects");
16441
+ const claudeDir = path31.join(claudeConfigDir(), "projects");
15849
16442
  if (fs34.existsSync(claudeDir) && listCorpusTranscripts(claudeDir).length > 0) {
15850
16443
  return claudeDir;
15851
16444
  }
15852
- const home = os17.homedir();
16445
+ const home = os16.homedir();
15853
16446
  const appData = process.env["APPDATA"] || path31.join(home, "AppData", "Roaming");
15854
16447
  const candidates = [
15855
16448
  claudeDir,
@@ -16046,13 +16639,13 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
16046
16639
  if (name === "Read" || name === "Skill") {
16047
16640
  const denyTemplate = DENY_TEMPLATES.find((t) => t.tool === name && t.re.test(text));
16048
16641
  if (denyTemplate !== void 0) {
16049
- const path44 = readPathById.get(id) ?? "";
16050
- const lastSlash = path44.lastIndexOf("/");
16642
+ const path43 = readPathById.get(id) ?? "";
16643
+ const lastSlash = path43.lastIndexOf("/");
16051
16644
  openDenies.push({
16052
16645
  kind: denyTemplate.kind,
16053
16646
  withheldBytes: parseWithheldBytes(denyTemplate.kind, text),
16054
- path: path44,
16055
- basename: lastSlash === -1 ? path44 : path44.slice(lastSlash + 1),
16647
+ path: path43,
16648
+ basename: lastSlash === -1 ? path43 : path43.slice(lastSlash + 1),
16056
16649
  callIndexAtDeny: perCall.length,
16057
16650
  compactEpochAtOpen: compactEpoch,
16058
16651
  compactEpochAtWindow: null,
@@ -16524,267 +17117,17 @@ function formatSessionAudit(s) {
16524
17117
 
16525
17118
  // src/session_read.ts
16526
17119
  init_define_import_meta_env();
16527
- import * as fs36 from "node:fs";
16528
- import * as path33 from "node:path";
16529
- import * as readline2 from "node:readline";
16530
-
16531
- // src/waste.ts
16532
- init_define_import_meta_env();
16533
17120
  import * as fs35 from "node:fs";
16534
- import * as os18 from "node:os";
16535
17121
  import * as path32 from "node:path";
16536
- function projectTranscriptsDir(projectRoot) {
16537
- const rootStr = path32.resolve(projectRoot);
16538
- const slug = rootStr.replace(/[^A-Za-z0-9]/g, "-");
16539
- return path32.join(os18.homedir(), ".claude", "projects", slug);
16540
- }
16541
- function findLatestTranscript(projectRoot) {
16542
- const dir = projectTranscriptsDir(projectRoot);
16543
- let entries;
16544
- try {
16545
- entries = fs35.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
16546
- } catch {
16547
- return null;
16548
- }
16549
- let best = null;
16550
- for (const name of entries) {
16551
- const full = path32.join(dir, name);
16552
- let stat;
16553
- try {
16554
- stat = fs35.statSync(full);
16555
- } catch {
16556
- continue;
16557
- }
16558
- if (best === null || stat.mtimeMs > best.mtimeMs) best = { file: full, mtimeMs: stat.mtimeMs };
16559
- }
16560
- return best?.file ?? null;
16561
- }
16562
- function safeStringify(value) {
16563
- try {
16564
- return JSON.stringify(value) ?? "";
16565
- } catch {
16566
- return "";
16567
- }
16568
- }
16569
- var FILE_PATH_TOOLS = /* @__PURE__ */ new Set(["Read", "Edit", "Write", "NotebookEdit", "MultiEdit"]);
16570
- function extractFilePath(name, input) {
16571
- if (!FILE_PATH_TOOLS.has(name)) return null;
16572
- if (input === null || typeof input !== "object") return null;
16573
- const o = input;
16574
- const fp = o["file_path"];
16575
- if (typeof fp === "string") return fp;
16576
- const notebookPath = o["notebook_path"];
16577
- return typeof notebookPath === "string" ? notebookPath : null;
16578
- }
16579
- function extractCommand(name, input) {
16580
- if (name !== "Bash") return null;
16581
- if (input === null || typeof input !== "object") return null;
16582
- const cmd = input["command"];
16583
- return typeof cmd === "string" ? cmd : null;
16584
- }
16585
- function summarizeInput(name, input, filePath, command) {
16586
- if (filePath !== null) return filePath;
16587
- if (command !== null) return command.length > 100 ? `${command.slice(0, 100)}\u2026` : command;
16588
- if (input === null || typeof input !== "object") return "";
16589
- const o = input;
16590
- for (const key of ["pattern", "query", "path", "description", "prompt"]) {
16591
- const v = o[key];
16592
- if (typeof v === "string") return v.length > 100 ? `${v.slice(0, 100)}\u2026` : v;
16593
- }
16594
- const s = safeStringify(input);
16595
- return s.length > 100 ? `${s.slice(0, 100)}\u2026` : s;
16596
- }
16597
- function extractResultText(content) {
16598
- if (typeof content === "string") return content;
16599
- if (Array.isArray(content)) {
16600
- return content.map((b) => {
16601
- if (b !== null && typeof b === "object" && b["type"] === "text") {
16602
- const t = b["text"];
16603
- return typeof t === "string" ? t : "";
16604
- }
16605
- return "";
16606
- }).join("\n");
16607
- }
16608
- return "";
16609
- }
16610
- function parseTranscript2(transcriptPath) {
16611
- const raw = fs35.readFileSync(transcriptPath, "utf-8");
16612
- const calls = [];
16613
- const resultTextById = /* @__PURE__ */ new Map();
16614
- const assistantTurns = [];
16615
- const resident = createResidentContextStats();
16616
- let seq = 0;
16617
- for (const line of raw.split("\n")) {
16618
- const trimmed = line.trim();
16619
- if (trimmed === "") continue;
16620
- let obj;
16621
- try {
16622
- obj = JSON.parse(trimmed);
16623
- } catch {
16624
- continue;
16625
- }
16626
- if (obj === null || typeof obj !== "object") continue;
16627
- const o = obj;
16628
- accumulateResidentLine(resident, o, trimmed.length);
16629
- const message = o["message"];
16630
- if (message === null || typeof message !== "object") continue;
16631
- const messageObj = message;
16632
- const content = messageObj["content"];
16633
- if (!Array.isArray(content)) continue;
16634
- const role = typeof messageObj["role"] === "string" ? messageObj["role"] : null;
16635
- const cwd = typeof o["cwd"] === "string" ? o["cwd"] : null;
16636
- let turnTextTokens = 0;
16637
- let sawTextBlock = false;
16638
- for (const block of content) {
16639
- if (block === null || typeof block !== "object") continue;
16640
- const b = block;
16641
- if (b["type"] === "tool_use" && typeof b["id"] === "string" && typeof b["name"] === "string") {
16642
- const name = b["name"];
16643
- const input = b["input"];
16644
- const filePath = extractFilePath(name, input);
16645
- const command = extractCommand(name, input);
16646
- calls.push({
16647
- seq: seq++,
16648
- id: b["id"],
16649
- name,
16650
- input,
16651
- filePath,
16652
- command,
16653
- summary: summarizeInput(name, input, filePath, command),
16654
- cwd
16655
- });
16656
- } else if (b["type"] === "tool_result" && typeof b["tool_use_id"] === "string") {
16657
- const id = b["tool_use_id"];
16658
- if (!resultTextById.has(id)) {
16659
- resultTextById.set(id, extractResultText(b["content"]));
16660
- }
16661
- } else if (role === "assistant" && b["type"] === "text" && typeof b["text"] === "string") {
16662
- sawTextBlock = true;
16663
- turnTextTokens += estimateTokens(b["text"]);
16664
- }
16665
- }
16666
- if (role === "assistant" && sawTextBlock) assistantTurns.push(turnTextTokens);
16667
- }
16668
- return { calls, resultTextById, assistantTurns, resident };
16669
- }
16670
- function costPerCall(parsed) {
16671
- return parsed.calls.map((c) => ({
16672
- ...c,
16673
- tokens: estimateTokens(parsed.resultTextById.get(c.id) ?? "")
16674
- }));
16675
- }
16676
- function aggregateSortedDesc(pairs) {
16677
- return [...pairs].map(([key, tokens]) => ({ key, tokens })).sort((a, b) => b.tokens - a.tokens);
16678
- }
16679
- function tokensByTool(costs) {
16680
- const m = /* @__PURE__ */ new Map();
16681
- for (const c of costs) m.set(c.name, (m.get(c.name) ?? 0) + c.tokens);
16682
- return aggregateSortedDesc(m);
16683
- }
16684
- function tokensByFile(costs) {
16685
- const m = /* @__PURE__ */ new Map();
16686
- for (const c of costs) {
16687
- if (c.filePath === null) continue;
16688
- m.set(c.filePath, (m.get(c.filePath) ?? 0) + c.tokens);
16689
- }
16690
- return aggregateSortedDesc(m);
16691
- }
16692
- function topExpensiveCalls(costs, n) {
16693
- return [...costs].sort((a, b) => b.tokens - a.tokens).slice(0, n);
16694
- }
16695
- function neverTouchedAgain(costs) {
16696
- const readsByFile = /* @__PURE__ */ new Map();
16697
- for (const c of costs) {
16698
- if (c.name !== "Read" || c.filePath === null) continue;
16699
- const arr = readsByFile.get(c.filePath) ?? [];
16700
- arr.push(c);
16701
- readsByFile.set(c.filePath, arr);
16702
- }
16703
- const out2 = [];
16704
- for (const [filePath, reads] of readsByFile) {
16705
- const firstSeq = Math.min(...reads.map((r) => r.seq));
16706
- const touchedLater = costs.some((c) => {
16707
- if (c.seq <= firstSeq) return false;
16708
- if (c.filePath === filePath) return true;
16709
- return safeStringify(c.input).includes(filePath);
16710
- });
16711
- if (!touchedLater) {
16712
- out2.push({ filePath, tokens: reads.reduce((n, r) => n + r.tokens, 0) });
16713
- }
16714
- }
16715
- return out2.sort((a, b) => b.tokens - a.tokens);
16716
- }
16717
- async function repeatedUncompressedBashCommands(costs) {
16718
- const groups = /* @__PURE__ */ new Map();
16719
- for (const c of costs) {
16720
- if (c.name !== "Bash" || c.command === null) continue;
16721
- const norm = normalizeCommandForCacheKey(c.command);
16722
- const arr = groups.get(norm) ?? [];
16723
- arr.push(c);
16724
- groups.set(norm, arr);
16725
- }
16726
- const out2 = [];
16727
- for (const [norm, calls] of groups) {
16728
- if (calls.length < 2) continue;
16729
- let cached = false;
16730
- for (const c of calls) {
16731
- try {
16732
- const hash = await commandHash(c.command, c.cwd);
16733
- if (getBashOutput(hash) !== null) {
16734
- cached = true;
16735
- break;
16736
- }
16737
- } catch {
16738
- }
16739
- }
16740
- if (cached) continue;
16741
- const totalTokens = calls.reduce((n, c) => n + c.tokens, 0);
16742
- out2.push({
16743
- normalized: norm,
16744
- count: calls.length,
16745
- totalTokens,
16746
- avgTokens: Math.round(totalTokens / calls.length)
16747
- });
16748
- }
16749
- return out2.sort((a, b) => b.totalTokens - a.totalTokens);
16750
- }
16751
- function assistantOutputCost(assistantTurns) {
16752
- const turnCount = assistantTurns.length;
16753
- let generatedTokens = 0;
16754
- let resendCeilingTokens = 0;
16755
- for (let i = 0; i < turnCount; i++) {
16756
- const tokens = assistantTurns[i] ?? 0;
16757
- generatedTokens += tokens;
16758
- resendCeilingTokens += tokens * (turnCount - 1 - i);
16759
- }
16760
- return { turnCount, generatedTokens, resendCeilingTokens };
16761
- }
16762
- async function buildWasteReport(transcriptPath, opts = {}) {
16763
- const parsed = parseTranscript2(transcriptPath);
16764
- const costs = costPerCall(parsed);
16765
- const topN = opts.topN ?? 10;
16766
- return {
16767
- transcriptPath,
16768
- totalTokens: costs.reduce((n, c) => n + c.tokens, 0),
16769
- tokensByTool: tokensByTool(costs),
16770
- tokensByFile: tokensByFile(costs),
16771
- topCalls: topExpensiveCalls(costs, topN).map((c) => ({ seq: c.seq, name: c.name, summary: c.summary, tokens: c.tokens })),
16772
- neverTouchedAgain: neverTouchedAgain(costs),
16773
- repeatedUncompressedBash: await repeatedUncompressedBashCommands(costs),
16774
- assistantOutput: assistantOutputCost(parsed.assistantTurns),
16775
- residentContext: summarizeResidentContext(parsed.resident)
16776
- };
16777
- }
16778
-
16779
- // src/session_read.ts
17122
+ import * as readline2 from "node:readline";
16780
17123
  function resolveSessionTranscript(arg, opts = {}) {
16781
17124
  if (arg !== void 0 && arg !== "") {
16782
- if (fs36.existsSync(arg) && fs36.statSync(arg).isFile()) return arg;
17125
+ if (fs35.existsSync(arg) && fs35.statSync(arg).isFile()) return arg;
16783
17126
  const projectRoot2 = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
16784
17127
  const dir = projectTranscriptsDir(projectRoot2);
16785
17128
  const byId = arg.endsWith(".jsonl") ? arg : `${arg}.jsonl`;
16786
- const candidate = path33.join(dir, byId);
16787
- if (fs36.existsSync(candidate) && fs36.statSync(candidate).isFile()) return candidate;
17129
+ const candidate = path32.join(dir, byId);
17130
+ if (fs35.existsSync(candidate) && fs35.statSync(candidate).isFile()) return candidate;
16788
17131
  return null;
16789
17132
  }
16790
17133
  const projectRoot = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
@@ -16842,7 +17185,7 @@ function toolCallsForBlocks(blocks) {
16842
17185
  return blocks.filter((b) => b.type === "tool_use" && b.name !== void 0).map((b) => b.name);
16843
17186
  }
16844
17187
  async function* streamTurns(transcriptPath) {
16845
- const input = fs36.createReadStream(transcriptPath, { encoding: "utf8" });
17188
+ const input = fs35.createReadStream(transcriptPath, { encoding: "utf8" });
16846
17189
  const rl = readline2.createInterface({ input, crlfDelay: Infinity });
16847
17190
  try {
16848
17191
  let lineNumber = 0;
@@ -16941,9 +17284,9 @@ function formatSessionSlice(turns) {
16941
17284
 
16942
17285
  // src/cli_bootstrap_audit.ts
16943
17286
  init_define_import_meta_env();
16944
- import * as fs37 from "node:fs";
16945
- import * as os19 from "node:os";
16946
- import * as path34 from "node:path";
17287
+ import * as fs36 from "node:fs";
17288
+ import * as os17 from "node:os";
17289
+ import * as path33 from "node:path";
16947
17290
  import * as readline3 from "node:readline";
16948
17291
  var FRONTMATTER_KEYS = /* @__PURE__ */ new Set(["description", "tools", "allowed-tools", "allowed_tools"]);
16949
17292
  function parseBudget(name, value) {
@@ -16952,7 +17295,7 @@ function parseBudget(name, value) {
16952
17295
  return value;
16953
17296
  }
16954
17297
  async function readFrontmatter(filePath) {
16955
- const stream = fs37.createReadStream(filePath, { encoding: "utf8" });
17298
+ const stream = fs36.createReadStream(filePath, { encoding: "utf8" });
16956
17299
  const lines = readline3.createInterface({ input: stream, crlfDelay: Infinity });
16957
17300
  let first = true;
16958
17301
  let inFrontmatter = false;
@@ -16998,44 +17341,44 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
16998
17341
  let logicalRoot;
16999
17342
  let rootIsLink;
17000
17343
  try {
17001
- rootIsLink = (await fs37.promises.lstat(root)).isSymbolicLink();
17002
- logicalRoot = await fs37.promises.realpath(root);
17344
+ rootIsLink = (await fs36.promises.lstat(root)).isSymbolicLink();
17345
+ logicalRoot = await fs36.promises.realpath(root);
17003
17346
  } catch (error) {
17004
17347
  if (error.code === "ENOENT") return entries;
17005
- diagnostics.push({ path: path34.resolve(root), reason: "unreadable root" });
17348
+ diagnostics.push({ path: path33.resolve(root), reason: "unreadable root" });
17006
17349
  return entries;
17007
17350
  }
17008
17351
  if (rootIsLink && !followLinks) {
17009
- diagnostics.push({ path: path34.resolve(root), reason: "external root link skipped (use --follow-links)" });
17352
+ diagnostics.push({ path: path33.resolve(root), reason: "external root link skipped (use --follow-links)" });
17010
17353
  return entries;
17011
17354
  }
17012
17355
  const canonicalKey = (candidate) => process.platform === "win32" ? candidate.toLocaleLowerCase() : candidate;
17013
17356
  const isContained = (candidate, allowedRoot) => {
17014
- const relative3 = path34.relative(canonicalKey(allowedRoot), canonicalKey(candidate));
17015
- return relative3 === "" || relative3 !== ".." && !relative3.startsWith(`..${path34.sep}`) && !path34.isAbsolute(relative3);
17357
+ const relative3 = path33.relative(canonicalKey(allowedRoot), canonicalKey(candidate));
17358
+ return relative3 === "" || relative3 !== ".." && !relative3.startsWith(`..${path33.sep}`) && !path33.isAbsolute(relative3);
17016
17359
  };
17017
17360
  async function visit(candidate, allowedRoot, topLevel = false) {
17018
17361
  let real;
17019
17362
  let candidateIsLink;
17020
17363
  try {
17021
- candidateIsLink = (await fs37.promises.lstat(candidate)).isSymbolicLink();
17022
- real = await fs37.promises.realpath(candidate);
17364
+ candidateIsLink = (await fs36.promises.lstat(candidate)).isSymbolicLink();
17365
+ real = await fs36.promises.realpath(candidate);
17023
17366
  } catch {
17024
- diagnostics.push({ path: path34.resolve(candidate), reason: "broken or unreadable link" });
17367
+ diagnostics.push({ path: path33.resolve(candidate), reason: "broken or unreadable link" });
17025
17368
  return;
17026
17369
  }
17027
17370
  const withinAllowedRoot = allowedRoot === void 0 ? isContained(real, logicalRoot) : isContained(real, allowedRoot);
17028
17371
  const canFollowExternal = followLinks && topLevel;
17029
17372
  if (!withinAllowedRoot && !canFollowExternal) {
17030
- diagnostics.push({ path: path34.resolve(candidate), reason: candidateIsLink ? "external link skipped (use --follow-links)" : "canonical target outside scan root" });
17373
+ diagnostics.push({ path: path33.resolve(candidate), reason: candidateIsLink ? "external link skipped (use --follow-links)" : "canonical target outside scan root" });
17031
17374
  return;
17032
17375
  }
17033
17376
  const trustedRoot = !withinAllowedRoot && canFollowExternal ? real : allowedRoot ?? real;
17034
17377
  let stat;
17035
17378
  try {
17036
- stat = await fs37.promises.stat(real);
17379
+ stat = await fs36.promises.stat(real);
17037
17380
  } catch {
17038
- diagnostics.push({ path: path34.resolve(candidate), reason: "unreadable" });
17381
+ diagnostics.push({ path: path33.resolve(candidate), reason: "unreadable" });
17039
17382
  return;
17040
17383
  }
17041
17384
  const canonical = process.platform === "win32" ? real.toLocaleLowerCase() : real;
@@ -17044,16 +17387,16 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
17044
17387
  visitedDirs.add(canonical);
17045
17388
  let children;
17046
17389
  try {
17047
- children = await fs37.promises.readdir(real, { withFileTypes: true });
17390
+ children = await fs36.promises.readdir(real, { withFileTypes: true });
17048
17391
  } catch {
17049
- diagnostics.push({ path: path34.resolve(candidate), reason: "unreadable directory" });
17392
+ diagnostics.push({ path: path33.resolve(candidate), reason: "unreadable directory" });
17050
17393
  return;
17051
17394
  }
17052
17395
  children.sort((a, b) => a.name.localeCompare(b.name));
17053
- for (const child of children) await visit(path34.join(real, child.name), trustedRoot);
17396
+ for (const child of children) await visit(path33.join(real, child.name), trustedRoot);
17054
17397
  return;
17055
17398
  }
17056
- if (!stat.isFile() || path34.extname(real).toLowerCase() !== ".md") return;
17399
+ if (!stat.isFile() || path33.extname(real).toLowerCase() !== ".md") return;
17057
17400
  if (seenFiles.has(canonical)) return;
17058
17401
  seenFiles.add(canonical);
17059
17402
  try {
@@ -17063,35 +17406,35 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
17063
17406
  const toolsBytes = Buffer.byteLength(metadata.tools, "utf8");
17064
17407
  entries.push({
17065
17408
  kind,
17066
- path: path34.resolve(real),
17409
+ path: path33.resolve(real),
17067
17410
  description_bytes: descriptionBytes,
17068
17411
  tools_bytes: toolsBytes,
17069
17412
  metadata_bytes: descriptionBytes + toolsBytes,
17070
17413
  estimated_tokens: Math.floor((descriptionBytes + toolsBytes) / 4)
17071
17414
  });
17072
17415
  } catch {
17073
- diagnostics.push({ path: path34.resolve(candidate), reason: "unreadable file" });
17416
+ diagnostics.push({ path: path33.resolve(candidate), reason: "unreadable file" });
17074
17417
  }
17075
17418
  }
17076
17419
  try {
17077
- const children = await fs37.promises.readdir(logicalRoot, { withFileTypes: true });
17420
+ const children = await fs36.promises.readdir(logicalRoot, { withFileTypes: true });
17078
17421
  children.sort((a, b) => a.name.localeCompare(b.name));
17079
- for (const child of children) await visit(path34.join(logicalRoot, child.name), void 0, true);
17422
+ for (const child of children) await visit(path33.join(logicalRoot, child.name), void 0, true);
17080
17423
  } catch {
17081
- diagnostics.push({ path: path34.resolve(root), reason: "unreadable root" });
17424
+ diagnostics.push({ path: path33.resolve(root), reason: "unreadable root" });
17082
17425
  }
17083
17426
  return entries;
17084
17427
  }
17085
17428
  async function buildBootstrapAudit(opts = {}) {
17086
17429
  const project = resolveProjectRoot(opts.project === void 0 ? {} : { project: opts.project });
17087
- const home = path34.resolve(opts.home ?? os19.homedir());
17430
+ const home = path33.resolve(opts.home ?? os17.homedir());
17088
17431
  const context = buildStats(project, home, opts.project === void 0 ? [] : [opts.project]);
17089
17432
  const diagnostics = [];
17090
17433
  const top = opts.top === void 0 ? 10 : parseBudget("--top", opts.top) ?? 10;
17091
17434
  const visitedDirs = /* @__PURE__ */ new Set();
17092
17435
  const seenFiles = /* @__PURE__ */ new Set();
17093
- const agents = await scanMetadataRoot(path34.join(home, ".claude", "agents"), "agent", diagnostics, visitedDirs, seenFiles, opts.followLinks === true);
17094
- const skills = await scanMetadataRoot(path34.join(home, ".claude", "skills"), "skill", diagnostics, visitedDirs, seenFiles, opts.followLinks === true);
17436
+ const agents = await scanMetadataRoot(path33.join(claudeConfigDir(home), "agents"), "agent", diagnostics, visitedDirs, seenFiles, opts.followLinks === true);
17437
+ const skills = await scanMetadataRoot(path33.join(claudeConfigDir(home), "skills"), "skill", diagnostics, visitedDirs, seenFiles, opts.followLinks === true);
17095
17438
  const rankedEntries = [...agents, ...skills].sort((a, b) => b.metadata_bytes - a.metadata_bytes || a.path.localeCompare(b.path));
17096
17439
  const largestTotal = rankedEntries.length;
17097
17440
  const largest = rankedEntries.slice(0, top);
@@ -17158,8 +17501,8 @@ async function runBootstrapAudit(opts = {}) {
17158
17501
 
17159
17502
  // src/cli_memory.ts
17160
17503
  init_define_import_meta_env();
17161
- import * as fs38 from "node:fs";
17162
- import * as path35 from "node:path";
17504
+ import * as fs37 from "node:fs";
17505
+ import * as path34 from "node:path";
17163
17506
  function removeExactDupLines(text, report) {
17164
17507
  if (report.exactDupLines.length === 0) return text;
17165
17508
  const toRemove = new Set(report.exactDupLines.map(([, dupIdx]) => dupIdx));
@@ -17217,7 +17560,7 @@ function printReport(reports, clusters) {
17217
17560
  } else {
17218
17561
  for (const cluster of clusters) {
17219
17562
  w(
17220
- ` [${cluster.method}, similarity ${cluster.similarity}, ~${cluster.tokens} tok] ${cluster.members.map((m) => displaySafeText(path35.basename(m))).join(", ")}
17563
+ ` [${cluster.method}, similarity ${cluster.similarity}, ~${cluster.tokens} tok] ${cluster.members.map((m) => displaySafeText(path34.basename(m))).join(", ")}
17221
17564
  `
17222
17565
  );
17223
17566
  }
@@ -17232,7 +17575,7 @@ async function runMemoryCommand(opts = {}) {
17232
17575
  const claudeMds = findClaudeMdFiles(projectRoot);
17233
17576
  const reports = auditClaudeMd(claudeMds);
17234
17577
  const memoryMdPath = findMemoryMd(projectRoot);
17235
- const clusters = memoryMdPath !== null ? await findContentDuplicates(path35.dirname(memoryMdPath)) : [];
17578
+ const clusters = memoryMdPath !== null ? await findContentDuplicates(path34.dirname(memoryMdPath)) : [];
17236
17579
  printReport(reports, clusters);
17237
17580
  if (opts.fix !== true) return;
17238
17581
  const changes = [];
@@ -17240,7 +17583,7 @@ async function runMemoryCommand(opts = {}) {
17240
17583
  if (report.exactDupLines.length === 0) continue;
17241
17584
  let before;
17242
17585
  try {
17243
- before = fs38.readFileSync(report.path, "utf-8");
17586
+ before = fs37.readFileSync(report.path, "utf-8");
17244
17587
  } catch {
17245
17588
  continue;
17246
17589
  }
@@ -17278,13 +17621,13 @@ async function runMemoryCommand(opts = {}) {
17278
17621
 
17279
17622
  // src/cli_waste.ts
17280
17623
  init_define_import_meta_env();
17281
- import * as fs40 from "node:fs";
17282
- import * as path37 from "node:path";
17624
+ import * as fs39 from "node:fs";
17625
+ import * as path36 from "node:path";
17283
17626
 
17284
17627
  // src/copilot_waste.ts
17285
17628
  init_define_import_meta_env();
17286
- import * as fs39 from "node:fs";
17287
- import * as path36 from "node:path";
17629
+ import * as fs38 from "node:fs";
17630
+ import * as path35 from "node:path";
17288
17631
  var HOOK_RECORD_TYPES = /* @__PURE__ */ new Set(["hook.start", "hook.end"]);
17289
17632
  function splitInjectedBlocks(transformed) {
17290
17633
  const out2 = [];
@@ -17303,19 +17646,19 @@ function splitInjectedBlocks(transformed) {
17303
17646
  return out2;
17304
17647
  }
17305
17648
  function findLatestCopilotSession() {
17306
- const root = path36.join(copilotCliUserRoot(), "session-state");
17649
+ const root = path35.join(copilotCliUserRoot(), "session-state");
17307
17650
  let entries;
17308
17651
  try {
17309
- entries = fs39.readdirSync(root);
17652
+ entries = fs38.readdirSync(root);
17310
17653
  } catch {
17311
17654
  return null;
17312
17655
  }
17313
17656
  let best = null;
17314
17657
  let bestMtime = -Infinity;
17315
17658
  for (const entry of entries) {
17316
- const candidate = path36.join(root, entry, "events.jsonl");
17659
+ const candidate = path35.join(root, entry, "events.jsonl");
17317
17660
  try {
17318
- const st = fs39.statSync(candidate);
17661
+ const st = fs38.statSync(candidate);
17319
17662
  if (!st.isFile()) continue;
17320
17663
  if (st.mtimeMs > bestMtime) {
17321
17664
  bestMtime = st.mtimeMs;
@@ -17331,10 +17674,10 @@ function readNumber(source, key) {
17331
17674
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
17332
17675
  }
17333
17676
  function buildCopilotWasteReport(eventsPath) {
17334
- const raw = fs39.readFileSync(eventsPath, "utf-8");
17677
+ const raw = fs38.readFileSync(eventsPath, "utf-8");
17335
17678
  const report = {
17336
17679
  sessionPath: eventsPath,
17337
- sessionId: path36.basename(path36.dirname(eventsPath)),
17680
+ sessionId: path35.basename(path35.dirname(eventsPath)),
17338
17681
  turns: 0,
17339
17682
  tokens: null,
17340
17683
  blocks: [],
@@ -17613,8 +17956,8 @@ function printResidentContext(resident, w) {
17613
17956
  }
17614
17957
  async function runWasteCommand(opts = {}) {
17615
17958
  if (opts.copilot === true) {
17616
- const eventsPath = opts.transcript !== void 0 ? path37.resolve(opts.transcript) : findLatestCopilotSession();
17617
- if (eventsPath === null || !fs40.existsSync(eventsPath)) {
17959
+ const eventsPath = opts.transcript !== void 0 ? path36.resolve(opts.transcript) : findLatestCopilotSession();
17960
+ if (eventsPath === null || !fs39.existsSync(eventsPath)) {
17618
17961
  const detail = eventsPath === null ? "no Copilot CLI session found under <copilot-home>/session-state" : `Copilot session event log not found: ${eventsPath}`;
17619
17962
  if (opts.json === true) {
17620
17963
  process.stdout.write(`${displaySafeJson({ error: detail }, 0)}
@@ -17637,7 +17980,7 @@ async function runWasteCommand(opts = {}) {
17637
17980
  return;
17638
17981
  }
17639
17982
  const projectRoot = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
17640
- const transcriptPath = opts.transcript !== void 0 ? path37.resolve(opts.transcript) : findLatestTranscript(projectRoot);
17983
+ const transcriptPath = opts.transcript !== void 0 ? path36.resolve(opts.transcript) : findLatestTranscript(projectRoot);
17641
17984
  if (transcriptPath === null) {
17642
17985
  if (opts.json === true) {
17643
17986
  process.stdout.write(`${displaySafeJson({ error: "no session transcript found", project: projectRoot }, 0)}
@@ -17651,7 +17994,7 @@ async function runWasteCommand(opts = {}) {
17651
17994
  process.exitCode = 1;
17652
17995
  return;
17653
17996
  }
17654
- if (!fs40.existsSync(transcriptPath)) {
17997
+ if (!fs39.existsSync(transcriptPath)) {
17655
17998
  process.stderr.write(`token-goat: transcript not found: ${transcriptPath}
17656
17999
  `);
17657
18000
  process.exitCode = 1;
@@ -17668,13 +18011,13 @@ async function runWasteCommand(opts = {}) {
17668
18011
 
17669
18012
  // src/cli_mcp_audit.ts
17670
18013
  init_define_import_meta_env();
17671
- import * as fs41 from "node:fs";
17672
- import * as os20 from "node:os";
17673
- import * as path38 from "node:path";
18014
+ import * as fs40 from "node:fs";
18015
+ import * as os18 from "node:os";
18016
+ import * as path37 from "node:path";
17674
18017
  function readMcpJsonFile(configPath2) {
17675
18018
  try {
17676
- if (!fs41.existsSync(configPath2)) return null;
17677
- const content = fs41.readFileSync(configPath2, "utf-8");
18019
+ if (!fs40.existsSync(configPath2)) return null;
18020
+ const content = fs40.readFileSync(configPath2, "utf-8");
17678
18021
  const parsed = JSON.parse(content);
17679
18022
  const servers = parsed && typeof parsed === "object" ? parsed.mcpServers ?? ("servers" in parsed ? null : parsed) : null;
17680
18023
  return servers && typeof servers === "object" ? servers : null;
@@ -17690,8 +18033,8 @@ function driveLetterCaseVariants(p) {
17690
18033
  }
17691
18034
  function readClaudeJsonConfig(claudeJsonPath, projectRoot) {
17692
18035
  try {
17693
- if (!fs41.existsSync(claudeJsonPath)) return null;
17694
- const parsed = JSON.parse(fs41.readFileSync(claudeJsonPath, "utf-8"));
18036
+ if (!fs40.existsSync(claudeJsonPath)) return null;
18037
+ const parsed = JSON.parse(fs40.readFileSync(claudeJsonPath, "utf-8"));
17695
18038
  const projects = parsed && typeof parsed === "object" ? parsed.projects : null;
17696
18039
  if (!projects || typeof projects !== "object") return null;
17697
18040
  const slashForms = [projectRoot, projectRoot.replace(/\\/g, "/"), projectRoot.replace(/\//g, "\\")];
@@ -17709,9 +18052,9 @@ function readClaudeJsonConfig(claudeJsonPath, projectRoot) {
17709
18052
  }
17710
18053
  }
17711
18054
  function discoverMcpConfig(projectRoot, home) {
17712
- const mcpJsonPath = path38.join(projectRoot, ".mcp.json");
17713
- const claudeJsonPath = path38.join(home, ".claude.json");
17714
- const copilotJsonPath = path38.join(home, ".copilot", "mcp-config.json");
18055
+ const mcpJsonPath = path37.join(projectRoot, ".mcp.json");
18056
+ const claudeJsonPath = path37.join(home, ".claude.json");
18057
+ const copilotJsonPath = path37.join(home, ".copilot", "mcp-config.json");
17715
18058
  const sourcesChecked = [mcpJsonPath, claudeJsonPath, copilotJsonPath];
17716
18059
  const fromMcpJson = readMcpJsonFile(mcpJsonPath);
17717
18060
  if (fromMcpJson !== null) return { servers: fromMcpJson, sourcePath: mcpJsonPath, sourcesChecked };
@@ -17742,7 +18085,7 @@ function analyzeMcpCache() {
17742
18085
  }
17743
18086
  return serverMetrics;
17744
18087
  }
17745
- function buildMcpAuditReport(projectRoot, home = os20.homedir()) {
18088
+ function buildMcpAuditReport(projectRoot, home = os18.homedir()) {
17746
18089
  const discovery = discoverMcpConfig(projectRoot, home);
17747
18090
  const config = discovery.servers;
17748
18091
  const cacheMetrics = analyzeMcpCache();
@@ -17828,69 +18171,6 @@ async function runMcpAuditCommand(opts = {}) {
17828
18171
  printReport3(report);
17829
18172
  }
17830
18173
 
17831
- // src/cli_recall.ts
17832
- init_define_import_meta_env();
17833
- var RECALL_COMMAND = {
17834
- bash: "bash-output",
17835
- web: "web-output",
17836
- mcp: "mcp-output"
17837
- };
17838
- function fenceTagForCacheType(cacheType) {
17839
- return cacheType === "web" ? UNTRUSTED_WEB_TAG : UNTRUSTED_TOOL_TAG;
17840
- }
17841
- function fenceRecallListing(text, hits) {
17842
- const tags = new Set(hits.map((hit) => fenceTagForCacheType(hit.cacheType)));
17843
- const tag = tags.size === 1 ? [...tags][0] ?? UNTRUSTED_TOOL_TAG : UNTRUSTED_TOOL_TAG;
17844
- return fenceUntrusted(text, tag);
17845
- }
17846
- function fenceSnippetIfMatched(hit) {
17847
- const matches2 = scanAndRecord(hit.snippet);
17848
- if (matches2.length === 0) return hit.snippet;
17849
- return fenceUntrustedContent(hit.snippet, matches2, fenceTagForCacheType(hit.cacheType));
17850
- }
17851
- function printHits(query, hits) {
17852
- const w = (text) => {
17853
- process.stdout.write(text);
17854
- };
17855
- if (hits.length === 0) {
17856
- w(query === void 0 ? "No cache entries yet.\n" : `No cache entries match: ${query}
17857
- `);
17858
- return;
17859
- }
17860
- const body = hits.map((hit) => {
17861
- const label = hit.label.length > 80 ? hit.label.slice(0, 77) + "..." : hit.label;
17862
- const pointer = hit.blobPresent ? ` (token-goat ${RECALL_COMMAND[hit.cacheType]} ${hit.id})` : " (blob expired -- content below is all that remains)";
17863
- return `[${pad(hit.cacheType, 4)}] ${hit.id}
17864
- ${pointer}
17865
- ${label}
17866
- ${hit.snippet}
17867
- `;
17868
- }).join("\n");
17869
- w(`${fenceRecallListing(body, hits)}
17870
- `);
17871
- }
17872
- function runRecallCommand(query, opts = {}) {
17873
- const browse = query === void 0 || query.trim() === "";
17874
- const scope = {
17875
- ...opts.type !== void 0 ? { type: opts.type } : {},
17876
- ...opts.limit !== void 0 ? { limit: opts.limit } : {}
17877
- };
17878
- const effectiveLimit = opts.limit ?? RECALL_DEFAULT_LIMIT;
17879
- const fetched = browse ? listRecentRecall({ ...scope, limit: effectiveLimit + 1 }) : searchRecall(query, { ...scope, limit: effectiveLimit + 1 });
17880
- const hits = fetched.slice(0, effectiveLimit);
17881
- if (fetched.length > hits.length) {
17882
- process.stderr.write(`Showing ${hits.length} entries; more are available (raise --limit to see them).
17883
- `);
17884
- }
17885
- if (opts.json === true) {
17886
- const fenced = hits.map((hit) => ({ ...hit, snippet: fenceSnippetIfMatched(hit) }));
17887
- process.stdout.write(`${displaySafeJson(fenced, 0)}
17888
- `);
17889
- return;
17890
- }
17891
- printHits(browse ? void 0 : query, hits);
17892
- }
17893
-
17894
18174
  // src/cli_statusline.ts
17895
18175
  init_define_import_meta_env();
17896
18176
  var STDIN_TIMEOUT_MS = 1500;
@@ -17986,14 +18266,20 @@ function efficacyCell(row) {
17986
18266
  const pct3 = row.efficacyPct === null ? "n/a" : `${row.efficacyPct}%`;
17987
18267
  return isSuppressionCategory(row.category) ? `${pct3} *` : pct3;
17988
18268
  }
18269
+ function emittedCell(row) {
18270
+ return row.unobservable > 0 ? `${row.emitted} ~${row.unobservable}` : String(row.emitted);
18271
+ }
18272
+ function detectedCell(row) {
18273
+ return row.detected === 0 ? "-" : String(row.detected);
18274
+ }
17989
18275
  function printSummary(rows) {
17990
18276
  const w = (text) => {
17991
18277
  process.stdout.write(text);
17992
18278
  };
17993
- w(pad("category", 22) + pad("emitted", 9) + pad("acted-on", 10) + pad("efficacy", 12) + pad("suppressed", 17) + pad("manual+", 9) + pad("manual-", 9) + "spent\n");
18279
+ w(pad("category", 22) + pad("emitted", 9) + pad("undisplayed", 13) + pad("acted-on", 10) + pad("efficacy", 12) + pad("suppressed", 17) + pad("manual+", 9) + pad("manual-", 9) + "spent\n");
17994
18280
  for (const row of rows) {
17995
18281
  w(
17996
- pad(row.category, 22) + pad(String(row.emitted), 9) + pad(String(row.actedOn), 10) + pad(efficacyCell(row), 12) + pad(suppressedCell(row), 17) + pad(String(row.manualEffective), 9) + pad(String(row.manualIneffective), 9) + formatSpentCell(row) + "\n"
18282
+ pad(row.category, 22) + pad(emittedCell(row), 9) + pad(detectedCell(row), 13) + pad(String(row.actedOn), 10) + pad(efficacyCell(row), 12) + pad(suppressedCell(row), 17) + pad(String(row.manualEffective), 9) + pad(String(row.manualIneffective), 9) + formatSpentCell(row) + "\n"
17997
18283
  );
17998
18284
  }
17999
18285
  }
@@ -18030,7 +18316,7 @@ function runHintStatsCommand(opts = {}) {
18030
18316
  `);
18031
18317
  return;
18032
18318
  }
18033
- if (rows.every((r) => r.emitted === 0 && r.actedOn === 0)) {
18319
+ if (rows.every((r) => r.emitted === 0 && r.actedOn === 0 && r.unobservable === 0 && r.detected === 0)) {
18034
18320
  process.stdout.write("No hint emissions recorded yet \u2014 the zeros below are absence of data, not measured ineffectiveness.\n");
18035
18321
  }
18036
18322
  printSummary(rows);
@@ -18039,6 +18325,16 @@ function runHintStatsCommand(opts = {}) {
18039
18325
  '\n* Scored on an absence: these hints ask the agent NOT to do something, so a window that expires with no re-read counts as compliance. Their percentage is not comparable with the unmarked rows, which only count when the agent runs the command the hint named. A high starred figure means "the warned-against read was not seen", not "this hint persuaded anyone".\n'
18040
18326
  );
18041
18327
  }
18328
+ if (rows.some((r) => r.detected > 0)) {
18329
+ process.stdout.write(
18330
+ "\nundisplayed: detections that never reached the agent -- auto-suppressed, or declined by the hint's own net-benefit gate because the substitute it would have proposed was not smaller than the read it was redirecting. They cost nothing and are scored nowhere. A large figure against a small emitted count means the trigger is alive and the mute is doing the work; a `-` means it has gone quiet on its own.\n"
18331
+ );
18332
+ }
18333
+ if (rows.some((r) => r.unobservable > 0)) {
18334
+ process.stdout.write(
18335
+ "\n~N: N further emissions in that category carried no correlator -- no pointer a later command could have matched -- so no verdict about them was ever observed. They are not in the emitted or acted-on counts and not in the efficacy figure, which is scored only on the rows that could have gone either way. They ARE in the spend column: an unobservable hint costs the agent its bytes all the same.\n"
18336
+ );
18337
+ }
18042
18338
  const permanent = rows.filter((r) => r.suppressionPermanent);
18043
18339
  if (permanent.length > 0) {
18044
18340
  const names = permanent.map((r) => r.category).join(", ");
@@ -18104,7 +18400,7 @@ ${formatSessionOutline(turns)}`;
18104
18400
  }
18105
18401
  function sessionTranscriptSize(transcriptPath) {
18106
18402
  try {
18107
- return fs42.statSync(transcriptPath).size;
18403
+ return fs41.statSync(transcriptPath).size;
18108
18404
  } catch {
18109
18405
  return 0;
18110
18406
  }
@@ -18164,14 +18460,14 @@ function cmdHintStats(opts = {}) {
18164
18460
 
18165
18461
  // src/cli_skills.ts
18166
18462
  init_define_import_meta_env();
18167
- import * as fs43 from "node:fs";
18168
- import * as path39 from "node:path";
18463
+ import * as fs42 from "node:fs";
18464
+ import * as path38 from "node:path";
18169
18465
  async function cmdSkillBody(name, opts) {
18170
18466
  const filePath = await getSkillFilePath(name);
18171
18467
  if (filePath === null) {
18172
18468
  throw new CliError(`skill '${name}' not found`);
18173
18469
  }
18174
- const body = decodeSource(fs43.readFileSync(filePath));
18470
+ const body = decodeSource(fs42.readFileSync(filePath));
18175
18471
  if (opts.compact === true) {
18176
18472
  const emitted = extractCompactFromMarker(body) ?? body;
18177
18473
  out(emitted);
@@ -18198,7 +18494,7 @@ async function cmdSkillCompact(name, opts) {
18198
18494
  }
18199
18495
  let body2;
18200
18496
  try {
18201
- body2 = decodeSource(fs43.readFileSync(filePath));
18497
+ body2 = decodeSource(fs42.readFileSync(filePath));
18202
18498
  } catch {
18203
18499
  unresolvable++;
18204
18500
  continue;
@@ -18232,19 +18528,19 @@ async function cmdSkillCompact(name, opts) {
18232
18528
  if (!opts.path.trim()) {
18233
18529
  throw new CliError("--path cannot be empty");
18234
18530
  }
18235
- if (!fs43.existsSync(opts.path)) {
18531
+ if (!fs42.existsSync(opts.path)) {
18236
18532
  throw new CliError(`skill file not found: ${opts.path}`);
18237
18533
  }
18238
18534
  try {
18239
- body = fs43.readFileSync(opts.path, "utf-8");
18535
+ body = fs42.readFileSync(opts.path, "utf-8");
18240
18536
  } catch (e) {
18241
18537
  if (e.code === "ENOENT") {
18242
18538
  throw new CliError(`skill file not found: ${opts.path}`);
18243
18539
  }
18244
18540
  throw new CliError(`failed to read skill file '${opts.path}': ${extractErrorMessage(e)}`);
18245
18541
  }
18246
- cacheName = name ?? path39.basename(path39.dirname(path39.resolve(opts.path)));
18247
- sourcePath = path39.resolve(opts.path);
18542
+ cacheName = name ?? path38.basename(path38.dirname(path38.resolve(opts.path)));
18543
+ sourcePath = path38.resolve(opts.path);
18248
18544
  } else {
18249
18545
  if (name === void 0 || !name.trim()) {
18250
18546
  throw new CliError("skill-compact requires a <name> or --path <file>");
@@ -18253,7 +18549,7 @@ async function cmdSkillCompact(name, opts) {
18253
18549
  if (filePath === null) {
18254
18550
  throw new CliError(`skill '${name}' not found`);
18255
18551
  }
18256
- body = fs43.readFileSync(filePath, "utf-8");
18552
+ body = fs42.readFileSync(filePath, "utf-8");
18257
18553
  cacheName = name;
18258
18554
  sourcePath = filePath;
18259
18555
  }
@@ -18385,8 +18681,8 @@ async function cmdSkillDiff(name) {
18385
18681
  }
18386
18682
  const newer = versions[0];
18387
18683
  const older = versions[1];
18388
- const newerBody = await fs43.promises.readFile(path39.resolve(dir, `${newer.outputId}.txt`), "utf-8").catch(() => null);
18389
- const olderBody = await fs43.promises.readFile(path39.resolve(dir, `${older.outputId}.txt`), "utf-8").catch(() => null);
18684
+ const newerBody = await fs42.promises.readFile(path38.resolve(dir, `${newer.outputId}.txt`), "utf-8").catch(() => null);
18685
+ const olderBody = await fs42.promises.readFile(path38.resolve(dir, `${older.outputId}.txt`), "utf-8").catch(() => null);
18390
18686
  if (newerBody === null || olderBody === null) {
18391
18687
  out(`a cached version of '${name}' was evicted while diffing -- try again`);
18392
18688
  return;
@@ -18415,7 +18711,7 @@ async function cmdSkillSection(nameHeading, headingArg) {
18415
18711
  if (!filePath) {
18416
18712
  throw new CliError(`skill '${skillName}' not found`);
18417
18713
  }
18418
- const body = decodeSource(fs43.readFileSync(filePath));
18714
+ const body = decodeSource(fs42.readFileSync(filePath));
18419
18715
  const extracted = extractNamedSection(body, heading);
18420
18716
  if (!extracted) {
18421
18717
  const allHeadings = listSections(filePath);
@@ -18520,8 +18816,8 @@ async function buildResumePacket(sessionId) {
18520
18816
 
18521
18817
  // src/cli_stats.ts
18522
18818
  init_define_import_meta_env();
18523
- import * as fs44 from "node:fs";
18524
- import * as path40 from "node:path";
18819
+ import * as fs43 from "node:fs";
18820
+ import * as path39 from "node:path";
18525
18821
  function writeRaw(text) {
18526
18822
  const payload = colorStdout() ? text : stripAnsi(text);
18527
18823
  process.stdout.write(ensureNewline(payload));
@@ -18533,8 +18829,8 @@ function formatTopFiles(ranked) {
18533
18829
  if (ranked.length === 0) return "";
18534
18830
  const lines = ["Top files this session:"];
18535
18831
  for (const { path: filePath, count } of ranked) {
18536
- const basename10 = path40.basename(filePath);
18537
- lines.push(` ${count.toString().padStart(3)}x ${basename10} (${filePath})`);
18832
+ const basename11 = path39.basename(filePath);
18833
+ lines.push(` ${count.toString().padStart(3)}x ${basename11} (${filePath})`);
18538
18834
  }
18539
18835
  return lines.join("\n");
18540
18836
  }
@@ -18550,12 +18846,12 @@ function renderTopSessionFiles(topN = 5) {
18550
18846
  }
18551
18847
  function renderTopSessionFilesFromDisk(topN = 5, overrideSessionsDir) {
18552
18848
  try {
18553
- const sessionsDir = overrideSessionsDir ?? path40.join(dataDir(), "sessions");
18554
- if (!fs44.existsSync(sessionsDir)) return "";
18555
- const files = fs44.readdirSync(sessionsDir).filter((f) => f.endsWith(".json")).map((f) => ({ name: f, mtime: fs44.statSync(path40.join(sessionsDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).slice(0, 3);
18849
+ const dir = overrideSessionsDir ?? sessionsDir();
18850
+ if (!fs43.existsSync(dir)) return "";
18851
+ const files = fs43.readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => ({ name: f, mtime: fs43.statSync(path39.join(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).slice(0, 3);
18556
18852
  for (const { name } of files) {
18557
18853
  try {
18558
- const raw = fs44.readFileSync(path40.join(sessionsDir, name), "utf-8");
18854
+ const raw = fs43.readFileSync(path39.join(dir, name), "utf-8");
18559
18855
  const data = JSON.parse(raw);
18560
18856
  const filesList = data["files"];
18561
18857
  if (!Array.isArray(filesList)) continue;
@@ -18619,6 +18915,10 @@ function runStats(opts = {}) {
18619
18915
  renderMethodology(opts.json === true);
18620
18916
  return;
18621
18917
  }
18918
+ if (opts.hooks === true) {
18919
+ renderHookLatencyStats(void 0, opts.homeDir);
18920
+ return;
18921
+ }
18622
18922
  const window = opts.windowDays ?? 30;
18623
18923
  const summary = summarize(window, void 0, opts.homeDir);
18624
18924
  if (opts.json === true) {
@@ -18643,13 +18943,13 @@ function runStats(opts = {}) {
18643
18943
  }
18644
18944
 
18645
18945
  // src/cache_session_commands.ts
18646
- function emitErr3(text) {
18946
+ function emitErr4(text) {
18647
18947
  process.stderr.write(ensureNewline(text));
18648
18948
  }
18649
18949
  function capAndNote(rows, limit) {
18650
18950
  const shown = rows.slice(0, limit);
18651
18951
  if (shown.length < rows.length) {
18652
- emitErr3(`Showing ${shown.length} of ${rows.length} entries (raise --limit to see the rest).`);
18952
+ emitErr4(`Showing ${shown.length} of ${rows.length} entries (raise --limit to see the rest).`);
18653
18953
  }
18654
18954
  return shown;
18655
18955
  }
@@ -18659,11 +18959,11 @@ function parseLimitOpt(cmdName, limitStr, dflt = 30) {
18659
18959
  try {
18660
18960
  n = requireNonNegativeStrictInt("--limit", limitStr);
18661
18961
  } catch (e) {
18662
- emitErr3(`${cmdName}: --limit must be a positive number, got: "${limitStr}"`);
18962
+ emitErr4(`${cmdName}: --limit must be a positive number, got: "${limitStr}"`);
18663
18963
  throw new Error(`invalid --limit: ${limitStr}`, { cause: e });
18664
18964
  }
18665
18965
  if (n === 0) {
18666
- emitErr3(`${cmdName}: --limit must be a positive number, got: "${limitStr}"`);
18966
+ emitErr4(`${cmdName}: --limit must be a positive number, got: "${limitStr}"`);
18667
18967
  throw new Error(`invalid --limit: ${limitStr}`);
18668
18968
  }
18669
18969
  return n;
@@ -18695,9 +18995,10 @@ function cmdBashHistory(opts) {
18695
18995
  const allItems = blobs.map(({ id, mtime, value }) => {
18696
18996
  if (typeof value !== "object" || value === null) return null;
18697
18997
  const v = value;
18998
+ if (typeof v["command"] !== "string") return null;
18698
18999
  return {
18699
19000
  id,
18700
- command: typeof v["command"] === "string" ? v["command"] : "",
19001
+ command: v["command"],
18701
19002
  storedAt: typeof v["storedAt"] === "number" ? v["storedAt"] : mtime,
18702
19003
  exitCode: typeof v["exitCode"] === "number" ? v["exitCode"] : -1,
18703
19004
  sizeBytes: typeof v["sizeBytes"] === "number" ? v["sizeBytes"] : 0
@@ -19434,25 +19735,25 @@ function installVerificationNotice(harness) {
19434
19735
 
19435
19736
  // src/purge.ts
19436
19737
  init_define_import_meta_env();
19437
- import * as fs45 from "node:fs";
19438
- import * as path41 from "node:path";
19738
+ import * as fs44 from "node:fs";
19739
+ import * as path40 from "node:path";
19439
19740
  function directorySize(root) {
19440
19741
  let total = 0;
19441
19742
  let entries;
19442
19743
  try {
19443
- entries = fs45.readdirSync(root, { withFileTypes: true });
19744
+ entries = fs44.readdirSync(root, { withFileTypes: true });
19444
19745
  } catch {
19445
19746
  return 0;
19446
19747
  }
19447
19748
  for (const entry of entries) {
19448
- const full = path41.join(root, entry.name);
19749
+ const full = path40.join(root, entry.name);
19449
19750
  if (entry.isDirectory()) {
19450
19751
  total += directorySize(full);
19451
19752
  continue;
19452
19753
  }
19453
19754
  if (!entry.isFile()) continue;
19454
19755
  try {
19455
- total += fs45.statSync(full).size;
19756
+ total += fs44.statSync(full).size;
19456
19757
  } catch {
19457
19758
  }
19458
19759
  }
@@ -19471,7 +19772,7 @@ function purgeDataDirectories() {
19471
19772
  }
19472
19773
  const bytes = directorySize(root);
19473
19774
  try {
19474
- fs45.rmSync(root, { recursive: true, force: true });
19775
+ fs44.rmSync(root, { recursive: true, force: true });
19475
19776
  result.removed.push({ path: root, bytes });
19476
19777
  } catch (e) {
19477
19778
  result.failed.push({ path: root, reason: e instanceof Error ? e.message : String(e) });
@@ -19618,8 +19919,8 @@ async function getSectionContent(fileId, heading, opts = {}) {
19618
19919
 
19619
19920
  // src/cli_bench.ts
19620
19921
  init_define_import_meta_env();
19621
- import * as fs46 from "node:fs";
19622
- import * as path42 from "node:path";
19922
+ import * as fs45 from "node:fs";
19923
+ import * as path41 from "node:path";
19623
19924
  var ControlFilter = class extends ToolFilter {
19624
19925
  constructor(name, transform) {
19625
19926
  super();
@@ -19640,13 +19941,13 @@ var CONTROL_FILTERS = {
19640
19941
  };
19641
19942
  var MAX_BENCH_FILE_BYTES = 5 * 1024 * 1024;
19642
19943
  function readCaseFile(id, filePath, what) {
19643
- const bytes = fs46.statSync(filePath).size;
19944
+ const bytes = fs45.statSync(filePath).size;
19644
19945
  if (bytes > MAX_BENCH_FILE_BYTES) {
19645
19946
  throw new Error(
19646
19947
  `bench case ${id}: ${what} is ${bytes} bytes, over the ${MAX_BENCH_FILE_BYTES}-byte limit for a corpus file (${filePath})`
19647
19948
  );
19648
19949
  }
19649
- return fs46.readFileSync(filePath, "utf8");
19950
+ return fs45.readFileSync(filePath, "utf8");
19650
19951
  }
19651
19952
  function parseCase(id, metaPath, outputPath) {
19652
19953
  const metaText = readCaseFile(id, metaPath, "metadata");
@@ -19681,7 +19982,7 @@ function parseCase(id, metaPath, outputPath) {
19681
19982
  function loadCorpus(dir) {
19682
19983
  let entries;
19683
19984
  try {
19684
- entries = fs46.readdirSync(dir);
19985
+ entries = fs45.readdirSync(dir);
19685
19986
  } catch {
19686
19987
  throw new Error(`bench corpus not found: ${dir}`);
19687
19988
  }
@@ -19689,9 +19990,9 @@ function loadCorpus(dir) {
19689
19990
  for (const entry of entries.sort()) {
19690
19991
  if (!entry.endsWith(".json")) continue;
19691
19992
  const id = entry.slice(0, -".json".length);
19692
- const outputPath = path42.join(dir, `${id}.txt`);
19693
- if (!fs46.existsSync(outputPath)) throw new Error(`bench case ${id}: missing output file ${outputPath}`);
19694
- cases.push(parseCase(id, path42.join(dir, entry), outputPath));
19993
+ const outputPath = path41.join(dir, `${id}.txt`);
19994
+ if (!fs45.existsSync(outputPath)) throw new Error(`bench case ${id}: missing output file ${outputPath}`);
19995
+ cases.push(parseCase(id, path41.join(dir, entry), outputPath));
19695
19996
  }
19696
19997
  if (cases.length === 0) throw new Error(`bench corpus is empty: ${dir}`);
19697
19998
  return cases;
@@ -19811,10 +20112,10 @@ function appendTsv(file, report, commit) {
19811
20112
  report.mustKeepTotal,
19812
20113
  report.coveredFilters
19813
20114
  ].join(" ");
19814
- const dir = path42.dirname(path42.resolve(file));
19815
- fs46.mkdirSync(dir, { recursive: true });
19816
- const header = fs46.existsSync(file) ? "" : TSV_HEADER;
19817
- fs46.appendFileSync(file, header + row + "\n", "utf8");
20115
+ const dir = path41.dirname(path41.resolve(file));
20116
+ fs45.mkdirSync(dir, { recursive: true });
20117
+ const header = fs45.existsSync(file) ? "" : TSV_HEADER;
20118
+ fs45.appendFileSync(file, header + row + "\n", "utf8");
19818
20119
  }
19819
20120
  function currentCommit(cwd) {
19820
20121
  const opts = cwd === void 0 ? {} : { cwd };
@@ -19854,10 +20155,10 @@ function readBoundedText(text, file) {
19854
20155
  if (text === void 0 && file === void 0) throw new CliError("provide text or --file");
19855
20156
  let value;
19856
20157
  if (file !== void 0) {
19857
- if (fs47.statSync(file).size > CONTENT_MAX_INPUT_CHARS) {
20158
+ if (fs46.statSync(file).size > CONTENT_MAX_INPUT_CHARS) {
19858
20159
  throw new CliError(`file exceeds the ${CONTENT_MAX_INPUT_CHARS}-byte safety limit`);
19859
20160
  }
19860
- value = fs47.readFileSync(file, "utf8");
20161
+ value = fs46.readFileSync(file, "utf8");
19861
20162
  } else {
19862
20163
  if (text === void 0) throw new CliError("provide text or --file");
19863
20164
  value = text;
@@ -19967,6 +20268,7 @@ async function cmdIndex(pathArg, opts = {}) {
19967
20268
  const ixCfg = loadConfig().indexing;
19968
20269
  if ((loadConfig().indexing?.embeddings_enabled ?? true) && embeddingsDepsAvailable(getDb(dbPath))) {
19969
20270
  ensureEmbeddingProvenance(getDb(dbPath));
20271
+ pruneUnembeddableChunks(getDb(dbPath), ixCfg.max_chunks_per_file, deleteFileEmbeddings);
19970
20272
  }
19971
20273
  let indexed = 0;
19972
20274
  let failed = 0;
@@ -20003,20 +20305,23 @@ async function cmdIndex(pathArg, opts = {}) {
20003
20305
  continue;
20004
20306
  }
20005
20307
  const sha = fingerprintFile(key);
20006
- if (sha === null && !fs47.existsSync(key)) continue;
20308
+ if (sha === null && !fs46.existsSync(key)) continue;
20007
20309
  const entry = sha !== null ? getFileEntry(key, dbPath) : null;
20008
20310
  const spellingStale = entry !== null && indexedPathSpellingIsStale(entry.filePath, key);
20009
- const parseUnchanged = !force && !spellingStale && sha !== null && entry?.sha === sha && entry.parserSha === PARSER_FINGERPRINT;
20311
+ const parseUnchanged = !force && !spellingStale && sha !== null && entry?.sha === sha && entry.parserSha === parserFingerprintForLanguage(entry.language);
20010
20312
  const embeddingsEnabled = loadConfig().indexing?.embeddings_enabled ?? true;
20011
20313
  const depsAvailable = embeddingsEnabled && embeddingsDepsAvailable(getDb(dbPath));
20012
- const embedUnchanged = !force && parseUnchanged && sha !== null && isEmbedFresh(
20013
- entry?.embedSha,
20314
+ const embedFreshFor = (embedSha) => !force && !spellingStale && sha !== null && isEmbedFresh(
20315
+ embedSha,
20014
20316
  sha,
20015
20317
  embeddingsEnabled,
20016
20318
  depsAvailable,
20017
20319
  // See isEmbedFresh: an `oversize:` marker stays fresh only while indexing.large_file_symbol_only_kb is still what it was stamped under, so raising the threshold re-embeds the files it just admitted instead of leaving them permanently skipped. 0 matches no marker (config floors this key at 1), the safe direction for a partially-mocked config.
20018
- loadConfig().indexing?.large_file_symbol_only_kb ?? 0
20320
+ loadConfig().indexing?.large_file_symbol_only_kb ?? 0,
20321
+ // Same reasoning and same 0 fallback for the chunk-count marker.
20322
+ loadConfig().indexing?.max_chunks_per_file ?? 0
20019
20323
  );
20324
+ const embedUnchanged = embedFreshFor(entry?.embedSha);
20020
20325
  if (parseUnchanged && embedUnchanged) {
20021
20326
  skipped += 1;
20022
20327
  continue;
@@ -20036,9 +20341,10 @@ async function cmdIndex(pathArg, opts = {}) {
20036
20341
  }
20037
20342
  continue;
20038
20343
  }
20039
- if (!fs47.existsSync(key)) continue;
20344
+ if (!fs46.existsSync(key)) continue;
20040
20345
  }
20041
- if (!embedUnchanged) {
20346
+ const embedFresh = parseUnchanged ? embedUnchanged : embedFreshFor(getFileEntry(key, dbPath)?.embedSha);
20347
+ if (!embedFresh) {
20042
20348
  paintProgress("embedding");
20043
20349
  await indexFileEmbeddings(key, dbPath, sha ?? void 0);
20044
20350
  }
@@ -20096,7 +20402,7 @@ async function cmdMcpServe() {
20096
20402
  let StdioServerTransport;
20097
20403
  try {
20098
20404
  ;
20099
- ({ createMcpServer } = await import("./token-goat-chunk-6LGVCCMY.mjs"));
20405
+ ({ createMcpServer } = await import("./token-goat-chunk-DA6TIDLI.mjs"));
20100
20406
  ({ StdioServerTransport } = await import("./token-goat-chunk-EVC4TOLE.mjs"));
20101
20407
  } catch (err2) {
20102
20408
  process.stderr.write(
@@ -20121,11 +20427,11 @@ async function cmdHook(event, opts) {
20121
20427
  if (typeof opts.harness === "string" && opts.harness.length > 0) {
20122
20428
  process.env[ENV_KEYS.HARNESS_OVERRIDE] = opts.harness;
20123
20429
  }
20124
- const { relay } = await import("./token-goat-chunk-34OUJ2IE.mjs");
20430
+ const { relay } = await import("./token-goat-chunk-NYFYIRG3.mjs");
20125
20431
  await relay(event);
20126
20432
  }
20127
20433
  function projectHooksCommitNote(pathFiles, hooksConfigPath) {
20128
- const shim = hooksConfigPath === void 0 ? "" : ` (${displaySafePath(path43.join(path43.dirname(hooksConfigPath), HOOKS_SCRIPT_FILE))} is generated with them)`;
20434
+ const shim = hooksConfigPath === void 0 ? "" : ` (${displaySafePath(path42.join(path42.dirname(hooksConfigPath), HOOKS_SCRIPT_FILE))} is generated with them)`;
20129
20435
  return `Note: ${pathFiles.map((p) => displaySafePath(p)).join(", ")} ${pathFiles.length === 1 ? "holds" : "hold"} absolute paths to node and token-goat on this machine${shim}, so do not commit them: list them in .git/info/exclude or .gitignore.`;
20130
20436
  }
20131
20437
  function visualStudioManualSteps(scope) {
@@ -20158,7 +20464,7 @@ async function cmdInstall(opts) {
20158
20464
  if (opts.project === true && opts.user === true) {
20159
20465
  throw new Error("install takes either -p/--project or --user, not both.");
20160
20466
  }
20161
- await import("./token-goat-chunk-34OUJ2IE.mjs");
20467
+ await import("./token-goat-chunk-NYFYIRG3.mjs");
20162
20468
  const scope = opts.project === true ? "project" : "user";
20163
20469
  if (wantsClaudeCodeBase(opts)) {
20164
20470
  const result = installHooks(scope);
@@ -20304,16 +20610,16 @@ async function cmdInstall(opts) {
20304
20610
  );
20305
20611
  }
20306
20612
  try {
20307
- const skillDir2 = path43.join(homedir20(), ".claude", "skills");
20308
- if (fs47.existsSync(skillDir2)) {
20309
- const entries = fs47.readdirSync(skillDir2, { withFileTypes: true });
20613
+ const skillDir2 = path42.join(claudeConfigDir(), "skills");
20614
+ if (fs46.existsSync(skillDir2)) {
20615
+ const entries = fs46.readdirSync(skillDir2, { withFileTypes: true });
20310
20616
  const skillNames = [];
20311
20617
  const sessionId = getSessionId();
20312
20618
  for (const entry of entries) {
20313
20619
  if (!entry.isDirectory()) continue;
20314
- const skillFile = path43.join(skillDir2, entry.name, "SKILL.md");
20315
- if (fs47.existsSync(skillFile)) {
20316
- const body = fs47.readFileSync(skillFile, "utf-8");
20620
+ const skillFile = path42.join(skillDir2, entry.name, "SKILL.md");
20621
+ if (fs46.existsSync(skillFile)) {
20622
+ const body = fs46.readFileSync(skillFile, "utf-8");
20317
20623
  const compact = extractCompactFromMarker(body);
20318
20624
  if (compact === null) continue;
20319
20625
  const sourceSha = contentHash(body);
@@ -20324,9 +20630,9 @@ async function cmdInstall(opts) {
20324
20630
  if (skillNames.length > 0) {
20325
20631
  const dir = skillOutputsDir();
20326
20632
  ensureDirSync(dir);
20327
- const pregenPath = path43.join(dir, "pregen.json");
20633
+ const pregenPath = path42.join(dir, "pregen.json");
20328
20634
  const pregenData = { ts: Date.now(), names: skillNames };
20329
- await fs47.promises.writeFile(pregenPath, JSON.stringify(pregenData, null, 2));
20635
+ await fs46.promises.writeFile(pregenPath, JSON.stringify(pregenData, null, 2));
20330
20636
  out(`Pre-generated ${skillNames.length} skill compacts.`);
20331
20637
  }
20332
20638
  }
@@ -20472,7 +20778,8 @@ function cmdStats(opts = {}) {
20472
20778
  windowDays,
20473
20779
  full: opts.full === true,
20474
20780
  short: opts.short === true,
20475
- methodology: opts.methodology === true
20781
+ methodology: opts.methodology === true,
20782
+ hooks: opts.hooks === true
20476
20783
  };
20477
20784
  if (opts.homeDir !== void 0) {
20478
20785
  statsOpts.homeDir = opts.homeDir;
@@ -20505,7 +20812,7 @@ async function cmdDoctor(opts) {
20505
20812
  }
20506
20813
  }
20507
20814
  function _applyFiltersAndPrint(content, opts, fenceByProvenance = false, fenceTag = UNTRUSTED_WEB_TAG) {
20508
- const emit3 = (text) => {
20815
+ const emit4 = (text) => {
20509
20816
  if (!fenceByProvenance || text === "") {
20510
20817
  out(text);
20511
20818
  return text;
@@ -20544,7 +20851,7 @@ function _applyFiltersAndPrint(content, opts, fenceByProvenance = false, fenceTa
20544
20851
  }
20545
20852
  const rawLines = content.split(/\r?\n/);
20546
20853
  if (opts.full === true) {
20547
- return emit3(rawLines.join("\n"));
20854
+ return emit4(rawLines.join("\n"));
20548
20855
  }
20549
20856
  const lines = rawLines.length > 1 && rawLines[rawLines.length - 1] === "" ? rawLines.slice(0, -1) : rawLines;
20550
20857
  const headN = opts.head !== void 0 ? requireNonNegativeInt2("--head", opts.head) : 30;
@@ -20567,7 +20874,7 @@ function _applyFiltersAndPrint(content, opts, fenceByProvenance = false, fenceTa
20567
20874
  result = lines.slice(Math.max(0, lines.length - tailN));
20568
20875
  noteLineCap("last", "tail", result.length, lines.length);
20569
20876
  }
20570
- return emit3(result.join("\n"));
20877
+ return emit4(result.join("\n"));
20571
20878
  }
20572
20879
  function cmdBashOutput(id, opts) {
20573
20880
  const parseVerifyThreshold = (optVal) => {
@@ -20588,7 +20895,7 @@ function cmdBashOutput(id, opts) {
20588
20895
  }
20589
20896
  let content;
20590
20897
  try {
20591
- const st = fs47.statSync(opts.file);
20898
+ const st = fs46.statSync(opts.file);
20592
20899
  if (st.isFIFO() || st.isSocket()) {
20593
20900
  throw new CliError(`--file '${opts.file}' is a special file (FIFO or socket) \u2014 only regular files are supported`);
20594
20901
  }
@@ -20603,7 +20910,7 @@ function cmdBashOutput(id, opts) {
20603
20910
  `);
20604
20911
  }
20605
20912
  }
20606
- content = redactIfDotenv(opts.file, decodeSource(fs47.readFileSync(opts.file)));
20913
+ content = redactIfDotenv(opts.file, decodeSource(fs46.readFileSync(opts.file)));
20607
20914
  } catch (e) {
20608
20915
  if (e instanceof CliError) throw e;
20609
20916
  throw new CliError(`cannot read file: ${opts.file}`);
@@ -20667,15 +20974,15 @@ function cmdMcpOutput(id, opts) {
20667
20974
  if (opts.file.includes("\0")) {
20668
20975
  throw new CliError("--file path contains a null byte");
20669
20976
  }
20670
- if (!fs47.existsSync(opts.file)) {
20977
+ if (!fs46.existsSync(opts.file)) {
20671
20978
  throw new CliError(`file not found: ${opts.file}`);
20672
20979
  }
20673
20980
  try {
20674
- const st = fs47.statSync(opts.file);
20981
+ const st = fs46.statSync(opts.file);
20675
20982
  if (st.isFIFO() || st.isSocket()) {
20676
20983
  throw new CliError(`--file '${opts.file}' is a special file (FIFO or socket) \u2014 only regular files are supported`);
20677
20984
  }
20678
- content = decodeSource(fs47.readFileSync(opts.file));
20985
+ content = decodeSource(fs46.readFileSync(opts.file));
20679
20986
  } catch (e) {
20680
20987
  if (e instanceof CliError) throw e;
20681
20988
  throw new CliError(`cannot read file: ${opts.file}`);
@@ -20748,13 +21055,13 @@ async function cmdCompress(commandArgs, opts = {}) {
20748
21055
  process.exitCode = 1;
20749
21056
  return;
20750
21057
  }
20751
- const bashRunner = await import("./token-goat-chunk-2KTZ6J7T.mjs");
21058
+ const bashRunner = await import("./token-goat-chunk-P46RTOAQ.mjs");
20752
21059
  if (opts.compress === false) {
20753
21060
  process.exitCode = bashRunner.runRaw(command, parseTimeout(opts.timeout, bashRunner.DEFAULT_TIMEOUT_SECONDS));
20754
21061
  return;
20755
21062
  }
20756
21063
  const maxTokens = opts.maxTokens !== void 0 ? requireNonNegativeInt2("--max-tokens", opts.maxTokens) : 0;
20757
- process.exitCode = bashRunner.run(command, {
21064
+ process.exitCode = await bashRunner.run(command, {
20758
21065
  filterName: opts.filter,
20759
21066
  timeout: parseTimeout(opts.timeout, bashRunner.DEFAULT_TIMEOUT_SECONDS),
20760
21067
  maxTokens,
@@ -20798,9 +21105,10 @@ ${content}`;
20798
21105
  var originalHelpInformation = null;
20799
21106
  function buildProgram() {
20800
21107
  const program2 = new Command();
20801
- 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)");
21108
+ 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)").option("--notice <text>", "print this line to stdout before the command's own output");
20802
21109
  program2.hook("preAction", (thisCommand) => {
20803
- const cwdOverride = thisCommand.opts().cwd;
21110
+ const opts = thisCommand.opts();
21111
+ const cwdOverride = opts.cwd;
20804
21112
  if (cwdOverride !== void 0) {
20805
21113
  try {
20806
21114
  process.chdir(cwdOverride);
@@ -20808,6 +21116,7 @@ function buildProgram() {
20808
21116
  throw new Error(`--cwd ${cwdOverride}: ${extractErrorMessage(e)}`, { cause: e });
20809
21117
  }
20810
21118
  }
21119
+ if (opts.notice !== void 0) out(opts.notice);
20811
21120
  });
20812
21121
  const guard = (fn) => async (...args) => {
20813
21122
  process.exitCode = void 0;
@@ -20837,7 +21146,7 @@ function buildProgram() {
20837
21146
  process.exitCode = 1;
20838
21147
  }
20839
21148
  };
20840
- program2.command("symbol [name] [more...]").description("search for a symbol by name, or project-wide by --grep name pattern").option("-l, --limit <n>", "max results").option("-f, --file <path>", "restrict to one file").option("-k, --kind <kind>", "restrict to one kind (function, class, ...)").option("-p, --project [path]", "scope search to one project root instead of the global index (defaults to cwd)").option("-j, --json", "output as JSON").option("--grep <pattern>", "only show symbols whose name matches this regex (literal substring if it is not valid regex); cannot be combined with <name>").option("--exclude-tests", "hide symbols defined in a test file (opt-in; default output is unchanged)").option("--stats", "add per-result reference count and doc-coverage flag (project-wide count per NAME, not per definition site -- same-named symbols across files share a count)").action((name, more, opts) => {
21149
+ program2.command("symbol [name] [more...]").description("search for a symbol by name, or project-wide by --grep name pattern").option("-l, --limit <n>", "max results").option("-f, --file <path>", "restrict to one file").option("-k, --kind <kind>", "restrict to one kind (function, class, ...)").option("-p, --project [path]", "scope search to one project root instead of the global index (defaults to cwd)").option("-j, --json", "output as JSON").option("--grep <pattern>", "only show symbols whose name matches this regex (literal substring if it is not valid regex); cannot be combined with <name>").option("--exclude-tests", "hide symbols defined in a test file (opt-in; default output is unchanged)").option("--exclude-vendored", "hide symbols defined under a dependency or build directory the indexer skips (node_modules, dist, site-packages, ...) (opt-in; default output is unchanged)").option("--stats", "add per-result reference count and doc-coverage flag (project-wide count per NAME, not per definition site -- same-named symbols across files share a count)").action((name, more, opts) => {
20841
21150
  let projectRoot;
20842
21151
  if (opts.project === true) {
20843
21152
  projectRoot = resolveProjectRoot({ project: process.cwd() });
@@ -20858,6 +21167,7 @@ function buildProgram() {
20858
21167
  ...opts.json === true ? { json: true } : {},
20859
21168
  ...opts.grep !== void 0 ? { grep: opts.grep } : {},
20860
21169
  ...opts.excludeTests === true ? { excludeTests: true } : {},
21170
+ ...opts.excludeVendored === true ? { excludeVendored: true } : {},
20861
21171
  ...opts.stats === true ? { stats: true } : {}
20862
21172
  }),
20863
21173
  // `symbol` searches one name; there is no comma form that would search several.
@@ -20866,7 +21176,7 @@ function buildProgram() {
20866
21176
  );
20867
21177
  });
20868
21178
  program2.command("read <spec> [more...]").description(
20869
- "read one symbol's full body (spec: file::symbol; disambiguate a name shared by several classes with file::Parent.symbol; a trailing @LINE anchor -- file::symbol@LINE, or combined as file::Parent.symbol@LINE -- picks out a specific candidate by its exact starting line, for the case a Parent qualifier can't reach (e.g. a top-level definition); comma-separated file::a,b for a merged multi-symbol view, or a::x,b::y to merge symbols across several files)"
21179
+ "read one symbol's full body (spec: file::symbol; disambiguate a name shared by several classes with file::Parent.symbol; a trailing @LINE anchor -- file::symbol@LINE, or combined as file::Parent.symbol@LINE -- picks out a specific candidate by its exact starting line, for the case a Parent qualifier can't reach (e.g. a top-level definition); comma-separated file::a,b for a merged multi-symbol view, or a::x,b::y to merge symbols across several files; file:LINE or file:START-END resolves a line number to the region enclosing it -- the symbol, the file preamble, or the gap between two symbols -- while file@START-END serves those raw lines)"
20870
21180
  ).option("-j, --json", "output as JSON").option("--force-refresh", "reparse file from disk before querying (ignore stale index)").option("--stats", "add per-symbol reference count and doc-coverage flag").action(
20871
21181
  (spec, more, opts) => runExitText(
20872
21182
  () => noteExtraFileArgs(
@@ -20879,7 +21189,8 @@ function buildProgram() {
20879
21189
  ...opts.forceRefresh === true ? { forceRefresh: true } : {},
20880
21190
  ...opts.stats === true ? { stats: true } : {}
20881
21191
  }),
20882
- { noun: "spec" }
21192
+ // Whether the comma form the note names would actually run depends on the specs themselves, not on the command: `read` merges `::` specs and nothing else.
21193
+ { noun: "spec", mergeable: readSpecsMergeable([spec, ...more]) }
20883
21194
  )
20884
21195
  )
20885
21196
  );
@@ -20887,7 +21198,7 @@ function buildProgram() {
20887
21198
  "symbol body + callers + containing doc section in one call (spec: file::symbol; also accepts the file::symbol@LINE anchor form documented under `read`; comma-separated file::a,b for a merged multi-symbol view; cross-file a.ts::x,b.ts::y is also supported)"
20888
21199
  ).option("-j, --json", "output as JSON").option("--limit <n>", "max callers to show (default: 20)").option("-C, --context <n>", "lines of call-site source to show before and after each caller (default 0)").option("--exclude-tests", "hide callers whose call site lives in a test file (opt-in; default output is unchanged)").option("--grep <pattern>", "only show callers whose enclosing symbol name matches this regex (literal substring if it is not valid regex)").action(
20889
21200
  (spec, more, opts) => runExit(() => {
20890
- emitExtraFileArgsNote("brief", spec, more, { noun: "spec" });
21201
+ emitExtraFileArgsNote("brief", spec, more, { noun: "spec", mergeable: namedSpecsMergeable([spec, ...more]) });
20891
21202
  return runBrief({
20892
21203
  spec,
20893
21204
  ...opts.json === true ? { json: true } : {},
@@ -20905,7 +21216,7 @@ function buildProgram() {
20905
21216
  emitExtraFileArgsNote("section --list", spec, more, { mergeable: false });
20906
21217
  return runListSections({ file: spec, ...opts.json === true ? { json: true } : {}, ...opts.grep !== void 0 ? { grep: opts.grep } : {} });
20907
21218
  }) : runExitText(
20908
- () => noteExtraFileArgs("section", spec, more, () => runSection({ spec, ...opts.json === true ? { json: true } : {} }), { noun: "spec" })
21219
+ () => noteExtraFileArgs("section", spec, more, () => runSection({ spec, ...opts.json === true ? { json: true } : {} }), { noun: "spec", mergeable: namedSpecsMergeable([spec, ...more]) })
20909
21220
  )
20910
21221
  );
20911
21222
  program2.command("semantic [query]").description("semantic search (falls back to full-text search)").option("-l, --limit <n>", "max results").option("-j, --json", "output as JSON").option("--grep <pattern>", "filter to hits whose file path matches this regex (literal substring if it is not valid regex); matched against the path as rendered").option("--exclude-tests", "hide hits whose file is a test file (opt-in; default output is unchanged)").option("--preflight", "run semantic embedding preflight check and exit").option("--warm", "warm up the embedding model session in memory").action(guard(cmdSemantic));
@@ -20943,7 +21254,7 @@ function buildProgram() {
20943
21254
  "for a high-fanout symbol, group references by file (count only) and show only the top N files by reference count instead of a per-line dump"
20944
21255
  ).option("-C, --context <n>", "lines of call-site source to show before and after each reference (default 0)").option("-j, --json", "output as JSON").option("--exclude-tests", "hide references whose call site lives in a test file (opt-in; default output is unchanged)").option("--grep <pattern>", "filter to references whose call-site file path matches this regex (falls back to a literal substring match when the pattern does not compile); matched against the path as rendered, so ^src/ matches what you see in every form -- drops test/vendored hits from a wide-fanout symbol").action(
20945
21256
  (spec, more, opts) => runExit(() => {
20946
- emitExtraFileArgsNote("refs", spec, more, { noun: "spec" });
21257
+ emitExtraFileArgsNote("refs", spec, more, { noun: "spec", mergeable: namedSpecsMergeable([spec, ...more]) });
20947
21258
  return runRefs({
20948
21259
  spec,
20949
21260
  ...opts.callers === true ? { callers: true } : {},
@@ -20964,7 +21275,7 @@ function buildProgram() {
20964
21275
  program2.command("bench").description("replay a corpus of captured command output through the compressors; prints the byte savings ratio and a fidelity check, and exits 1 when a must-keep line was dropped").option("--corpus <dir>", "corpus directory (default: tests/fixtures/bench, relative to the working directory)").option("--tsv <path>", "append one result row, keyed by the current commit, creating the file and its header if absent").option("--validate", "score the corpus with control filters instead: proves the ratio has a floor and the fidelity guard can actually fail").option("-j, --json", "output as JSON").action(
20965
21276
  (opts) => runExitText(
20966
21277
  () => runBenchCommand({
20967
- corpus: opts.corpus ?? path43.join("tests", "fixtures", "bench"),
21278
+ corpus: opts.corpus ?? path42.join("tests", "fixtures", "bench"),
20968
21279
  ...opts.tsv !== void 0 ? { tsv: opts.tsv } : {},
20969
21280
  ...opts.validate === true ? { validate: true } : {},
20970
21281
  ...opts.json === true ? { json: true } : {}
@@ -20983,7 +21294,7 @@ function buildProgram() {
20983
21294
  worker.command("start").description("start the background indexer").action(guard(cmdWorkerStart));
20984
21295
  worker.command("stop").description("stop the background indexer").action(guard(cmdWorkerStop));
20985
21296
  worker.command("status").description("check if the indexer is running").action(guard(cmdWorkerStatus));
20986
- 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));
21297
+ 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("--hooks", "show the per-event/per-harness hook latency breakdown (median/p95/slowest/last-seen)").option("--window-days <days>", "days to include (0 = all time)", "30").option("--home-dir <path>", "home directory (for testing)").action(guard(cmdStats));
20987
21298
  program2.command("doctor").description("diagnose token-goat health").option("--context", "include context footprint analysis").option("--json", "emit check results as JSON instead of text").option("--repair", "automatically repair fixable issues (permissive settings, missing semantics models)").option("--fix", "alias for --repair").action(guard(cmdDoctor));
20988
21299
  program2.command("bash-output [id]").description("retrieve cached bash output by ID or file").option("--head <n>", "show first N lines").option("--tail <n>", "show last N lines").option("--grep <pattern>", "filter lines matching regex").option("--max-matches <n>", "cap --grep output to the first N matching lines").option("--section <heading>", "extract a specific section from the output").option("--full", "print the entire cached entry with no head/tail elision").option("--file <path>", "read from raw output file instead of cache").option("--transcript", "parse the --file as a JSONL agent transcript: keep assistant text blocks in order before filtering").option("--verify-last-write [seconds]", "verify the file or cache entry was written within [seconds] (default: 60s) to catch stale/no-op terminal output").option("--strict", "exit non-zero when --verify-last-write detects stale output").action(guard(cmdBashOutput));
20989
21300
  program2.command("web-output [id]").description("retrieve a cached WebFetch response body by ID").option("--head <n>", "show first N lines").option("--tail <n>", "show last N lines").option("--grep <pattern>", "filter lines matching regex").option("--max-matches <n>", "cap --grep output to the first N matching lines").option("--section <heading>", "extract a specific section from the response").option("--full", "print the entire cached entry with no head/tail elision").option("--raw", "return the body as actually fetched, before extractCleanText cleaning, instead of the default cleaned text").action(guard(cmdWebOutput));
@@ -20998,7 +21309,7 @@ function buildProgram() {
20998
21309
  program2.command("compress [command...]").alias("bash").alias("run").description("run a shell command (under a POSIX shell / bash) and emit a compressed view of its output").allowUnknownOption(true).option("-c, --cmd <command>", "the shell command to run, as one string (use / for paths across platforms)").option("--cmd-b64 <payload>", "the shell command as a base64-encoded string (preserves quotes, backslashes, and symbols across platforms)").option("-f, --filter <name>", "filter name (auto-detected from the command when omitted)").option("--timeout <seconds>", "wall-clock timeout in seconds (0 = built-in default)").option("--no-compress", "stream output raw without compression (debug the wrapper)").option("--profile <name>", "compression profile: aggressive | balanced | minimal").option("--max-tokens <n>", "post-compress token cap (0 = no cap)").option("-q, --quiet-success", "on exit code 0, emit only [tg: ok] summary and store full output for recall via bash-output").option("--native", "use native platform shell (e.g. cmd.exe on Windows) instead of bash, preserving Windows path backslashes").action(cmdCompress);
20999
21310
  program2.command("upgrade").alias("update").description("check for updates and upgrade token-goat to the latest version").option("--check", "check whether an update is available without installing").option("-j, --json", "emit version check status as JSON").action(
21000
21311
  (opts) => guard(async () => {
21001
- const { cmdUpgrade } = await import("./token-goat-chunk-K4DG6EEF.mjs");
21312
+ const { cmdUpgrade } = await import("./token-goat-chunk-VJQBYSXG.mjs");
21002
21313
  await cmdUpgrade(opts, () => cmdInstall({}));
21003
21314
  })()
21004
21315
  );