token-goat 2.9.26 → 2.9.29

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 (78) hide show
  1. package/README.md +30 -88
  2. package/SECURITY.md +14 -0
  3. package/dist/{token-goat-chunk-OZRSREKR.mjs → token-goat-chunk-2AWQ2R5X.mjs} +2 -2
  4. package/dist/{token-goat-chunk-BH4ZVRB2.mjs → token-goat-chunk-2NNUA2LR.mjs} +4 -4
  5. package/dist/token-goat-chunk-2WOUYWLM.mjs +955 -0
  6. package/dist/{token-goat-chunk-IKZLSRII.mjs → token-goat-chunk-435UQTKD.mjs} +5 -3
  7. package/dist/{token-goat-chunk-62IHOU6L.mjs → token-goat-chunk-44BM7DDQ.mjs} +5 -3
  8. package/dist/token-goat-chunk-52NSDWYY.mjs +96 -0
  9. package/dist/{token-goat-chunk-CXBOFPF4.mjs → token-goat-chunk-67BNIKKO.mjs} +32 -18
  10. package/dist/{token-goat-chunk-XBESNWEX.mjs → token-goat-chunk-6AK46ZT7.mjs} +2 -3
  11. package/dist/{token-goat-chunk-7SXPJOSZ.mjs → token-goat-chunk-7CPMDFGB.mjs} +166 -40
  12. package/dist/token-goat-chunk-ASLWOQ3I.mjs +14 -0
  13. package/dist/{token-goat-chunk-MWID3YOY.mjs → token-goat-chunk-BGYOZ6DO.mjs} +6 -4
  14. package/dist/{token-goat-chunk-PAV6NNY7.mjs → token-goat-chunk-C5YPNZKQ.mjs} +22 -8
  15. package/dist/{token-goat-chunk-D2X7XN5M.mjs → token-goat-chunk-CIQ2FTY3.mjs} +23 -242
  16. package/dist/token-goat-chunk-D4ETHJD6.mjs +246 -0
  17. package/dist/{token-goat-chunk-7Y6GWTII.mjs → token-goat-chunk-DNRPXHYH.mjs} +25 -17
  18. package/dist/{token-goat-chunk-L43RKNHA.mjs → token-goat-chunk-DNSPXQXQ.mjs} +2 -2
  19. package/dist/token-goat-chunk-DPNLUJWB.mjs +220 -0
  20. package/dist/token-goat-chunk-E4ZOJIZK.mjs +1382 -0
  21. package/dist/token-goat-chunk-E5TK5DNO.mjs +718 -0
  22. package/dist/{token-goat-chunk-VVROQBQJ.mjs → token-goat-chunk-EUSDXRXU.mjs} +62 -12
  23. package/dist/{token-goat-chunk-POOI26O6.mjs → token-goat-chunk-FTP4U4QA.mjs} +12 -8
  24. package/dist/token-goat-chunk-G23DWHV4.mjs +142 -0
  25. package/dist/{token-goat-chunk-MDV5VWF4.mjs → token-goat-chunk-G7IWXCFF.mjs} +223 -15
  26. package/dist/{token-goat-chunk-C2PG4K5D.mjs → token-goat-chunk-GLXRTTLE.mjs} +7 -5
  27. package/dist/{token-goat-chunk-M6CVNHTW.mjs → token-goat-chunk-H5J3C56Q.mjs} +95 -573
  28. package/dist/{token-goat-chunk-BAWKGODL.mjs → token-goat-chunk-HNFZZHFW.mjs} +2 -2
  29. package/dist/{token-goat-chunk-TG5ZPD6B.mjs → token-goat-chunk-HUYUHODZ.mjs} +1036 -759
  30. package/dist/token-goat-chunk-IVE6U7KV.mjs +53 -0
  31. package/dist/{token-goat-chunk-ZZT6PVB3.mjs → token-goat-chunk-IVWI2OCK.mjs} +72 -45
  32. package/dist/{token-goat-chunk-GMOUBOX4.mjs → token-goat-chunk-JTKCQMQA.mjs} +37 -5
  33. package/dist/{token-goat-chunk-H2CPLEMP.mjs → token-goat-chunk-KOPIR3KZ.mjs} +46 -23
  34. package/dist/token-goat-chunk-L7H3U27L.mjs +35 -0
  35. package/dist/{token-goat-chunk-FLEYOCAS.mjs → token-goat-chunk-LZNBQTDK.mjs} +6 -4
  36. package/dist/token-goat-chunk-MELOKFDJ.mjs +199 -0
  37. package/dist/{token-goat-chunk-RH27ZSPF.mjs → token-goat-chunk-NMBWLRHW.mjs} +164 -50
  38. package/dist/{token-goat-chunk-43JFN26X.mjs → token-goat-chunk-O6I2XU4P.mjs} +27 -967
  39. package/dist/{token-goat-chunk-G6EYO5CD.mjs → token-goat-chunk-ONXH6NZ5.mjs} +2 -87
  40. package/dist/token-goat-chunk-ORNFMIER.mjs +295 -0
  41. package/dist/{token-goat-chunk-LHUP4DXZ.mjs → token-goat-chunk-PGTH2U4C.mjs} +1 -1
  42. package/dist/{token-goat-chunk-5BQ7W7D7.mjs → token-goat-chunk-PKRWMHVP.mjs} +76 -59
  43. package/dist/{token-goat-chunk-DA43OE7L.mjs → token-goat-chunk-PQ53BSCF.mjs} +1 -1
  44. package/dist/{token-goat-chunk-STYRLQIW.mjs → token-goat-chunk-PYSUADX4.mjs} +2 -2
  45. package/dist/{token-goat-chunk-JALB3KWJ.mjs → token-goat-chunk-QO3LXVVR.mjs} +4247 -5005
  46. package/dist/{token-goat-chunk-L45IWWMC.mjs → token-goat-chunk-RLP6J7WR.mjs} +6 -6
  47. package/dist/token-goat-chunk-RQGGMPCR.mjs +205 -0
  48. package/dist/{token-goat-chunk-MBDJR6HU.mjs → token-goat-chunk-RTMVLHWX.mjs} +3708 -4003
  49. package/dist/{token-goat-chunk-E3K3BTEQ.mjs → token-goat-chunk-S4VSXURL.mjs} +8 -6
  50. package/dist/{token-goat-chunk-LKSXAMJB.mjs → token-goat-chunk-SE6E4BKJ.mjs} +1 -1
  51. package/dist/token-goat-chunk-TCQANXYC.mjs +351 -0
  52. package/dist/token-goat-chunk-TDUT2CL6.mjs +564 -0
  53. package/dist/{token-goat-chunk-LRIZ7K3F.mjs → token-goat-chunk-TGOXNAHW.mjs} +54 -111
  54. package/dist/{token-goat-chunk-WNBSCSAI.mjs → token-goat-chunk-UA5BKWWQ.mjs} +1 -1
  55. package/dist/{token-goat-chunk-RIB6XY2V.mjs → token-goat-chunk-UXSXQQIY.mjs} +1 -1
  56. package/dist/{token-goat-chunk-7EPYB34H.mjs → token-goat-chunk-VMFWNHGR.mjs} +84 -73
  57. package/dist/token-goat-chunk-VQ6F6T7X.mjs +36 -0
  58. package/dist/{token-goat-chunk-5M3NRJD2.mjs → token-goat-chunk-VZQEWRVH.mjs} +6 -4
  59. package/dist/{token-goat-chunk-OSUFN2FV.mjs → token-goat-chunk-XB5KKOCG.mjs} +5 -25
  60. package/dist/{token-goat-chunk-6E2IDLHF.mjs → token-goat-chunk-XYWPOSCN.mjs} +57 -615
  61. package/dist/{token-goat-chunk-CQHY4SL7.mjs → token-goat-chunk-YAGLESS3.mjs} +8 -5
  62. package/dist/token-goat-chunk-YKSRAWUU.mjs +33 -0
  63. package/dist/{token-goat-chunk-F2ARFHLJ.mjs → token-goat-chunk-YOOVFPTP.mjs} +2 -2
  64. package/dist/{token-goat-chunk-EEIDFMEM.mjs → token-goat-chunk-YZAAZU2S.mjs} +12 -3
  65. package/dist/token-goat-chunk-Z6KBMOBI.mjs +53 -0
  66. package/dist/{token-goat-chunk-4GEDH2WU.mjs → token-goat-chunk-ZER6EAXZ.mjs} +81 -70
  67. package/dist/token-goat-hook-client.cjs +475 -0
  68. package/dist/token-goat-hook-client.mjs +26 -0
  69. package/dist/token-goat-hook.mjs +31 -21
  70. package/dist/token-goat.core.mjs +9 -39
  71. package/docs/C4_RUNTIME_ARCHITECTURE.md +2 -2
  72. package/docs/cli.md +35 -23
  73. package/docs/install.md +18 -14
  74. package/package.json +1 -1
  75. package/dist/token-goat-chunk-ADMR3QVF.mjs +0 -41
  76. package/dist/token-goat-chunk-E6B2UFDF.mjs +0 -30
  77. package/dist/token-goat-chunk-GKZ2US5B.mjs +0 -187
  78. package/dist/token-goat-chunk-MZTO4FXH.mjs +0 -26
@@ -1,17 +1,5 @@
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-BH4ZVRB2.mjs";
6
- import {
7
- buildCopilotWasteReport,
8
- findLatestCopilotSession,
9
- findProjectSession,
10
- isCopilotTranscript
11
- } from "./token-goat-chunk-PAV6NNY7.mjs";
12
- import {
13
- buildResumePacket
14
- } from "./token-goat-chunk-L45IWWMC.mjs";
15
3
  import {
16
4
  ALL_SYMBOLS_IN_FILE_LIMIT,
17
5
  AMBIGUOUS_HEADING_LIMIT,
@@ -21,7 +9,7 @@ import {
21
9
  compressText,
22
10
  computeFileFingerprint,
23
11
  createHandoff,
24
- didYouMean,
12
+ didYouMeanLines,
25
13
  emit,
26
14
  emitErr,
27
15
  enclosingSymbol,
@@ -90,6 +78,7 @@ import {
90
78
  runScreenshot,
91
79
  runSection,
92
80
  runSemantic,
81
+ runSemanticMulti,
93
82
  runSimilar,
94
83
  runSkeleton,
95
84
  runSqliteQuery,
@@ -104,10 +93,27 @@ import {
104
93
  runYamlQuery,
105
94
  runZipList,
106
95
  runZipRead,
96
+ statThroughHandle,
107
97
  symbolNamesInFile,
108
98
  upsertNote,
109
99
  warnIfFilesStale
110
- } from "./token-goat-chunk-RH27ZSPF.mjs";
100
+ } from "./token-goat-chunk-NMBWLRHW.mjs";
101
+ import "./token-goat-chunk-VZQEWRVH.mjs";
102
+ import {
103
+ compareSemver
104
+ } from "./token-goat-chunk-2NNUA2LR.mjs";
105
+ import "./token-goat-chunk-PQ53BSCF.mjs";
106
+ import {
107
+ MCP_DISABLE_NOTE,
108
+ buildCopilotWasteReport,
109
+ findLatestCopilotSession,
110
+ findProjectSession,
111
+ isCopilotTranscript,
112
+ unusedMcpServers
113
+ } from "./token-goat-chunk-C5YPNZKQ.mjs";
114
+ import {
115
+ buildResumePacket
116
+ } from "./token-goat-chunk-RLP6J7WR.mjs";
111
117
  import {
112
118
  DEFAULT_RECONCILE_BUDGET_MS,
113
119
  HOOK_EVENTS,
@@ -126,19 +132,9 @@ import {
126
132
  runRecallCommand,
127
133
  runReconcile,
128
134
  storeWebOutput
129
- } from "./token-goat-chunk-7Y6GWTII.mjs";
135
+ } from "./token-goat-chunk-DNRPXHYH.mjs";
130
136
  import {
131
- buildWasteReport,
132
- extractResultText,
133
- findLatestTranscript,
134
- projectTranscriptsDir,
135
- safeStringify
136
- } from "./token-goat-chunk-62IHOU6L.mjs";
137
- import {
138
- buildManifest
139
- } from "./token-goat-chunk-MDV5VWF4.mjs";
140
- import {
141
- GEMINI_TOOL_NAME_MAP,
137
+ HINT_CATEGORIES,
142
138
  MAX_FILES_SCANNED,
143
139
  SKIP_DIRS,
144
140
  WORKER_HEARTBEAT_STALE_MS,
@@ -153,6 +149,7 @@ import {
153
149
  compactDoc,
154
150
  compactPathFor,
155
151
  confirmAndApply,
152
+ dataDirWriteRefusal,
156
153
  detectWalkMode,
157
154
  dirtyQueuePathFor,
158
155
  drainHeartbeatPathFor,
@@ -170,6 +167,7 @@ import {
170
167
  formatSymbolLocation,
171
168
  getDirtyPathsFor,
172
169
  getEmbeddingCoverage,
170
+ getHintSpendTotals,
173
171
  getHintStatsSummary,
174
172
  getHintStatsTotals,
175
173
  getParserFreshness,
@@ -208,17 +206,32 @@ import {
208
206
  visionTokensSavedByText,
209
207
  walkProject,
210
208
  writeCompact
211
- } from "./token-goat-chunk-MBDJR6HU.mjs";
209
+ } from "./token-goat-chunk-RTMVLHWX.mjs";
210
+ import "./token-goat-chunk-L7H3U27L.mjs";
211
+ import "./token-goat-chunk-RRNZMM3A.mjs";
212
+ import {
213
+ GEMINI_TOOL_NAME_MAP
214
+ } from "./token-goat-chunk-E5TK5DNO.mjs";
212
215
  import {
213
216
  CompressedOutput,
214
217
  TOOL_FILTERS,
215
218
  ToolFilter,
216
219
  deliverCompressed,
217
220
  detectFromCommand
218
- } from "./token-goat-chunk-4GEDH2WU.mjs";
221
+ } from "./token-goat-chunk-ZER6EAXZ.mjs";
219
222
  import {
223
+ buildWasteReport,
224
+ extractResultText,
225
+ findLatestTranscript,
226
+ projectTranscriptsDir,
227
+ safeStringify
228
+ } from "./token-goat-chunk-44BM7DDQ.mjs";
229
+ import {
230
+ COPILOT_CLI_HOOK_SCRIPT,
220
231
  HOOKS_SCRIPT_FILE,
232
+ LEGACY_HOOKS_SCRIPT_FILE,
221
233
  MATERIALIZE_SHRUNK_IMAGE_JS,
234
+ SHIM_TRY_SERVER,
222
235
  buildGuidanceBlock,
223
236
  buildGuidanceBody,
224
237
  bundledCliPath,
@@ -253,7 +266,14 @@ import {
253
266
  visualStudioProjectMcpPath,
254
267
  visualStudioSolutionVscodeMcpPath,
255
268
  visualStudioUserMcpPath
256
- } from "./token-goat-chunk-VVROQBQJ.mjs";
269
+ } from "./token-goat-chunk-EUSDXRXU.mjs";
270
+ import {
271
+ buildManifest,
272
+ clearAll,
273
+ loadEntries,
274
+ setEntry,
275
+ unsetEntry
276
+ } from "./token-goat-chunk-G7IWXCFF.mjs";
257
277
  import {
258
278
  AGENT_SALT_MARKER,
259
279
  CONTEXT_AUTOCOMPACT_TOKENS,
@@ -266,8 +286,6 @@ import {
266
286
  findLatestSessionId,
267
287
  formatAge,
268
288
  getContextPressure,
269
- getSessionFiles,
270
- getSessionId,
271
289
  getSkillFilePath,
272
290
  incrementSkillHit,
273
291
  listOutputs,
@@ -279,27 +297,39 @@ import {
279
297
  skillOutputsDir,
280
298
  storeCompact,
281
299
  storeOutput
282
- } from "./token-goat-chunk-M6CVNHTW.mjs";
300
+ } from "./token-goat-chunk-H5J3C56Q.mjs";
301
+ import {
302
+ getSessionFiles,
303
+ getSessionId
304
+ } from "./token-goat-chunk-TDUT2CL6.mjs";
283
305
  import {
284
- capJsonRows,
285
306
  claudeConfigDir,
286
- estimateTokens,
287
- estimateTokensFromLength,
288
307
  formatBytes,
289
- formatTokenEstimate,
290
- trimToBudget
291
- } from "./token-goat-chunk-G6EYO5CD.mjs";
308
+ formatTokenEstimate
309
+ } from "./token-goat-chunk-ONXH6NZ5.mjs";
292
310
  import {
293
311
  BASH_OUTPUT_SUBDIR,
294
- clipLongMatchLine,
295
- combineStreams,
296
312
  getBashOutput,
297
313
  isRecallCacheType
298
- } from "./token-goat-chunk-43JFN26X.mjs";
314
+ } from "./token-goat-chunk-O6I2XU4P.mjs";
299
315
  import {
300
- C,
301
316
  DEFAULT_MAX_AGE_MS,
302
317
  DEFAULT_MAX_COUNT,
318
+ listBlobs,
319
+ pruneBlobs
320
+ } from "./token-goat-chunk-D4ETHJD6.mjs";
321
+ import {
322
+ capJsonRows,
323
+ estimateTokens,
324
+ estimateTokensFromLength,
325
+ trimToBudget
326
+ } from "./token-goat-chunk-52NSDWYY.mjs";
327
+ import {
328
+ clipLongMatchLine,
329
+ combineStreams
330
+ } from "./token-goat-chunk-2WOUYWLM.mjs";
331
+ import {
332
+ C,
303
333
  HOOK_STATS_RETENTION_DAYS,
304
334
  RESET,
305
335
  _useRichStats,
@@ -308,8 +338,6 @@ import {
308
338
  fg,
309
339
  formatLocalTimestamp,
310
340
  getGlobalDb,
311
- listBlobs,
312
- pruneBlobs,
313
341
  pruneStalePatternCoveredUnmappedTools,
314
342
  readUnmappedTools,
315
343
  recordStat,
@@ -321,19 +349,28 @@ import {
321
349
  statsHasHarnessColumn,
322
350
  stripAnsiEscapes,
323
351
  summarize
324
- } from "./token-goat-chunk-D2X7XN5M.mjs";
352
+ } from "./token-goat-chunk-CIQ2FTY3.mjs";
353
+ import {
354
+ queryServers,
355
+ serverStatuses
356
+ } from "./token-goat-chunk-RQGGMPCR.mjs";
357
+ import {
358
+ readMarker,
359
+ touchMarker
360
+ } from "./token-goat-chunk-DPNLUJWB.mjs";
361
+ import "./token-goat-chunk-SE6E4BKJ.mjs";
325
362
  import {
326
363
  docxOutline,
327
364
  docxTables,
328
365
  docxText,
329
366
  formatDocxTables
330
- } from "./token-goat-chunk-WNBSCSAI.mjs";
367
+ } from "./token-goat-chunk-UA5BKWWQ.mjs";
331
368
  import {
332
369
  pptxNotesText,
333
370
  pptxOutline,
334
371
  pptxSlideText,
335
372
  pptxTextGrep
336
- } from "./token-goat-chunk-BAWKGODL.mjs";
373
+ } from "./token-goat-chunk-HNFZZHFW.mjs";
337
374
  import {
338
375
  formatXlsxColumns,
339
376
  formatXlsxRange,
@@ -342,11 +379,16 @@ import {
342
379
  querySheet,
343
380
  rangeSheet,
344
381
  xlsxColumns
345
- } from "./token-goat-chunk-STYRLQIW.mjs";
382
+ } from "./token-goat-chunk-PYSUADX4.mjs";
346
383
  import {
347
384
  formatCsvTable,
348
385
  parseWhereSpecs
349
- } from "./token-goat-chunk-L43RKNHA.mjs";
386
+ } from "./token-goat-chunk-DNSPXQXQ.mjs";
387
+ import "./token-goat-chunk-3BTK54F3.mjs";
388
+ import "./token-goat-chunk-UXSXQQIY.mjs";
389
+ import "./token-goat-chunk-XTQAOTSO.mjs";
390
+ import "./token-goat-chunk-AH6QILZM.mjs";
391
+ import "./token-goat-chunk-PGTH2U4C.mjs";
350
392
  import {
351
393
  assetEmbedSha,
352
394
  deleteFileEmbeddings,
@@ -368,18 +410,21 @@ import {
368
410
  maxChunksEmbedSha,
369
411
  missingTreeSitterGrammarPackages,
370
412
  modelFilesPresent,
413
+ oversizeEmbedSha,
371
414
  parserFingerprintForLanguage,
372
415
  readSection,
373
416
  treeSitterCoreAvailable,
374
417
  treeSitterCoreLoadError
375
- } from "./token-goat-chunk-7EPYB34H.mjs";
418
+ } from "./token-goat-chunk-VMFWNHGR.mjs";
376
419
  import {
377
420
  getFileEntry,
378
421
  getProjectFileEntries,
422
+ projectScopeClause,
379
423
  querySymbols
380
- } from "./token-goat-chunk-IKZLSRII.mjs";
424
+ } from "./token-goat-chunk-435UQTKD.mjs";
381
425
  import {
382
426
  TREE_SITTER_LANGUAGES,
427
+ allowReadOnlyIndex,
383
428
  detectLanguage,
384
429
  detectLanguageOfFile,
385
430
  fenceFor,
@@ -388,14 +433,13 @@ import {
388
433
  matchGroovySlashy,
389
434
  nonTreeSitterLanguageCount,
390
435
  redactIfDotenv
391
- } from "./token-goat-chunk-7SXPJOSZ.mjs";
436
+ } from "./token-goat-chunk-7CPMDFGB.mjs";
392
437
  import {
393
438
  CONFIG_KEY_ENV_OVERRIDES,
394
439
  PROJECT_LOCKED_KEYS,
395
440
  PROJECT_LOCKED_SECTIONS,
396
441
  SUPPORTED_OCR_LANG_CODES,
397
442
  defaultConfig,
398
- envBool,
399
443
  fileIsAbsent,
400
444
  findProject,
401
445
  fingerprintContent,
@@ -404,8 +448,10 @@ import {
404
448
  getLastConfigParseError,
405
449
  getLastProjectConfigParseError,
406
450
  getProjectConfigInfo,
451
+ hookServerEnabled,
407
452
  invalidateConfigCache,
408
453
  isSupportedOcrLang,
454
+ isUnderSystemTemp,
409
455
  lastProjectConfigLockedKeys,
410
456
  loadConfig,
411
457
  loadPersistedConfig,
@@ -417,22 +463,21 @@ import {
417
463
  stringify,
418
464
  validateEnumField,
419
465
  validateNumericField
420
- } from "./token-goat-chunk-LRIZ7K3F.mjs";
466
+ } from "./token-goat-chunk-TGOXNAHW.mjs";
467
+ import "./token-goat-chunk-XB5KKOCG.mjs";
468
+ import "./token-goat-chunk-YZAAZU2S.mjs";
469
+ import {
470
+ envBool
471
+ } from "./token-goat-chunk-IVE6U7KV.mjs";
421
472
  import {
422
- ENV_KEYS,
423
473
  LOCK_WAIT_MS_HARDENED,
424
- PACKAGE_NAME,
425
- VERSION,
426
474
  assertProjectScopeTarget,
427
475
  assertWriteInScope,
428
476
  atomicWriteBytes,
429
477
  atomicWriteText,
430
478
  backupFile,
431
- canonicalize,
432
479
  cappedSourceBytesSaved,
433
- configPath,
434
480
  countNoun,
435
- dataDir,
436
481
  decodeSource,
437
482
  detectSourceEncoding,
438
483
  encodeSource,
@@ -440,19 +485,16 @@ import {
440
485
  ensureNewline,
441
486
  escapeRegExp,
442
487
  extractErrorMessage,
443
- foldPath,
444
- globalDbPath,
488
+ hasCreatedConfig,
445
489
  hookCommandFor,
446
490
  hookExecPartsFor,
447
491
  installSingleFilePlugin,
448
492
  isTestFile,
449
493
  isUnderBlockedRoot,
450
- isWindows,
451
494
  pad,
452
495
  projectPathIsConsultable,
453
496
  projectScopeRoot,
454
497
  pushAll,
455
- quoteShellPath,
456
498
  recordCreatedConfig,
457
499
  redactUrlQuery,
458
500
  removeCreatedBackups,
@@ -460,16 +502,13 @@ import {
460
502
  removeFileInScope,
461
503
  requireNonNegativeStrictInt,
462
504
  requirePositiveStrictInt,
463
- resolveOnPath,
464
505
  runGit,
465
- sleepSync,
466
506
  stripDelimitedBlock,
467
507
  stripLower,
468
508
  stripOwnHooksFromMap,
469
509
  stripStaleGroupHooks,
470
510
  takeCreatedConfig,
471
511
  toKB,
472
- tokenGoatHome,
473
512
  uninstallSingleFilePlugin,
474
513
  upsertDelimitedBlock,
475
514
  withExtension,
@@ -478,7 +517,15 @@ import {
478
517
  withRetryOnLock,
479
518
  writeIfDifferent,
480
519
  writeJsonSettings
481
- } from "./token-goat-chunk-6E2IDLHF.mjs";
520
+ } from "./token-goat-chunk-XYWPOSCN.mjs";
521
+ import {
522
+ canonicalize,
523
+ foldPath,
524
+ isWindows,
525
+ quoteShellPath,
526
+ resolveOnPath,
527
+ sleepSync
528
+ } from "./token-goat-chunk-TCQANXYC.mjs";
482
529
  import {
483
530
  UNTRUSTED_FILE_TAG,
484
531
  UNTRUSTED_TOOL_TAG,
@@ -493,9 +540,19 @@ import {
493
540
  resolveIndexPath,
494
541
  toDisplayPath
495
542
  } from "./token-goat-chunk-NMTKNYGF.mjs";
543
+ import {
544
+ ENV_KEYS,
545
+ PACKAGE_NAME,
546
+ VERSION,
547
+ configPath,
548
+ dataDir,
549
+ globalDbPath,
550
+ tokenGoatHome
551
+ } from "./token-goat-chunk-MELOKFDJ.mjs";
552
+ import "./token-goat-chunk-Y4AFKTHK.mjs";
496
553
  import {
497
554
  compileGuardedRegex
498
- } from "./token-goat-chunk-GMOUBOX4.mjs";
555
+ } from "./token-goat-chunk-JTKCQMQA.mjs";
499
556
  import {
500
557
  __export,
501
558
  init_define_import_meta_env
@@ -3626,9 +3683,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3626
3683
  * @param {string} [path]
3627
3684
  * @return {(string|null|Command)}
3628
3685
  */
3629
- executableDir(path42) {
3630
- if (path42 === void 0) return this._executableDir;
3631
- this._executableDir = path42;
3686
+ executableDir(path41) {
3687
+ if (path41 === void 0) return this._executableDir;
3688
+ this._executableDir = path41;
3632
3689
  return this;
3633
3690
  }
3634
3691
  /**
@@ -3928,8 +3985,8 @@ function attemptedCommandName(argv) {
3928
3985
  }
3929
3986
 
3930
3987
  // src/cli.ts
3931
- import * as fs45 from "fs";
3932
- import * as path41 from "path";
3988
+ import * as fs44 from "fs";
3989
+ import * as path40 from "path";
3933
3990
 
3934
3991
  // src/walk_index.ts
3935
3992
  init_define_import_meta_env();
@@ -3991,6 +4048,18 @@ init_define_import_meta_env();
3991
4048
 
3992
4049
  // src/bridges/shim_common.ts
3993
4050
  init_define_import_meta_env();
4051
+ var SHIM_FILE = "token-goat-shim.cjs";
4052
+ var LEGACY_SHIM_FILE = "token-goat-shim.js";
4053
+ function legacyShimForwarder(noOp) {
4054
+ const answer = noOp === "" ? "" : `
4055
+ try { process.stdout.write('${noOp}') } catch {}`;
4056
+ return `#!/usr/bin/env node
4057
+ // Forwards to token-goat-shim.cjs for hook commands wired before the shim was renamed.
4058
+ import('./token-goat-shim.cjs').catch(() => {${answer}
4059
+ process.exitCode = 0
4060
+ });
4061
+ `;
4062
+ }
3994
4063
  var SHIM_REQUIRES = `const { spawnSync } = require('node:child_process')
3995
4064
  const path = require('node:path')
3996
4065
  const { pathToFileURL } = require('node:url')`;
@@ -4022,10 +4091,12 @@ var SHIM_TRY_IN_PROCESS = `// Attempts the in-process hook call: import()s dist/
4022
4091
  async function tryInProcess(entryPath, eventName, input, harnessWaitMs) {
4023
4092
  if (!entryPath) return undefined
4024
4093
  try {
4094
+ const payload = JSON.parse(input)
4095
+ const served = await tryServer(entryPath, eventName, input, harnessWaitMs)
4096
+ if (served !== undefined) return served
4025
4097
  const hookLibPath = path.join(path.dirname(entryPath), 'token-goat-hook.mjs')
4026
4098
  if (!require('node:fs').existsSync(hookLibPath)) return undefined
4027
4099
  const mod = await import(pathToFileURL(hookLibPath).href)
4028
- const payload = JSON.parse(input)
4029
4100
  return await mod.relayInProcess(eventName, payload, harnessWaitMs)
4030
4101
  } catch {
4031
4102
  return undefined
@@ -4111,6 +4182,8 @@ ${SHIM_REQUIRES}
4111
4182
 
4112
4183
  ${SHIM_VALID_HOOK_EVENTS}
4113
4184
 
4185
+ ${SHIM_TRY_SERVER}
4186
+
4114
4187
  ${SHIM_TRY_IN_PROCESS}
4115
4188
 
4116
4189
  ${SHIM_MAX_BUFFER_CONST}
@@ -4210,6 +4283,8 @@ var CANONICAL_SKILL_MD = gunzipSync(Buffer.from(CANONICAL_SKILL_GZIP_BASE64, "ba
4210
4283
  var HOOK_EVENT_MAP = [
4211
4284
  ["PreToolUse", "pre_tool_use"],
4212
4285
  ["PostToolUse", "post_tool_use"],
4286
+ // A failed call never reaches PostToolUse: Claude Code 2.1.281 sends a missing-file Read and a non-zero Bash exit here instead, carrying `error`, `is_interrupt` and `duration_ms` (captured, 2026-09-24).
4287
+ ["PostToolUseFailure", "post_tool_use_failure"],
4213
4288
  ["PreCompact", "pre_compact"],
4214
4289
  ["PostCompact", "post_compact"],
4215
4290
  ["UserPromptSubmit", "user_prompt_submit"],
@@ -4259,21 +4334,46 @@ function execFormSupportedForVersionOutput(stdout) {
4259
4334
  function expectedHookEntryFor(scriptPath, event) {
4260
4335
  return claudeExecFormHooksSupported() ? hookExecPartsFor(scriptPath, event) : { command: hookCommandFor(scriptPath, event) };
4261
4336
  }
4337
+ function realPathOrNull(p) {
4338
+ if (!path3.isAbsolute(p)) return null;
4339
+ try {
4340
+ return fs3.realpathSync.native(p);
4341
+ } catch {
4342
+ return null;
4343
+ }
4344
+ }
4345
+ function sameHookWord(a, b) {
4346
+ if (a === b) return true;
4347
+ const realA = realPathOrNull(a);
4348
+ return realA !== null && realA === realPathOrNull(b);
4349
+ }
4350
+ function stringFormHookWords(command) {
4351
+ const words = [];
4352
+ for (const m of command.matchAll(/"((?:\\.|[^"\\])*)"|(\S+)/g)) {
4353
+ const quoted = m[1];
4354
+ words.push(quoted === void 0 ? m[2] : process.platform === "win32" ? quoted : quoted.replace(/\\(.)/g, "$1"));
4355
+ }
4356
+ return words;
4357
+ }
4262
4358
  function hookEntryMatches(command, args, expected) {
4263
- if (command !== expected.command) return false;
4264
- const a = args ?? [];
4265
- const b = expected.args ?? [];
4266
- return a.length === b.length && a.every((v, i) => v === b[i]);
4359
+ const execForm = expected.args !== void 0;
4360
+ if (!execForm && args !== void 0 && args.length > 0) return false;
4361
+ const a = execForm ? [command, ...args ?? []] : stringFormHookWords(command);
4362
+ const b = execForm ? [expected.command, ...expected.args] : stringFormHookWords(expected.command);
4363
+ return a.length === b.length && a.every((v, i) => sameHookWord(v, b[i]));
4267
4364
  }
4268
4365
  function claudeHookScriptPath() {
4269
- return path3.join(claudeConfigDir(), "hooks", "token-goat-shim.js");
4366
+ return path3.join(claudeConfigDir(), "hooks", SHIM_FILE);
4270
4367
  }
4271
- function anyScopeReferencesShim(scriptPath, currentScope, alreadyStripped) {
4368
+ function claudeLegacyHookScriptPath() {
4369
+ return path3.join(claudeConfigDir(), "hooks", LEGACY_SHIM_FILE);
4370
+ }
4371
+ function anyScopeReferencesShim(scriptPaths, currentScope, alreadyStripped) {
4272
4372
  const referencesIn = (map) => {
4273
4373
  for (const groups of Object.values(map ?? {})) {
4274
4374
  for (const group of groups) {
4275
4375
  for (const h of group.hooks ?? []) {
4276
- if (h.command.includes(scriptPath) || (h.args ?? []).some((a) => a.includes(scriptPath))) return true;
4376
+ if (scriptPaths.some((p) => h.command.includes(p) || (h.args ?? []).some((a) => a.includes(p)))) return true;
4277
4377
  }
4278
4378
  }
4279
4379
  }
@@ -4342,7 +4442,9 @@ function installHooksScoped(scope) {
4342
4442
  const scriptPath = claudeHookScriptPath();
4343
4443
  const scriptChanged = withInstallScope(void 0, () => {
4344
4444
  ensureDirSync(path3.dirname(scriptPath));
4345
- return writeIfDifferent(scriptPath, CLAUDECODE_HOOK_SCRIPT);
4445
+ const forwarderChanged = writeIfDifferent(claudeLegacyHookScriptPath(), legacyShimForwarder("{}"));
4446
+ const scriptChanged2 = writeIfDifferent(scriptPath, CLAUDECODE_HOOK_SCRIPT);
4447
+ return scriptChanged2 || forwarderChanged;
4346
4448
  });
4347
4449
  let settingsChanged = false;
4348
4450
  for (const [eventKey, eventArg] of HOOK_EVENT_MAP) {
@@ -4409,8 +4511,8 @@ function uninstallHooksScoped(scope) {
4409
4511
  const removed = stripOwnHooksFromMap(hooks, isTokenGoatHookCommand);
4410
4512
  const scriptPath = claudeHookScriptPath();
4411
4513
  let removedScript = false;
4412
- if (!anyScopeReferencesShim(scriptPath, scope, hooks)) {
4413
- removedScript = withInstallScope(void 0, () => removeFileInScope(scriptPath));
4514
+ if (!anyScopeReferencesShim([scriptPath, claudeLegacyHookScriptPath()], scope, hooks)) {
4515
+ removedScript = withInstallScope(void 0, () => [scriptPath, claudeLegacyHookScriptPath()].map((p2) => removeFileInScope(p2)).some(Boolean));
4414
4516
  }
4415
4517
  if (!removed) return removedScript;
4416
4518
  if (Object.keys(hooks).length === 0) {
@@ -4423,16 +4525,19 @@ function uninstallHooksScoped(scope) {
4423
4525
  return true;
4424
4526
  }
4425
4527
  function isInstalled(scope = "user") {
4426
- const settings = readSettings(settingsPath(scope));
4427
- const hooks = settings.hooks;
4428
- if (hooks === void 0) return false;
4528
+ if (!fs3.existsSync(claudeHookScriptPath())) return false;
4529
+ const missing = missingHookEvents(scope);
4530
+ return missing !== null && missing.length === 0;
4531
+ }
4532
+ function missingHookEvents(scope = "user") {
4533
+ const hooks = readSettings(settingsPath(scope)).hooks;
4534
+ if (hooks === void 0) return null;
4535
+ if (!HOOK_EVENT_MAP.some(([eventKey]) => groupHasTokenGoat(hooks[eventKey], isTokenGoatHookCommand))) return null;
4429
4536
  const scriptPath = claudeHookScriptPath();
4430
- if (!fs3.existsSync(scriptPath)) return false;
4431
- for (const [eventKey, eventArg] of HOOK_EVENT_MAP) {
4537
+ return HOOK_EVENT_MAP.filter(([eventKey, eventArg]) => {
4432
4538
  const expected = expectedHookEntryFor(scriptPath, eventArg);
4433
- if (!groupHasTokenGoat(hooks[eventKey], (c, a) => hookEntryMatches(c, a, expected))) return false;
4434
- }
4435
- return true;
4539
+ return !groupHasTokenGoat(hooks[eventKey], (c, a) => hookEntryMatches(c, a, expected));
4540
+ }).map(([eventKey]) => eventKey);
4436
4541
  }
4437
4542
  var CLAUDE_MD_BEGIN = "<!-- token-goat-begin -->";
4438
4543
  var CLAUDE_MD_END = "<!-- token-goat-end -->";
@@ -4586,6 +4691,8 @@ function stripTg(value) {
4586
4691
  return value
4587
4692
  }
4588
4693
 
4694
+ ${SHIM_TRY_SERVER}
4695
+
4589
4696
  ${SHIM_TRY_IN_PROCESS}
4590
4697
 
4591
4698
  ${SHIM_MAX_BUFFER_CONST}
@@ -4705,7 +4812,10 @@ function codexAgentsPath() {
4705
4812
  return path4.join(os2.homedir(), ".codex", "AGENTS.md");
4706
4813
  }
4707
4814
  function codexHookScriptPath() {
4708
- return path4.join(os2.homedir(), ".codex", "hooks", "token-goat-shim.js");
4815
+ return path4.join(os2.homedir(), ".codex", "hooks", SHIM_FILE);
4816
+ }
4817
+ function codexLegacyHookScriptPath() {
4818
+ return path4.join(os2.homedir(), ".codex", "hooks", LEGACY_SHIM_FILE);
4709
4819
  }
4710
4820
  function readCodexConfig(p, opts = {}) {
4711
4821
  let raw;
@@ -4762,6 +4872,7 @@ function installCodex() {
4762
4872
  const scriptPath = codexHookScriptPath();
4763
4873
  ensureDirSync(path4.dirname(scriptPath));
4764
4874
  atomicWriteText(scriptPath, CODEX_HOOK_SCRIPT);
4875
+ atomicWriteText(codexLegacyHookScriptPath(), legacyShimForwarder("{}"));
4765
4876
  const config = readCodexConfig(configPath2, { strict: true });
4766
4877
  const hooks = config.hooks ?? {};
4767
4878
  let hooksChanged = false;
@@ -4915,10 +5026,12 @@ function uninstallCodex() {
4915
5026
  if (stripAgentsBlock(agentsPath)) {
4916
5027
  removedAny = true;
4917
5028
  }
4918
- try {
4919
- fs4.unlinkSync(scriptPath);
4920
- removedAny = true;
4921
- } catch {
5029
+ for (const p of [scriptPath, codexLegacyHookScriptPath()]) {
5030
+ try {
5031
+ fs4.unlinkSync(p);
5032
+ removedAny = true;
5033
+ } catch {
5034
+ }
4922
5035
  }
4923
5036
  removeCreatedBackups(configPath2);
4924
5037
  return removedAny;
@@ -5296,6 +5409,8 @@ function toKimi(parsed) {
5296
5409
  return ''
5297
5410
  }
5298
5411
 
5412
+ ${SHIM_TRY_SERVER}
5413
+
5299
5414
  ${SHIM_TRY_IN_PROCESS}
5300
5415
 
5301
5416
  ${SHIM_MAX_BUFFER_CONST}
@@ -5369,7 +5484,10 @@ function kimiAgentsPath() {
5369
5484
  return path7.join(kimiHome(), "AGENTS.md");
5370
5485
  }
5371
5486
  function kimiHookScriptPath() {
5372
- return path7.join(kimiHome(), "hooks", "token-goat-shim.js");
5487
+ return path7.join(kimiHome(), "hooks", SHIM_FILE);
5488
+ }
5489
+ function kimiLegacyHookScriptPath() {
5490
+ return path7.join(kimiHome(), "hooks", LEGACY_SHIM_FILE);
5373
5491
  }
5374
5492
  function kimiSkillDir() {
5375
5493
  return path7.join(kimiHome(), "skills", "token-goat");
@@ -5439,6 +5557,7 @@ function installKimi() {
5439
5557
  const scriptPath = kimiHookScriptPath();
5440
5558
  ensureDirSync(path7.dirname(scriptPath));
5441
5559
  atomicWriteText(scriptPath, KIMI_HOOK_SCRIPT);
5560
+ atomicWriteText(kimiLegacyHookScriptPath(), legacyShimForwarder(""));
5442
5561
  const config = readKimiConfig(configPath2, { strict: true });
5443
5562
  const existing = Array.isArray(config.hooks) ? config.hooks : [];
5444
5563
  const desired = KIMI_HOOK_EVENTS.map((event) => ({
@@ -5490,12 +5609,14 @@ function uninstallKimi() {
5490
5609
  }
5491
5610
  } catch {
5492
5611
  }
5493
- try {
5494
- if (fs7.existsSync(kimiHookScriptPath())) {
5495
- fs7.rmSync(kimiHookScriptPath(), { force: true });
5496
- removed = true;
5612
+ for (const p of [kimiHookScriptPath(), kimiLegacyHookScriptPath()]) {
5613
+ try {
5614
+ if (fs7.existsSync(p)) {
5615
+ fs7.rmSync(p, { force: true });
5616
+ removed = true;
5617
+ }
5618
+ } catch {
5497
5619
  }
5498
- } catch {
5499
5620
  }
5500
5621
  removeCreatedBackups(configPath2);
5501
5622
  return removed;
@@ -5589,7 +5710,7 @@ function resolveEntryPath(): string | undefined {
5589
5710
  // undefined (triggering the spawnSync fallback below) when entryPath is absent, the
5590
5711
  // sibling file doesn't exist (an install predating this file), or anything else goes
5591
5712
  // wrong -- this must never throw.
5592
- type RelayInProcessFn = (event: string, payload: unknown) => Promise<string>;
5713
+ type RelayInProcessFn = (event: string, payload: unknown, harnessWaitMs?: number, opts?: { elapsedMs?: () => number }) => Promise<string>;
5593
5714
  let cachedRelayInProcess: RelayInProcessFn | undefined;
5594
5715
  async function resolveRelayInProcess(): Promise<RelayInProcessFn | undefined> {
5595
5716
  if (cachedRelayInProcess) return cachedRelayInProcess;
@@ -5657,11 +5778,13 @@ function callHookViaSpawn(event: string, payload: Record<string, unknown>): Reco
5657
5778
  // its own fail-open null rather than being force-killed by pi's own hook timeout
5658
5779
  // budget, ~5000ms) when the in-process path is unavailable or throws.
5659
5780
  async function callHook(event: string, payload: Record<string, unknown>): Promise<Record<string, unknown> | null> {
5781
+ // Timed from here, not from process start: this host lives far longer than one call, so the hook library's default clock (process age) would record the host's uptime as this call's duration.
5782
+ const start = performance.now();
5660
5783
  const relay = await resolveRelayInProcess();
5661
5784
  if (relay) {
5662
5785
  try {
5663
5786
  process.env.TOKEN_GOAT_HARNESS_OVERRIDE = "pi";
5664
- const out = await relay(event, payload);
5787
+ const out = await relay(event, payload, undefined, { elapsedMs: () => performance.now() - start });
5665
5788
  const trimmed = out?.trim();
5666
5789
  if (!trimmed) return null;
5667
5790
  return JSON.parse(trimmed) as Record<string, unknown>;
@@ -6023,10 +6146,12 @@ function callHookViaSpawn(event, payload) {
6023
6146
  // its own fail-open null rather than being force-killed by the host's own hook timeout
6024
6147
  // budget, ~5000ms) when the in-process path is unavailable or throws.
6025
6148
  async function callHook(event, payload) {
6149
+ // Timed from here, not from process start: this host lives far longer than one call, so the hook library's default clock (process age) would record the host's uptime as this call's duration.
6150
+ const start = performance.now();
6026
6151
  const relay = await resolveRelayInProcess();
6027
6152
  if (relay) {
6028
6153
  try {
6029
- const out = await relay(event, payload);
6154
+ const out = await relay(event, payload, undefined, { elapsedMs: () => performance.now() - start });
6030
6155
  const trimmed = out ? out.trim() : "";
6031
6156
  if (!trimmed) return null;
6032
6157
  return JSON.parse(trimmed);
@@ -6556,6 +6681,8 @@ function allowResponse(eventName) {
6556
6681
  return eventName === 'pre_tool_use' ? '{"decision":"allow"}' : '{}'
6557
6682
  }
6558
6683
 
6684
+ ${SHIM_TRY_SERVER}
6685
+
6559
6686
  ${SHIM_TRY_IN_PROCESS}
6560
6687
 
6561
6688
  ${SHIM_MAX_BUFFER_CONST}
@@ -6649,7 +6776,10 @@ function grokConfigPath() {
6649
6776
  return path11.join(grokHooksDir(), "token-goat.json");
6650
6777
  }
6651
6778
  function grokHookScriptPath() {
6652
- return path11.join(grokHooksDir(), "token-goat-shim.js");
6779
+ return path11.join(grokHooksDir(), SHIM_FILE);
6780
+ }
6781
+ function grokLegacyHookScriptPath() {
6782
+ return path11.join(grokHooksDir(), LEGACY_SHIM_FILE);
6653
6783
  }
6654
6784
  function buildConfig(scriptPath) {
6655
6785
  const hooks = {};
@@ -6663,11 +6793,12 @@ function buildConfig(scriptPath) {
6663
6793
  function installGrok() {
6664
6794
  const configPath2 = grokConfigPath();
6665
6795
  const scriptPath = grokHookScriptPath();
6796
+ const forwarderChanged = writeIfDifferent(grokLegacyHookScriptPath(), legacyShimForwarder("{}"));
6666
6797
  const scriptChanged = writeIfDifferent(scriptPath, GROK_HOOK_SCRIPT);
6667
6798
  const desiredText = `${JSON.stringify(buildConfig(scriptPath), null, 2)}
6668
6799
  `;
6669
6800
  const configChanged = writeIfDifferent(configPath2, desiredText, true);
6670
- return { configPath: configPath2, hookScriptPath: scriptPath, alreadyInstalled: !scriptChanged && !configChanged };
6801
+ return { configPath: configPath2, hookScriptPath: scriptPath, alreadyInstalled: !scriptChanged && !forwarderChanged && !configChanged };
6671
6802
  }
6672
6803
  function uninstallGrok() {
6673
6804
  const configPath2 = grokConfigPath();
@@ -6678,10 +6809,12 @@ function uninstallGrok() {
6678
6809
  removedAny = true;
6679
6810
  } catch {
6680
6811
  }
6681
- try {
6682
- fs11.unlinkSync(scriptPath);
6683
- removedAny = true;
6684
- } catch {
6812
+ for (const p of [scriptPath, grokLegacyHookScriptPath()]) {
6813
+ try {
6814
+ fs11.unlinkSync(p);
6815
+ removedAny = true;
6816
+ } catch {
6817
+ }
6685
6818
  }
6686
6819
  removeCreatedBackups(configPath2);
6687
6820
  return removedAny;
@@ -7169,6 +7302,9 @@ init_define_import_meta_env();
7169
7302
  import { spawnSync as spawnSync2 } from "node:child_process";
7170
7303
  import * as fs15 from "node:fs";
7171
7304
  import * as path15 from "node:path";
7305
+ var ProcessListTimeout = class extends Error {
7306
+ };
7307
+ var PROCESS_LIST_TIMEOUT_MS = 2e4;
7172
7308
  var INTERPRETER_BASENAMES = /* @__PURE__ */ new Set(["node.exe", "python.exe", "python3.exe", "pwsh.exe", "powershell.exe", "cmd.exe"]);
7173
7309
  function describeProcess(commandLine) {
7174
7310
  for (const m of commandLine.matchAll(/[^\s"]+\.(?:js|mjs|cjs|ts|py|exe)\b/gi)) {
@@ -7179,11 +7315,14 @@ function describeProcess(commandLine) {
7179
7315
  return trimmed.length > 0 ? trimmed.length > 60 ? trimmed.slice(0, 60) + "\u2026" : trimmed : "(no command line)";
7180
7316
  }
7181
7317
  function checkMcpProcessHealth(processes) {
7182
- if (processes === null) {
7318
+ if (!Array.isArray(processes)) {
7319
+ const failure = processes;
7320
+ const query = `Get-CimInstance Win32_Process -Filter "Name='node.exe'" | Select-Object ProcessId,ParentProcessId,CommandLine`;
7321
+ const advice = failure.transient === true ? `Run doctor again once the machine is less busy, or list them yourself with: ${query}` : `List them yourself with: ${query}`;
7183
7322
  return {
7184
7323
  name: "MCP process health",
7185
7324
  status: "warn",
7186
- message: "could not read the process list (PowerShell did not answer), so duplicate MCP launchers and orphaned Node processes were not checked"
7325
+ message: `could not read the process list (${displaySafeText(failure.reason)}), so duplicate MCP launchers and orphaned Node processes were not checked. ${advice}`
7187
7326
  };
7188
7327
  }
7189
7328
  const byPid = new Set(processes.map((process3) => process3.processId));
@@ -7216,19 +7355,37 @@ function runProcessListCommand() {
7216
7355
  const shell = path15.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
7217
7356
  const result = spawnSync2(fs15.existsSync(shell) ? shell : "powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", command], {
7218
7357
  encoding: "utf8",
7219
- timeout: 2e4,
7358
+ timeout: PROCESS_LIST_TIMEOUT_MS,
7220
7359
  maxBuffer: 10 * 1024 * 1024,
7221
7360
  windowsHide: true
7222
7361
  });
7223
- if (result.error !== void 0) throw result.error;
7224
- return result.stdout ?? "";
7362
+ return processListOutput(result);
7363
+ }
7364
+ function processListOutput(result) {
7365
+ if (result.error !== void 0) {
7366
+ const code = result.error.code;
7367
+ if (code === "ETIMEDOUT") throw new ProcessListTimeout(`PowerShell did not finish within ${PROCESS_LIST_TIMEOUT_MS / 1e3}s`);
7368
+ if (code === "ENOENT") throw new Error("powershell.exe was not found");
7369
+ throw result.error;
7370
+ }
7371
+ const stdout = result.stdout ?? "";
7372
+ if (result.status !== 0 && stdout.trim() === "") {
7373
+ const firstLine2 = (result.stderr ?? "").split(/\r?\n/).map((line) => line.trim()).find((line) => line !== "");
7374
+ throw new Error(`PowerShell exited with code ${String(result.status)}${firstLine2 !== void 0 ? `: ${firstLine2}` : ""}`);
7375
+ }
7376
+ return stdout;
7225
7377
  }
7226
7378
  function readWindowsProcesses(runCommand = runProcessListCommand) {
7227
7379
  if (process.platform !== "win32") return [];
7228
7380
  try {
7229
7381
  const output = runCommand().trim();
7230
7382
  if (output === "") return [];
7231
- const parsed = JSON.parse(output);
7383
+ let parsed;
7384
+ try {
7385
+ parsed = JSON.parse(output);
7386
+ } catch {
7387
+ return { reason: "PowerShell printed something other than the JSON process list" };
7388
+ }
7232
7389
  const rows = Array.isArray(parsed) ? parsed : [parsed];
7233
7390
  return rows.flatMap((row) => {
7234
7391
  if (typeof row !== "object" || row === null) return [];
@@ -7241,8 +7398,9 @@ function readWindowsProcesses(runCommand = runProcessListCommand) {
7241
7398
  commandLine: typeof value["CommandLine"] === "string" ? value["CommandLine"] : ""
7242
7399
  }];
7243
7400
  });
7244
- } catch {
7245
- return null;
7401
+ } catch (e) {
7402
+ const reason = e instanceof Error ? e.message : String(e);
7403
+ return e instanceof ProcessListTimeout ? { reason, transient: true } : { reason };
7246
7404
  }
7247
7405
  }
7248
7406
  function checkWorkerRunning(dataDir2) {
@@ -7329,6 +7487,15 @@ function checkVscodeClaudeHooks(useClaudeHooks, claudeHooksInstalled, vscodeHook
7329
7487
  message: vscodeHooksInstalled2 ? "VS Code has chat.useClaudeHooks on and token-goat hooks are in both ~/.claude/settings.json and the Copilot hooks file, so each hook fires twice in VS Code. Turn chat.useClaudeHooks off in VS Code settings." : 'VS Code has chat.useClaudeHooks on, so it runs the token-goat hooks from ~/.claude/settings.json in Claude Code wire format, which VS Code reads only in part. Run "token-goat install --vscode" and turn chat.useClaudeHooks off in VS Code settings.'
7330
7488
  };
7331
7489
  }
7490
+ function checkClaudeHookEvents(missing) {
7491
+ const name = "Claude Code hook events";
7492
+ if (missing.user === null && missing.project === null) return null;
7493
+ const gaps = [];
7494
+ if (missing.user !== null && missing.user.length > 0) gaps.push(`user scope lacks ${missing.user.join(", ")}; run: token-goat install`);
7495
+ if (missing.project !== null && missing.project.length > 0) gaps.push(`project scope lacks ${missing.project.join(", ")}; run: token-goat install --project`);
7496
+ if (gaps.length === 0) return { name, status: "ok", message: "every event this build handles is wired" };
7497
+ return { name, status: "warn", message: `${gaps.join(". ")}. Those events never reach token-goat until then; restart any running session afterwards.` };
7498
+ }
7332
7499
  function dedupeByResolvedPath(paths) {
7333
7500
  const seen = /* @__PURE__ */ new Set();
7334
7501
  return paths.filter((p) => {
@@ -7565,35 +7732,31 @@ function freelistBytes(header) {
7565
7732
  const rawPageSize = header.readUInt16BE(16);
7566
7733
  return (rawPageSize === 1 ? 65536 : rawPageSize) * header.readUInt32BE(36);
7567
7734
  }
7568
- var CATEGORY_COLUMNS = [
7569
- { 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" },
7570
- { name: "refs", table: "refs", column: "context", command: "'token-goat reclaim-index --rebuild' drops and re-derives them" },
7571
- { name: "chunk text", table: "chunks", column: "text", command: "'token-goat reclaim-index --rebuild' drops and re-derives them" },
7572
- { name: "stats detail", table: "stats", column: "detail", command: "ages out on its own (180-day retention) -- no manual command needed" }
7735
+ var STORAGE_GROUPS = [
7736
+ { name: "refs", owns: /^refs$/, command: "grows with the files indexed, so it shrinks only when files leave the index" },
7737
+ { name: "symbols and their search index", owns: /^symbols(_fts\w*)?$/, command: "grows with the files indexed, so it shrinks only when files leave the index" },
7738
+ {
7739
+ name: "embeddings",
7740
+ owns: /^(chunks|chunk_vectors\w*)$/,
7741
+ command: "these back 'semantic'; to drop them, set indexing.embeddings_enabled = false and indexing.auto_reclaim_embeddings = true, then run 'token-goat doctor --repair'"
7742
+ },
7743
+ { name: "usage stats", owns: /^(stats\w*|hint_\w+|unmapped_tools)$/, command: "ages out on its own (180-day retention)" },
7744
+ { name: "recall cache", owns: /^cache_recall\w*$/, command: "ages out on its own" }
7573
7745
  ];
7574
- var VECTOR_BYTES_PER_ROW = 384 * 4;
7746
+ var OWNER_BYTES_SQL = `SELECT COALESCE(m.tbl_name, s.name) AS owner, SUM(s.pgsize) AS bytes FROM dbstat AS s LEFT JOIN sqlite_master AS m ON m.name = s.name WHERE s.aggregate = 1 GROUP BY owner`;
7575
7747
  function dbCategoryBreakdown(dbPath) {
7576
- const db = getDb(dbPath);
7577
- const shares = [];
7578
- for (const cat of CATEGORY_COLUMNS) {
7579
- try {
7580
- const row = db.prepare(`SELECT COALESCE(SUM(LENGTH(${cat.column})), 0) as bytes FROM ${cat.table}`).get();
7581
- if (row.bytes > 0) shares.push({ name: cat.name, bytes: row.bytes, command: cat.command });
7582
- } catch {
7583
- }
7584
- }
7748
+ let rows;
7585
7749
  try {
7586
- const row = db.prepare("SELECT COUNT(*) as n FROM chunk_vectors").get();
7587
- if (row.n > 0) {
7588
- shares.push({
7589
- name: "embedding vectors",
7590
- bytes: row.n * VECTOR_BYTES_PER_ROW,
7591
- command: "'token-goat reclaim-index --rebuild' drops them (set indexing.embeddings_enabled = false first so they don't regrow)"
7592
- });
7593
- }
7750
+ rows = getDb(dbPath).prepare(OWNER_BYTES_SQL).all();
7594
7751
  } catch {
7752
+ return [];
7595
7753
  }
7596
- return shares.sort((a, b) => b.bytes - a.bytes);
7754
+ const shares = STORAGE_GROUPS.map((group) => ({
7755
+ name: group.name,
7756
+ bytes: rows.filter((r) => group.owns.test(r.owner)).reduce((sum, r) => sum + r.bytes, 0),
7757
+ command: group.command
7758
+ }));
7759
+ return shares.filter((share) => share.bytes > 0).sort((a, b) => b.bytes - a.bytes);
7597
7760
  }
7598
7761
  function oversizeDbMessage(dbPath, sizeBytes, freeBytes, tempRows, categories = [], topConsumers = [], thresholdMb = 1500, autoReclaimEmbeddings = false) {
7599
7762
  const mb = (bytes) => Math.round(bytes / (1024 * 1024));
@@ -7604,15 +7767,18 @@ function oversizeDbMessage(dbPath, sizeBytes, freeBytes, tempRows, categories =
7604
7767
  advice.push(`'token-goat doctor --repair' will automatically reclaim embedding vectors and compact global.db`);
7605
7768
  }
7606
7769
  const head = `global.db is ${mb(sizeBytes)} MB at ${displaySafeText(dbPath)} (larger than threshold of ${thresholdMb} MB). `;
7607
- const totalCategoryBytes = categories.reduce((sum, c) => sum + c.bytes, 0);
7770
+ const listed = categories.slice(0, 3);
7771
+ const totalCategoryBytes = listed.reduce((sum, c) => sum + c.bytes, 0);
7608
7772
  const unmeasuredBytes = sizeBytes - totalCategoryBytes;
7609
- const breakdown = totalCategoryBytes > 0 && sizeBytes > 0 ? ` Where it went: ${categories.slice(0, 3).map((c) => `${c.name} ${mb(c.bytes)} MB (${Math.round(c.bytes / sizeBytes * 100)}%) -- ${c.command}`).join("; ")}.${unmeasuredBytes >= sizeBytes / 20 ? ` The other ${mb(unmeasuredBytes)} MB is row overhead and indexes, which these commands do not measure.` : ""}` : "";
7773
+ const breakdown = totalCategoryBytes > 0 && sizeBytes > 0 ? ` Where it went: ${listed.map((c) => `${c.name} ${mb(c.bytes)} MB (${Math.round(c.bytes / sizeBytes * 100)}%) -- ${c.command}`).join("; ")}.${unmeasuredBytes >= sizeBytes / 20 ? ` The other ${mb(unmeasuredBytes)} MB is smaller tables and free pages.` : ""}` : "";
7610
7774
  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}`;
7611
- if (topConsumers.length > 0) {
7775
+ const [top] = topConsumers;
7776
+ if (top !== void 0) {
7612
7777
  const list = topConsumers.map((c) => `${path17.basename(c.root) || c.root} (${countNoun(c.fileCount, "file")})`).join(", ");
7613
7778
  base += ` Top index consumers: ${list}.`;
7779
+ base += ` To shrink it, take out a project you do not need surgical reads in: 'token-goat project exclude "${displaySafeText(top.root)}"' removes its rows, then 'token-goat reclaim-index' returns the space.`;
7614
7780
  }
7615
- return base;
7781
+ return `${base} If this size is expected, raise indexing.max_db_size_mb above ${Math.ceil(sizeBytes / (1024 * 1024))}.`;
7616
7782
  }
7617
7783
  function checkDbExists(dataDir2, maxDbSizeMb) {
7618
7784
  const dbPath = path17.join(dataDir2, "global.db");
@@ -7716,6 +7882,29 @@ function checkSymbolCount(dbPath, rootDir) {
7716
7882
  }
7717
7883
  }
7718
7884
  var EMBED_COVERAGE_WARN_FRACTION = 0.25;
7885
+ function unembeddedReasons(dbPath, rootDir, symbolOnlyKb, maxChunks) {
7886
+ const db = getDb(dbPath);
7887
+ const scope = rootDir === void 0 ? null : projectScopeClause("path");
7888
+ const rows = db.prepare(`SELECT path, sha, embed_sha FROM files${scope === null ? "" : ` WHERE ${scope.clause}`}`).all(...scope === null || rootDir === void 0 ? [] : scope.params(rootDir));
7889
+ let owed = 0;
7890
+ let overSizeCap = 0;
7891
+ for (const row of rows) {
7892
+ if (isUnderSystemTemp(row.path)) continue;
7893
+ if (row.embed_sha === oversizeEmbedSha(row.sha, symbolOnlyKb)) overSizeCap += 1;
7894
+ else if (!isEmbedFresh(row.embed_sha ?? void 0, row.sha, true, true, symbolOnlyKb, maxChunks)) owed += 1;
7895
+ }
7896
+ return { owed, overSizeCap };
7897
+ }
7898
+ function unembeddedReasonText(reasons, sizeKb) {
7899
+ let text = "";
7900
+ if (reasons.owed > 0) {
7901
+ text += ` ${countNoun(reasons.owed, "file")} ${reasons.owed === 1 ? "is" : "are"} still owed an embed: the worker embeds them while it is idle, or run 'token-goat index' in the project to embed them now.`;
7902
+ }
7903
+ if (reasons.overSizeCap > 0) {
7904
+ text += ` ${countNoun(reasons.overSizeCap, "file")} ${reasons.overSizeCap === 1 ? "is" : "are"} over indexing.large_file_symbol_only_kb (currently ${sizeKb} KB) and indexed for symbols only; raise it with 'token-goat config set indexing.large_file_symbol_only_kb <KB>' and the next 'token-goat index' embeds them.`;
7905
+ }
7906
+ return text;
7907
+ }
7719
7908
  function checkEmbeddingCoverage(dbPath, rootDir) {
7720
7909
  if (!fs18.existsSync(dbPath)) {
7721
7910
  return { name: "Embedding coverage", status: "ok", message: "no database yet" };
@@ -7735,7 +7924,7 @@ function checkEmbeddingCoverage(dbPath, rootDir) {
7735
7924
  return {
7736
7925
  name: "Embedding coverage",
7737
7926
  status: "warn",
7738
- message: `only ${embeddedFiles} of ${indexedFiles} indexed file(s) (${pct3}%) have embeddings \u2014 'semantic' searches those files only, and reports finding nothing in the same words it uses after searching everything. Files over indexing.large_file_symbol_only_kb (currently ${sizeKb} KB) are indexed for symbols only and are the usual reason; raise it with 'token-goat config set indexing.large_file_symbol_only_kb <KB>' and re-embed with 'token-goat index --force' to widen coverage. Exact symbol lookups are unaffected`
7927
+ message: `only ${embeddedFiles} of ${indexedFiles} indexed file(s) (${pct3}%) have embeddings, so 'semantic' searches those files only, and reports finding nothing in the same words it uses after searching everything.` + unembeddedReasonText(unembeddedReasons(dbPath, rootDir, sizeKb, cfg.indexing.max_chunks_per_file), sizeKb) + ` Exact symbol lookups are unaffected`
7739
7928
  };
7740
7929
  }
7741
7930
  return {
@@ -7989,16 +8178,33 @@ function checkDiskSpace(dataDir2) {
7989
8178
  }
7990
8179
  return { name: "Disk Space", status: "warn", message: "disk space check unavailable on this platform" };
7991
8180
  }
7992
- function checkCopilotCli(configPath2, scriptPath) {
7993
- if (!fs18.existsSync(configPath2) || !fs18.existsSync(scriptPath)) {
8181
+ function checkCopilotCli(configPath2, scriptPath, scope = "user") {
8182
+ if (!fs18.existsSync(configPath2)) {
7994
8183
  return null;
7995
8184
  }
8185
+ const name = scope === "project" ? "Copilot CLI (project)" : "Copilot CLI";
8186
+ const { install, harness } = copilotHooksRecovery(path17.dirname(configPath2), scope);
8187
+ if (scope === "project" && !hasCreatedConfig(configPath2)) {
8188
+ return {
8189
+ name,
8190
+ status: "warn",
8191
+ message: `hook config at ${configPath2} was not written by token-goat on this machine, so doctor does not run the command it names. If these hooks are yours, run "${install}" to rewrite them here.`
8192
+ };
8193
+ }
8194
+ if (!fs18.existsSync(scriptPath)) {
8195
+ if (!fs18.existsSync(path17.join(path17.dirname(scriptPath), LEGACY_HOOKS_SCRIPT_FILE))) return null;
8196
+ return {
8197
+ name,
8198
+ status: "warn",
8199
+ message: `hooks at ${path17.dirname(scriptPath)} still run ${LEGACY_HOOKS_SCRIPT_FILE} from an older token-goat build, which Node loads as an ES module under any package.json that says "type": "module" and then fails every tool call. Recovery: run "${install}", then fully restart ${harness}.`
8200
+ };
8201
+ }
7996
8202
  let config;
7997
8203
  try {
7998
8204
  config = JSON.parse(fs18.readFileSync(configPath2, "utf-8"));
7999
8205
  } catch (err2) {
8000
8206
  return {
8001
- name: "Copilot CLI",
8207
+ name,
8002
8208
  status: "fail",
8003
8209
  message: `hook config at ${configPath2} is not valid JSON: ${extractErrorMessage(err2, "unknown error")}`
8004
8210
  };
@@ -8006,17 +8212,17 @@ function checkCopilotCli(configPath2, scriptPath) {
8006
8212
  const preToolUseCommand = config.hooks?.["preToolUse"]?.[0]?.command;
8007
8213
  if (typeof preToolUseCommand !== "string" || preToolUseCommand === "") {
8008
8214
  return {
8009
- name: "Copilot CLI",
8215
+ name,
8010
8216
  status: "fail",
8011
- message: `hook config at ${configPath2} has no preToolUse entry; run: token-goat install --copilot`
8217
+ message: `hook config at ${configPath2} has no preToolUse entry; run: ${install}`
8012
8218
  };
8013
8219
  }
8014
8220
  const bakedExecPath = /^"([^"]+)"/.exec(preToolUseCommand)?.[1];
8015
8221
  if (bakedExecPath !== void 0 && !fs18.existsSync(bakedExecPath)) {
8016
8222
  return {
8017
- name: "Copilot CLI",
8223
+ name,
8018
8224
  status: "fail",
8019
- message: `hook points at a node binary that no longer exists (${bakedExecPath}) -- likely stale after an nvm/fnm/volta node upgrade. Recovery: run "token-goat install --copilot", then fully restart Copilot CLI (renaming/reinstalling the hook has no effect on an already-running session -- Copilot caches hook configs at startup).`
8225
+ message: `hook points at a node binary that no longer exists (${bakedExecPath}) -- likely stale after an nvm/fnm/volta node upgrade. Recovery: run "${install}", then fully restart ${harness} (renaming/reinstalling the hook has no effect on an already-running session -- hook configs are cached at startup).`
8020
8226
  };
8021
8227
  }
8022
8228
  const synthetic = JSON.stringify({
@@ -8034,28 +8240,56 @@ function checkCopilotCli(configPath2, scriptPath) {
8034
8240
  });
8035
8241
  if (res.error) {
8036
8242
  return {
8037
- name: "Copilot CLI",
8243
+ name,
8038
8244
  status: "fail",
8039
- message: `hook failed to launch: ${extractErrorMessage(res.error, "unknown error")}. Recovery: run "token-goat install --copilot", then fully restart Copilot CLI.`
8245
+ message: `hook failed to launch: ${extractErrorMessage(res.error, "unknown error")}. Recovery: run "${install}", then fully restart ${harness}.`
8040
8246
  };
8041
8247
  }
8042
8248
  if (res.status !== 0) {
8043
8249
  return {
8044
- name: "Copilot CLI",
8250
+ name,
8045
8251
  status: "fail",
8046
- message: `hook exited with status ${res.status} -- Copilot's preToolUse fails closed on a non-zero exit and denies every tool call for the rest of the session. Recovery: run "token-goat install --copilot", then fully restart Copilot CLI (a live session won't pick up the fix).`
8252
+ message: `hook exited with status ${res.status} -- Copilot's preToolUse fails closed on a non-zero exit and denies every tool call for the rest of the session. Recovery: run "${install}", then fully restart ${harness} (a live session won't pick up the fix).`
8047
8253
  };
8048
8254
  }
8049
8255
  try {
8050
8256
  JSON.parse(res.stdout ?? "");
8051
8257
  } catch {
8052
8258
  return {
8053
- name: "Copilot CLI",
8259
+ name,
8054
8260
  status: "fail",
8055
- message: 'hook did not return valid JSON -- Copilot treats this as a hook error and denies every tool call. Recovery: run "token-goat install --copilot", then fully restart Copilot CLI.'
8261
+ message: `hook did not return valid JSON -- Copilot treats this as a hook error and denies every tool call. Recovery: run "${install}", then fully restart ${harness}.`
8262
+ };
8263
+ }
8264
+ if (!shimIsCurrent(scriptPath, COPILOT_CLI_HOOK_SCRIPT)) {
8265
+ return { name, status: "warn", message: staleShimMessage(scriptPath, "Copilot", install) + `, then fully restart ${harness}.` };
8266
+ }
8267
+ return { name, status: "ok", message: "preToolUse hook invokes cleanly and returns valid JSON" };
8268
+ }
8269
+ function copilotHooksRecovery(hooksDir, scope) {
8270
+ const owners = readCopilotHooksOwners(hooksDir);
8271
+ if (!owners.has("copilot") && owners.has("vscode")) {
8272
+ return { install: scope === "project" ? "token-goat install --vscode" : "token-goat install --vscode --user", harness: "VS Code" };
8273
+ }
8274
+ return { install: scope === "project" ? "token-goat install --copilot --local" : "token-goat install --copilot", harness: "Copilot CLI" };
8275
+ }
8276
+ function shimIsCurrent(scriptPath, expected) {
8277
+ return fs18.readFileSync(scriptPath, "utf-8").replace(/\r\n/g, "\n") === expected.replace(/\r\n/g, "\n");
8278
+ }
8279
+ function staleShimMessage(scriptPath, harness, reinstall) {
8280
+ return `hook shim at ${scriptPath} was written by an older token-goat build, so fixes to it since then are not reaching ${harness}. Recovery: run "${reinstall}"`;
8281
+ }
8282
+ function checkHookShim(name, scriptPath, expected, reinstall) {
8283
+ if (!fs18.existsSync(scriptPath)) {
8284
+ if (!fs18.existsSync(path17.join(path17.dirname(scriptPath), LEGACY_SHIM_FILE))) return null;
8285
+ return {
8286
+ name,
8287
+ status: "warn",
8288
+ message: `hooks at ${path17.dirname(scriptPath)} still run ${LEGACY_SHIM_FILE} from an older token-goat build, which Node loads as an ES module under any package.json that says "type": "module" and then fails every tool call. Recovery: run "${reinstall}", then restart any running session.`
8056
8289
  };
8057
8290
  }
8058
- return { name: "Copilot CLI", status: "ok", message: "preToolUse hook invokes cleanly and returns valid JSON" };
8291
+ if (shimIsCurrent(scriptPath, expected)) return { name, status: "ok", message: `hook shim at ${scriptPath} matches this build` };
8292
+ return { name, status: "warn", message: staleShimMessage(scriptPath, name, reinstall) + ", then restart any running session." };
8059
8293
  }
8060
8294
  var COMPACTION_CHANNEL_WINDOW = 5;
8061
8295
  var COMPACTION_STATS_BATCH_SIZE = 50;
@@ -8192,6 +8426,28 @@ function checkUnmappedTools(dbPath, options) {
8192
8426
  return { name, status: "warn", message: `could not read the tool-name histogram: ${extractErrorMessage(e)}` };
8193
8427
  }
8194
8428
  }
8429
+ function checkWorker(dir) {
8430
+ if (checkWorkerRunning(dir)) return { name: "Worker", status: "ok", message: "running" };
8431
+ const refusal2 = fs18.existsSync(dir) ? dataDirWriteRefusal(dir) : void 0;
8432
+ if (refusal2 !== void 0) {
8433
+ return { name: "Worker", status: "fail", message: `not running, and cannot start: ${displaySafeText(dir)} cannot be written (${displaySafeText(extractErrorMessage(refusal2))}); make it writable, then run 'token-goat worker start'` };
8434
+ }
8435
+ return { name: "Worker", status: "warn", message: "not running" };
8436
+ }
8437
+ function checkHookServer(state) {
8438
+ const name = "Hook server";
8439
+ if (!state.enabled) return { name, status: "ok", message: "off (hooks.server or TOKEN_GOAT_HOOK_SERVER); every hook call starts its own process" };
8440
+ if (state.statuses.length > 0) return { name, status: "ok", message: `${state.statuses.length} running (${state.statuses.map((s) => `slot ${s.slot}, pid ${s.pid}, ${s.served} served`).join("; ")})` };
8441
+ if (state.failure !== void 0) return { name, status: "warn", message: `not running, and the last start failed: ${state.failure}. Run 'token-goat hook-server run' to see it in the foreground` };
8442
+ return { name, status: "ok", message: "not running; the next hook call starts one" };
8443
+ }
8444
+ async function runDoctorChecks(dataDir2, configPath2, rootDir, processes) {
8445
+ const results = runDoctor(dataDir2, configPath2, rootDir, processes);
8446
+ const enabled = hookServerEnabled();
8447
+ const dir = dataDir2 || dataDir();
8448
+ results.push(checkHookServer({ enabled, statuses: enabled ? await serverStatuses(dir) : [], failure: readMarker("failed", dir) }));
8449
+ return results;
8450
+ }
8195
8451
  function runDoctor(dataDir2, configPath2, rootDir, processes) {
8196
8452
  const results = [];
8197
8453
  const actualDataDir = dataDir2 || dataDir();
@@ -8199,7 +8455,7 @@ function runDoctor(dataDir2, configPath2, rootDir, processes) {
8199
8455
  results.push(checkTsCompiler());
8200
8456
  results.push(checkTreeSitter());
8201
8457
  results.push(checkStrayClaudeMdBlocks());
8202
- results.push(checkWorkerRunning(actualDataDir) ? { name: "Worker", status: "ok", message: "running" } : { name: "Worker", status: "warn", message: "not running" });
8458
+ results.push(checkWorker(actualDataDir));
8203
8459
  const cfg = loadConfig(rootDir);
8204
8460
  results.push(checkDbExists(actualDataDir, cfg.indexing.max_db_size_mb));
8205
8461
  results.push(checkSymbolBodySize(path17.join(actualDataDir, "global.db")));
@@ -8218,6 +8474,14 @@ function runDoctor(dataDir2, configPath2, rootDir, processes) {
8218
8474
  results.push(checkDiskSpace(actualDataDir));
8219
8475
  const copilotResult = checkCopilotCli(copilotCliConfigPath(), copilotCliScriptPath());
8220
8476
  if (copilotResult) results.push(copilotResult);
8477
+ const copilotProjectResult = checkCopilotCli(copilotCliConfigPath({ local: true }), copilotCliScriptPath({ local: true }), "project");
8478
+ if (copilotProjectResult) results.push(copilotProjectResult);
8479
+ const claudeShimResult = checkHookShim("Claude Code", claudeHookScriptPath(), CLAUDECODE_HOOK_SCRIPT, isInstalled("user") || !isInstalled("project") ? "token-goat install" : "token-goat install --project");
8480
+ if (claudeShimResult) results.push(claudeShimResult);
8481
+ const claudeEventsResult = checkClaudeHookEvents({ user: missingHookEvents("user"), project: missingHookEvents("project") });
8482
+ if (claudeEventsResult) results.push(claudeEventsResult);
8483
+ const codexShimResult = checkHookShim("Codex", codexHookScriptPath(), CODEX_HOOK_SCRIPT, "token-goat install --codex");
8484
+ if (codexShimResult) results.push(codexShimResult);
8221
8485
  const vscodeHooksResult = checkVscodeClaudeHooks(
8222
8486
  vscodeUsesClaudeHooks(),
8223
8487
  isInstalled("user") || isInstalled("project"),
@@ -8432,7 +8696,7 @@ async function runDoctorAndExit(opts) {
8432
8696
  console.log();
8433
8697
  }
8434
8698
  }
8435
- const results = runDoctor(options.dataDir, options.configPath, options.rootDir, options.processes);
8699
+ const results = await runDoctorChecks(options.dataDir, options.configPath, options.rootDir, options.processes);
8436
8700
  printDoctorResults(results);
8437
8701
  if (options.context === true) {
8438
8702
  console.log("\n## Context footprint\n");
@@ -8470,7 +8734,7 @@ async function runDoctorAndExit(opts) {
8470
8734
  }
8471
8735
  }
8472
8736
  try {
8473
- const { checkUpdateStatus } = await import("./token-goat-chunk-CQHY4SL7.mjs");
8737
+ const { checkUpdateStatus } = await import("./token-goat-chunk-YAGLESS3.mjs");
8474
8738
  const update = await checkUpdateStatus(1500);
8475
8739
  if (update.updateAvailable && update.latest) {
8476
8740
  console.log(`
@@ -8566,7 +8830,7 @@ function generateCompactHelp() {
8566
8830
  " -h, --help display help for command",
8567
8831
  "",
8568
8832
  "Reads: symbol, read, brief, section, semantic, skeleton, outline, refs, scope,",
8569
- " similar, context-for, call-chain, impact, callers, dead, test-for",
8833
+ " similar, context-for, call-chain, impact, callers, dead, test-for, locate",
8570
8834
  "",
8571
8835
  "Analysis: arch, affected, blame, deps, types, coverage-gaps, changed, diff, log,",
8572
8836
  " exports, imports, find, grep",
@@ -8579,30 +8843,29 @@ function generateCompactHelp() {
8579
8843
  "",
8580
8844
  "Index & Search: index, map, reconcile, doctor, commands, ask, answer, pack, tokens,",
8581
8845
  " budget, failures, todo, trace, logfold, lockdeps, dep-docs, recent, hot,",
8582
- " snapshot-snapshot, baseline, cost, coverage-report-gaps, clean-cache,",
8846
+ " baseline, cost, coverage-report-gaps, clean-cache,",
8583
8847
  " cache-audit, reclaim-index, project",
8584
8848
  "",
8585
8849
  "Session: stats, waste, session-outline, session-audit, session-slice,",
8586
8850
  " session-summary, context-stats, bootstrap-audit, memory, hint-stats,",
8587
- " session-schema, mcp-audit",
8851
+ " session-schema, mcp-audit, audit",
8588
8852
  "",
8589
8853
  "Skills: skill-body, skill-compact, skill-list, skill-size, skill-history,",
8590
8854
  " skill-diff, skill-section",
8591
8855
  "",
8592
8856
  "Compression: compress-text, retrieve, handoff-create, handoff-resolve,",
8593
- " compress, recall, bash-output, web-output, mcp-output, compress-text,",
8594
- " compress, bench",
8857
+ " compress, recall, bash-output, web-output, mcp-output, bench",
8595
8858
  "",
8596
8859
  "Config & Integration: install, uninstall, upgrade, mcp-serve, mcp-status, hook,",
8597
- " bridges-status, worker, statusline, version, config, config-get,",
8860
+ " bridges-status, worker, hook-server, statusline, version, config, config-get,",
8598
8861
  " capabilities, help",
8599
8862
  "",
8600
8863
  "Data: note, note-add, note-get, note-list, describe, sqlite-query,",
8601
8864
  " sqlite-tables, sqlite-schema, csv-profile, csv-query",
8602
8865
  "",
8603
- "Utils: ignores, bash-history, web-history, openapi-op, openapi-outline,",
8604
- " gdrive-sections, screenshot, opencode-*, fetch-image, image-meta,",
8605
- " image-text, start, stop, resume, replace, insert-section, pr-slice,",
8866
+ "Utils: ignores, bash-history, web-history, history, openapi-op, openapi-outline,",
8867
+ " gdrive-sections, screenshot, fetch-image, image-meta,",
8868
+ " image-text, resume, replace, insert-section, pr-slice,",
8606
8869
  " sharepoint-resolve, transcript, transcript-outline, video-chapters,",
8607
8870
  " write-file, zip-list, zip-read, mcp-history, compact-doc, compact-hint,",
8608
8871
  " conflicts, prune-cache",
@@ -8904,12 +9167,9 @@ function performHttpFetch(targetUrl, opts) {
8904
9167
  });
8905
9168
  }
8906
9169
 
8907
- // src/config_commands.ts
8908
- function saveConfigSafe(cfg) {
8909
- ensureDirSync(path18.dirname(configPath()));
8910
- saveConfig(cfg);
8911
- }
8912
- function levenshtein(a, b, cap = 3) {
9170
+ // src/util_suggest.ts
9171
+ init_define_import_meta_env();
9172
+ function levenshteinDistance(a, b, cap = 3) {
8913
9173
  if (Math.abs(a.length - b.length) > cap) return cap + 1;
8914
9174
  const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
8915
9175
  for (let i = 1; i <= a.length; i++) {
@@ -8922,8 +9182,17 @@ function levenshtein(a, b, cap = 3) {
8922
9182
  }
8923
9183
  return prev[b.length] ?? cap + 1;
8924
9184
  }
9185
+ function suggestPackageNames(query, names) {
9186
+ return [...new Set(names)].map((n) => ({ n, d: levenshteinDistance(query.toLowerCase(), n.toLowerCase()) })).filter((x) => x.d <= 3).sort((a, b) => a.d - b.d).slice(0, 5).map((x) => x.n);
9187
+ }
9188
+
9189
+ // src/config_commands.ts
9190
+ function saveConfigSafe(cfg) {
9191
+ ensureDirSync(path18.dirname(configPath()));
9192
+ saveConfig(cfg);
9193
+ }
8925
9194
  function closestKeys(unknown, known) {
8926
- return known.map((k) => ({ k, d: levenshtein(unknown, k) })).filter((x) => x.d <= 3).sort((a, b) => a.d - b.d).slice(0, 3).map((x) => x.k);
9195
+ return known.map((k) => ({ k, d: levenshteinDistance(unknown, k) })).filter((x) => x.d <= 3).sort((a, b) => a.d - b.d).slice(0, 3).map((x) => x.k);
8927
9196
  }
8928
9197
  function didYouMeanKeySuffix(unknownKey) {
8929
9198
  const knownKeys = flattenConfig(defaultConfig()).map(([k]) => k);
@@ -11511,11 +11780,12 @@ function openWithinRoot(rootReal, p) {
11511
11780
  if (!isPathWithinRoot(rootReal, realPath)) return "outside-root";
11512
11781
  let realStat;
11513
11782
  try {
11514
- realStat = fs21.statSync(realPath);
11783
+ realStat = statThroughHandle(realPath);
11515
11784
  } catch {
11516
11785
  return "outside-root";
11517
11786
  }
11518
- if (realStat.dev !== stat.dev || realStat.ino !== stat.ino) return "outside-root";
11787
+ const fdStat = fs21.fstatSync(fd, { bigint: true });
11788
+ if (realStat.dev !== fdStat.dev || realStat.ino !== fdStat.ino) return "outside-root";
11519
11789
  ownershipTransferred = true;
11520
11790
  return { fd, stat };
11521
11791
  } finally {
@@ -13522,9 +13792,9 @@ function cmdNoteAdd(file, opts) {
13522
13792
  const messages = [`No symbol named '${opts.symbol}' is indexed in '${file}'`];
13523
13793
  const allNames = symbolNamesInFile(resolvedPath);
13524
13794
  const available = rankSimilarNames(allNames, opts.symbol);
13525
- if (available.length > 0) messages.push(didYouMean(available));
13795
+ if (available.length > 0) messages.push(...didYouMeanLines(available));
13526
13796
  else if (allNames.length > 0) messages.push(`Try: token-goat outline ${file}`);
13527
- throw new CliError(messages.join("\n"));
13797
+ throw new CliError(messages);
13528
13798
  }
13529
13799
  symbol = opts.symbol;
13530
13800
  fingerprint = fingerprintContent(match2.body);
@@ -13853,9 +14123,9 @@ function cmdInsertSection(file, opts) {
13853
14123
  const allHeadings = listSections(file);
13854
14124
  const messages = [`Section '${opts.after}' not found in '${file}'`];
13855
14125
  const available = filterSimilarHeadings(allHeadings, opts.after);
13856
- if (available.length > 0) messages.push(didYouMean(available));
14126
+ if (available.length > 0) messages.push(...didYouMeanLines(available));
13857
14127
  else if (allHeadings.length > 0) messages.push(`Try: token-goat outline ${file}`);
13858
- throw new CliError(messages.join("\n"));
14128
+ throw new CliError(messages);
13859
14129
  }
13860
14130
  if (result.occurrences !== void 0) {
13861
14131
  const lines = [
@@ -13867,7 +14137,7 @@ function cmdInsertSection(file, opts) {
13867
14137
  if (result.occurrences.length > AMBIGUOUS_HEADING_LIMIT) {
13868
14138
  lines.push(` (${result.occurrences.length - AMBIGUOUS_HEADING_LIMIT} more not shown)`);
13869
14139
  }
13870
- throw new CliError(lines.join("\n"));
14140
+ throw new CliError(lines);
13871
14141
  }
13872
14142
  let rawBytes;
13873
14143
  try {
@@ -14012,13 +14282,13 @@ function registerFormatCommands(program2, guard) {
14012
14282
  []
14013
14283
  ).option("--head <n>", "limit to the first N matching rows").option("--json", "emit rows as a JSON array of objects instead of CSV").option("--delimiter <char>", "field delimiter (default: ,)").option("--no-header", "treat the first row as data, not a header (columns become col1, col2, ...)").action(guard(cmdCsvQuery));
14014
14284
  program2.command("csv-profile <file>").description("per-column type/null/distinct/range summary of a CSV instead of a raw Read").option("--delimiter <char>", "field delimiter (default: ,)").option("--no-header", "treat the first row as data, not a header (columns become col1, col2, ...)").action(guard(cmdCsvProfile));
14015
- program2.command("json-outline <file>").description("structural summary of a JSON document (array shape / object key types) instead of a raw Read").option("--json", "emit the outline as JSON instead of text").action(guard(cmdJsonOutline));
14285
+ program2.command("json-outline <file>").description("structural summary of a JSON document (array shape / object key types) instead of a raw Read").option("--json", "emit the outline as JSON instead of text").option("--filter <text>", "list only the top-level keys containing TEXT (case-insensitive), with a count of how many matched").action(guard(cmdJsonOutline));
14016
14286
  program2.command("json-query <file> <path>").description(
14017
- "extract one value or a projected/filtered subset from a JSON document by dot-path instead of a raw Read\n\npath grammar: dot-separated keys with optional bracket segments -- [n] index, [*] wildcard (projects every element/value), [field=value] filter (keeps array elements whose field stringifies to value). Examples: data.items[3].name, items[*].id, items[status=active]"
14287
+ 'extract one value or a projected/filtered subset from a JSON document by dot-path instead of a raw Read\n\npath grammar: dot-separated keys with optional bracket segments -- [n] index, [*] wildcard (projects every element/value), [field=value] filter (keeps array elements whose field stringifies to value), ["key"] for a key holding a dot or space. Examples: data.items[3].name, items[*].id, items[status=active], ["a.b"].c'
14018
14288
  ).option("--head <n>", "limit a projected/filtered result to the first N items").option("--json", "emit the result as JSON instead of text").action(guard(cmdJsonQuery));
14019
- program2.command("yaml-outline <file>").description("structural summary of a YAML document (array shape / object key types) instead of a raw Read -- multi-document streams (---separated) outline as an array of documents").option("--json", "emit the outline as JSON instead of text").action(guard(cmdYamlOutline));
14289
+ program2.command("yaml-outline <file>").description("structural summary of a YAML document (array shape / object key types) instead of a raw Read -- multi-document streams (---separated) outline as an array of documents").option("--json", "emit the outline as JSON instead of text").option("--filter <text>", "list only the top-level keys containing TEXT (case-insensitive), with a count of how many matched").action(guard(cmdYamlOutline));
14020
14290
  program2.command("yaml-query <file> <path>").description(
14021
- "extract one value or a projected/filtered subset from a YAML document by dot-path instead of a raw Read (same grammar as json-query)\n\npath grammar: dot-separated keys with optional bracket segments -- [n] index, [*] wildcard (projects every element/value), [field=value] filter (keeps array elements whose field stringifies to value). Examples: spec.containers[0].image, items[*].name, items[kind=Service]"
14291
+ 'extract one value or a projected/filtered subset from a YAML document by dot-path instead of a raw Read (same grammar as json-query)\n\npath grammar: dot-separated keys with optional bracket segments -- [n] index, [*] wildcard (projects every element/value), [field=value] filter (keeps array elements whose field stringifies to value), ["key"] for a key holding a dot or space. Examples: spec.containers[0].image, items[*].name, items[kind=Service]'
14022
14292
  ).option("--head <n>", "limit a projected/filtered result to the first N items").option("--json", "emit the result as JSON instead of text").action(guard(cmdYamlQuery));
14023
14293
  program2.command("xml-outline <file>").description("structural summary of an XML document (element hierarchy / attribute names / child counts) instead of a raw Read").option("--json", "emit the outline as JSON instead of text").option("--max-depth <n>", "max depth of element hierarchy to show").option("--depth <n>", "alias for --max-depth").action(guard(cmdXmlOutline));
14024
14294
  program2.command("xml-query <file> [path]").description(
@@ -14383,184 +14653,44 @@ function runAffected(opts) {
14383
14653
 
14384
14654
  // src/text_commands.ts
14385
14655
  init_define_import_meta_env();
14386
- import * as fs32 from "fs";
14387
- import * as path29 from "path";
14656
+ import * as fs31 from "fs";
14657
+ import * as path28 from "path";
14388
14658
 
14389
- // src/project_memory.ts
14659
+ // src/text_todo.ts
14390
14660
  init_define_import_meta_env();
14391
- import * as fs28 from "node:fs";
14392
- import * as path26 from "node:path";
14393
- var MAX_ENTRIES = 30;
14394
- var KEY_RE2 = /^[A-Za-z0-9_-]{1,80}$/;
14395
- function ordinal(a, b) {
14396
- return a < b ? -1 : a > b ? 1 : 0;
14397
- }
14398
- function memoryPath(projectHash) {
14399
- return path26.join(dataDir(), "projects", `${projectHash}_memory.toml`);
14400
- }
14401
- function validateKey2(key) {
14402
- if (!KEY_RE2.test(key)) {
14403
- throw new Error(
14404
- `Invalid memory key ${JSON.stringify(key)}: use only letters, digits, hyphens, underscores (max 80 chars)`
14405
- );
14406
- }
14661
+ import * as fs28 from "fs";
14662
+ import * as path26 from "path";
14663
+ var DEFAULT_KINDS = ["TODO", "FIXME", "HACK", "XXX", "NOTE"];
14664
+ function splitLines(text) {
14665
+ return text.split(/\r?\n/);
14407
14666
  }
14408
- function parseTOML(content) {
14409
- const result = {};
14410
- for (const line of content.split("\n")) {
14411
- const trimmed = line.trim();
14412
- if (!trimmed || trimmed.startsWith("#")) {
14667
+ function countUnescapedQuotes(text, quoteChar) {
14668
+ let count = 0;
14669
+ let backslashes = 0;
14670
+ for (const ch of text) {
14671
+ if (ch === "\\") {
14672
+ backslashes++;
14413
14673
  continue;
14414
14674
  }
14415
- const match2 = trimmed.match(/^([A-Za-z0-9_-]+)\s*=\s*"(.*)"\s*$/);
14416
- if (match2) {
14417
- const [, key, value] = match2;
14418
- if (key && value !== void 0) {
14419
- const unescaped = value.replace(/\\([\\nrt"])/g, (_, c) => {
14420
- switch (c) {
14421
- case "\\":
14422
- return "\\";
14423
- case "n":
14424
- return "\n";
14425
- case "r":
14426
- return "\r";
14427
- case '"':
14428
- return '"';
14429
- default:
14430
- return _;
14431
- }
14432
- });
14433
- result[key] = unescaped;
14434
- }
14435
- }
14675
+ if (ch === quoteChar && backslashes % 2 === 0) count++;
14676
+ backslashes = 0;
14436
14677
  }
14437
- return result;
14678
+ return count;
14679
+ }
14680
+ function isInsideStringLiteral2(line, markerIndex) {
14681
+ const before = line.slice(0, markerIndex);
14682
+ const dqCount = countUnescapedQuotes(before, '"');
14683
+ return dqCount % 2 !== 0;
14438
14684
  }
14439
- function loadRaw(filePath) {
14685
+ function isMarkerOccurrence(matched, hasColon) {
14686
+ return hasColon || matched === matched.toUpperCase();
14687
+ }
14688
+ function scanFileForTodos(filePath, kindSet) {
14689
+ let text;
14440
14690
  try {
14441
- if (!fs28.existsSync(filePath)) {
14442
- return {};
14443
- }
14444
- const content = fs28.readFileSync(filePath, "utf-8");
14445
- return parseTOML(content);
14691
+ text = decodeSource(fs28.readFileSync(filePath));
14446
14692
  } catch {
14447
- return {};
14448
- }
14449
- }
14450
- function save(filePath, entries) {
14451
- const lines = [];
14452
- const sorted = Object.entries(entries).sort(([a], [b]) => ordinal(a, b));
14453
- for (const [k, v] of sorted) {
14454
- const escaped = v.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\r/g, "\\r").replace(/\n/g, "\\n");
14455
- lines.push(`${k} = "${escaped}"`);
14456
- }
14457
- const content = lines.length > 0 ? lines.join("\n") + "\n" : "";
14458
- const dir = path26.dirname(filePath);
14459
- ensureDirSync(dir);
14460
- atomicWriteText(filePath, content);
14461
- }
14462
- function loadEntries(projectHash) {
14463
- return loadRaw(memoryPath(projectHash));
14464
- }
14465
- function setEntry(projectHash, key, value) {
14466
- validateKey2(key);
14467
- const p = memoryPath(projectHash);
14468
- const dir = path26.dirname(p);
14469
- ensureDirSync(dir);
14470
- const doSet = () => {
14471
- const entries = loadRaw(p);
14472
- const isNewKey = !(key in entries);
14473
- if (isNewKey && Object.keys(entries).length >= MAX_ENTRIES) {
14474
- const keysToKeep = MAX_ENTRIES - 1;
14475
- const allKeys = Object.keys(entries).sort((a, b) => ordinal(a, b));
14476
- for (const k of allKeys.slice(keysToKeep)) {
14477
- delete entries[k];
14478
- }
14479
- }
14480
- entries[key] = value;
14481
- save(p, entries);
14482
- return true;
14483
- };
14484
- if (withFileLock(`${p}.lock`, doSet) === void 0) doSet();
14485
- }
14486
- function unsetEntry(projectHash, key) {
14487
- validateKey2(key);
14488
- const p = memoryPath(projectHash);
14489
- const doUnset = () => {
14490
- const entries = loadRaw(p);
14491
- if (key in entries) {
14492
- delete entries[key];
14493
- save(p, entries);
14494
- }
14495
- return true;
14496
- };
14497
- if (withFileLock(`${p}.lock`, doUnset) === void 0) doUnset();
14498
- }
14499
- function clearAll(projectHash) {
14500
- const p = memoryPath(projectHash);
14501
- const doClear = () => {
14502
- if (fs28.existsSync(p)) {
14503
- save(p, {});
14504
- }
14505
- return true;
14506
- };
14507
- if (withFileLock(`${p}.lock`, doClear) === void 0) doClear();
14508
- }
14509
-
14510
- // src/util_suggest.ts
14511
- init_define_import_meta_env();
14512
- function packageNameDistance(a, b, cap = 3) {
14513
- if (Math.abs(a.length - b.length) > cap) return cap + 1;
14514
- const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
14515
- for (let i = 1; i <= a.length; i++) {
14516
- const curr = [i];
14517
- for (let j = 1; j <= b.length; j++) {
14518
- const cost = a[i - 1] === b[j - 1] ? 0 : 1;
14519
- curr.push(Math.min((curr[j - 1] ?? 0) + 1, (prev[j] ?? 0) + 1, (prev[j - 1] ?? 0) + cost));
14520
- }
14521
- prev.splice(0, prev.length, ...curr);
14522
- }
14523
- return prev[b.length] ?? cap + 1;
14524
- }
14525
- function suggestPackageNames(query, names) {
14526
- return [...new Set(names)].map((n) => ({ n, d: packageNameDistance(query.toLowerCase(), n.toLowerCase()) })).filter((x) => x.d <= 3).sort((a, b) => a.d - b.d).slice(0, 5).map((x) => x.n);
14527
- }
14528
-
14529
- // src/text_todo.ts
14530
- init_define_import_meta_env();
14531
- import * as fs29 from "fs";
14532
- import * as path27 from "path";
14533
- var DEFAULT_KINDS = ["TODO", "FIXME", "HACK", "XXX", "NOTE"];
14534
- function splitLines(text) {
14535
- return text.split(/\r?\n/);
14536
- }
14537
- function countUnescapedQuotes(text, quoteChar) {
14538
- let count = 0;
14539
- let backslashes = 0;
14540
- for (const ch of text) {
14541
- if (ch === "\\") {
14542
- backslashes++;
14543
- continue;
14544
- }
14545
- if (ch === quoteChar && backslashes % 2 === 0) count++;
14546
- backslashes = 0;
14547
- }
14548
- return count;
14549
- }
14550
- function isInsideStringLiteral2(line, markerIndex) {
14551
- const before = line.slice(0, markerIndex);
14552
- const dqCount = countUnescapedQuotes(before, '"');
14553
- return dqCount % 2 !== 0;
14554
- }
14555
- function isMarkerOccurrence(matched, hasColon) {
14556
- return hasColon || matched === matched.toUpperCase();
14557
- }
14558
- function scanFileForTodos(filePath, kindSet) {
14559
- let text;
14560
- try {
14561
- text = decodeSource(fs29.readFileSync(filePath));
14562
- } catch {
14563
- return [];
14693
+ return [];
14564
14694
  }
14565
14695
  const kindPattern = [...kindSet].map(escapeRegExp).join("|");
14566
14696
  const re = new RegExp(`\\b(${kindPattern})\\b(\\s*:)?\\s*(.*)`, "gi");
@@ -14586,9 +14716,9 @@ function collectTodoFiles(patterns) {
14586
14716
  if (patterns.length > 0) {
14587
14717
  const results = [];
14588
14718
  for (const p of patterns) {
14589
- const abs = path27.resolve(p);
14719
+ const abs = path26.resolve(p);
14590
14720
  try {
14591
- const stat = fs29.statSync(abs);
14721
+ const stat = fs28.statSync(abs);
14592
14722
  if (stat.isDirectory()) {
14593
14723
  pushAll(results, walkProject(abs).files);
14594
14724
  } else {
@@ -14653,11 +14783,11 @@ function cmdTodo(patterns, opts) {
14653
14783
 
14654
14784
  // src/text_trace.ts
14655
14785
  init_define_import_meta_env();
14656
- import * as fs30 from "fs";
14657
- import * as path28 from "path";
14786
+ import * as fs29 from "fs";
14787
+ import * as path27 from "path";
14658
14788
  function readInput(src) {
14659
- if (src !== void 0) return fs30.readFileSync(src, "utf8");
14660
- return fs30.readFileSync(0, "utf8");
14789
+ if (src !== void 0) return fs29.readFileSync(src, "utf8");
14790
+ return fs29.readFileSync(0, "utf8");
14661
14791
  }
14662
14792
  function splitLines2(text) {
14663
14793
  return text.split(/\r?\n/);
@@ -14869,7 +14999,7 @@ function isProjectFrame(framePath, cwd) {
14869
14999
  if (framePath.includes("/rustc/")) return false;
14870
15000
  if (framePath.includes(".cargo/registry") || framePath.includes(".cargo\\registry")) return false;
14871
15001
  if (/^<.+>$/.test(framePath)) return false;
14872
- if (!path28.isAbsolute(framePath) && !framePath.startsWith("..")) return true;
15002
+ if (!path27.isAbsolute(framePath) && !framePath.startsWith("..")) return true;
14873
15003
  return false;
14874
15004
  }
14875
15005
  function resolveFrameSymbol(frame, projectRoot) {
@@ -14977,7 +15107,7 @@ function cmdTrace(src, opts) {
14977
15107
 
14978
15108
  // src/text_logfold.ts
14979
15109
  init_define_import_meta_env();
14980
- import * as fs31 from "fs";
15110
+ import * as fs30 from "fs";
14981
15111
 
14982
15112
  // src/filters.ts
14983
15113
  init_define_import_meta_env();
@@ -15107,8 +15237,8 @@ function applyFiltersAndFold(lines, noNormalize, foldRepeats) {
15107
15237
  return folded;
15108
15238
  }
15109
15239
  function readInput2(src) {
15110
- if (src !== void 0) return fs31.readFileSync(src, "utf8");
15111
- return fs31.readFileSync(0, "utf8");
15240
+ if (src !== void 0) return fs30.readFileSync(src, "utf8");
15241
+ return fs30.readFileSync(0, "utf8");
15112
15242
  }
15113
15243
  function splitLines3(text) {
15114
15244
  return text.split(/\r?\n/);
@@ -15159,21 +15289,21 @@ var LOCK_PRIORITY = [
15159
15289
  "Cargo.lock"
15160
15290
  ];
15161
15291
  function findLockfile(startPath) {
15162
- const stat = fs32.statSync(startPath, { throwIfNoEntry: false });
15292
+ const stat = fs31.statSync(startPath, { throwIfNoEntry: false });
15163
15293
  if (stat !== void 0 && stat.isFile()) {
15164
15294
  return { file: startPath, others: [] };
15165
15295
  }
15166
- const dir = stat !== void 0 && stat.isDirectory() ? startPath : path29.dirname(startPath);
15296
+ const dir = stat !== void 0 && stat.isDirectory() ? startPath : path28.dirname(startPath);
15167
15297
  const found = [];
15168
15298
  for (const name of LOCK_PRIORITY) {
15169
- const candidate = path29.join(dir, name);
15170
- if (fs32.existsSync(candidate)) found.push(candidate);
15299
+ const candidate = path28.join(dir, name);
15300
+ if (fs31.existsSync(candidate)) found.push(candidate);
15171
15301
  }
15172
15302
  try {
15173
- const entries = fs32.readdirSync(dir);
15303
+ const entries = fs31.readdirSync(dir);
15174
15304
  for (const e of entries) {
15175
15305
  if (/^requirements.*\.txt$/.test(e)) {
15176
- const p = path29.join(dir, e);
15306
+ const p = path28.join(dir, e);
15177
15307
  if (!found.includes(p)) found.push(p);
15178
15308
  }
15179
15309
  }
@@ -15370,8 +15500,8 @@ function parsePnpmLock(content) {
15370
15500
  return deps;
15371
15501
  }
15372
15502
  function parseLockFile(filePath) {
15373
- const base = path29.basename(filePath);
15374
- const content = fs32.readFileSync(filePath, "utf8");
15503
+ const base = path28.basename(filePath);
15504
+ const content = fs31.readFileSync(filePath, "utf8");
15375
15505
  if (base === "package-lock.json") return { format: "npm", deps: parsePackageLockJson(content) };
15376
15506
  if (base === "yarn.lock") return { format: "yarn", deps: parseYarnLock(content) };
15377
15507
  if (base === "pnpm-lock.yaml") return { format: "pnpm", deps: parsePnpmLock(content) };
@@ -15444,7 +15574,7 @@ function cmdLockdepsPackage(lockfile, format, deps, query, json) {
15444
15574
  }
15445
15575
  const primary = matches2.find((d) => d.kind === "direct") ?? matches2[0];
15446
15576
  const otherVersions = [...new Set(matches2.filter((d) => d.version !== primary.version).map((d) => d.version))];
15447
- const graph = format === "npm" ? buildNpmEdges(fs32.readFileSync(lockfile, "utf8")) : null;
15577
+ const graph = format === "npm" ? buildNpmEdges(fs31.readFileSync(lockfile, "utf8")) : null;
15448
15578
  const graphAvailable = graph !== null;
15449
15579
  const dependsOn = graph !== null ? [...graph.edges.get(query) ?? []].sort() : [];
15450
15580
  const dependedOnBy = graph !== null ? findReverseDirectDeps(query, graph.edges, graph.directNames) : [];
@@ -15484,7 +15614,7 @@ Depended on by direct/top-level deps (${dependedOnBy.length}):
15484
15614
  `);
15485
15615
  }
15486
15616
  function cmdLockdeps(filePath, opts) {
15487
- const target = filePath !== void 0 ? path29.resolve(filePath) : process.cwd();
15617
+ const target = filePath !== void 0 ? path28.resolve(filePath) : process.cwd();
15488
15618
  const found = findLockfile(target);
15489
15619
  if (found === null) {
15490
15620
  throw new Error("No lockfile found. Expected one of: " + LOCK_PRIORITY.join(", ") + ", requirements*.txt");
@@ -15572,20 +15702,20 @@ function cmdNote(action, key, value, opts) {
15572
15702
  throw new Error(`Unknown note action: ${action}. Use: set, get, unset, list, clear`);
15573
15703
  }
15574
15704
  function loadAllSessionReadCounts() {
15575
- const sessionsDir2 = path29.join(tokenGoatHome(), "sessions");
15705
+ const sessionsDir2 = path28.join(tokenGoatHome(), "sessions");
15576
15706
  const totals = /* @__PURE__ */ new Map();
15577
15707
  let entries;
15578
15708
  try {
15579
- entries = fs32.readdirSync(sessionsDir2, { withFileTypes: true });
15709
+ entries = fs31.readdirSync(sessionsDir2, { withFileTypes: true });
15580
15710
  } catch {
15581
15711
  return totals;
15582
15712
  }
15583
15713
  for (const entry of entries) {
15584
15714
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
15585
- const filePath = path29.join(sessionsDir2, entry.name);
15715
+ const filePath = path28.join(sessionsDir2, entry.name);
15586
15716
  let raw;
15587
15717
  try {
15588
- raw = JSON.parse(fs32.readFileSync(filePath, "utf8"));
15718
+ raw = JSON.parse(fs31.readFileSync(filePath, "utf8"));
15589
15719
  } catch {
15590
15720
  continue;
15591
15721
  }
@@ -15718,7 +15848,7 @@ function cmdIgnores(opts) {
15718
15848
  }
15719
15849
  function countPackIgnorePatterns(root) {
15720
15850
  try {
15721
- return fs32.readFileSync(path29.join(root, ".tokengoatignore"), "utf8").split("\n").map((ln) => ln.trim()).filter((ln) => ln.length > 0 && !ln.startsWith("#")).length;
15851
+ return fs31.readFileSync(path28.join(root, ".tokengoatignore"), "utf8").split("\n").map((ln) => ln.trim()).filter((ln) => ln.length > 0 && !ln.startsWith("#")).length;
15722
15852
  } catch {
15723
15853
  return 0;
15724
15854
  }
@@ -15727,8 +15857,8 @@ function countPackIgnorePatterns(root) {
15727
15857
  // src/dep_docs.ts
15728
15858
  init_define_import_meta_env();
15729
15859
  import { createRequire } from "node:module";
15730
- import * as fs33 from "node:fs";
15731
- import * as path30 from "node:path";
15860
+ import * as fs32 from "node:fs";
15861
+ import * as path29 from "node:path";
15732
15862
  var _require = createRequire(import.meta.url);
15733
15863
  var _ts = null;
15734
15864
  var _tsLoadAttempted = false;
@@ -15747,21 +15877,21 @@ function loadTs() {
15747
15877
  }
15748
15878
  function dirExists(p) {
15749
15879
  try {
15750
- return fs33.statSync(p).isDirectory();
15880
+ return fs32.statSync(p).isDirectory();
15751
15881
  } catch {
15752
15882
  return false;
15753
15883
  }
15754
15884
  }
15755
15885
  function fileExists(p) {
15756
15886
  try {
15757
- return fs33.statSync(p).isFile();
15887
+ return fs32.statSync(p).isFile();
15758
15888
  } catch {
15759
15889
  return false;
15760
15890
  }
15761
15891
  }
15762
15892
  function readFileTextOrNull(p) {
15763
15893
  try {
15764
- return fs33.readFileSync(p, "utf8");
15894
+ return fs32.readFileSync(p, "utf8");
15765
15895
  } catch {
15766
15896
  return null;
15767
15897
  }
@@ -15769,7 +15899,7 @@ function readFileTextOrNull(p) {
15769
15899
  function listInstalledPackageNames(nodeModulesDir) {
15770
15900
  let entries;
15771
15901
  try {
15772
- entries = fs33.readdirSync(nodeModulesDir, { withFileTypes: true });
15902
+ entries = fs32.readdirSync(nodeModulesDir, { withFileTypes: true });
15773
15903
  } catch {
15774
15904
  return [];
15775
15905
  }
@@ -15779,7 +15909,7 @@ function listInstalledPackageNames(nodeModulesDir) {
15779
15909
  if (entry.name.startsWith("@")) {
15780
15910
  let scoped;
15781
15911
  try {
15782
- scoped = fs33.readdirSync(path30.join(nodeModulesDir, entry.name), { withFileTypes: true });
15912
+ scoped = fs32.readdirSync(path29.join(nodeModulesDir, entry.name), { withFileTypes: true });
15783
15913
  } catch {
15784
15914
  continue;
15785
15915
  }
@@ -15796,12 +15926,12 @@ var README_NAME_RE = /^readme(\.(md|markdown|txt))?$/i;
15796
15926
  function findReadmeFile(pkgDir) {
15797
15927
  const direct = findReadmeIn(pkgDir);
15798
15928
  if (direct !== null) return direct;
15799
- return findReadmeIn(path30.join(pkgDir, "docs"));
15929
+ return findReadmeIn(path29.join(pkgDir, "docs"));
15800
15930
  }
15801
15931
  function findReadmeIn(dir) {
15802
15932
  let entries;
15803
15933
  try {
15804
- entries = fs33.readdirSync(dir, { withFileTypes: true });
15934
+ entries = fs32.readdirSync(dir, { withFileTypes: true });
15805
15935
  } catch {
15806
15936
  return null;
15807
15937
  }
@@ -15813,7 +15943,7 @@ function findReadmeIn(dir) {
15813
15943
  return 1;
15814
15944
  };
15815
15945
  matches2.sort((a, b) => rank(a.name) - rank(b.name));
15816
- return path30.join(dir, matches2[0].name);
15946
+ return path29.join(dir, matches2[0].name);
15817
15947
  }
15818
15948
  function typesPackageDirName(pkgName) {
15819
15949
  if (pkgName.startsWith("@")) {
@@ -15840,30 +15970,30 @@ function resolveTypesLocation(pkgDir, pkgJson, nodeModulesDir, pkgName) {
15840
15970
  if (declared !== void 0) {
15841
15971
  const candidates = [declared, declared.endsWith(".d.ts") ? declared : `${declared}.d.ts`, declared.replace(/\.[cm]?[jt]s$/, ".d.ts")];
15842
15972
  for (const c of candidates) {
15843
- const p = path30.join(pkgDir, c);
15973
+ const p = path29.join(pkgDir, c);
15844
15974
  if (fileExists(p)) return { path: p, source: "bundled" };
15845
15975
  }
15846
15976
  }
15847
15977
  const main = typeof pkgJson["main"] === "string" ? pkgJson["main"] : "index.js";
15848
- const mainDts = path30.join(pkgDir, main.replace(/\.[cm]?js$/, ".d.ts"));
15978
+ const mainDts = path29.join(pkgDir, main.replace(/\.[cm]?js$/, ".d.ts"));
15849
15979
  if (fileExists(mainDts)) return { path: mainDts, source: "bundled" };
15850
- const indexDts = path30.join(pkgDir, "index.d.ts");
15980
+ const indexDts = path29.join(pkgDir, "index.d.ts");
15851
15981
  if (fileExists(indexDts)) return { path: indexDts, source: "bundled" };
15852
15982
  const typesDirName = typesPackageDirName(pkgName);
15853
- const typesPkgDir = path30.join(nodeModulesDir, "@types", typesDirName);
15854
- const typesPkgJsonRaw = readFileTextOrNull(path30.join(typesPkgDir, "package.json"));
15983
+ const typesPkgDir = path29.join(nodeModulesDir, "@types", typesDirName);
15984
+ const typesPkgJsonRaw = readFileTextOrNull(path29.join(typesPkgDir, "package.json"));
15855
15985
  if (typesPkgJsonRaw !== null) {
15856
15986
  try {
15857
15987
  const typesPkgJson = JSON.parse(typesPkgJsonRaw);
15858
15988
  const entry = (typeof typesPkgJson["types"] === "string" ? typesPkgJson["types"] : void 0) ?? (typeof typesPkgJson["main"] === "string" ? typesPkgJson["main"] : void 0) ?? "index.d.ts";
15859
15989
  const entryCandidates = [entry, entry.endsWith(".d.ts") ? entry : `${entry}.d.ts`, entry.replace(/\.[cm]?[jt]s$/, ".d.ts")];
15860
15990
  for (const c of entryCandidates) {
15861
- const entryPath = path30.join(typesPkgDir, c);
15991
+ const entryPath = path29.join(typesPkgDir, c);
15862
15992
  if (fileExists(entryPath)) return { path: entryPath, source: "@types" };
15863
15993
  }
15864
15994
  } catch {
15865
15995
  }
15866
- const fallback = path30.join(typesPkgDir, "index.d.ts");
15996
+ const fallback = path29.join(typesPkgDir, "index.d.ts");
15867
15997
  if (fileExists(fallback)) return { path: fallback, source: "@types" };
15868
15998
  }
15869
15999
  return null;
@@ -15930,16 +16060,16 @@ function recordDepDocsStat(fullSourceBytes, emittedText, packageName) {
15930
16060
  }
15931
16061
  function runDepDocs(opts) {
15932
16062
  const root = resolveProjectRoot({ project: opts.projectRoot ?? process.cwd() });
15933
- const nodeModulesDir = path30.join(root, "node_modules");
15934
- const pkgDir = path30.join(nodeModulesDir, opts.packageName);
15935
- const pkgJsonPath = path30.join(pkgDir, "package.json");
16063
+ const nodeModulesDir = path29.join(root, "node_modules");
16064
+ const pkgDir = path29.join(nodeModulesDir, opts.packageName);
16065
+ const pkgJsonPath = path29.join(pkgDir, "package.json");
15936
16066
  if (!dirExists(pkgDir) || !fileExists(pkgJsonPath)) {
15937
16067
  const known = listInstalledPackageNames(nodeModulesDir);
15938
16068
  const suggestions = suggestPackageNames(opts.packageName, known);
15939
16069
  const hint = suggestions.length > 0 ? ` (did you mean: ${suggestions.map(displaySafeText).join(", ")}?)` : "";
15940
16070
  return { text: `Package '${displaySafeText(opts.packageName)}' not found under ${displaySafeText(nodeModulesDir)}${hint}`, code: 1 };
15941
16071
  }
15942
- const pkgJsonRaw = fs33.readFileSync(pkgJsonPath, "utf8");
16072
+ const pkgJsonRaw = fs32.readFileSync(pkgJsonPath, "utf8");
15943
16073
  let pkgJson;
15944
16074
  try {
15945
16075
  pkgJson = JSON.parse(pkgJsonRaw);
@@ -16315,8 +16445,7 @@ function collectCapabilities(config = loadConfig()) {
16315
16445
  id: "at_rest.symbol_index",
16316
16446
  kind: "at-rest",
16317
16447
  what: "Stores the text of indexed source files in a local database, so a symbol can be returned without re-reading the file.",
16318
- // Always on: it is the product. Reported anyway, because a reviewer deciding about data at
16319
- // rest needs the always-on items more than the optional ones.
16448
+ // Always on: it is the product. Reported anyway, because a reviewer deciding about data at rest needs the always-on items more than the optional ones.
16320
16449
  enabled: true,
16321
16450
  controlledBy: "always on (this is what the tool does)",
16322
16451
  enforcedAt: "src/db.ts"
@@ -16328,15 +16457,24 @@ function collectCapabilities(config = loadConfig()) {
16328
16457
  enabled: true,
16329
16458
  controlledBy: "always on, 24-hour expiry",
16330
16459
  enforcedAt: "src/disk_cache.ts"
16460
+ },
16461
+ {
16462
+ id: "ipc.hook_server",
16463
+ kind: "local-ipc",
16464
+ what: "Keeps up to three hook servers running between hook calls, each on a named pipe (Windows) or Unix socket (elsewhere) that answers only a caller holding the key file in the data directory.",
16465
+ // Read the way the server reads it, which a project's config cannot change.
16466
+ enabled: hookServerEnabled(),
16467
+ controlledBy: "hooks.server (global config only), TOKEN_GOAT_HOOK_SERVER",
16468
+ enforcedAt: "src/hook_server.ts::runHookServer"
16331
16469
  }
16332
16470
  ];
16333
16471
  }
16334
16472
  function renderCapabilities(caps) {
16335
16473
  const lines = [];
16336
- for (const kind of ["egress", "at-rest"]) {
16474
+ for (const [kind, heading] of [["egress", "Can send data off this machine:"], ["at-rest", "Leaves data on this machine:"], ["local-ipc", "Listens for other processes on this machine:"]]) {
16337
16475
  const group = caps.filter((c) => c.kind === kind);
16338
16476
  if (group.length === 0) continue;
16339
- lines.push(kind === "egress" ? "Can send data off this machine:" : "Leaves data on this machine:");
16477
+ lines.push(heading);
16340
16478
  for (const c of group) {
16341
16479
  lines.push(` [${c.enabled ? "ON " : "OFF"}] ${displaySafeText(c.id)}`);
16342
16480
  lines.push(` ${c.what}`);
@@ -16352,13 +16490,13 @@ function renderCapabilities(caps) {
16352
16490
 
16353
16491
  // src/cli_session.ts
16354
16492
  init_define_import_meta_env();
16355
- import * as fs40 from "node:fs";
16493
+ import * as fs39 from "node:fs";
16356
16494
 
16357
16495
  // src/session_audit.ts
16358
16496
  init_define_import_meta_env();
16359
- import * as fs34 from "node:fs";
16497
+ import * as fs33 from "node:fs";
16360
16498
  import * as os16 from "node:os";
16361
- import * as path31 from "node:path";
16499
+ import * as path30 from "node:path";
16362
16500
  import * as readline from "node:readline";
16363
16501
  var MODEL_VISIBLE_CATEGORIES = ["toolResults", "assistantText", "assistantThinking", "toolUseInputs", "attachments", "userTurns", "harnessMeta", "system"];
16364
16502
  function computeCalibration(estimated, measured) {
@@ -16419,10 +16557,13 @@ var ATTACHMENT_VISIBLE_FIELDS = {
16419
16557
  };
16420
16558
  var CACHE_WRITE_MULTIPLIER = 1.25;
16421
16559
  var CACHE_READ_MULTIPLIER = 0.1;
16422
- var READ_DIVERT_MARKER_RE = /(?:was already read this session|Already read |You've already read|Use `token-goat (?:section|read|bash-output|config-get|skeleton)|token-goat bash-output --file)/;
16560
+ var READ_DIVERT_MARKER_RE = /(?:was already read this session|Already read |You've already read|(?:Use|Run) `token-goat (?:section|read|bash-output|config-get|skeleton|json-query|yaml-query)|token-goat bash-output --file)/;
16423
16561
  var READ_DIVERT_MAX_BYTES = 2500;
16424
16562
  var READ_FULL_SERVE_MIN_BYTES = 10240;
16425
16563
  var DENY_TEMPLATES = [
16564
+ // hint_target.ts's sharpenRepeatedDeny replaces a verbatim repeat of any deny with this one line, so the kind the first copy had is not recoverable from the second.
16565
+ { kind: "repeat_refusal_deny", re: /Repeat refusal of this exact call/, tool: "Read" },
16566
+ { kind: "skill_repeat_refusal_deny", re: /Repeat refusal of this exact call/, tool: "Skill" },
16426
16567
  { kind: "node_modules_deny", re: /node_modules is typically noise/, tool: "Read" },
16427
16568
  { kind: "lock_file_deny", re: /Lock files are rarely useful to read in full/, tool: "Read" },
16428
16569
  { kind: "tsbuildinfo_deny", re: /TypeScript incremental build cache file/, tool: "Read" },
@@ -16432,13 +16573,7 @@ var DENY_TEMPLATES = [
16432
16573
  // Must stay ABOVE markdown_heading_tree_deny: this deny's message embeds the same "Large markdown file (N headings)" guidance block, so the generic alternative would swallow it and the new intervention would be uncountable. DENY_TEMPLATES is scanned with `.find`, so the more specific wording has to come first.
16433
16574
  { kind: "subagent_markdown_first_read_deny", re: /Subagent first read of a large markdown file/, tool: "Read" },
16434
16575
  { kind: "markdown_heading_tree_deny", re: /Large markdown file \(\d+ headings\)/, tool: "Read" },
16435
- // The first alternative is a wording hooks_read.ts no longer emits (it claimed a compact-manifest
16436
- // section that never existed, reworded in 3d044feb). It stays because this table classifies a
16437
- // historical corpus, not just today's output: transcripts written before the rewording still
16438
- // carry the old text, and dropping the alternative silently shrank this kind from 51 events to
16439
- // 30 -- 41% of its history -- with a green suite. A superseded alternative is only removable
16440
- // once no transcript contains it, which source code cannot tell you. See the superseded-wording
16441
- // test in tests/deny_outcomes.test.ts.
16576
+ // The first alternative is a wording hooks_read.ts no longer emits (it claimed a compact-manifest section that never existed, reworded in 3d044feb). It stays because this table classifies a historical corpus, not just today's output: transcripts written before the rewording still carry the old text, and dropping the alternative silently shrank this kind from 51 events to 30 -- 41% of its history -- with a green suite. A superseded alternative is only removable once no transcript contains it, which source code cannot tell you. See the superseded-wording test in tests/deny_outcomes.test.ts.
16442
16577
  { kind: "memory_md_reread_deny", re: /MEMORY\.md was read this session\. Its content is in the compact manifest|already read this session\. Memory files rarely change mid-session/, tool: "Read" },
16443
16578
  { kind: "improve_state_reread_deny", re: /Orchestrator state already read this session/, tool: "Read" },
16444
16579
  { kind: "env_reread_deny", re: /Environment files rarely change mid-session/, tool: "Read" },
@@ -16448,12 +16583,12 @@ var DENY_TEMPLATES = [
16448
16583
  { kind: "session_artifact_large_deny", re: /(?:Session transcript|Tool-result file) is large \(/, tool: "Read" },
16449
16584
  { kind: "session_artifact_generic_reread_deny", re: /already read this session\. Use `token-goat bash-output --file/, tool: "Read" },
16450
16585
  { kind: "truncated_read_deny", re: /File was truncated on last read \(>33K tokens\)/, tool: "Read" },
16451
- { kind: "doc_unchanged_deny", re: /is unchanged since last read\. Use `token-goat (?:section|read)/, tool: "Read" },
16452
- { kind: "doc_diff_deny", re: /Content changed since last read of [\s\S]*?Use `token-goat (?:section|read)/, tool: "Read" },
16586
+ { kind: "doc_unchanged_deny", re: /is unchanged since last read\. (?:Use|Run) `token-goat (?:section|read|skeleton|json-query|yaml-query)/, tool: "Read" },
16587
+ { kind: "doc_diff_deny", re: /Content changed since last read of [\s\S]*?(?:Use|Run) `token-goat (?:section|read|skeleton|json-query|yaml-query)/, tool: "Read" },
16453
16588
  { kind: "read_served_deny", re: /was already served in this session, byte for byte/, tool: "Read" },
16454
16589
  // Its own kind rather than folded into read_served_deny above, whose stat name this branch shares: the wording is disjoint (a span, not a byte-for-byte whole serve) and so is the follow-up it invites, since the model still needs part of that span and has to choose a narrower call rather than recall the whole result.
16455
16590
  { kind: "range_reread_deny", re: /Lines \d+\.\.\d+ of [\s\S]*?was already read this session/, tool: "Read" },
16456
- { kind: "markdown_already_read_deny", re: /Markdown file already read this session\. Use `token-goat section/, tool: "Read" },
16591
+ { kind: "markdown_already_read_deny", re: /Markdown file already read this session\./, tool: "Read" },
16457
16592
  { kind: "read_count_deny", re: /(?:Read|Tried to read) this file \d+ times already/, tool: "Read" },
16458
16593
  // Shares read_count_deny's stat name and matches none of its wording: this branch fires on the SHAPE of the reads (consecutive line windows walking one file) rather than on their count, and says so.
16459
16594
  { kind: "sequential_paging_deny", re: /Sequential line-range paging detected on /, tool: "Read" },
@@ -16543,9 +16678,9 @@ function toolResultText(content) {
16543
16678
  return "";
16544
16679
  }
16545
16680
  function listCorpusTranscripts(corpusDir) {
16546
- if (!fs34.existsSync(corpusDir)) return [];
16681
+ if (!fs33.existsSync(corpusDir)) return [];
16547
16682
  try {
16548
- const stat = fs34.statSync(corpusDir);
16683
+ const stat = fs33.statSync(corpusDir);
16549
16684
  if (stat.isFile()) {
16550
16685
  return corpusDir.endsWith(".jsonl") ? [corpusDir] : [];
16551
16686
  }
@@ -16555,13 +16690,13 @@ function listCorpusTranscripts(corpusDir) {
16555
16690
  const found = [];
16556
16691
  const walk = (dir, entries) => {
16557
16692
  for (const entry of entries) {
16558
- const full = path31.join(dir, entry.name);
16693
+ const full = path30.join(dir, entry.name);
16559
16694
  if (entry.name.endsWith(".jsonl")) {
16560
16695
  found.push(full);
16561
16696
  } else if (entry.isDirectory()) {
16562
16697
  let inner;
16563
16698
  try {
16564
- inner = fs34.readdirSync(full, { withFileTypes: true });
16699
+ inner = fs33.readdirSync(full, { withFileTypes: true });
16565
16700
  } catch {
16566
16701
  continue;
16567
16702
  }
@@ -16570,31 +16705,41 @@ function listCorpusTranscripts(corpusDir) {
16570
16705
  }
16571
16706
  };
16572
16707
  try {
16573
- walk(corpusDir, fs34.readdirSync(corpusDir, { withFileTypes: true }));
16708
+ walk(corpusDir, fs33.readdirSync(corpusDir, { withFileTypes: true }));
16574
16709
  } catch {
16575
16710
  }
16576
16711
  return found.sort();
16577
16712
  }
16713
+ var MS_PER_DAY = 24 * 60 * 60 * 1e3;
16714
+ function modifiedSince(files, sinceMs) {
16715
+ return files.filter((file) => {
16716
+ try {
16717
+ return fs33.statSync(file).mtimeMs >= sinceMs;
16718
+ } catch {
16719
+ return false;
16720
+ }
16721
+ });
16722
+ }
16578
16723
  function defaultCorpusDir() {
16579
- const claudeDir = path31.join(claudeConfigDir(), "projects");
16580
- if (fs34.existsSync(claudeDir) && listCorpusTranscripts(claudeDir).length > 0) {
16724
+ const claudeDir = path30.join(claudeConfigDir(), "projects");
16725
+ if (fs33.existsSync(claudeDir) && listCorpusTranscripts(claudeDir).length > 0) {
16581
16726
  return claudeDir;
16582
16727
  }
16583
16728
  const home = os16.homedir();
16584
- const appData = process.env["APPDATA"] || path31.join(home, "AppData", "Roaming");
16729
+ const appData = process.env["APPDATA"] || path30.join(home, "AppData", "Roaming");
16585
16730
  const candidates = [
16586
16731
  claudeDir,
16587
- path31.join(appData, "Code", "User", "workspaceStorage"),
16588
- path31.join(appData, "Code - Insiders", "User", "workspaceStorage"),
16589
- path31.join(home, "Library", "Application Support", "Code", "User", "workspaceStorage"),
16590
- path31.join(home, "Library", "Application Support", "Code - Insiders", "User", "workspaceStorage"),
16591
- path31.join(home, ".config", "Code", "User", "workspaceStorage"),
16592
- path31.join(home, ".config", "Code - Insiders", "User", "workspaceStorage"),
16593
- path31.join(home, ".copilot", "session-state")
16732
+ path30.join(appData, "Code", "User", "workspaceStorage"),
16733
+ path30.join(appData, "Code - Insiders", "User", "workspaceStorage"),
16734
+ path30.join(home, "Library", "Application Support", "Code", "User", "workspaceStorage"),
16735
+ path30.join(home, "Library", "Application Support", "Code - Insiders", "User", "workspaceStorage"),
16736
+ path30.join(home, ".config", "Code", "User", "workspaceStorage"),
16737
+ path30.join(home, ".config", "Code - Insiders", "User", "workspaceStorage"),
16738
+ path30.join(home, ".copilot", "session-state")
16594
16739
  ];
16595
16740
  for (const cand of candidates) {
16596
16741
  try {
16597
- if (fs34.existsSync(cand) && listCorpusTranscripts(cand).length > 0) {
16742
+ if (fs33.existsSync(cand) && listCorpusTranscripts(cand).length > 0) {
16598
16743
  return cand;
16599
16744
  }
16600
16745
  } catch {
@@ -16602,11 +16747,13 @@ function defaultCorpusDir() {
16602
16747
  }
16603
16748
  return claudeDir;
16604
16749
  }
16605
- async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneObservations, bashHeadMap, denyRows) {
16750
+ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneObservations, bashHeadMap, denyRows, toolErrorAcc) {
16606
16751
  const isLane = filePath.split(/[\\/]/).includes("subagents");
16607
16752
  let laneFirstPrefix = null;
16608
16753
  let laneBriefBytes = -1;
16609
16754
  const toolNameById = /* @__PURE__ */ new Map();
16755
+ const modelById = /* @__PURE__ */ new Map();
16756
+ const bashCommandById = /* @__PURE__ */ new Map();
16610
16757
  const bashHeadById = /* @__PURE__ */ new Map();
16611
16758
  let pendingBashReread = [];
16612
16759
  const readCallById = /* @__PURE__ */ new Map();
@@ -16645,7 +16792,7 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
16645
16792
  }
16646
16793
  pendingBashReread = keptBash;
16647
16794
  };
16648
- const stream = fs34.createReadStream(filePath);
16795
+ const stream = fs33.createReadStream(filePath);
16649
16796
  const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
16650
16797
  const streamFailure = new Promise((_, reject) => stream.on("error", reject));
16651
16798
  const consume = (async () => {
@@ -16709,11 +16856,15 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
16709
16856
  if (!toolNameById.has(block["id"])) {
16710
16857
  toolNameById.set(block["id"], name);
16711
16858
  toolRollup(toolMap, name).calls += 1;
16859
+ const model = typeof message.model === "string" && message.model !== "" ? message.model : void 0;
16860
+ if (model !== void 0) modelById.set(block["id"], model);
16861
+ toolErrorAcc.countCall(name, model);
16712
16862
  const input = block["input"] !== null && typeof block["input"] === "object" ? block["input"] : {};
16713
16863
  let readNorm;
16714
16864
  let editNorm;
16715
16865
  if (name === "Bash" && typeof input["command"] === "string") {
16716
16866
  bashHeadById.set(block["id"], commandHead(input["command"]));
16867
+ bashCommandById.set(block["id"], input["command"]);
16717
16868
  } else if (name === "Read" && typeof input["file_path"] === "string") {
16718
16869
  readNorm = normalizeReadPath(input["file_path"]);
16719
16870
  const priorEpoch = readPathEpoch.get(readNorm);
@@ -16767,9 +16918,17 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
16767
16918
  const name = toolNameById.get(id) ?? "(unknown)";
16768
16919
  addCategory(s.estimated.toolResults, bytes);
16769
16920
  const roll = toolRollup(toolMap, name);
16770
- if (!toolNameById.has(id)) roll.calls += 1;
16921
+ if (!toolNameById.has(id)) {
16922
+ roll.calls += 1;
16923
+ toolErrorAcc.countCall(name, void 0);
16924
+ }
16771
16925
  roll.resultBytes += bytes;
16772
16926
  roll.resultEstTokens += estimateTokensFromLength(bytes);
16927
+ if (block["is_error"] === true) {
16928
+ const denialKind = obj["toolDenialKind"];
16929
+ const command = bashCommandById.get(id);
16930
+ toolErrorAcc.countError(name, modelById.get(id), text, { ...typeof denialKind === "string" ? { denialKind } : {}, ...command !== void 0 ? { command } : {} });
16931
+ }
16773
16932
  if (name === "Edit" && block["is_error"] === true) {
16774
16933
  s.editErrorBaseline.totalErrors += 1;
16775
16934
  editErrorById.set(id, true);
@@ -16777,13 +16936,13 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
16777
16936
  if (name === "Read" || name === "Skill") {
16778
16937
  const denyTemplate = DENY_TEMPLATES.find((t) => t.tool === name && t.re.test(text));
16779
16938
  if (denyTemplate !== void 0) {
16780
- const path42 = readPathById.get(id) ?? "";
16781
- const lastSlash = path42.lastIndexOf("/");
16939
+ const path41 = readPathById.get(id) ?? "";
16940
+ const lastSlash = path41.lastIndexOf("/");
16782
16941
  openDenies.push({
16783
16942
  kind: denyTemplate.kind,
16784
16943
  withheldBytes: parseWithheldBytes(denyTemplate.kind, text),
16785
- path: path42,
16786
- basename: lastSlash === -1 ? path42 : path42.slice(lastSlash + 1),
16944
+ path: path41,
16945
+ basename: lastSlash === -1 ? path41 : path41.slice(lastSlash + 1),
16787
16946
  callIndexAtDeny: perCall.length,
16788
16947
  compactEpochAtOpen: compactEpoch,
16789
16948
  compactEpochAtWindow: null,
@@ -16973,7 +17132,7 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
16973
17132
  if (isLane) {
16974
17133
  let agentType = "(none)";
16975
17134
  try {
16976
- const meta = JSON.parse(fs34.readFileSync(filePath.replace(/\.jsonl$/i, ".meta.json"), "utf8"));
17135
+ const meta = JSON.parse(fs33.readFileSync(filePath.replace(/\.jsonl$/i, ".meta.json"), "utf8"));
16977
17136
  if (typeof meta["agentType"] === "string" && meta["agentType"] !== "") agentType = meta["agentType"];
16978
17137
  } catch {
16979
17138
  }
@@ -17054,14 +17213,19 @@ function aggregateDenyOutcomes(rows) {
17054
17213
  return result.sort((a, b) => b.count - a.count || a.kind.localeCompare(b.kind));
17055
17214
  }
17056
17215
  async function auditSessionCorpus(opts = {}) {
17057
- const corpusDir = path31.resolve(opts.dir ?? defaultCorpusDir());
17058
- if (!fs34.existsSync(corpusDir)) {
17216
+ const corpusDir = path30.resolve(opts.dir ?? defaultCorpusDir());
17217
+ if (!fs33.existsSync(corpusDir)) {
17059
17218
  throw new Error(`session corpus directory not found: ${corpusDir}`);
17060
17219
  }
17061
- const files = listCorpusTranscripts(corpusDir);
17062
- if (files.length === 0) {
17220
+ const windowDays = opts.windowDays ?? 0;
17221
+ const allFiles = listCorpusTranscripts(corpusDir);
17222
+ if (allFiles.length === 0) {
17063
17223
  throw new Error(`no .jsonl session transcripts found under ${corpusDir}`);
17064
17224
  }
17225
+ const files = windowDays === 0 ? allFiles : modifiedSince(allFiles, Date.now() - windowDays * MS_PER_DAY);
17226
+ if (files.length === 0) {
17227
+ throw new Error(`no .jsonl session transcripts under ${corpusDir} were modified in the last ${windowDays} days`);
17228
+ }
17065
17229
  const started = Date.now();
17066
17230
  const summary = {
17067
17231
  corpusDir,
@@ -17096,8 +17260,11 @@ async function auditSessionCorpus(opts = {}) {
17096
17260
  denyOutcomes: [],
17097
17261
  editErrorBaseline: { totalEdits: 0, totalErrors: 0, rate: 0 },
17098
17262
  lineTypes: {},
17099
- omissionMarkers: { fires: 0, linesOmitted: 0 }
17263
+ omissionMarkers: { fires: 0, linesOmitted: 0 },
17264
+ windowDays,
17265
+ toolErrors: { byTool: [], byModel: [], unknownPrefixes: [] }
17100
17266
  };
17267
+ const toolErrorAcc = (await import("./token-goat-chunk-G23DWHV4.mjs")).newToolErrorAccumulator();
17101
17268
  const toolMap = /* @__PURE__ */ new Map();
17102
17269
  const attachmentMap = /* @__PURE__ */ new Map();
17103
17270
  const hookMap = /* @__PURE__ */ new Map();
@@ -17106,7 +17273,7 @@ async function auditSessionCorpus(opts = {}) {
17106
17273
  const denyRows = [];
17107
17274
  for (const file of files) {
17108
17275
  try {
17109
- await auditOneFile(file, summary, toolMap, attachmentMap, hookMap, laneObservations, bashHeadMap, denyRows);
17276
+ await auditOneFile(file, summary, toolMap, attachmentMap, hookMap, laneObservations, bashHeadMap, denyRows, toolErrorAcc);
17110
17277
  summary.filesScanned += 1;
17111
17278
  } catch {
17112
17279
  summary.filesFailed += 1;
@@ -17146,6 +17313,7 @@ async function auditSessionCorpus(opts = {}) {
17146
17313
  summary.denyOutcomes = aggregateDenyOutcomes(denyRows);
17147
17314
  summary.editErrorBaseline.rate = summary.editErrorBaseline.totalEdits === 0 ? 0 : summary.editErrorBaseline.totalErrors / summary.editErrorBaseline.totalEdits;
17148
17315
  summary.calibration = computeCalibration(summary.estimated, summary.measured);
17316
+ summary.toolErrors = toolErrorAcc.finish();
17149
17317
  summary.runtimeMs = Date.now() - started;
17150
17318
  return summary;
17151
17319
  }
@@ -17266,17 +17434,17 @@ function formatSessionAudit(s) {
17266
17434
 
17267
17435
  // src/session_read.ts
17268
17436
  init_define_import_meta_env();
17269
- import * as fs35 from "node:fs";
17270
- import * as path32 from "node:path";
17437
+ import * as fs34 from "node:fs";
17438
+ import * as path31 from "node:path";
17271
17439
  import * as readline2 from "node:readline";
17272
17440
  function resolveSessionTranscript(arg, opts = {}) {
17273
17441
  if (arg !== void 0 && arg !== "") {
17274
- if (fs35.existsSync(arg) && fs35.statSync(arg).isFile()) return arg;
17442
+ if (fs34.existsSync(arg) && fs34.statSync(arg).isFile()) return arg;
17275
17443
  const projectRoot2 = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
17276
17444
  const dir = projectTranscriptsDir(projectRoot2);
17277
17445
  const byId = arg.endsWith(".jsonl") ? arg : `${arg}.jsonl`;
17278
- const candidate = path32.join(dir, byId);
17279
- if (fs35.existsSync(candidate) && fs35.statSync(candidate).isFile()) return candidate;
17446
+ const candidate = path31.join(dir, byId);
17447
+ if (fs34.existsSync(candidate) && fs34.statSync(candidate).isFile()) return candidate;
17280
17448
  return null;
17281
17449
  }
17282
17450
  const projectRoot = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
@@ -17334,7 +17502,7 @@ function toolCallsForBlocks(blocks) {
17334
17502
  return blocks.filter((b) => b.type === "tool_use" && b.name !== void 0).map((b) => b.name);
17335
17503
  }
17336
17504
  async function* streamTurns(transcriptPath) {
17337
- const input = fs35.createReadStream(transcriptPath, { encoding: "utf8" });
17505
+ const input = fs34.createReadStream(transcriptPath, { encoding: "utf8" });
17338
17506
  const rl = readline2.createInterface({ input, crlfDelay: Infinity });
17339
17507
  try {
17340
17508
  let lineNumber = 0;
@@ -17433,9 +17601,9 @@ function formatSessionSlice(turns) {
17433
17601
 
17434
17602
  // src/cli_bootstrap_audit.ts
17435
17603
  init_define_import_meta_env();
17436
- import * as fs36 from "node:fs";
17604
+ import * as fs35 from "node:fs";
17437
17605
  import * as os17 from "node:os";
17438
- import * as path33 from "node:path";
17606
+ import * as path32 from "node:path";
17439
17607
  import * as readline3 from "node:readline";
17440
17608
  var FRONTMATTER_KEYS = /* @__PURE__ */ new Set(["description", "tools", "allowed-tools", "allowed_tools"]);
17441
17609
  function parseBudget(name, value) {
@@ -17444,7 +17612,7 @@ function parseBudget(name, value) {
17444
17612
  return value;
17445
17613
  }
17446
17614
  async function readFrontmatter(filePath) {
17447
- const stream = fs36.createReadStream(filePath, { encoding: "utf8" });
17615
+ const stream = fs35.createReadStream(filePath, { encoding: "utf8" });
17448
17616
  const lines = readline3.createInterface({ input: stream, crlfDelay: Infinity });
17449
17617
  let first = true;
17450
17618
  let inFrontmatter = false;
@@ -17490,44 +17658,44 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
17490
17658
  let logicalRoot;
17491
17659
  let rootIsLink;
17492
17660
  try {
17493
- rootIsLink = (await fs36.promises.lstat(root)).isSymbolicLink();
17494
- logicalRoot = await fs36.promises.realpath(root);
17661
+ rootIsLink = (await fs35.promises.lstat(root)).isSymbolicLink();
17662
+ logicalRoot = await fs35.promises.realpath(root);
17495
17663
  } catch (error) {
17496
17664
  if (error.code === "ENOENT") return entries;
17497
- diagnostics.push({ path: path33.resolve(root), reason: "unreadable root" });
17665
+ diagnostics.push({ path: path32.resolve(root), reason: "unreadable root" });
17498
17666
  return entries;
17499
17667
  }
17500
17668
  if (rootIsLink && !followLinks) {
17501
- diagnostics.push({ path: path33.resolve(root), reason: "external root link skipped (use --follow-links)" });
17669
+ diagnostics.push({ path: path32.resolve(root), reason: "external root link skipped (use --follow-links)" });
17502
17670
  return entries;
17503
17671
  }
17504
17672
  const canonicalKey = (candidate) => process.platform === "win32" ? candidate.toLocaleLowerCase() : candidate;
17505
17673
  const isContained = (candidate, allowedRoot) => {
17506
- const relative3 = path33.relative(canonicalKey(allowedRoot), canonicalKey(candidate));
17507
- return relative3 === "" || relative3 !== ".." && !relative3.startsWith(`..${path33.sep}`) && !path33.isAbsolute(relative3);
17674
+ const relative3 = path32.relative(canonicalKey(allowedRoot), canonicalKey(candidate));
17675
+ return relative3 === "" || relative3 !== ".." && !relative3.startsWith(`..${path32.sep}`) && !path32.isAbsolute(relative3);
17508
17676
  };
17509
17677
  async function visit(candidate, allowedRoot, topLevel = false) {
17510
17678
  let real;
17511
17679
  let candidateIsLink;
17512
17680
  try {
17513
- candidateIsLink = (await fs36.promises.lstat(candidate)).isSymbolicLink();
17514
- real = await fs36.promises.realpath(candidate);
17681
+ candidateIsLink = (await fs35.promises.lstat(candidate)).isSymbolicLink();
17682
+ real = await fs35.promises.realpath(candidate);
17515
17683
  } catch {
17516
- diagnostics.push({ path: path33.resolve(candidate), reason: "broken or unreadable link" });
17684
+ diagnostics.push({ path: path32.resolve(candidate), reason: "broken or unreadable link" });
17517
17685
  return;
17518
17686
  }
17519
17687
  const withinAllowedRoot = allowedRoot === void 0 ? isContained(real, logicalRoot) : isContained(real, allowedRoot);
17520
17688
  const canFollowExternal = followLinks && topLevel;
17521
17689
  if (!withinAllowedRoot && !canFollowExternal) {
17522
- diagnostics.push({ path: path33.resolve(candidate), reason: candidateIsLink ? "external link skipped (use --follow-links)" : "canonical target outside scan root" });
17690
+ diagnostics.push({ path: path32.resolve(candidate), reason: candidateIsLink ? "external link skipped (use --follow-links)" : "canonical target outside scan root" });
17523
17691
  return;
17524
17692
  }
17525
17693
  const trustedRoot = !withinAllowedRoot && canFollowExternal ? real : allowedRoot ?? real;
17526
17694
  let stat;
17527
17695
  try {
17528
- stat = await fs36.promises.stat(real);
17696
+ stat = await fs35.promises.stat(real);
17529
17697
  } catch {
17530
- diagnostics.push({ path: path33.resolve(candidate), reason: "unreadable" });
17698
+ diagnostics.push({ path: path32.resolve(candidate), reason: "unreadable" });
17531
17699
  return;
17532
17700
  }
17533
17701
  const canonical = process.platform === "win32" ? real.toLocaleLowerCase() : real;
@@ -17536,16 +17704,16 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
17536
17704
  visitedDirs.add(canonical);
17537
17705
  let children;
17538
17706
  try {
17539
- children = await fs36.promises.readdir(real, { withFileTypes: true });
17707
+ children = await fs35.promises.readdir(real, { withFileTypes: true });
17540
17708
  } catch {
17541
- diagnostics.push({ path: path33.resolve(candidate), reason: "unreadable directory" });
17709
+ diagnostics.push({ path: path32.resolve(candidate), reason: "unreadable directory" });
17542
17710
  return;
17543
17711
  }
17544
17712
  children.sort((a, b) => a.name.localeCompare(b.name));
17545
- for (const child of children) await visit(path33.join(real, child.name), trustedRoot);
17713
+ for (const child of children) await visit(path32.join(real, child.name), trustedRoot);
17546
17714
  return;
17547
17715
  }
17548
- if (!stat.isFile() || path33.extname(real).toLowerCase() !== ".md") return;
17716
+ if (!stat.isFile() || path32.extname(real).toLowerCase() !== ".md") return;
17549
17717
  if (seenFiles.has(canonical)) return;
17550
17718
  seenFiles.add(canonical);
17551
17719
  try {
@@ -17555,35 +17723,35 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
17555
17723
  const toolsBytes = Buffer.byteLength(metadata.tools, "utf8");
17556
17724
  entries.push({
17557
17725
  kind,
17558
- path: path33.resolve(real),
17726
+ path: path32.resolve(real),
17559
17727
  description_bytes: descriptionBytes,
17560
17728
  tools_bytes: toolsBytes,
17561
17729
  metadata_bytes: descriptionBytes + toolsBytes,
17562
17730
  estimated_tokens: Math.floor((descriptionBytes + toolsBytes) / 4)
17563
17731
  });
17564
17732
  } catch {
17565
- diagnostics.push({ path: path33.resolve(candidate), reason: "unreadable file" });
17733
+ diagnostics.push({ path: path32.resolve(candidate), reason: "unreadable file" });
17566
17734
  }
17567
17735
  }
17568
17736
  try {
17569
- const children = await fs36.promises.readdir(logicalRoot, { withFileTypes: true });
17737
+ const children = await fs35.promises.readdir(logicalRoot, { withFileTypes: true });
17570
17738
  children.sort((a, b) => a.name.localeCompare(b.name));
17571
- for (const child of children) await visit(path33.join(logicalRoot, child.name), void 0, true);
17739
+ for (const child of children) await visit(path32.join(logicalRoot, child.name), void 0, true);
17572
17740
  } catch {
17573
- diagnostics.push({ path: path33.resolve(root), reason: "unreadable root" });
17741
+ diagnostics.push({ path: path32.resolve(root), reason: "unreadable root" });
17574
17742
  }
17575
17743
  return entries;
17576
17744
  }
17577
17745
  async function buildBootstrapAudit(opts = {}) {
17578
17746
  const project = resolveProjectRoot(opts.project === void 0 ? {} : { project: opts.project });
17579
- const home = path33.resolve(opts.home ?? os17.homedir());
17747
+ const home = path32.resolve(opts.home ?? os17.homedir());
17580
17748
  const context = buildStats(project, home, opts.project === void 0 ? [] : [opts.project]);
17581
17749
  const diagnostics = [];
17582
17750
  const top = opts.top === void 0 ? 10 : parseBudget("--top", opts.top) ?? 10;
17583
17751
  const visitedDirs = /* @__PURE__ */ new Set();
17584
17752
  const seenFiles = /* @__PURE__ */ new Set();
17585
- const agents = await scanMetadataRoot(path33.join(claudeConfigDir(home), "agents"), "agent", diagnostics, visitedDirs, seenFiles, opts.followLinks === true);
17586
- const skills = await scanMetadataRoot(path33.join(claudeConfigDir(home), "skills"), "skill", diagnostics, visitedDirs, seenFiles, opts.followLinks === true);
17753
+ const agents = await scanMetadataRoot(path32.join(claudeConfigDir(home), "agents"), "agent", diagnostics, visitedDirs, seenFiles, opts.followLinks === true);
17754
+ const skills = await scanMetadataRoot(path32.join(claudeConfigDir(home), "skills"), "skill", diagnostics, visitedDirs, seenFiles, opts.followLinks === true);
17587
17755
  const rankedEntries = [...agents, ...skills].sort((a, b) => b.metadata_bytes - a.metadata_bytes || a.path.localeCompare(b.path));
17588
17756
  const largestTotal = rankedEntries.length;
17589
17757
  const largest = rankedEntries.slice(0, top);
@@ -17650,8 +17818,8 @@ async function runBootstrapAudit(opts = {}) {
17650
17818
 
17651
17819
  // src/cli_memory.ts
17652
17820
  init_define_import_meta_env();
17653
- import * as fs37 from "node:fs";
17654
- import * as path34 from "node:path";
17821
+ import * as fs36 from "node:fs";
17822
+ import * as path33 from "node:path";
17655
17823
  function removeExactDupLines(text, report) {
17656
17824
  if (report.exactDupLines.length === 0) return text;
17657
17825
  const toRemove = new Set(report.exactDupLines.map(([, dupIdx]) => dupIdx));
@@ -17709,7 +17877,7 @@ function printReport(reports, clusters) {
17709
17877
  } else {
17710
17878
  for (const cluster of clusters) {
17711
17879
  w(
17712
- ` [${cluster.method}, similarity ${cluster.similarity}, ~${cluster.tokens} tok] ${cluster.members.map((m) => displaySafeText(path34.basename(m))).join(", ")}
17880
+ ` [${cluster.method}, similarity ${cluster.similarity}, ~${cluster.tokens} tok] ${cluster.members.map((m) => displaySafeText(path33.basename(m))).join(", ")}
17713
17881
  `
17714
17882
  );
17715
17883
  }
@@ -17724,7 +17892,7 @@ async function runMemoryCommand(opts = {}) {
17724
17892
  const claudeMds = findClaudeMdFiles(projectRoot);
17725
17893
  const reports = auditClaudeMd(claudeMds);
17726
17894
  const memoryMdPath = findMemoryMd(projectRoot);
17727
- const clusters = memoryMdPath !== null ? await findContentDuplicates(path34.dirname(memoryMdPath)) : [];
17895
+ const clusters = memoryMdPath !== null ? await findContentDuplicates(path33.dirname(memoryMdPath)) : [];
17728
17896
  printReport(reports, clusters);
17729
17897
  if (opts.fix !== true) return;
17730
17898
  const changes = [];
@@ -17732,7 +17900,7 @@ async function runMemoryCommand(opts = {}) {
17732
17900
  if (report.exactDupLines.length === 0) continue;
17733
17901
  let before;
17734
17902
  try {
17735
- before = fs37.readFileSync(report.path, "utf-8");
17903
+ before = fs36.readFileSync(report.path, "utf-8");
17736
17904
  } catch {
17737
17905
  continue;
17738
17906
  }
@@ -17770,8 +17938,8 @@ async function runMemoryCommand(opts = {}) {
17770
17938
 
17771
17939
  // src/cli_waste.ts
17772
17940
  init_define_import_meta_env();
17773
- import * as fs38 from "node:fs";
17774
- import * as path35 from "node:path";
17941
+ import * as fs37 from "node:fs";
17942
+ import * as path34 from "node:path";
17775
17943
  function printCopilotReport(report) {
17776
17944
  const w = (text) => {
17777
17945
  process.stdout.write(text);
@@ -17825,8 +17993,9 @@ function printCopilotReport(report) {
17825
17993
  } else {
17826
17994
  for (const server of mcp.servers) {
17827
17995
  const tokens = server.estimatedTokens.toLocaleString();
17996
+ const calls = report.mcpCalls === null ? "" : `, ${countNoun(report.mcpCalls[server.serverName] ?? 0, "call")} this session`;
17828
17997
  w(` ${server.serverName}: ${countNoun(server.toolCount, "tool")}, `);
17829
- w(`${formatBytes(server.definitionBytes)}, ~${tokens} tok
17998
+ w(`${formatBytes(server.definitionBytes)}, ~${tokens} tok${calls}
17830
17999
  `);
17831
18000
  }
17832
18001
  const mcpTokens = mcp.servers.reduce((sum, server) => sum + server.estimatedTokens, 0);
@@ -17846,6 +18015,15 @@ function printCopilotReport(report) {
17846
18015
  w(" tool-filter flags above still can.\n");
17847
18016
  }
17848
18017
  w(" Dropping a server saves its line above on every request for the rest of the session.\n");
18018
+ const unused = unusedMcpServers(report);
18019
+ if (unused === null) {
18020
+ w(" This log records no tool calls, so it cannot say which servers went unused.\n");
18021
+ } else {
18022
+ for (const server of unused) w(` Never called this session: \`copilot mcp disable ${server.serverName}\` drops it from future sessions.
18023
+ `);
18024
+ if (unused.length > 0) w(` ${MCP_DISABLE_NOTE}
18025
+ `);
18026
+ }
17849
18027
  }
17850
18028
  if (mcp.unreadable > 0) {
17851
18029
  const verb = mcp.unreadable === 1 ? "is" : "are";
@@ -17972,8 +18150,8 @@ function printResidentContext(resident, w) {
17972
18150
  async function runWasteCommand(opts = {}) {
17973
18151
  const projectRoot = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
17974
18152
  if (opts.transcript !== void 0) {
17975
- const resolvedPath = path35.resolve(opts.transcript);
17976
- if (!fs38.existsSync(resolvedPath)) {
18153
+ const resolvedPath = path34.resolve(opts.transcript);
18154
+ if (!fs37.existsSync(resolvedPath)) {
17977
18155
  if (opts.copilot === true) {
17978
18156
  const detail = `Copilot session event log not found: ${resolvedPath}`;
17979
18157
  if (opts.json === true) {
@@ -18013,7 +18191,7 @@ async function runWasteCommand(opts = {}) {
18013
18191
  }
18014
18192
  if (opts.copilot === true) {
18015
18193
  const eventsPath = opts.project !== void 0 ? findLatestCopilotSession({ projectRoot }) : findLatestCopilotSession({ projectRoot }) ?? findLatestCopilotSession();
18016
- if (eventsPath === null || !fs38.existsSync(eventsPath)) {
18194
+ if (eventsPath === null || !fs37.existsSync(eventsPath)) {
18017
18195
  const detail = eventsPath === null ? "no Copilot CLI session found under <copilot-home>/session-state" : `Copilot session event log not found: ${eventsPath}`;
18018
18196
  if (opts.json === true) {
18019
18197
  process.stdout.write(`${displaySafeJson({ error: detail }, 0)}
@@ -18070,13 +18248,13 @@ async function runWasteCommand(opts = {}) {
18070
18248
 
18071
18249
  // src/cli_mcp_audit.ts
18072
18250
  init_define_import_meta_env();
18073
- import * as fs39 from "node:fs";
18251
+ import * as fs38 from "node:fs";
18074
18252
  import * as os18 from "node:os";
18075
- import * as path36 from "node:path";
18253
+ import * as path35 from "node:path";
18076
18254
  function readMcpJsonFile(configPath2) {
18077
18255
  try {
18078
- if (!fs39.existsSync(configPath2)) return null;
18079
- const content = fs39.readFileSync(configPath2, "utf-8");
18256
+ if (!fs38.existsSync(configPath2)) return null;
18257
+ const content = fs38.readFileSync(configPath2, "utf-8");
18080
18258
  const parsed = JSON.parse(content);
18081
18259
  const servers = parsed && typeof parsed === "object" ? parsed.mcpServers ?? ("servers" in parsed ? null : parsed) : null;
18082
18260
  return servers && typeof servers === "object" ? servers : null;
@@ -18092,8 +18270,8 @@ function driveLetterCaseVariants(p) {
18092
18270
  }
18093
18271
  function readClaudeJsonConfig(claudeJsonPath, projectRoot) {
18094
18272
  try {
18095
- if (!fs39.existsSync(claudeJsonPath)) return null;
18096
- const parsed = JSON.parse(fs39.readFileSync(claudeJsonPath, "utf-8"));
18273
+ if (!fs38.existsSync(claudeJsonPath)) return null;
18274
+ const parsed = JSON.parse(fs38.readFileSync(claudeJsonPath, "utf-8"));
18097
18275
  const projects = parsed && typeof parsed === "object" ? parsed.projects : null;
18098
18276
  if (!projects || typeof projects !== "object") return null;
18099
18277
  const slashForms = [projectRoot, projectRoot.replace(/\\/g, "/"), projectRoot.replace(/\//g, "\\")];
@@ -18111,9 +18289,9 @@ function readClaudeJsonConfig(claudeJsonPath, projectRoot) {
18111
18289
  }
18112
18290
  }
18113
18291
  function discoverMcpConfig(projectRoot, home) {
18114
- const mcpJsonPath = path36.join(projectRoot, ".mcp.json");
18115
- const claudeJsonPath = path36.join(home, ".claude.json");
18116
- const copilotJsonPath = path36.join(home, ".copilot", "mcp-config.json");
18292
+ const mcpJsonPath = path35.join(projectRoot, ".mcp.json");
18293
+ const claudeJsonPath = path35.join(home, ".claude.json");
18294
+ const copilotJsonPath = path35.join(home, ".copilot", "mcp-config.json");
18117
18295
  const sourcesChecked = [mcpJsonPath, claudeJsonPath, copilotJsonPath];
18118
18296
  const fromMcpJson = readMcpJsonFile(mcpJsonPath);
18119
18297
  if (fromMcpJson !== null) return { servers: fromMcpJson, sourcePath: mcpJsonPath, sourcesChecked };
@@ -18312,6 +18490,11 @@ async function runStatuslineCommand(opts = {}) {
18312
18490
 
18313
18491
  // src/cli_hint_stats.ts
18314
18492
  init_define_import_meta_env();
18493
+ var SESSION_JSON_SCOPE = {
18494
+ session: ["emitted", "actedOn", "efficacyPct", "unobservable", "detected", "bytesEmitted", "legacyEmissions", "totals.spentBytes", "totals.legacyEmissions"],
18495
+ allTime: ["suppressed", "suppressionPermanent", "manualEffective", "manualIneffective"],
18496
+ unavailable: ["totals.savedBytes"]
18497
+ };
18315
18498
  function formatSpentCell(row) {
18316
18499
  if (row.emitted === 0) return "n/a";
18317
18500
  if (row.bytesEmitted === null) return "n/a (legacy)";
@@ -18351,6 +18534,16 @@ TOTAL saved-bytes=${totals.savedBytes} (all-time, every hint kind) spent-byt
18351
18534
  `
18352
18535
  );
18353
18536
  }
18537
+ function printSessionTotals(totals) {
18538
+ const spent = totals.spentBytes === null ? "n/a" : String(totals.spentBytes);
18539
+ const legacyNote = totals.legacyEmissions > 0 ? ` (excludes ${totals.legacyEmissions} legacy emission(s) recorded before spend tracking)` : "";
18540
+ process.stdout.write(
18541
+ `
18542
+ TOTAL (this session) spent-bytes=${spent} (hint_emissions ledger only)${legacyNote}
18543
+ saved-bytes: not available per session, because the stats ledger it comes from records no session id. Run \`token-goat hint-stats\` without --session-id for the all-time figure.
18544
+ `
18545
+ );
18546
+ }
18354
18547
  function runHintStatsCommand(opts = {}) {
18355
18548
  if (opts.reset === true) {
18356
18549
  resetHintStats();
@@ -18369,13 +18562,24 @@ function runHintStatsCommand(opts = {}) {
18369
18562
  `);
18370
18563
  return;
18371
18564
  }
18372
- const rows = getHintStatsSummary();
18565
+ const session = opts.sessionId;
18566
+ const rows = getHintStatsSummary(session);
18373
18567
  if (opts.json === true) {
18374
- process.stdout.write(`${displaySafeJson(rows, 0)}
18568
+ const payload = session === void 0 ? rows : { session, scope: SESSION_JSON_SCOPE, rows, totals: { savedBytes: null, ...getHintSpendTotals(session) } };
18569
+ process.stdout.write(`${displaySafeJson(payload, 0)}
18375
18570
  `);
18376
18571
  return;
18377
18572
  }
18378
- if (rows.every((r) => r.emitted === 0 && r.actedOn === 0 && r.unobservable === 0 && r.detected === 0)) {
18573
+ const noEmissions = rows.every((r) => r.emitted === 0 && r.actedOn === 0 && r.unobservable === 0 && r.detected === 0);
18574
+ if (session !== void 0) {
18575
+ process.stdout.write(
18576
+ `Session: ${displaySafeText(session)}
18577
+ emitted, undisplayed, acted-on, efficacy and spent-bytes count this session only. suppressed, manual+ and manual- are all-time, across every session: suppression is decided on the whole history, and manual marks carry no session.
18578
+ `
18579
+ );
18580
+ if (noEmissions) process.stdout.write("No hint emissions were recorded for this session, so every per-session figure below is 0.\n");
18581
+ }
18582
+ if (session === void 0 && noEmissions) {
18379
18583
  process.stdout.write("No hint emissions recorded yet \u2014 the zeros below are absence of data, not measured ineffectiveness.\n");
18380
18584
  }
18381
18585
  printSummary(rows);
@@ -18401,7 +18605,8 @@ function runHintStatsCommand(opts = {}) {
18401
18605
  hints.backoff_thresholds is empty, so suppression here is permanent rather than a self-healing throttle: ${names} will not emit again on any occasion. Set backoff_thresholds to re-enable probe emissions, or run \`token-goat hint-stats --reset\` to clear the ledger.
18402
18606
  `);
18403
18607
  }
18404
- printTotals(getHintStatsTotals());
18608
+ if (session === void 0) printTotals(getHintStatsTotals());
18609
+ else printSessionTotals(getHintSpendTotals(session));
18405
18610
  }
18406
18611
 
18407
18612
  // src/cli_session.ts
@@ -18434,7 +18639,7 @@ function cmdWaste(opts = {}) {
18434
18639
  });
18435
18640
  }
18436
18641
  async function cmdAudit(opts = {}) {
18437
- const { runAuditCommand } = await import("./token-goat-chunk-CXBOFPF4.mjs");
18642
+ const { runAuditCommand } = await import("./token-goat-chunk-67BNIKKO.mjs");
18438
18643
  return runAuditCommand({
18439
18644
  ...opts.project !== void 0 ? { project: opts.project } : {},
18440
18645
  ...opts.transcript !== void 0 ? { transcript: opts.transcript } : {},
@@ -18444,10 +18649,15 @@ async function cmdAudit(opts = {}) {
18444
18649
  async function cmdSessionAudit(opts = {}) {
18445
18650
  let summary;
18446
18651
  try {
18447
- summary = await auditSessionCorpus({ ...opts.dir !== void 0 ? { dir: opts.dir } : {} });
18652
+ summary = await auditSessionCorpus({ ...opts.dir !== void 0 ? { dir: opts.dir } : {}, ...opts.windowDays !== void 0 ? { windowDays: opts.windowDays } : {} });
18448
18653
  } catch (err2) {
18449
18654
  throw new CliError(err2 instanceof Error ? err2.message : String(err2));
18450
18655
  }
18656
+ if (opts.toolErrors === true) {
18657
+ const { formatToolErrorCensus } = await import("./token-goat-chunk-G23DWHV4.mjs");
18658
+ out(opts.json === true ? displaySafeJson({ filesScanned: summary.filesScanned, windowDays: summary.windowDays, ...summary.toolErrors }, 0) : formatToolErrorCensus(summary.toolErrors, summary));
18659
+ return;
18660
+ }
18451
18661
  out(opts.json === true ? displaySafeJson(summary, 0) : formatSessionAudit(summary));
18452
18662
  }
18453
18663
  async function cmdSessionOutline(sessionIdOrPath, opts = {}) {
@@ -18467,7 +18677,7 @@ ${formatSessionOutline(turns)}`;
18467
18677
  }
18468
18678
  function sessionTranscriptSize(transcriptPath) {
18469
18679
  try {
18470
- return fs40.statSync(transcriptPath).size;
18680
+ return fs39.statSync(transcriptPath).size;
18471
18681
  } catch {
18472
18682
  return 0;
18473
18683
  }
@@ -18511,30 +18721,46 @@ function cmdStatusline(opts = {}) {
18511
18721
  return runStatuslineCommand({ ...opts.json === true ? { json: true } : {} });
18512
18722
  }
18513
18723
  function cmdHintStats(opts = {}) {
18724
+ if (opts.sessionId !== void 0) {
18725
+ const mutating = [opts.reset === true ? "--reset" : null, opts.markEffective !== void 0 ? "--mark-effective" : null, opts.markIneffective !== void 0 ? "--mark-ineffective" : null].filter((f) => f !== null);
18726
+ if (mutating.length > 0) {
18727
+ throw new CliError(`--session-id cannot be combined with ${mutating.join(", ")}: those change cross-session state (every session's emissions and marks), not one session's view`);
18728
+ }
18729
+ }
18730
+ const session = opts.sessionId === void 0 ? void 0 : resolveHintStatsSession(opts.sessionId);
18514
18731
  if (opts.markEffective !== void 0 && !isHintCategory(opts.markEffective)) {
18515
- throw new CliError(`--mark-effective must be one of: bash_redirect, bash_recall, read_reread_dedup, read_structural_nav, edit_reread_suggest (got: ${opts.markEffective})`);
18732
+ throw new CliError(`--mark-effective must be one of: ${HINT_CATEGORIES.join(", ")} (got: ${opts.markEffective})`);
18516
18733
  }
18517
18734
  if (opts.markIneffective !== void 0 && !isHintCategory(opts.markIneffective)) {
18518
- throw new CliError(`--mark-ineffective must be one of: bash_redirect, bash_recall, read_reread_dedup, read_structural_nav, edit_reread_suggest (got: ${opts.markIneffective})`);
18735
+ throw new CliError(`--mark-ineffective must be one of: ${HINT_CATEGORIES.join(", ")} (got: ${opts.markIneffective})`);
18519
18736
  }
18520
18737
  runHintStatsCommand({
18521
18738
  ...opts.json === true ? { json: true } : {},
18522
18739
  ...opts.reset === true ? { reset: true } : {},
18523
18740
  ...opts.markEffective !== void 0 && isHintCategory(opts.markEffective) ? { markEffective: opts.markEffective } : {},
18524
- ...opts.markIneffective !== void 0 && isHintCategory(opts.markIneffective) ? { markIneffective: opts.markIneffective } : {}
18741
+ ...opts.markIneffective !== void 0 && isHintCategory(opts.markIneffective) ? { markIneffective: opts.markIneffective } : {},
18742
+ ...session !== void 0 ? { sessionId: session } : {}
18525
18743
  });
18526
18744
  }
18745
+ function resolveHintStatsSession(arg) {
18746
+ const trimmed = arg.trim();
18747
+ if (trimmed === "") throw new CliError("--session-id needs a session id, or `latest` for the most recent session");
18748
+ if (trimmed !== "latest") return trimmed;
18749
+ const latest = findLatestSessionId();
18750
+ if (latest === null) throw new CliError("--session-id latest: no session state found on disk; pass a session id explicitly");
18751
+ return latest;
18752
+ }
18527
18753
 
18528
18754
  // src/cli_skills.ts
18529
18755
  init_define_import_meta_env();
18530
- import * as fs41 from "node:fs";
18531
- import * as path37 from "node:path";
18756
+ import * as fs40 from "node:fs";
18757
+ import * as path36 from "node:path";
18532
18758
  async function cmdSkillBody(name, opts) {
18533
18759
  const filePath = await getSkillFilePath(name);
18534
18760
  if (filePath === null) {
18535
18761
  throw new CliError(`skill '${name}' not found`);
18536
18762
  }
18537
- const body = decodeSource(fs41.readFileSync(filePath));
18763
+ const body = decodeSource(fs40.readFileSync(filePath));
18538
18764
  if (opts.compact === true) {
18539
18765
  const emitted = extractCompactFromMarker(body) ?? body;
18540
18766
  out(emitted);
@@ -18561,7 +18787,7 @@ async function cmdSkillCompact(name, opts) {
18561
18787
  }
18562
18788
  let body2;
18563
18789
  try {
18564
- body2 = decodeSource(fs41.readFileSync(filePath));
18790
+ body2 = decodeSource(fs40.readFileSync(filePath));
18565
18791
  } catch {
18566
18792
  unresolvable++;
18567
18793
  continue;
@@ -18595,19 +18821,19 @@ async function cmdSkillCompact(name, opts) {
18595
18821
  if (!opts.path.trim()) {
18596
18822
  throw new CliError("--path cannot be empty");
18597
18823
  }
18598
- if (!fs41.existsSync(opts.path)) {
18824
+ if (!fs40.existsSync(opts.path)) {
18599
18825
  throw new CliError(`skill file not found: ${opts.path}`);
18600
18826
  }
18601
18827
  try {
18602
- body = fs41.readFileSync(opts.path, "utf-8");
18828
+ body = fs40.readFileSync(opts.path, "utf-8");
18603
18829
  } catch (e) {
18604
18830
  if (e.code === "ENOENT") {
18605
18831
  throw new CliError(`skill file not found: ${opts.path}`);
18606
18832
  }
18607
18833
  throw new CliError(`failed to read skill file '${opts.path}': ${extractErrorMessage(e)}`);
18608
18834
  }
18609
- cacheName = name ?? path37.basename(path37.dirname(path37.resolve(opts.path)));
18610
- sourcePath = path37.resolve(opts.path);
18835
+ cacheName = name ?? path36.basename(path36.dirname(path36.resolve(opts.path)));
18836
+ sourcePath = path36.resolve(opts.path);
18611
18837
  } else {
18612
18838
  if (name === void 0 || !name.trim()) {
18613
18839
  throw new CliError("skill-compact requires a <name> or --path <file>");
@@ -18616,7 +18842,7 @@ async function cmdSkillCompact(name, opts) {
18616
18842
  if (filePath === null) {
18617
18843
  throw new CliError(`skill '${name}' not found`);
18618
18844
  }
18619
- body = fs41.readFileSync(filePath, "utf-8");
18845
+ body = fs40.readFileSync(filePath, "utf-8");
18620
18846
  cacheName = name;
18621
18847
  sourcePath = filePath;
18622
18848
  }
@@ -18748,8 +18974,8 @@ async function cmdSkillDiff(name) {
18748
18974
  }
18749
18975
  const newer = versions[0];
18750
18976
  const older = versions[1];
18751
- const newerBody = await fs41.promises.readFile(path37.resolve(dir, `${newer.outputId}.txt`), "utf-8").catch(() => null);
18752
- const olderBody = await fs41.promises.readFile(path37.resolve(dir, `${older.outputId}.txt`), "utf-8").catch(() => null);
18977
+ const newerBody = await fs40.promises.readFile(path36.resolve(dir, `${newer.outputId}.txt`), "utf-8").catch(() => null);
18978
+ const olderBody = await fs40.promises.readFile(path36.resolve(dir, `${older.outputId}.txt`), "utf-8").catch(() => null);
18753
18979
  if (newerBody === null || olderBody === null) {
18754
18980
  out(`a cached version of '${name}' was evicted while diffing -- try again`);
18755
18981
  return;
@@ -18778,7 +19004,7 @@ async function cmdSkillSection(nameHeading, headingArg) {
18778
19004
  if (!filePath) {
18779
19005
  throw new CliError(`skill '${skillName}' not found`);
18780
19006
  }
18781
- const body = decodeSource(fs41.readFileSync(filePath));
19007
+ const body = decodeSource(fs40.readFileSync(filePath));
18782
19008
  const extracted = extractNamedSection(body, heading);
18783
19009
  if (!extracted) {
18784
19010
  const allHeadings = listSections(filePath);
@@ -18788,10 +19014,10 @@ async function cmdSkillSection(nameHeading, headingArg) {
18788
19014
  }
18789
19015
  const messages = [`Section '${heading}' not found in skill '${skillName}'`];
18790
19016
  const available = filterSimilarHeadings(allHeadings, heading);
18791
- if (available.length > 0) messages.push(didYouMean(available));
19017
+ if (available.length > 0) messages.push(...didYouMeanLines(available));
18792
19018
  else if (allHeadings.length === 0) messages.push(`skill '${skillName}' has no headings`);
18793
19019
  else messages.push(`Try: token-goat outline ${filePath}`);
18794
- throw new CliError(messages.join("\n"));
19020
+ throw new CliError(messages);
18795
19021
  }
18796
19022
  out(extracted);
18797
19023
  }
@@ -18801,8 +19027,8 @@ init_define_import_meta_env();
18801
19027
 
18802
19028
  // src/cli_stats.ts
18803
19029
  init_define_import_meta_env();
18804
- import * as fs42 from "node:fs";
18805
- import * as path38 from "node:path";
19030
+ import * as fs41 from "node:fs";
19031
+ import * as path37 from "node:path";
18806
19032
  function writeRaw(text) {
18807
19033
  const payload = colorStdout() ? text : stripAnsiEscapes(text);
18808
19034
  process.stdout.write(ensureNewline(payload));
@@ -18814,7 +19040,7 @@ function formatTopFiles(ranked) {
18814
19040
  if (ranked.length === 0) return "";
18815
19041
  const lines = ["Top files this session:"];
18816
19042
  for (const { path: filePath, count } of ranked) {
18817
- const basename10 = path38.basename(filePath);
19043
+ const basename10 = path37.basename(filePath);
18818
19044
  lines.push(` ${count.toString().padStart(3)}x ${basename10} (${filePath})`);
18819
19045
  }
18820
19046
  return lines.join("\n");
@@ -18832,11 +19058,11 @@ function renderTopSessionFiles(topN = 5) {
18832
19058
  function renderTopSessionFilesFromDisk(topN = 5, overrideSessionsDir) {
18833
19059
  try {
18834
19060
  const dir = overrideSessionsDir ?? sessionsDir();
18835
- if (!fs42.existsSync(dir)) return "";
18836
- const files = fs42.readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => ({ name: f, mtime: fs42.statSync(path38.join(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).slice(0, 3);
19061
+ if (!fs41.existsSync(dir)) return "";
19062
+ const files = fs41.readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => ({ name: f, mtime: fs41.statSync(path37.join(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).slice(0, 3);
18837
19063
  for (const { name } of files) {
18838
19064
  try {
18839
- const raw = fs42.readFileSync(path38.join(dir, name), "utf-8");
19065
+ const raw = fs41.readFileSync(path37.join(dir, name), "utf-8");
18840
19066
  const data = JSON.parse(raw);
18841
19067
  const filesList = data["files"];
18842
19068
  if (!Array.isArray(filesList)) continue;
@@ -18858,6 +19084,7 @@ var METHODOLOGY = {
18858
19084
  billing: "They are not GitHub Copilot usage, provider-reported token consumption, or billing data.",
18859
19085
  byte_derived_formula: "Most read, hook, and command entries go through savedTokensFromBytes in src/stats.ts, which is Math.round(bytes_saved / 4). Image entries are the exception: they are priced in visual tokens, not bytes.",
18860
19086
  filter_estimates: "Output compressors record their filter-calculated delta, measured against what the harness would actually have delivered rather than the full command output. Image entries are priced in visual tokens (28x28-pixel patches, capped at the configured vision tier), because an image is not billed by its byte size; where the pixel dimensions are unavailable the tier ceiling is used.",
19087
+ read_command_baselines: "A read command is credited the bytes of what it replaced, less what it printed. Naming a file (read, section, outline and the rest) replaces reading that file whole, capped at 100,000 bytes per file, about what one Read returns before it truncates. A search over files the caller did not name (symbol NAME, semantic) replaces a search plus a read of the one file wanted, so it is credited the largest matched file under the same cap, not every match. refs is credited the one line per reference a plain text search prints, and changed the diff it stands in for; both are shell output, so both are first cut to what the harness would have delivered of it.",
18861
19088
  advisory_events: "Zero-byte, zero-token advisory events show that guidance fired, not that an agent followed it.",
18862
19089
  audit: "Use stats --full or stats --json for source and command breakdowns; reconcile billing with provider-exported usage data."
18863
19090
  };
@@ -18873,6 +19100,7 @@ function renderMethodology(json = false) {
18873
19100
  `\`tokens saved\` is a ${METHODOLOGY.estimate_scope.toLowerCase()} ${METHODOLOGY.billing}`,
18874
19101
  "",
18875
19102
  `- ${METHODOLOGY.byte_derived_formula}`,
19103
+ `- ${METHODOLOGY.read_command_baselines}`,
18876
19104
  `- ${METHODOLOGY.filter_estimates}`,
18877
19105
  `- ${METHODOLOGY.advisory_events}`,
18878
19106
  `- ${METHODOLOGY.audit}`,
@@ -18971,6 +19199,19 @@ function getNewestSessionFiles() {
18971
19199
  const filesArr = Array.isArray(raw["files"]) ? raw["files"] : [];
18972
19200
  return { id: newest.id, sessionCount: blobs.length, filesArr };
18973
19201
  }
19202
+ function emitHistoryOutput(allItems, limit, json, emptyMessage, printTable) {
19203
+ const sorted = allItems.sort((a, b) => b.storedAt - a.storedAt);
19204
+ const items = capAndNote(sorted, limit);
19205
+ if (json === true) {
19206
+ process.stdout.write(displaySafeJson(items) + "\n");
19207
+ return;
19208
+ }
19209
+ if (items.length === 0) {
19210
+ process.stdout.write(emptyMessage);
19211
+ return;
19212
+ }
19213
+ printTable(items);
19214
+ }
18974
19215
  function cmdBashHistory(opts) {
18975
19216
  const limit = parseLimitOpt("bash-history", opts.limit);
18976
19217
  const blobs = listBlobs(BASH_OUTPUT_SUBDIR);
@@ -18985,24 +19226,17 @@ function cmdBashHistory(opts) {
18985
19226
  exitCode: typeof v["exitCode"] === "number" ? v["exitCode"] : -1,
18986
19227
  sizeBytes: typeof v["sizeBytes"] === "number" ? v["sizeBytes"] : 0
18987
19228
  };
18988
- }).filter((x) => x !== null).sort((a, b) => b.storedAt - a.storedAt);
18989
- const items = capAndNote(allItems, limit);
18990
- if (opts.json === true) {
18991
- process.stdout.write(displaySafeJson(items) + "\n");
18992
- return;
18993
- }
18994
- if (items.length === 0) {
18995
- process.stdout.write("No bash output entries cached.\n");
18996
- return;
18997
- }
18998
- process.stdout.write(`${pad("id", 18)} ${pad("bytes", 8)} ${pad("exit", 4)} command
19229
+ }).filter((x) => x !== null);
19230
+ emitHistoryOutput(allItems, limit, opts.json, "No bash output entries cached.\n", (items) => {
19231
+ process.stdout.write(`${pad("id", 18)} ${pad("bytes", 8)} ${pad("exit", 4)} command
18999
19232
  `);
19000
- for (const item of items) {
19001
- const flatCommand = displaySafeText(item.command);
19002
- const preview = flatCommand.length > 80 ? flatCommand.slice(0, 77) + "..." : flatCommand;
19003
- process.stdout.write(`${pad(displaySafeText(item.id), 18)} ${pad(String(item.sizeBytes), 8)} ${pad(String(item.exitCode), 4)} ${preview}
19233
+ for (const item of items) {
19234
+ const flatCommand = displaySafeText(item.command);
19235
+ const preview = flatCommand.length > 80 ? flatCommand.slice(0, 77) + "..." : flatCommand;
19236
+ process.stdout.write(`${pad(displaySafeText(item.id), 18)} ${pad(String(item.sizeBytes), 8)} ${pad(String(item.exitCode), 4)} ${preview}
19004
19237
  `);
19005
- }
19238
+ }
19239
+ });
19006
19240
  }
19007
19241
  function cmdWebHistory(opts) {
19008
19242
  const limit = parseLimitOpt("web-history", opts.limit);
@@ -19016,22 +19250,15 @@ function cmdWebHistory(opts) {
19016
19250
  bytes: typeof v["content"] === "string" ? Buffer.byteLength(v["content"], "utf8") : 0,
19017
19251
  storedAt: mtime
19018
19252
  };
19019
- }).filter((x) => x !== null).sort((a, b) => b.storedAt - a.storedAt);
19020
- const items = capAndNote(allItems, limit);
19021
- if (opts.json === true) {
19022
- process.stdout.write(displaySafeJson(items) + "\n");
19023
- return;
19024
- }
19025
- if (items.length === 0) {
19026
- process.stdout.write("No web output entries cached.\n");
19027
- return;
19028
- }
19029
- process.stdout.write(`${pad("id", 18)} ${pad("bytes", 8)} url
19253
+ }).filter((x) => x !== null);
19254
+ emitHistoryOutput(allItems, limit, opts.json, "No web output entries cached.\n", (items) => {
19255
+ process.stdout.write(`${pad("id", 18)} ${pad("bytes", 8)} url
19030
19256
  `);
19031
- for (const item of items) {
19032
- process.stdout.write(`${pad(displaySafeText(item.id), 18)} ${pad(String(item.bytes), 8)} ${displaySafeText(item.url)}
19257
+ for (const item of items) {
19258
+ process.stdout.write(`${pad(displaySafeText(item.id), 18)} ${pad(String(item.bytes), 8)} ${displaySafeText(item.url)}
19033
19259
  `);
19034
- }
19260
+ }
19261
+ });
19035
19262
  }
19036
19263
  function cmdMcpHistory(opts) {
19037
19264
  const limit = parseLimitOpt("mcp-history", opts.limit);
@@ -19047,22 +19274,15 @@ function cmdMcpHistory(opts) {
19047
19274
  storedAt: typeof v["storedAt"] === "number" ? v["storedAt"] : mtime,
19048
19275
  sizeBytes: typeof v["sizeBytes"] === "number" ? v["sizeBytes"] : 0
19049
19276
  };
19050
- }).filter((x) => x !== null).sort((a, b) => b.storedAt - a.storedAt);
19051
- const items = capAndNote(allItems, limit);
19052
- if (opts.json === true) {
19053
- process.stdout.write(displaySafeJson(items) + "\n");
19054
- return;
19055
- }
19056
- if (items.length === 0) {
19057
- process.stdout.write("No mcp output entries cached.\n");
19058
- return;
19059
- }
19060
- process.stdout.write(`${pad("id", 18)} ${pad("bytes", 8)} tool
19277
+ }).filter((x) => x !== null);
19278
+ emitHistoryOutput(allItems, limit, opts.json, "No mcp output entries cached.\n", (items) => {
19279
+ process.stdout.write(`${pad("id", 18)} ${pad("bytes", 8)} tool
19061
19280
  `);
19062
- for (const item of items) {
19063
- process.stdout.write(`${pad(displaySafeText(item.id), 18)} ${pad(String(item.sizeBytes), 8)} ${displaySafeText(item.toolName)}
19281
+ for (const item of items) {
19282
+ process.stdout.write(`${pad(displaySafeText(item.id), 18)} ${pad(String(item.sizeBytes), 8)} ${displaySafeText(item.toolName)}
19064
19283
  `);
19065
- }
19284
+ }
19285
+ });
19066
19286
  }
19067
19287
  function cmdCleanCache(opts) {
19068
19288
  const removed = {};
@@ -19296,7 +19516,7 @@ function registerSessionCommands(program2, guard) {
19296
19516
  })
19297
19517
  )
19298
19518
  );
19299
- program2.command("capabilities").description("report every capability that can send data off this machine, and its current state").option("-j, --json", "emit as JSON, for asserting on in a pipeline").action(
19519
+ program2.command("capabilities").description("report every capability that can send data off this machine, leave data on it, or listen for other processes on it, and its current state").option("-j, --json", "emit as JSON, for asserting on in a pipeline").action(
19300
19520
  guard((opts) => {
19301
19521
  const caps = collectCapabilities();
19302
19522
  console.log(opts.json ? displaySafeJson({ version: VERSION, capabilities: caps }) : renderCapabilities(caps));
@@ -19309,10 +19529,17 @@ function registerSessionCommands(program2, guard) {
19309
19529
  program2.command("audit").description("session retrospective: analyze the current session transcript (Copilot CLI or Claude Code) and emit a Maintainer Feedback Card").option("--project <path>", "project root to analyze").option("--transcript <path>", "explicit transcript path (default: active or newest session for this project)").option("-j, --json", "output as JSON").action(guard(cmdAudit));
19310
19530
  program2.command("session-outline [session-id-or-path]").description("turn-by-turn structure (role, preview, tool calls, approx size) of a Claude Code session JSONL transcript, instead of a raw Read; defaults to the current project's most recent session").option("--project <path>", "project root to resolve the session transcript against").option("--json", "output JSON").action(guard(cmdSessionOutline));
19311
19531
  program2.command("session-slice [session-id-or-path]").description("full content of one turn range from a Claude Code session JSONL transcript (see session-outline for turn numbers), instead of a raw Read").requiredOption("--range <spec>", "turn range, e.g. 5-9 or 12 (see session-outline for turn numbers)").option("--project <path>", "project root to resolve the session transcript against").option("--json", "output JSON").action(guard(cmdSessionSlice));
19312
- program2.command("session-audit").alias("audit-session").description("corpus-wide token attribution across every local Claude Code session transcript: measured billed usage, estimated content size by source and by tool, and billed cost by session position (aggregate counts only, never transcript content)").option("--dir <path>", "transcript corpus root to scan (default: ~/.claude/projects)").option("--json", "output JSON").action(guard(cmdSessionAudit));
19532
+ program2.command("session-audit").alias("audit-session").description("corpus-wide token attribution across every local Claude Code session transcript: measured billed usage, estimated content size by source and by tool, and billed cost by session position (aggregate counts only, never transcript content)").option("--dir <path>", "transcript corpus root to scan (default: ~/.claude/projects)").option("--window-days <days>", "only transcripts whose file was modified in the last N days (0 = all time)", "0").option("--tool-errors", "print only the failed-tool-call report: calls, errors, expected failures by reason and unknown ones, per tool and per model, with the top unknown error prefixes").option("--json", "output JSON").action(guard(
19533
+ (opts) => cmdSessionAudit({
19534
+ ...opts.dir !== void 0 ? { dir: opts.dir } : {},
19535
+ ...opts.json === true ? { json: true } : {},
19536
+ ...opts.toolErrors === true ? { toolErrors: true } : {},
19537
+ windowDays: requireNonNegativeInt("--window-days", opts.windowDays)
19538
+ })
19539
+ ));
19313
19540
  program2.command("mcp-audit").description("MCP server schema cost-vs-usage report: estimate per-server token cost from cached tool calls").option("--project <path>", "project root to analyze").option("--json", "output JSON").action(guard(cmdMcpAudit));
19314
19541
  program2.command("recall [query]").description("search across every cached bash-output, web-output, and mcp-output entry (full-text); with no query, list them newest-first").option("--type <type>", "filter to one cache type: bash, web, or mcp").option("--limit <n>", "max results to return (default: 10)").option("--json", "output JSON").action(guard(cmdRecall));
19315
- program2.command("hint-stats").description("per-category efficacy report for token-goat's discretionary hint hooks (emitted/acted-on/suppression)").option("--json", "output JSON").option("--reset", "clear all tracked emissions and manual marks").option("--mark-effective <category>", 'record a manual "effective" vote for a hint category').option("--mark-ineffective <category>", 'record a manual "ineffective" vote for a hint category').action(guard(cmdHintStats));
19542
+ program2.command("hint-stats").description("per-category efficacy report for token-goat's discretionary hint hooks (emitted/acted-on/suppression); all-time unless --session-id narrows it").option("--json", "output JSON").option("--session-id <id>", "scope emission counts, efficacy and spend to one session id (or `latest`); suppression and manual marks stay all-time, and saved-bytes has no per-session figure").option("--reset", "clear all tracked emissions and manual marks").option("--mark-effective <category>", 'record a manual "effective" vote for a hint category').option("--mark-ineffective <category>", 'record a manual "ineffective" vote for a hint category').action(guard(cmdHintStats));
19316
19543
  program2.command("statusline").description("render one line of terminal status text from a harness statusline payload on stdin (Claude Code statusLine.command)").option("--json", "emit the underlying data as JSON instead of a rendered line (debug)").action(guard(cmdStatusline));
19317
19544
  program2.command("skill-body <name>").description("retrieve a skill's cached body").option("-c, --compact", "print compact slice instead of full body").action(guard(cmdSkillBody));
19318
19545
  program2.command("skill-compact [name]").description("regenerate and cache compact slice for a skill").option("--path <file>", "read the skill body from this file instead of resolving by name").option("--all", "regenerate compacts for all skills, skipping fresh ones").action(guard(cmdSkillCompact));
@@ -19388,11 +19615,44 @@ function registerSessionCommands(program2, guard) {
19388
19615
  program2.command("describe [target] [table]").description("describe columns and structure of a session store table or SQLite database file instead of querying SELECT *").option("-j, --json", "output as JSON").action(guard(cmdDescribe));
19389
19616
  }
19390
19617
 
19618
+ // src/cli_hook_server.ts
19619
+ init_define_import_meta_env();
19620
+ async function cmdHookServerRun(opts) {
19621
+ const slot = Number(opts.slot ?? "0");
19622
+ const { runHookServer } = await import("./token-goat-chunk-ORNFMIER.mjs");
19623
+ try {
19624
+ await runHookServer(slot, (argv) => run(argv));
19625
+ } catch (e) {
19626
+ const message = e instanceof Error ? e.message : String(e);
19627
+ touchMarker("failed", `slot ${slot}: ${message}`);
19628
+ throw new CliError(message);
19629
+ }
19630
+ }
19631
+ function formatHookServerStatus(s, now = Date.now()) {
19632
+ return `slot ${s.slot}: pid ${s.pid}, v${s.version}, up ${formatAge(now - s.startedAt)}, served ${s.served}, errors ${s.errors}, idle ${formatAge(now - s.lastUsedAt)}`;
19633
+ }
19634
+ async function cmdHookServerStatus(opts) {
19635
+ const statuses = await serverStatuses();
19636
+ if (opts.json === true) {
19637
+ out(displaySafeJson(statuses));
19638
+ return;
19639
+ }
19640
+ if (statuses.length === 0) {
19641
+ out("No hook server is running. The next hook call starts one.");
19642
+ return;
19643
+ }
19644
+ for (const s of statuses) out(formatHookServerStatus(s));
19645
+ }
19646
+ async function cmdHookServerStop() {
19647
+ const stopped = await queryServers("stop");
19648
+ out(stopped.length === 0 ? "No hook server is running." : `Stopped ${stopped.length} hook server${stopped.length === 1 ? "" : "s"}.`);
19649
+ }
19650
+
19391
19651
  // src/bridges_status.ts
19392
19652
  init_define_import_meta_env();
19393
19653
  var NO_SERVER_HANDLER_REASON = "token-goat has no registered server-side handler for this event (zero registerHook('notification'|'stop', ...) call sites in src/) -- wiring it client-side would currently be a no-op";
19394
19654
  var NO_POST_COMPACT_EVENT_REASON = "post_compact is a Claude Code event (hook_event_name PostCompact, carrying compact_summary); no equivalent has been confirmed for this harness, so it is left unwired rather than guessed at";
19395
- var NO_SEPARATE_FAILURE_EVENT_REASON = "post_tool_use_failure exists because Copilot CLI routes a failed tool result to a separate postToolUseFailure event instead of postToolUse. No separate failure event is wired for this harness, so a failed tool call either arrives on post_tool_use like any other result or is not seen; which of the two has not been checked here";
19655
+ var NO_SEPARATE_FAILURE_EVENT_REASON = "post_tool_use_failure exists because Copilot CLI (postToolUseFailure) and Claude Code (PostToolUseFailure) route a failed tool result to a separate event instead of their post-tool one. No separate failure event is wired for this harness, so a failed tool call either arrives on post_tool_use like any other result or is not seen; which of the two has not been checked here";
19396
19656
  var COPILOT_NO_POST_COMPACT_REASON = "Copilot CLI has no post-compaction hook: its HookType enum (schemas/api.schema.json, 1.0.79 and 1.0.80) declares preCompact and no postCompact, and both preCompact call sites in app.js await the hook and never assign its result. Whether the summary is reachable another way is open. app.js does emit session.compaction_complete carrying summaryContent, and the session event writer subscribes to '*' -- but emit() vs emitEphemeral() does NOT gate that writer: dispatchEventHandlers runs outside the ephemeral branch in emitInternal, on('*') early-returns past every filter, and the writer's callback applies no filter of its own before handing the JSON to native recordEventJson. The durable-vs-ephemeral decision is in Rust (api_session_event_writer.rs) and was not readable. Nor is the event uniformly summary-bearing: two of the four emit sites are the branches taken when no summary exists, summaryContent is optional in the schema, and the relay session class emits the same event via emitEphemeral. No compaction has ever been observed on the machines checked, so nothing empirical anchors any of it. Context can be written back on the next turn through userPromptSubmitted additionalContext";
19397
19657
  var VERIFICATION_RANK = {
19398
19658
  dogfooded: 0,
@@ -19411,9 +19671,8 @@ var BRIDGE_CAPABILITY_MATRIX = [
19411
19671
  sourceFile: "src/install.ts (HOOK_EVENT_MAP)",
19412
19672
  verification: "dogfooded",
19413
19673
  verificationNote: "The harness this repo is developed and released under; CLAUDE.md requires every CLI/hook change be run against the built binary here before it ships.",
19414
- implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact", "post_compact", "user_prompt_submit", "subagent_stop", "session_start"]),
19674
+ implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "post_tool_use_failure", "pre_compact", "post_compact", "user_prompt_submit", "subagent_stop", "session_start"]),
19415
19675
  reasons: [
19416
- { events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
19417
19676
  { events: ["notification", "stop"], reason: NO_SERVER_HANDLER_REASON }
19418
19677
  ]
19419
19678
  },
@@ -19450,55 +19709,7 @@ var BRIDGE_CAPABILITY_MATRIX = [
19450
19709
  }
19451
19710
  ]
19452
19711
  },
19453
- /**
19454
- * What Copilot CLI's wired events can actually carry, read from the shipping 1.0.80 bundle
19455
- * rather than from its hooks reference page. A row's `implemented` set says an event is wired;
19456
- * it deliberately says nothing about how much of that event's response the harness honors, and
19457
- * for Copilot the difference matters:
19458
- *
19459
- * - `pre_tool_use` deny is real, and the reason text reaches the model. The native string table
19460
- * (runtime.node offset 98057208) carries `permissionDecision`, `permissionDecisionReason`, the
19461
- * default "No reason provided." and the formatter "Denied by preToolUse hook: " as distinct
19462
- * literals, and app.js sets `{textResultForLlm: reason, resultType: "denied"}` per denial and
19463
- * filters that call out of execution. This was previously only an inference drawn from an
19464
- * incident comment; it is now confirmed. The fail-closed "(hook errored)" deny-all strings sit
19465
- * beside these, so that incident was behavior layered on top of a working deny handler, not a
19466
- * substitute for one.
19467
- * - `post_tool_use` honors `modifiedResult`, so compression, injection fencing and image shrink
19468
- * do reach the model: `postToolExecution` (app.js offset 2043150) assigns the returned
19469
- * `toolResultJson` onto the tool result in place.
19470
- * - `post_tool_use` drops `additionalContext` on the JS path -- no supplier for the
19471
- * `onAdditionalContext` callback anywhere in the bundle, and no `additional_contexts` key in
19472
- * that event's native return payload, unlike its pre-tool sibling. Whether native folds it
19473
- * into the returned result instead was not verified.
19474
- * - Failed tool calls never reach `post_tool_use` at all. Copilot routes them to a separate
19475
- * `postToolUseFailure` event, which is handed only a stringified error and honors only
19476
- * `additionalContext`; `modifiedResult` is documented as not honored there. So on Copilot the
19477
- * output of a failed tool call reaches the model unfenced, uncompressed and unshrunk, and no
19478
- * response shape token-goat's shim can emit changes that. Known gap, deliberately recorded.
19479
- * What `additionalContext` does on that event is no longer doc-derived: app.js offset 2043380
19480
- * reads the native processor's return and, when it carries `additionalContext`, either folds
19481
- * it into `textResultForLlm` (if `appendFailureContextToToolResult` is set) or formats it and
19482
- * pushes `{content, source: "system"}` onto `toolResult.newMessages`. So the channel is real
19483
- * and model-visible -- it just carries advice alongside the failure, never a replacement for
19484
- * it. It is wired now, as its own `post_tool_use_failure` event rather than a reuse of
19485
- * `post_tool_use`: routing failures through the success event would let success-path handlers
19486
- * mark a file as successfully read when the read failed. Because the channel spends tokens
19487
- * instead of saving them, its handler (`src/hooks_tool_failure.ts`) stays silent on a first
19488
- * failure and speaks only on an exact repeat.
19489
- * - `pre_compact` is wired and fires, but nothing it returns can reach the model. Both dispatch
19490
- * sites call it in statement position and drop the result: app.js offset 2467452 (the manual
19491
- * `/compact` path) and offset 2571216 (`case "execute_pre_compact_hook"`). The contrast is
19492
- * what makes this conclusive rather than an absence of evidence -- the same `event()` runner's
19493
- * return IS read for other events, e.g. `notification` at offset 2296123 assigns it and
19494
- * forwards `additionalContext` as a prepended system prompt. The compaction prompt is then
19495
- * built from session history and the user's own focus text, with no hook channel into it.
19496
- * So token-goat's `pre_compact` registration here is observe-only by construction, not merely
19497
- * unused: a future attempt to inject a session manifest through it would silently do nothing.
19498
- * Not verified: whether the native declarative runner applies `additionalContext` itself. The
19499
- * trail ends in stripped Rust. That it would make the explicit JS handling for `notification`
19500
- * and `userPromptSubmitted` redundant is an argument, not a proof.
19501
- */
19712
+ /** What Copilot CLI's wired events can actually carry, read from the shipping 1.0.80 bundle rather than from its hooks reference page. A row's `implemented` set says an event is wired; it deliberately says nothing about how much of that event's response the harness honors, and for Copilot the difference matters: - `pre_tool_use` deny is real, and the reason text reaches the model. The native string table (runtime.node offset 98057208) carries `permissionDecision`, `permissionDecisionReason`, the default "No reason provided." and the formatter "Denied by preToolUse hook: " as distinct literals, and app.js sets `{textResultForLlm: reason, resultType: "denied"}` per denial and filters that call out of execution. This was previously only an inference drawn from an incident comment; it is now confirmed. The fail-closed "(hook errored)" deny-all strings sit beside these, so that incident was behavior layered on top of a working deny handler, not a substitute for one. - `post_tool_use` honors `modifiedResult`, so compression, injection fencing and image shrink do reach the model: `postToolExecution` (app.js offset 2043150) assigns the returned `toolResultJson` onto the tool result in place. - `post_tool_use` drops `additionalContext` on the JS path -- no supplier for the `onAdditionalContext` callback anywhere in the bundle, and no `additional_contexts` key in that event's native return payload, unlike its pre-tool sibling. Whether native folds it into the returned result instead was not verified. - Failed tool calls never reach `post_tool_use` at all. Copilot routes them to a separate `postToolUseFailure` event, which is handed only a stringified error and honors only `additionalContext`; `modifiedResult` is documented as not honored there. So on Copilot the output of a failed tool call reaches the model unfenced, uncompressed and unshrunk, and no response shape token-goat's shim can emit changes that. Known gap, deliberately recorded. What `additionalContext` does on that event is no longer doc-derived: app.js offset 2043380 reads the native processor's return and, when it carries `additionalContext`, either folds it into `textResultForLlm` (if `appendFailureContextToToolResult` is set) or formats it and pushes `{content, source: "system"}` onto `toolResult.newMessages`. So the channel is real and model-visible -- it just carries advice alongside the failure, never a replacement for it. It is wired now, as its own `post_tool_use_failure` event rather than a reuse of `post_tool_use`: routing failures through the success event would let success-path handlers mark a file as successfully read when the read failed. Because the channel spends tokens instead of saving them, its handler (`src/hooks_tool_failure.ts`) stays silent on a first failure and speaks only on an exact repeat. - `pre_compact` is wired and fires, but nothing it returns can reach the model. Both dispatch sites call it in statement position and drop the result: app.js offset 2467452 (the manual `/compact` path) and offset 2571216 (`case "execute_pre_compact_hook"`). The contrast is what makes this conclusive rather than an absence of evidence -- the same `event()` runner's return IS read for other events, e.g. `notification` at offset 2296123 assigns it and forwards `additionalContext` as a prepended system prompt. The compaction prompt is then built from session history and the user's own focus text, with no hook channel into it. So token-goat's `pre_compact` registration here is observe-only by construction, not merely unused: a future attempt to inject a session manifest through it would silently do nothing. Not verified: whether the native declarative runner applies `additionalContext` itself. The trail ends in stripped Rust. That it would make the explicit JS handling for `notification` and `userPromptSubmitted` redundant is an argument, not a proof. */
19502
19713
  {
19503
19714
  harness: "copilot_cli",
19504
19715
  label: "Copilot CLI",
@@ -19723,25 +19934,25 @@ function installVerificationNotice(harness) {
19723
19934
 
19724
19935
  // src/purge.ts
19725
19936
  init_define_import_meta_env();
19726
- import * as fs43 from "node:fs";
19727
- import * as path39 from "node:path";
19937
+ import * as fs42 from "node:fs";
19938
+ import * as path38 from "node:path";
19728
19939
  function directorySize(root) {
19729
19940
  let total = 0;
19730
19941
  let entries;
19731
19942
  try {
19732
- entries = fs43.readdirSync(root, { withFileTypes: true });
19943
+ entries = fs42.readdirSync(root, { withFileTypes: true });
19733
19944
  } catch {
19734
19945
  return 0;
19735
19946
  }
19736
19947
  for (const entry of entries) {
19737
- const full = path39.join(root, entry.name);
19948
+ const full = path38.join(root, entry.name);
19738
19949
  if (entry.isDirectory()) {
19739
19950
  total += directorySize(full);
19740
19951
  continue;
19741
19952
  }
19742
19953
  if (!entry.isFile()) continue;
19743
19954
  try {
19744
- total += fs43.statSync(full).size;
19955
+ total += fs42.statSync(full).size;
19745
19956
  } catch {
19746
19957
  }
19747
19958
  }
@@ -19760,7 +19971,7 @@ function purgeDataDirectories() {
19760
19971
  }
19761
19972
  const bytes = directorySize(root);
19762
19973
  try {
19763
- fs43.rmSync(root, { recursive: true, force: true });
19974
+ fs42.rmSync(root, { recursive: true, force: true });
19764
19975
  result.removed.push({ path: root, bytes });
19765
19976
  } catch (e) {
19766
19977
  result.failed.push({ path: root, reason: e instanceof Error ? e.message : String(e) });
@@ -19907,8 +20118,8 @@ async function getSectionContent(fileId, heading, opts = {}) {
19907
20118
 
19908
20119
  // src/cli_bench.ts
19909
20120
  init_define_import_meta_env();
19910
- import * as fs44 from "node:fs";
19911
- import * as path40 from "node:path";
20121
+ import * as fs43 from "node:fs";
20122
+ import * as path39 from "node:path";
19912
20123
  var ControlFilter = class extends ToolFilter {
19913
20124
  constructor(name, transform) {
19914
20125
  super();
@@ -19929,13 +20140,13 @@ var CONTROL_FILTERS = {
19929
20140
  };
19930
20141
  var MAX_BENCH_FILE_BYTES = 5 * 1024 * 1024;
19931
20142
  function readCaseFile(id, filePath, what) {
19932
- const bytes = fs44.statSync(filePath).size;
20143
+ const bytes = fs43.statSync(filePath).size;
19933
20144
  if (bytes > MAX_BENCH_FILE_BYTES) {
19934
20145
  throw new Error(
19935
20146
  `bench case ${id}: ${what} is ${bytes} bytes, over the ${MAX_BENCH_FILE_BYTES}-byte limit for a corpus file (${filePath})`
19936
20147
  );
19937
20148
  }
19938
- return fs44.readFileSync(filePath, "utf8");
20149
+ return fs43.readFileSync(filePath, "utf8");
19939
20150
  }
19940
20151
  function parseCase(id, metaPath, outputPath) {
19941
20152
  const metaText = readCaseFile(id, metaPath, "metadata");
@@ -19970,7 +20181,7 @@ function parseCase(id, metaPath, outputPath) {
19970
20181
  function loadCorpus(dir) {
19971
20182
  let entries;
19972
20183
  try {
19973
- entries = fs44.readdirSync(dir);
20184
+ entries = fs43.readdirSync(dir);
19974
20185
  } catch {
19975
20186
  throw new Error(`bench corpus not found: ${dir}`);
19976
20187
  }
@@ -19978,9 +20189,9 @@ function loadCorpus(dir) {
19978
20189
  for (const entry of entries.sort()) {
19979
20190
  if (!entry.endsWith(".json")) continue;
19980
20191
  const id = entry.slice(0, -".json".length);
19981
- const outputPath = path40.join(dir, `${id}.txt`);
19982
- if (!fs44.existsSync(outputPath)) throw new Error(`bench case ${id}: missing output file ${outputPath}`);
19983
- cases.push(parseCase(id, path40.join(dir, entry), outputPath));
20192
+ const outputPath = path39.join(dir, `${id}.txt`);
20193
+ if (!fs43.existsSync(outputPath)) throw new Error(`bench case ${id}: missing output file ${outputPath}`);
20194
+ cases.push(parseCase(id, path39.join(dir, entry), outputPath));
19984
20195
  }
19985
20196
  if (cases.length === 0) throw new Error(`bench corpus is empty: ${dir}`);
19986
20197
  return cases;
@@ -20100,10 +20311,10 @@ function appendTsv(file, report, commit) {
20100
20311
  report.mustKeepTotal,
20101
20312
  report.coveredFilters
20102
20313
  ].join(" ");
20103
- const dir = path40.dirname(path40.resolve(file));
20104
- fs44.mkdirSync(dir, { recursive: true });
20105
- const header = fs44.existsSync(file) ? "" : TSV_HEADER;
20106
- fs44.appendFileSync(file, header + row + "\n", "utf8");
20314
+ const dir = path39.dirname(path39.resolve(file));
20315
+ fs43.mkdirSync(dir, { recursive: true });
20316
+ const header = fs43.existsSync(file) ? "" : TSV_HEADER;
20317
+ fs43.appendFileSync(file, header + row + "\n", "utf8");
20107
20318
  }
20108
20319
  function currentCommit(cwd) {
20109
20320
  const opts = cwd === void 0 ? {} : { cwd };
@@ -20130,7 +20341,16 @@ function runBenchCommand(opts) {
20130
20341
 
20131
20342
  // src/cli.ts
20132
20343
  var CliError = class extends Error {
20344
+ lines;
20345
+ constructor(message) {
20346
+ super(typeof message === "string" ? message : message.join("\n"));
20347
+ this.lines = typeof message === "string" ? void 0 : message;
20348
+ }
20133
20349
  };
20350
+ function formatCommandError(e) {
20351
+ if (e instanceof CliError && e.lines !== void 0) return "token-goat: " + e.lines.map(displaySafeText).join("\n");
20352
+ return "token-goat: " + displaySafeText(extractErrorMessage(e));
20353
+ }
20134
20354
  function out(text) {
20135
20355
  const payload = colorStdout() ? text : stripAnsiEscapes(text);
20136
20356
  process.stdout.write(ensureNewline(payload));
@@ -20143,10 +20363,10 @@ function readBoundedText(text, file) {
20143
20363
  if (text === void 0 && file === void 0) throw new CliError("provide text or --file");
20144
20364
  let value;
20145
20365
  if (file !== void 0) {
20146
- if (fs45.statSync(file).size > CONTENT_MAX_INPUT_CHARS) {
20366
+ if (fs44.statSync(file).size > CONTENT_MAX_INPUT_CHARS) {
20147
20367
  throw new CliError(`file exceeds the ${CONTENT_MAX_INPUT_CHARS}-byte safety limit`);
20148
20368
  }
20149
- value = fs45.readFileSync(file, "utf8");
20369
+ value = fs44.readFileSync(file, "utf8");
20150
20370
  } else {
20151
20371
  if (text === void 0) throw new CliError("provide text or --file");
20152
20372
  value = text;
@@ -20191,19 +20411,23 @@ function cmdHandoffResolve(name, opts) {
20191
20411
  if (result === null) throw new CliError(`no local handoff named "${name}" in this project`);
20192
20412
  out(typeof result === "string" ? result : formatCompression(result, false, "payload withheld: inlining it would cost more tokens than the original text; pass --full to get the original text back outright"));
20193
20413
  }
20194
- async function cmdSemantic(query, opts) {
20414
+ async function cmdSemantic(query, more, opts) {
20195
20415
  if (!query && !opts.preflight && !opts.warm) {
20196
20416
  throw new CliError("missing required argument: query");
20197
20417
  }
20418
+ if (opts.preflight === true && more.length > 0) {
20419
+ throw new CliError("--preflight checks the embedding setup and runs no query; drop the queries, or drop --preflight to search");
20420
+ }
20198
20421
  const limit = opts.limit !== void 0 ? requireNonNegativeInt("--limit", opts.limit) : 20;
20199
- const { text, code } = await runSemantic(query ?? "", {
20422
+ const semanticOpts = {
20200
20423
  limit,
20201
20424
  ...opts.json === true ? { json: true } : {},
20202
20425
  ...opts.grep !== void 0 ? { grep: opts.grep } : {},
20203
20426
  ...opts.excludeTests === true ? { excludeTests: true } : {},
20204
20427
  ...opts.preflight === true ? { preflight: true } : {},
20205
20428
  ...opts.warm === true ? { warm: true } : {}
20206
- });
20429
+ };
20430
+ const { text, code } = more.length > 0 ? await runSemanticMulti([query ?? "", ...more], semanticOpts) : await runSemantic(query ?? "", semanticOpts);
20207
20431
  (opts.json === true || code === 0 ? out : err)(text);
20208
20432
  process.exitCode = code;
20209
20433
  }
@@ -20269,7 +20493,7 @@ async function cmdIndex(pathArg, opts = {}) {
20269
20493
  continue;
20270
20494
  }
20271
20495
  const sha = fingerprintFile(key);
20272
- if (sha === null && !fs45.existsSync(key)) continue;
20496
+ if (sha === null && !fs44.existsSync(key)) continue;
20273
20497
  recordKnownRootThrottled(key, dataDir(), dbPath);
20274
20498
  const entry = sha !== null ? getFileEntry(key, dbPath) : null;
20275
20499
  const spellingStale = entry !== null && indexedPathSpellingIsStale(entry.filePath, key);
@@ -20306,7 +20530,7 @@ async function cmdIndex(pathArg, opts = {}) {
20306
20530
  }
20307
20531
  continue;
20308
20532
  }
20309
- if (!fs45.existsSync(key)) continue;
20533
+ if (!fs44.existsSync(key)) continue;
20310
20534
  }
20311
20535
  const embedFresh = parseUnchanged ? embedUnchanged : embedFreshFor(getFileEntry(key, dbPath)?.embedSha);
20312
20536
  if (!embedFresh) {
@@ -20367,7 +20591,7 @@ async function cmdMcpServe() {
20367
20591
  let StdioServerTransport;
20368
20592
  try {
20369
20593
  ;
20370
- ({ createMcpServer } = await import("./token-goat-chunk-5BQ7W7D7.mjs"));
20594
+ ({ createMcpServer } = await import("./token-goat-chunk-PKRWMHVP.mjs"));
20371
20595
  ({ StdioServerTransport } = await import("./token-goat-chunk-EVC4TOLE.mjs"));
20372
20596
  } catch (err2) {
20373
20597
  process.stderr.write(
@@ -20392,11 +20616,11 @@ async function cmdHook(event, opts) {
20392
20616
  if (typeof opts.harness === "string" && opts.harness.length > 0) {
20393
20617
  process.env[ENV_KEYS.HARNESS_OVERRIDE] = opts.harness;
20394
20618
  }
20395
- const { relay } = await import("./token-goat-chunk-ADMR3QVF.mjs");
20619
+ const { relay } = await import("./token-goat-chunk-Z6KBMOBI.mjs");
20396
20620
  await relay(event);
20397
20621
  }
20398
20622
  function projectHooksCommitNote(pathFiles, hooksConfigPath) {
20399
- const shim = hooksConfigPath === void 0 ? "" : ` (${displaySafePath(path41.join(path41.dirname(hooksConfigPath), HOOKS_SCRIPT_FILE))} is generated with them)`;
20623
+ const shim = hooksConfigPath === void 0 ? "" : ` (${displaySafePath(path40.join(path40.dirname(hooksConfigPath), HOOKS_SCRIPT_FILE))} is generated with them)`;
20400
20624
  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.`;
20401
20625
  }
20402
20626
  function visualStudioManualSteps(scope) {
@@ -20429,7 +20653,7 @@ async function cmdInstall(opts) {
20429
20653
  if (opts.project === true && opts.user === true) {
20430
20654
  throw new Error("install takes either -p/--project or --user, not both.");
20431
20655
  }
20432
- await import("./token-goat-chunk-ADMR3QVF.mjs");
20656
+ await import("./token-goat-chunk-Z6KBMOBI.mjs");
20433
20657
  const scope = opts.project === true ? "project" : "user";
20434
20658
  if (wantsClaudeCodeBase(opts)) {
20435
20659
  const result = installHooks(scope);
@@ -20575,16 +20799,16 @@ async function cmdInstall(opts) {
20575
20799
  );
20576
20800
  }
20577
20801
  try {
20578
- const skillDir2 = path41.join(claudeConfigDir(), "skills");
20579
- if (fs45.existsSync(skillDir2)) {
20580
- const entries = fs45.readdirSync(skillDir2, { withFileTypes: true });
20802
+ const skillDir2 = path40.join(claudeConfigDir(), "skills");
20803
+ if (fs44.existsSync(skillDir2)) {
20804
+ const entries = fs44.readdirSync(skillDir2, { withFileTypes: true });
20581
20805
  const skillNames = [];
20582
20806
  const sessionId = getSessionId();
20583
20807
  for (const entry of entries) {
20584
20808
  if (!entry.isDirectory()) continue;
20585
- const skillFile = path41.join(skillDir2, entry.name, "SKILL.md");
20586
- if (fs45.existsSync(skillFile)) {
20587
- const body = fs45.readFileSync(skillFile, "utf-8");
20809
+ const skillFile = path40.join(skillDir2, entry.name, "SKILL.md");
20810
+ if (fs44.existsSync(skillFile)) {
20811
+ const body = fs44.readFileSync(skillFile, "utf-8");
20588
20812
  const compact = extractCompactFromMarker(body);
20589
20813
  if (compact === null) continue;
20590
20814
  const sourceSha = contentHash(body);
@@ -20595,9 +20819,9 @@ async function cmdInstall(opts) {
20595
20819
  if (skillNames.length > 0) {
20596
20820
  const dir = skillOutputsDir();
20597
20821
  ensureDirSync(dir);
20598
- const pregenPath = path41.join(dir, "pregen.json");
20822
+ const pregenPath = path40.join(dir, "pregen.json");
20599
20823
  const pregenData = { ts: Date.now(), names: skillNames };
20600
- await fs45.promises.writeFile(pregenPath, JSON.stringify(pregenData, null, 2));
20824
+ await fs44.promises.writeFile(pregenPath, JSON.stringify(pregenData, null, 2));
20601
20825
  out(`Pre-generated ${skillNames.length} skill compacts.`);
20602
20826
  }
20603
20827
  }
@@ -20611,7 +20835,7 @@ function cmdMcpStatus(opts) {
20611
20835
  const scope = opts.project === true ? { projectRoot: process.cwd() } : {};
20612
20836
  out(displaySafeJson(opts.vscode === true ? vscodeDecoderConfigured(scope) : visualStudioMcpStatus(scope), 0));
20613
20837
  }
20614
- function cmdUninstall(opts) {
20838
+ async function cmdUninstall(opts) {
20615
20839
  if (opts.project === true && opts.user === true) {
20616
20840
  throw new Error("uninstall takes either -p/--project or --user, not both.");
20617
20841
  }
@@ -20661,7 +20885,7 @@ function cmdUninstall(opts) {
20661
20885
  if (opts.hermes === true) {
20662
20886
  out("No separate Hermes integration to remove (it shares the Claude Code hook entries).");
20663
20887
  }
20664
- if (opts.purge === true) runPurge();
20888
+ if (opts.purge === true) await runPurge();
20665
20889
  }
20666
20890
  function leftoverIntegrations(opts) {
20667
20891
  const candidates = [
@@ -20703,7 +20927,8 @@ function leftoverIntegrations(opts) {
20703
20927
  }
20704
20928
  return found;
20705
20929
  }
20706
- function runPurge() {
20930
+ async function runPurge() {
20931
+ await queryServers("stop");
20707
20932
  if (isWorkerRunning()) {
20708
20933
  err('token-goat: the background worker is running, so --purge would delete files it is about to rewrite. Run "token-goat worker stop" first.');
20709
20934
  return;
@@ -20764,7 +20989,7 @@ async function cmdDoctor(opts) {
20764
20989
  doctorOpts.rootDir = project.root;
20765
20990
  }
20766
20991
  if (opts.json === true) {
20767
- const results = runDoctor(doctorOpts.dataDir, doctorOpts.configPath, doctorOpts.rootDir);
20992
+ const results = await runDoctorChecks(doctorOpts.dataDir, doctorOpts.configPath, doctorOpts.rootDir);
20768
20993
  out(displaySafeJson(results, 0));
20769
20994
  if (results.some((r) => r.status === "fail")) {
20770
20995
  throw new CliError("doctor checks failed");
@@ -20860,7 +21085,7 @@ function cmdBashOutput(id, opts) {
20860
21085
  }
20861
21086
  let content;
20862
21087
  try {
20863
- const st = fs45.statSync(opts.file);
21088
+ const st = fs44.statSync(opts.file);
20864
21089
  if (st.isFIFO() || st.isSocket()) {
20865
21090
  throw new CliError(`--file '${opts.file}' is a special file (FIFO or socket) \u2014 only regular files are supported`);
20866
21091
  }
@@ -20875,7 +21100,7 @@ function cmdBashOutput(id, opts) {
20875
21100
  `);
20876
21101
  }
20877
21102
  }
20878
- content = redactIfDotenv(opts.file, decodeSource(fs45.readFileSync(opts.file)));
21103
+ content = redactIfDotenv(opts.file, decodeSource(fs44.readFileSync(opts.file)));
20879
21104
  } catch (e) {
20880
21105
  if (e instanceof CliError) throw e;
20881
21106
  throw new CliError(`cannot read file: ${opts.file}`);
@@ -20939,15 +21164,15 @@ function cmdMcpOutput(id, opts) {
20939
21164
  if (opts.file.includes("\0")) {
20940
21165
  throw new CliError("--file path contains a null byte");
20941
21166
  }
20942
- if (!fs45.existsSync(opts.file)) {
21167
+ if (!fs44.existsSync(opts.file)) {
20943
21168
  throw new CliError(`file not found: ${opts.file}`);
20944
21169
  }
20945
21170
  try {
20946
- const st = fs45.statSync(opts.file);
21171
+ const st = fs44.statSync(opts.file);
20947
21172
  if (st.isFIFO() || st.isSocket()) {
20948
21173
  throw new CliError(`--file '${opts.file}' is a special file (FIFO or socket) \u2014 only regular files are supported`);
20949
21174
  }
20950
- content = decodeSource(fs45.readFileSync(opts.file));
21175
+ content = decodeSource(fs44.readFileSync(opts.file));
20951
21176
  } catch (e) {
20952
21177
  if (e instanceof CliError) throw e;
20953
21178
  throw new CliError(`cannot read file: ${opts.file}`);
@@ -21020,9 +21245,14 @@ async function cmdCompress(commandArgs, opts = {}) {
21020
21245
  process.exitCode = 1;
21021
21246
  return;
21022
21247
  }
21023
- const bashRunner = await import("./token-goat-chunk-E6B2UFDF.mjs");
21248
+ const bashRunner = await import("./token-goat-chunk-VQ6F6T7X.mjs");
21024
21249
  if (opts.compress === false) {
21025
- process.exitCode = bashRunner.runRaw(command, parseTimeout(opts.timeout, bashRunner.DEFAULT_TIMEOUT_SECONDS));
21250
+ process.exitCode = bashRunner.runRaw(
21251
+ command,
21252
+ parseTimeout(opts.timeout, bashRunner.DEFAULT_TIMEOUT_SECONDS),
21253
+ opts.native,
21254
+ opts.shell
21255
+ );
21026
21256
  return;
21027
21257
  }
21028
21258
  const maxTokens = opts.maxTokens !== void 0 ? requireNonNegativeInt("--max-tokens", opts.maxTokens) : 0;
@@ -21030,12 +21260,14 @@ async function cmdCompress(commandArgs, opts = {}) {
21030
21260
  filterName: opts.filter,
21031
21261
  timeout: parseTimeout(opts.timeout, bashRunner.DEFAULT_TIMEOUT_SECONDS),
21032
21262
  maxTokens,
21263
+ ...opts.capHintB64 !== void 0 ? { capHint: Buffer.from(opts.capHintB64, "base64").toString("utf8") } : {},
21033
21264
  ...opts.profile !== void 0 ? { compressionProfile: opts.profile } : {},
21034
21265
  ...opts.quietSuccess === true ? { quietSuccess: true } : {},
21035
- ...opts.native === true ? { nativeShell: true } : {}
21266
+ ...opts.native === true ? { nativeShell: true } : {},
21267
+ ...opts.shell !== void 0 ? { shellType: opts.shell } : {}
21036
21268
  });
21037
21269
  } catch (e) {
21038
- err(`token-goat: ${displaySafeText(extractErrorMessage(e))}`);
21270
+ err(formatCommandError(e));
21039
21271
  process.exitCode = 1;
21040
21272
  }
21041
21273
  }
@@ -21067,11 +21299,41 @@ ${content}`;
21067
21299
  const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(toEmit, "utf8"));
21068
21300
  recordStat("gdrive_sections", bytesSaved, savedTokensFromBytes(bytesSaved));
21069
21301
  }
21302
+ var READ_ONLY_INDEX_COMMANDS = /* @__PURE__ */ new Set([
21303
+ "symbol",
21304
+ "read",
21305
+ "brief",
21306
+ "section",
21307
+ "semantic",
21308
+ "skeleton",
21309
+ "outline",
21310
+ "refs",
21311
+ "answer",
21312
+ "ask",
21313
+ "map",
21314
+ "exports",
21315
+ "imports",
21316
+ "find",
21317
+ "locate",
21318
+ "callers",
21319
+ "call-chain",
21320
+ "impact",
21321
+ "dead",
21322
+ "deps",
21323
+ "types",
21324
+ "scope",
21325
+ "similar",
21326
+ "context-for",
21327
+ "test-for"
21328
+ ]);
21070
21329
  var originalHelpInformation = null;
21071
21330
  function buildProgram() {
21072
21331
  const program2 = new Command();
21073
21332
  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");
21074
- program2.hook("preAction", (thisCommand) => {
21333
+ program2.hook("preAction", (thisCommand, actionCommand) => {
21334
+ if (actionCommand.parent === program2 && READ_ONLY_INDEX_COMMANDS.has(actionCommand.name())) {
21335
+ allowReadOnlyIndex(() => err("token-goat: the index database cannot be written here, so this run reads it without writing: no stats are recorded and changed files are not reindexed."));
21336
+ }
21075
21337
  const opts = thisCommand.opts();
21076
21338
  const cwdOverride = opts.cwd;
21077
21339
  if (cwdOverride !== void 0) {
@@ -21106,8 +21368,7 @@ function buildProgram() {
21106
21368
  process.exitCode = 0;
21107
21369
  }
21108
21370
  } catch (e) {
21109
- const msg = extractErrorMessage(e);
21110
- err(`token-goat: ${displaySafeText(msg)}`);
21371
+ err(formatCommandError(e));
21111
21372
  process.exitCode = 1;
21112
21373
  }
21113
21374
  };
@@ -21196,7 +21457,7 @@ function buildProgram() {
21196
21457
  )
21197
21458
  );
21198
21459
  });
21199
- 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));
21460
+ program2.command("semantic [query] [more...]").description("semantic search (falls back to full-text search); several queries run in one call, one headed block per query").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));
21200
21461
  const registerSymbolListing = (name, description, run2) => {
21201
21462
  program2.command(`${name} <file> [more...]`).description(description).option("-j, --json", "output as JSON").option("--min-lines <n>", "only show symbols at least N lines long").option("--grep <pattern>", "only show symbols whose name matches this regex (literal substring if it is not valid regex)").option("--force-refresh", "reparse file from disk before querying (ignore stale index)").option("--stats", "add per-symbol reference count and doc-coverage flag").action(
21202
21463
  (file, more, opts) => runExitText(
@@ -21252,7 +21513,7 @@ function buildProgram() {
21252
21513
  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(
21253
21514
  (opts) => runExitText(
21254
21515
  () => runBenchCommand({
21255
- corpus: opts.corpus ?? path41.join("tests", "fixtures", "bench"),
21516
+ corpus: opts.corpus ?? path40.join("tests", "fixtures", "bench"),
21256
21517
  ...opts.tsv !== void 0 ? { tsv: opts.tsv } : {},
21257
21518
  ...opts.validate === true ? { validate: true } : {},
21258
21519
  ...opts.json === true ? { json: true } : {}
@@ -21264,13 +21525,17 @@ function buildProgram() {
21264
21525
  program2.command("handoff-create <name> [text]").description("create a project-local named compressed handoff").option("--file <path>", "read handoff text from a local file instead of the argument").action(guard(cmdHandoffCreate));
21265
21526
  program2.command("handoff-resolve <name>").description("resolve a project-local handoff compactly, or return it in full").option("--full", "return the full handoff text").action(guard(cmdHandoffResolve));
21266
21527
  program2.command("hook <event>").description("hook relay entrypoint (reads JSON on stdin)").option("--harness <name>", "override harness detection for this invocation (sets TOKEN_GOAT_HARNESS_OVERRIDE)").action(guard(cmdHook));
21267
- program2.command("install").description("install hooks into Claude Code settings").option("-p, --project", "install into project scope instead of user scope").option("--user", "with --vscode, install into user scope instead of this project (every project at once, but nothing past the first folder of a multi-root workspace)").option("--codex", "also patch Codex CLI (~/.codex/config.toml, ~/.codex/AGENTS.md)").option("--gemini", "also patch Gemini CLI (~/.gemini/settings.json)").option("--qwen", "also patch Qwen Code (~/.qwen/settings.json)").option("--kimi", "also register a Kimi Code hook config, shim, instructions block and skill ($KIMI_CODE_HOME or ~/.kimi-code: config.toml, hooks/token-goat-shim.js, AGENTS.md, skills/token-goat/SKILL.md)").option("--pi", "also drop a pi (pi-coding-agent) extension (~/.pi/agent/extensions/token-goat.ts)").option("--opencode", "also drop an opencode plugin (~/.config/opencode/plugins/token-goat.ts, %APPDATA%\\opencode\\plugins\\token-goat.ts on Windows)").option("--hermes", "verify token-goat hooks are present for Hermes Agent (writes nothing new)").option("--openclaw", "also register an OpenClaw plugin (~/.openclaw/openclaw.json, ~/.openclaw/plugins/token-goat.ts)").option("--copilot", "also register a Copilot CLI hook config and routing block (~/.copilot/hooks/token-goat.json, ~/.copilot/hooks/token-goat-shim.js, ~/.copilot/copilot-instructions.md; with --local, <project>/.github/hooks/token-goat.json, <project>/.github/hooks/token-goat-shim.js, <project>/.github/copilot-instructions.md)").option("--grok", "also register a Grok CLI (xAI Grok Build) hook config (~/.grok/hooks/token-goat.json, ~/.grok/hooks/token-goat-shim.js)").option("--vscode", "also configure a VS Code MCP server (the workspace .vscode/mcp.json by default; --user for the user-profile mcp.json) and Copilot routing guidance").option("--visualstudio", "also configure a Visual Studio (2022 17.14+ / 2026) Copilot MCP server and routing guidance, no hooks (%USERPROFILE%\\.mcp.json and %USERPROFILE%\\copilot-instructions.md; -p/--project for <project>/.mcp.json and <project>/.github/copilot-instructions.md)").option("--zed", "also register token-goat as a Zed MCP context server (%APPDATA%\\Zed\\settings.json on Windows, ~/.config/zed/settings.json elsewhere, plus a generated shim script); Zed has no hooks API, so this is user scope only, no -p/--project support").option("--cursor", 'also register a Cursor MCP server (~/.cursor/mcp.json by default; -p/--project for <project>/.cursor/mcp.json); writes no Cursor hooks config -- Cursor already imports the Claude Code hooks "token-goat install" writes to ~/.claude/settings.json').option("--local", "with --pi, install the project-local extension (<project>/.pi/extensions/token-goat.ts) instead of the global one").action(guard(cmdInstall));
21268
- program2.command("uninstall").description("remove token-goat hooks from Claude Code settings").option("-p, --project", "uninstall from project scope instead of user scope").option("--user", "with --vscode, remove the user-scope install instead of this project one").option("--codex", "also strip the Codex CLI integration (~/.codex/config.toml, ~/.codex/AGENTS.md)").option("--gemini", "also strip the Gemini CLI integration (~/.gemini/settings.json)").option("--qwen", "also strip the Qwen Code integration (~/.qwen/settings.json)").option("--kimi", "also strip the Kimi Code integration (config.toml hooks, hooks/token-goat-shim.js, the AGENTS.md block and skills/token-goat under $KIMI_CODE_HOME or ~/.kimi-code)").option("--pi", "also remove the pi (pi-coding-agent) extension").option("--opencode", "also remove the opencode plugin").option("--hermes", "no-op verification flag for symmetry with install (removes no files)").option("--openclaw", "also remove the OpenClaw plugin and config entry").option("--copilot", "also remove the Copilot CLI hook config and shim script, and strip the token-goat block from ~/.copilot/copilot-instructions.md (or <project>/.github/copilot-instructions.md with --local)").option("--grok", "also remove the Grok CLI hook config and shim script").option("--vscode", "also remove the VS Code MCP server (project scope by default; --user for the user-profile one) and routing guidance").option("--visualstudio", "also remove the Visual Studio MCP server entry and routing guidance (user scope by default; -p/--project for the project one)").option("--zed", "also remove the Zed MCP context server entry and its generated shim script").option("--cursor", "also remove the Cursor MCP server entry (user scope by default; -p/--project for the project one)").option("--local", "with --pi, remove the project-local extension instead of the global one").option("--purge", "also delete the data directories (index, caches, session state, logs); refuses while the worker is running").action(guard(cmdUninstall));
21528
+ program2.command("install").description("install hooks into Claude Code settings, or with harness flags into those harnesses instead").option("-p, --project", "install into project scope instead of user scope").option("--user", "with --vscode, install into user scope instead of this project (every project at once, but nothing past the first folder of a multi-root workspace)").option("--codex", "patch Codex CLI (~/.codex/config.toml, ~/.codex/AGENTS.md)").option("--gemini", "patch Gemini CLI (~/.gemini/settings.json)").option("--qwen", "patch Qwen Code (~/.qwen/settings.json)").option("--kimi", "register a Kimi Code hook config, shim, instructions block and skill ($KIMI_CODE_HOME or ~/.kimi-code: config.toml, hooks/token-goat-shim.cjs, AGENTS.md, skills/token-goat/SKILL.md)").option("--pi", "drop a pi (pi-coding-agent) extension (~/.pi/agent/extensions/token-goat.ts)").option("--opencode", "drop an opencode plugin (~/.config/opencode/plugins/token-goat.ts, %APPDATA%\\opencode\\plugins\\token-goat.ts on Windows)").option("--hermes", "verify token-goat hooks are present for Hermes Agent (writes nothing new)").option("--openclaw", "register an OpenClaw plugin (~/.openclaw/openclaw.json, ~/.openclaw/plugins/token-goat.ts)").option("--copilot", "register a Copilot CLI hook config and routing block (~/.copilot/hooks/token-goat.json, ~/.copilot/hooks/token-goat-shim.cjs, ~/.copilot/copilot-instructions.md; with --local, <project>/.github/hooks/token-goat.json, <project>/.github/hooks/token-goat-shim.cjs, <project>/.github/copilot-instructions.md)").option("--grok", "register a Grok CLI (xAI Grok Build) hook config (~/.grok/hooks/token-goat.json, ~/.grok/hooks/token-goat-shim.cjs)").option("--vscode", "configure a VS Code MCP server (the workspace .vscode/mcp.json by default; --user for the user-profile mcp.json) and Copilot routing guidance").option("--visualstudio", "configure a Visual Studio (2022 17.14+ / 2026) Copilot MCP server and routing guidance, no hooks (%USERPROFILE%\\.mcp.json and %USERPROFILE%\\copilot-instructions.md; -p/--project for <project>/.mcp.json and <project>/.github/copilot-instructions.md)").option("--zed", "register token-goat as a Zed MCP context server (%APPDATA%\\Zed\\settings.json on Windows, ~/.config/zed/settings.json elsewhere, plus a generated shim script); Zed has no hooks API, so this is user scope only, no -p/--project support").option("--cursor", 'register a Cursor MCP server (~/.cursor/mcp.json by default; -p/--project for <project>/.cursor/mcp.json); writes no Cursor hooks config -- Cursor already imports the Claude Code hooks "token-goat install" writes to ~/.claude/settings.json').option("--local", "with --pi, install the project-local extension (<project>/.pi/extensions/token-goat.ts) instead of the global one").action(guard(cmdInstall));
21529
+ program2.command("uninstall").description("remove token-goat hooks from Claude Code settings, or with harness flags from those harnesses instead").option("-p, --project", "uninstall from project scope instead of user scope").option("--user", "with --vscode, remove the user-scope install instead of this project one").option("--codex", "strip the Codex CLI integration (~/.codex/config.toml, ~/.codex/AGENTS.md)").option("--gemini", "strip the Gemini CLI integration (~/.gemini/settings.json)").option("--qwen", "strip the Qwen Code integration (~/.qwen/settings.json)").option("--kimi", "strip the Kimi Code integration (config.toml hooks, hooks/token-goat-shim.cjs, the AGENTS.md block and skills/token-goat under $KIMI_CODE_HOME or ~/.kimi-code)").option("--pi", "remove the pi (pi-coding-agent) extension").option("--opencode", "remove the opencode plugin").option("--hermes", "no-op verification flag for symmetry with install (removes no files)").option("--openclaw", "remove the OpenClaw plugin and config entry").option("--copilot", "remove the Copilot CLI hook config and shim script, and strip the token-goat block from ~/.copilot/copilot-instructions.md (or <project>/.github/copilot-instructions.md with --local)").option("--grok", "remove the Grok CLI hook config and shim script").option("--vscode", "remove the VS Code MCP server (project scope by default; --user for the user-profile one) and routing guidance").option("--visualstudio", "remove the Visual Studio MCP server entry and routing guidance (user scope by default; -p/--project for the project one)").option("--zed", "remove the Zed MCP context server entry and its generated shim script").option("--cursor", "remove the Cursor MCP server entry (user scope by default; -p/--project for the project one)").option("--local", "with --pi, remove the project-local extension instead of the global one").option("--purge", "also delete the data directories (index, caches, session state, logs); refuses while the worker is running").action(guard(cmdUninstall));
21269
21530
  program2.command("mcp-status").description("check whether an MCP integration is already configured (used by the VS Code extension)").option("--vscode", "check VS Code mcp.json (user scope, plus workspace scope with --project)").option("--visualstudio", "check the Visual Studio %USERPROFILE%\\.mcp.json (plus the project .mcp.json with --project)").option("-p, --project", "also check the project file: .vscode/mcp.json for --vscode, .mcp.json for --visualstudio (relative to --cwd or the real cwd)").action(guard(cmdMcpStatus));
21270
21531
  const worker = program2.command("worker").description("background indexer lifecycle");
21271
21532
  worker.command("start").description("start the background indexer").action(guard(cmdWorkerStart));
21272
21533
  worker.command("stop").description("stop the background indexer").action(guard(cmdWorkerStop));
21273
21534
  worker.command("status").description("check if the indexer is running").action(guard(cmdWorkerStatus));
21535
+ const hookServer = program2.command("hook-server").description("resident processes that answer hook calls and read-only commands without starting Node for each one");
21536
+ hookServer.command("run", { hidden: true }).description("serve one slot (started automatically by the first hook call)").option("--slot <n>", "which slot to serve", "0").action(guard(cmdHookServerRun));
21537
+ hookServer.command("status").description("list the running hook servers").option("-j, --json", "output as JSON").action(guard(cmdHookServerStatus));
21538
+ hookServer.command("stop").description("stop every running hook server (the next hook call starts a fresh one)").action(guard(cmdHookServerStop));
21274
21539
  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));
21275
21540
  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));
21276
21541
  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));
@@ -21283,10 +21548,10 @@ function buildProgram() {
21283
21548
  registerAnalysisCommands(program2, guard);
21284
21549
  registerSessionCommands(program2, guard);
21285
21550
  program2.command("gdrive-sections <file-id>").description("fetch and list sections from a public Google Doc").option("--heading <name>", "get content of one named section").option("--fresh", "skip the on-disk cache and force a live fetch").action(guard(cmdGdriveSections));
21286
- 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);
21551
+ 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("--shell <type>", "shell interpreter to run under: bash | pwsh | powershell | native").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("--cap-hint-b64 <payload>", "base64 text printed after the output when --max-tokens cut it (the Bash hook sets it to the narrower read command)").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);
21287
21552
  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(
21288
21553
  (opts) => guard(async () => {
21289
- const { cmdUpgrade } = await import("./token-goat-chunk-CQHY4SL7.mjs");
21554
+ const { cmdUpgrade } = await import("./token-goat-chunk-YAGLESS3.mjs");
21290
21555
  await cmdUpgrade(opts, () => cmdInstall({}));
21291
21556
  })()
21292
21557
  );
@@ -21303,8 +21568,19 @@ function buildProgram() {
21303
21568
  return;
21304
21569
  }
21305
21570
  if (cmd) {
21571
+ const known = program2.commands.some((sub) => sub.name() === cmd || sub.aliases().includes(cmd));
21572
+ if (!known) {
21573
+ err(`token-goat: unknown command '${displaySafeText(cmd)}'. Run 'token-goat commands --grep <pattern>' to search the list.`);
21574
+ process.exitCode = 1;
21575
+ return;
21576
+ }
21306
21577
  const argv = [process.argv[0] ?? "node", process.argv[1] ?? "token-goat", cmd, "--help"];
21307
- program2.parse(argv);
21578
+ try {
21579
+ program2.parse(argv);
21580
+ } catch (e) {
21581
+ const code = e.code;
21582
+ if (code !== "commander.help" && code !== "commander.helpDisplayed") throw e;
21583
+ }
21308
21584
  } else {
21309
21585
  out(generateCompactHelp());
21310
21586
  }
@@ -21325,7 +21601,7 @@ async function run(argv = process.argv) {
21325
21601
  return;
21326
21602
  }
21327
21603
  if (argv[2] === "--batch-serve" && typeof argv[3] === "string") {
21328
- const { serveBatch } = await import("./token-goat-chunk-E3K3BTEQ.mjs");
21604
+ const { serveBatch } = await import("./token-goat-chunk-ASLWOQ3I.mjs");
21329
21605
  serveBatch(argv[3], (a) => run(a));
21330
21606
  return;
21331
21607
  }
@@ -21349,80 +21625,81 @@ async function run(argv = process.argv) {
21349
21625
  process.exitCode = 1;
21350
21626
  return;
21351
21627
  }
21352
- const msg = extractErrorMessage(e);
21353
- err(`token-goat: ${displaySafeText(msg)}`);
21628
+ err(formatCommandError(e));
21354
21629
  process.exitCode = 1;
21355
21630
  }
21356
21631
  }
21357
-
21358
21632
  export {
21359
- requireInt,
21360
- requireNonNegativeInt,
21361
- requirePositiveInt,
21362
- expandGlobs,
21363
- fenceFileText,
21364
- fenceFileFieldIfMatched,
21365
- fileSizeOrZero,
21366
- cmdPdfExtract,
21367
- cmdPdfLocate,
21368
- cmdPdfOutline,
21369
- cmdPdfMeta,
21370
- cmdVideoChapters,
21371
- cmdSharepointResolve,
21372
- cmdXlsxSheets,
21373
- cmdXlsxHead,
21374
- cmdXlsxColumns,
21375
- cmdXlsxRange,
21376
- cmdXlsxQuery,
21377
- cmdPptxOutline,
21378
- cmdPptxSlide,
21379
- cmdPptxNotes,
21380
- cmdPptxText,
21633
+ CliError,
21634
+ _applyFiltersAndPrint,
21635
+ applyExitOverride,
21636
+ buildProgram,
21637
+ cmdCsvProfile,
21638
+ cmdCsvQuery,
21639
+ cmdDescribe,
21381
21640
  cmdDocxOutline,
21382
21641
  cmdDocxTables,
21383
21642
  cmdDocxText,
21384
- cmdTranscriptOutline,
21385
- cmdTranscript,
21386
- cmdCsvQuery,
21387
- cmdCsvProfile,
21388
- cmdJsonOutline,
21389
- cmdJsonQuery,
21390
- cmdYamlOutline,
21391
- cmdYamlQuery,
21392
- cmdXmlOutline,
21393
- cmdXmlQuery,
21643
+ cmdHtmlLint,
21394
21644
  cmdHtmlOutline,
21395
21645
  cmdHtmlQuery,
21396
- cmdHtmlLint,
21397
- cmdOpenApiOutline,
21398
- cmdOpenApiOp,
21399
- cmdZipList,
21400
- cmdZipRead,
21401
- cmdNoteAdd,
21402
- cmdWriteFile,
21403
- cmdReplace,
21404
- cmdInsertSection,
21405
- fenceOcrText,
21406
21646
  cmdImageMeta,
21407
21647
  cmdImageText,
21408
- cmdSqliteTables,
21409
- cmdSqliteSchema,
21410
- cmdSqliteQuery,
21648
+ cmdIndex,
21649
+ cmdInsertSection,
21650
+ cmdJsonOutline,
21651
+ cmdJsonQuery,
21652
+ cmdNoteAdd,
21653
+ cmdOpenApiOp,
21654
+ cmdOpenApiOutline,
21655
+ cmdPdfExtract,
21656
+ cmdPdfLocate,
21657
+ cmdPdfMeta,
21658
+ cmdPdfOutline,
21659
+ cmdPptxNotes,
21660
+ cmdPptxOutline,
21661
+ cmdPptxSlide,
21662
+ cmdPptxText,
21663
+ cmdReplace,
21664
+ cmdSessionSchema,
21665
+ cmdSharepointResolve,
21411
21666
  cmdSkillBody,
21412
21667
  cmdSkillCompact,
21413
- cmdSkillList,
21414
- cmdSkillSize,
21415
- cmdSkillHistory,
21416
21668
  cmdSkillDiff,
21669
+ cmdSkillHistory,
21670
+ cmdSkillList,
21417
21671
  cmdSkillSection,
21418
- CliError,
21419
- out,
21672
+ cmdSkillSize,
21673
+ cmdSqliteQuery,
21674
+ cmdSqliteSchema,
21675
+ cmdSqliteTables,
21676
+ cmdTranscript,
21677
+ cmdTranscriptOutline,
21678
+ cmdVideoChapters,
21679
+ cmdWriteFile,
21680
+ cmdXlsxColumns,
21681
+ cmdXlsxHead,
21682
+ cmdXlsxQuery,
21683
+ cmdXlsxRange,
21684
+ cmdXlsxSheets,
21685
+ cmdXmlOutline,
21686
+ cmdXmlQuery,
21687
+ cmdYamlOutline,
21688
+ cmdYamlQuery,
21689
+ cmdZipList,
21690
+ cmdZipRead,
21420
21691
  err,
21421
- cmdIndex,
21422
- visualStudioManualSteps,
21692
+ expandGlobs,
21693
+ fenceFileFieldIfMatched,
21694
+ fenceFileText,
21695
+ fenceOcrText,
21696
+ fileSizeOrZero,
21697
+ formatCommandError,
21423
21698
  leftoverIntegrations,
21424
- _applyFiltersAndPrint,
21425
- buildProgram,
21426
- applyExitOverride,
21427
- run
21699
+ out,
21700
+ requireInt,
21701
+ requireNonNegativeInt,
21702
+ requirePositiveInt,
21703
+ run,
21704
+ visualStudioManualSteps
21428
21705
  };