token-goat 2.6.36 → 2.8.0

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 (34) hide show
  1. package/README.md +31 -5
  2. package/SECURITY.md +46 -17
  3. package/dist/{token-goat-chunk-UFOVM7ZN.mjs → token-goat-chunk-2F6TFBZE.mjs} +1274 -367
  4. package/dist/token-goat-chunk-324QOJYZ.mjs +91 -0
  5. package/dist/{token-goat-chunk-V465YKOR.mjs → token-goat-chunk-44Y77VHR.mjs} +365 -23
  6. package/dist/{token-goat-chunk-CNDOJ3ZP.mjs → token-goat-chunk-4KZILRZN.mjs} +2 -2
  7. package/dist/token-goat-chunk-5CVKO3DA.mjs +185 -0
  8. package/dist/token-goat-chunk-A62K4XW2.mjs +23 -0
  9. package/dist/{token-goat-chunk-VYMGEVZS.mjs → token-goat-chunk-AO6MFFTW.mjs} +317 -11
  10. package/dist/{token-goat-chunk-65BKISIS.mjs → token-goat-chunk-FBGBTICM.mjs} +4 -4
  11. package/dist/{token-goat-chunk-LN6OUHTV.mjs → token-goat-chunk-J35AKWEQ.mjs} +5 -5
  12. package/dist/{token-goat-hook-chunk-BDR6C6IE.mjs → token-goat-chunk-LVCBDJVE.mjs} +318 -51
  13. package/dist/token-goat-chunk-R4SR7MQY.mjs +486 -0
  14. package/dist/{token-goat-chunk-IYTVE6KN.mjs → token-goat-chunk-SRAR6DOK.mjs} +133 -141
  15. package/dist/{token-goat-chunk-MGOUYAA2.mjs → token-goat-chunk-TUPJRK7R.mjs} +1 -1
  16. package/dist/{token-goat-chunk-KYFJC37X.mjs → token-goat-chunk-VXSYZGBA.mjs} +582 -135
  17. package/dist/{token-goat-chunk-DG53MVNJ.mjs → token-goat-chunk-WN5T5EW5.mjs} +212 -212
  18. package/dist/token-goat-hook.mjs +7 -7
  19. package/dist/token-goat.core.mjs +5 -5
  20. package/package.json +9 -7
  21. package/dist/token-goat-chunk-FRTBMRP7.mjs +0 -10048
  22. package/dist/token-goat-hook-chunk-3QYSN4QV.mjs +0 -14764
  23. package/dist/token-goat-hook-chunk-3ZDBWJDF.mjs +0 -13659
  24. package/dist/token-goat-hook-chunk-5UH54CW6.mjs +0 -912
  25. package/dist/token-goat-hook-chunk-6ODM3MP7.mjs +0 -706
  26. package/dist/token-goat-hook-chunk-A77A26A7.mjs +0 -18184
  27. package/dist/token-goat-hook-chunk-BUOCULAM.mjs +0 -29
  28. package/dist/token-goat-hook-chunk-C6GIABOX.mjs +0 -15971
  29. package/dist/token-goat-hook-chunk-E257IGSN.mjs +0 -153
  30. package/dist/token-goat-hook-chunk-MW5HPEGD.mjs +0 -10411
  31. package/dist/token-goat-hook-chunk-QSCYNJ2B.mjs +0 -23
  32. package/dist/token-goat-hook-chunk-RFRLWOQH.mjs +0 -11
  33. package/dist/token-goat-hook-chunk-XUMIVYEN.mjs +0 -109
  34. package/dist/token-goat-hook-chunk-Y2WHX2P3.mjs +0 -6463
@@ -16,7 +16,7 @@ import {
16
16
  findSpecSeparator,
17
17
  getTrackedFiles,
18
18
  healStaleIndex,
19
- isAvailable,
19
+ isAvailable as isAvailable2,
20
20
  listSections,
21
21
  load,
22
22
  loadError,
@@ -84,17 +84,21 @@ import {
84
84
  runZipRead,
85
85
  symbolNamesInFile,
86
86
  upsertNote
87
- } from "./token-goat-chunk-IYTVE6KN.mjs";
87
+ } from "./token-goat-chunk-SRAR6DOK.mjs";
88
88
  import {
89
89
  BASH_OUTPUT_SUBDIR,
90
90
  GEMINI_TOOL_NAME_MAP,
91
91
  HOOK_EVENTS,
92
92
  WEB_OUTPUT_SUBDIR,
93
+ accumulateResidentLine,
93
94
  buildCommandManifest,
94
95
  checkSymbolBodySize,
95
96
  commandHash,
97
+ createResidentContextStats,
96
98
  filterCommandManifest,
99
+ formatBytes,
97
100
  formatCommandManifest,
101
+ formatTokenEstimate,
98
102
  getBashOutput,
99
103
  getWebOutput,
100
104
  getWebOutputByUrlFromDisk,
@@ -102,10 +106,12 @@ import {
102
106
  isRecallCacheType,
103
107
  listRecentRecall,
104
108
  normalizeCommandForCacheKey,
109
+ readCopilotMcpTools,
105
110
  readStdinJson,
106
111
  searchRecall,
107
- storeWebOutput
108
- } from "./token-goat-chunk-VYMGEVZS.mjs";
112
+ storeWebOutput,
113
+ summarizeResidentContext
114
+ } from "./token-goat-chunk-AO6MFFTW.mjs";
109
115
  import {
110
116
  AGENT_SALT_MARKER,
111
117
  CONTEXT_AUTOCOMPACT_TOKENS,
@@ -132,16 +138,20 @@ import {
132
138
  confirmAndApply,
133
139
  contentHash,
134
140
  copilotCliConfigPath,
141
+ copilotCliMcpToolsDir,
135
142
  copilotCliScriptPath,
143
+ copilotCliUserRoot,
136
144
  detectWalkMode,
137
145
  dirtyQueuePathFor,
138
146
  docxOutline,
139
147
  docxText,
140
148
  drainHeartbeatPathFor,
149
+ embeddingBackendLoadError,
141
150
  embeddingsDepsAvailable,
142
151
  emptyIndexMessage,
143
152
  enqueueDirtyPathSafe,
144
153
  estimateTokens,
154
+ estimateTokensFromLength,
145
155
  extractChecklistSection,
146
156
  extractCompactFromMarker,
147
157
  extractNamedSection,
@@ -182,6 +192,7 @@ import {
182
192
  installKimi,
183
193
  installSkill,
184
194
  installVscode,
195
+ isAvailable,
185
196
  isCodexInstalled,
186
197
  isCompactFresh,
187
198
  isCopilotCliInstalled,
@@ -243,7 +254,7 @@ import {
243
254
  vscodeDecoderConfigured,
244
255
  walkProject,
245
256
  writeCompact
246
- } from "./token-goat-chunk-UFOVM7ZN.mjs";
257
+ } from "./token-goat-chunk-2F6TFBZE.mjs";
247
258
  import {
248
259
  C,
249
260
  CONFIG_KEY_ENV_OVERRIDES,
@@ -324,7 +335,7 @@ import {
324
335
  withFileLock,
325
336
  withRetryOnLock,
326
337
  writeJsonSettings
327
- } from "./token-goat-chunk-V465YKOR.mjs";
338
+ } from "./token-goat-chunk-44Y77VHR.mjs";
328
339
  import {
329
340
  __export
330
341
  } from "./token-goat-chunk-AEX54RUZ.mjs";
@@ -3440,9 +3451,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3440
3451
  * @param {string} [path]
3441
3452
  * @return {(string|null|Command)}
3442
3453
  */
3443
- executableDir(path27) {
3444
- if (path27 === void 0) return this._executableDir;
3445
- this._executableDir = path27;
3454
+ executableDir(path28) {
3455
+ if (path28 === void 0) return this._executableDir;
3456
+ this._executableDir = path28;
3446
3457
  return this;
3447
3458
  }
3448
3459
  /**
@@ -3741,8 +3752,8 @@ function attemptedCommandName(argv) {
3741
3752
  }
3742
3753
 
3743
3754
  // src/cli.ts
3744
- import * as fs26 from "fs";
3745
- import * as path26 from "path";
3755
+ import * as fs27 from "fs";
3756
+ import * as path27 from "path";
3746
3757
  import { homedir as homedir11 } from "os";
3747
3758
 
3748
3759
  // src/walk_index.ts
@@ -4998,13 +5009,19 @@ function isOpenclawInstalled() {
4998
5009
 
4999
5010
  // src/bridges_status.ts
5000
5011
  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";
5012
+ 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";
5013
+ 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";
5014
+ 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";
5001
5015
  var BRIDGE_CAPABILITY_MATRIX = [
5002
5016
  {
5003
5017
  harness: "claudecode",
5004
5018
  label: "Claude Code",
5005
5019
  sourceFile: "src/install.ts (HOOK_EVENT_MAP)",
5006
- implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact", "user_prompt_submit", "subagent_stop", "session_start"]),
5007
- reasons: [{ events: ["notification", "stop"], reason: NO_SERVER_HANDLER_REASON }]
5020
+ implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact", "post_compact", "user_prompt_submit", "subagent_stop", "session_start"]),
5021
+ reasons: [
5022
+ { events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
5023
+ { events: ["notification", "stop"], reason: NO_SERVER_HANDLER_REASON }
5024
+ ]
5008
5025
  },
5009
5026
  {
5010
5027
  harness: "codex",
@@ -5012,6 +5029,8 @@ var BRIDGE_CAPABILITY_MATRIX = [
5012
5029
  sourceFile: "src/bridges/codex_install.ts (CODEX_HOOK_EVENTS, CODEX_GLOBAL_HOOK_EVENTS)",
5013
5030
  implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact", "user_prompt_submit", "subagent_stop"]),
5014
5031
  reasons: [
5032
+ { events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
5033
+ { events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
5015
5034
  { events: ["notification", "stop"], reason: NO_SERVER_HANDLER_REASON },
5016
5035
  {
5017
5036
  events: ["session_start"],
@@ -5025,6 +5044,8 @@ var BRIDGE_CAPABILITY_MATRIX = [
5025
5044
  sourceFile: "src/bridges/grok_install.ts (GROK_HOOK_EVENTS)",
5026
5045
  implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact", "user_prompt_submit", "subagent_stop"]),
5027
5046
  reasons: [
5047
+ { events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
5048
+ { events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
5028
5049
  { events: ["notification", "stop"], reason: NO_SERVER_HANDLER_REASON },
5029
5050
  {
5030
5051
  events: ["session_start"],
@@ -5032,6 +5053,55 @@ var BRIDGE_CAPABILITY_MATRIX = [
5032
5053
  }
5033
5054
  ]
5034
5055
  },
5056
+ /**
5057
+ * What Copilot CLI's wired events can actually carry, read from the shipping 1.0.80 bundle
5058
+ * rather than from its hooks reference page. A row's `implemented` set says an event is wired;
5059
+ * it deliberately says nothing about how much of that event's response the harness honors, and
5060
+ * for Copilot the difference matters:
5061
+ *
5062
+ * - `pre_tool_use` deny is real, and the reason text reaches the model. The native string table
5063
+ * (runtime.node offset 98057208) carries `permissionDecision`, `permissionDecisionReason`, the
5064
+ * default "No reason provided." and the formatter "Denied by preToolUse hook: " as distinct
5065
+ * literals, and app.js sets `{textResultForLlm: reason, resultType: "denied"}` per denial and
5066
+ * filters that call out of execution. This was previously only an inference drawn from an
5067
+ * incident comment; it is now confirmed. The fail-closed "(hook errored)" deny-all strings sit
5068
+ * beside these, so that incident was behavior layered on top of a working deny handler, not a
5069
+ * substitute for one.
5070
+ * - `post_tool_use` honors `modifiedResult`, so compression, injection fencing and image shrink
5071
+ * do reach the model: `postToolExecution` (app.js offset 2043150) assigns the returned
5072
+ * `toolResultJson` onto the tool result in place.
5073
+ * - `post_tool_use` drops `additionalContext` on the JS path -- no supplier for the
5074
+ * `onAdditionalContext` callback anywhere in the bundle, and no `additional_contexts` key in
5075
+ * that event's native return payload, unlike its pre-tool sibling. Whether native folds it
5076
+ * into the returned result instead was not verified.
5077
+ * - Failed tool calls never reach `post_tool_use` at all. Copilot routes them to a separate
5078
+ * `postToolUseFailure` event, which is handed only a stringified error and honors only
5079
+ * `additionalContext`; `modifiedResult` is documented as not honored there. So on Copilot the
5080
+ * output of a failed tool call reaches the model unfenced, uncompressed and unshrunk, and no
5081
+ * response shape token-goat's shim can emit changes that. Known gap, deliberately recorded.
5082
+ * What `additionalContext` does on that event is no longer doc-derived: app.js offset 2043380
5083
+ * reads the native processor's return and, when it carries `additionalContext`, either folds
5084
+ * it into `textResultForLlm` (if `appendFailureContextToToolResult` is set) or formats it and
5085
+ * pushes `{content, source: "system"}` onto `toolResult.newMessages`. So the channel is real
5086
+ * and model-visible -- it just carries advice alongside the failure, never a replacement for
5087
+ * it. It is wired now, as its own `post_tool_use_failure` event rather than a reuse of
5088
+ * `post_tool_use`: routing failures through the success event would let success-path handlers
5089
+ * mark a file as successfully read when the read failed. Because the channel spends tokens
5090
+ * instead of saving them, its handler (`src/hooks_tool_failure.ts`) stays silent on a first
5091
+ * failure and speaks only on an exact repeat.
5092
+ * - `pre_compact` is wired and fires, but nothing it returns can reach the model. Both dispatch
5093
+ * sites call it in statement position and drop the result: app.js offset 2467452 (the manual
5094
+ * `/compact` path) and offset 2571216 (`case "execute_pre_compact_hook"`). The contrast is
5095
+ * what makes this conclusive rather than an absence of evidence -- the same `event()` runner's
5096
+ * return IS read for other events, e.g. `notification` at offset 2296123 assigns it and
5097
+ * forwards `additionalContext` as a prepended system prompt. The compaction prompt is then
5098
+ * built from session history and the user's own focus text, with no hook channel into it.
5099
+ * So token-goat's `pre_compact` registration here is observe-only by construction, not merely
5100
+ * unused: a future attempt to inject a session manifest through it would silently do nothing.
5101
+ * Not verified: whether the native declarative runner applies `additionalContext` itself. The
5102
+ * trail ends in stripped Rust. That it would make the explicit JS handling for `notification`
5103
+ * and `userPromptSubmitted` redundant is an argument, not a proof.
5104
+ */
5035
5105
  {
5036
5106
  harness: "copilot_cli",
5037
5107
  label: "Copilot CLI",
@@ -5043,12 +5113,14 @@ var BRIDGE_CAPABILITY_MATRIX = [
5043
5113
  "pre_compact",
5044
5114
  "stop",
5045
5115
  "subagent_stop",
5046
- "user_prompt_submit"
5116
+ "user_prompt_submit",
5117
+ "post_tool_use_failure"
5047
5118
  ]),
5048
5119
  reasons: [
5120
+ { events: ["post_compact"], reason: COPILOT_NO_POST_COMPACT_REASON },
5049
5121
  {
5050
5122
  events: ["notification"],
5051
- reason: "Copilot CLI has a real 'notification' hook event, but copilot_cli.ts's COPILOT_TO_TG_EVENT deliberately leaves it (and sessionEnd/postToolUseFailure/subagentStart/errorOccurred/permissionRequest) unimplemented rather than guessed at"
5123
+ reason: "Copilot CLI has a real 'notification' hook event, but copilot_cli.ts's COPILOT_TO_TG_EVENT deliberately leaves it (and sessionEnd/subagentStart/errorOccurred/permissionRequest) unimplemented rather than guessed at"
5052
5124
  }
5053
5125
  ]
5054
5126
  },
@@ -5058,6 +5130,8 @@ var BRIDGE_CAPABILITY_MATRIX = [
5058
5130
  sourceFile: "src/bridges/gemini_install.ts (GEMINI_HOOK_EVENTS)",
5059
5131
  implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact"]),
5060
5132
  reasons: [
5133
+ { events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
5134
+ { events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
5061
5135
  {
5062
5136
  events: ["notification", "stop", "user_prompt_submit", "subagent_stop", "session_start"],
5063
5137
  reason: `Gemini CLI's hooks integration only wires BeforeTool/AfterTool/PreCompress (README "Gemini CLI users")`
@@ -5070,6 +5144,8 @@ var BRIDGE_CAPABILITY_MATRIX = [
5070
5144
  sourceFile: "src/bridges/qwen_install.ts (QWEN_HOOK_EVENTS)",
5071
5145
  implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact", "user_prompt_submit", "subagent_stop"]),
5072
5146
  reasons: [
5147
+ { events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
5148
+ { events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
5073
5149
  {
5074
5150
  events: ["notification"],
5075
5151
  reason: "Only five Qwen Code events have a token-goat handler; every other real event (Notification, SessionEnd, PostToolUseFailure, StopFailure, SubagentStart, PermissionRequest, TodoCreated, TodoCompleted) is left unimplemented rather than guessed at (qwen_install.ts)"
@@ -5086,7 +5162,11 @@ var BRIDGE_CAPABILITY_MATRIX = [
5086
5162
  label: "Kimi Code CLI",
5087
5163
  sourceFile: "src/bridges/kimi_install.ts (KIMI_EVENT_ARG), src/bridges/kimi.ts (KIMI_HOOK_SCRIPT)",
5088
5164
  implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact", "user_prompt_submit", "subagent_stop", "session_start"]),
5089
- reasons: [{ events: ["notification", "stop"], reason: NO_SERVER_HANDLER_REASON }]
5165
+ reasons: [
5166
+ { events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
5167
+ { events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
5168
+ { events: ["notification", "stop"], reason: NO_SERVER_HANDLER_REASON }
5169
+ ]
5090
5170
  },
5091
5171
  {
5092
5172
  harness: "opencode",
@@ -5094,6 +5174,8 @@ var BRIDGE_CAPABILITY_MATRIX = [
5094
5174
  sourceFile: "src/bridges/opencode.ts",
5095
5175
  implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact"]),
5096
5176
  reasons: [
5177
+ { events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
5178
+ { events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
5097
5179
  {
5098
5180
  events: ["notification", "stop", "user_prompt_submit", "subagent_stop", "session_start"],
5099
5181
  reason: "opencode's plugin API only exposes three relevant hooks -- tool.execute.before, tool.execute.after, experimental.session.compacting (opencode.ts module docstring, verified against opencode's real source)"
@@ -5106,6 +5188,8 @@ var BRIDGE_CAPABILITY_MATRIX = [
5106
5188
  sourceFile: "src/bridges/openclaw.ts",
5107
5189
  implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact"]),
5108
5190
  reasons: [
5191
+ { events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
5192
+ { events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
5109
5193
  {
5110
5194
  events: ["notification", "stop", "user_prompt_submit", "subagent_stop", "session_start"],
5111
5195
  reason: "OpenClaw's in-process plugin API only exposes before_tool_call/after_tool_call/before_compaction as api.on() handlers relevant here"
@@ -5118,6 +5202,8 @@ var BRIDGE_CAPABILITY_MATRIX = [
5118
5202
  sourceFile: "src/bridges/pi.ts",
5119
5203
  implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact"]),
5120
5204
  reasons: [
5205
+ { events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
5206
+ { events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
5121
5207
  {
5122
5208
  events: ["notification", "stop", "user_prompt_submit", "subagent_stop", "session_start"],
5123
5209
  reason: "pi's extension API subscribes to session_start/tool_call/tool_result/session_before_compact/session_compact, of which only tool_call/tool_result/session_before_compact map onto real HOOK_EVENTS names (pre_tool_use/post_tool_use/pre_compact) -- pi's own session_start event is never forwarded to token-goat's callHook()"
@@ -5484,7 +5570,7 @@ function purgeDataDirectories() {
5484
5570
  }
5485
5571
  return result;
5486
5572
  }
5487
- function formatBytes(bytes) {
5573
+ function formatBytes2(bytes) {
5488
5574
  if (bytes < 1024) return `${bytes} B`;
5489
5575
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
5490
5576
  if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
@@ -5505,8 +5591,8 @@ function formatTopFiles(ranked) {
5505
5591
  if (ranked.length === 0) return "";
5506
5592
  const lines = ["Top files this session:"];
5507
5593
  for (const { path: filePath, count } of ranked) {
5508
- const basename7 = path10.basename(filePath);
5509
- lines.push(` ${count.toString().padStart(3)}x ${basename7} (${filePath})`);
5594
+ const basename8 = path10.basename(filePath);
5595
+ lines.push(` ${count.toString().padStart(3)}x ${basename8} (${filePath})`);
5510
5596
  }
5511
5597
  return lines.join("\n");
5512
5598
  }
@@ -5856,7 +5942,7 @@ function checkInstall() {
5856
5942
  }
5857
5943
  }
5858
5944
  function checkTsCompiler() {
5859
- if (isAvailable()) {
5945
+ if (isAvailable2()) {
5860
5946
  return { name: "TypeScript compiler", status: "ok", message: "available" };
5861
5947
  }
5862
5948
  const err2 = loadError();
@@ -5866,6 +5952,27 @@ function checkTsCompiler() {
5866
5952
  message: err2 !== null ? `unavailable: ${extractErrorMessage(err2)}` : "unavailable (not attempted)"
5867
5953
  };
5868
5954
  }
5955
+ function checkEmbeddings(config) {
5956
+ const name = "Embeddings";
5957
+ if ((config.indexing?.embeddings_enabled ?? true) === false) {
5958
+ return { name, status: "ok", message: "disabled by config (indexing.embeddings_enabled)" };
5959
+ }
5960
+ if (isAvailable()) return { name, status: "ok", message: "available" };
5961
+ const err2 = embeddingBackendLoadError();
5962
+ const code = err2?.code;
5963
+ if (code === "MODULE_NOT_FOUND" || code === "ERR_MODULE_NOT_FOUND") {
5964
+ return {
5965
+ name,
5966
+ status: "warn",
5967
+ message: "onnxruntime-node is not installed, so semantic falls back to keyword search \u2014 install it with: npm install -g onnxruntime-node (drop -g if token-goat is a project dependency)"
5968
+ };
5969
+ }
5970
+ return {
5971
+ name,
5972
+ status: "warn",
5973
+ message: err2 !== null ? `onnxruntime-node is installed but failed to load: ${extractErrorMessage(err2)}` : "unavailable (not attempted)"
5974
+ };
5975
+ }
5869
5976
  function checkConfigValid(configPath2) {
5870
5977
  if (!fs12.existsSync(configPath2)) {
5871
5978
  return {
@@ -6064,6 +6171,46 @@ function dataDirPermissionResult(dataDirPath) {
6064
6171
  return { name: "Security data dir", status: "warn", message: "could not be read, so its permissions are unknown" };
6065
6172
  }
6066
6173
  }
6174
+ var COMPACTION_CHANNEL_WINDOW = 5;
6175
+ function checkCompactionChannel(dbPath) {
6176
+ const name = "Compaction channel";
6177
+ if (!fs12.existsSync(dbPath)) {
6178
+ return { name, status: "ok", message: "no database yet" };
6179
+ }
6180
+ try {
6181
+ const db = getDb(dbPath);
6182
+ const present = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'stats'").get();
6183
+ if (present === void 0) {
6184
+ return { name, status: "ok", message: "no compaction has been measured yet" };
6185
+ }
6186
+ const rows = db.prepare("SELECT detail FROM stats WHERE kind = 'compact_summary' ORDER BY rowid DESC LIMIT ?").all(COMPACTION_CHANNEL_WINDOW * 4);
6187
+ const conclusive = [];
6188
+ for (const row of rows) {
6189
+ const m = /manifest_paths=(\d+)\/(\d+)/.exec(row.detail ?? "");
6190
+ if (m === null) continue;
6191
+ const sampled = Number(m[2]);
6192
+ if (sampled === 0) continue;
6193
+ conclusive.push({ survived: Number(m[1]), sampled });
6194
+ if (conclusive.length >= COMPACTION_CHANNEL_WINDOW) break;
6195
+ }
6196
+ if (conclusive.length === 0) {
6197
+ return { name, status: "ok", message: "no compaction has been measured yet" };
6198
+ }
6199
+ const dead = conclusive.filter((c) => c.survived === 0).length;
6200
+ if (conclusive.length >= COMPACTION_CHANNEL_WINDOW && dead === conclusive.length) {
6201
+ return {
6202
+ name,
6203
+ status: "warn",
6204
+ message: `none of the last ${conclusive.length} compaction summaries kept a single file path token-goat sent ahead of them -- Claude Code may have stopped feeding a PreCompact hook's output to the summarizer, which would make the session manifest a no-op`
6205
+ };
6206
+ }
6207
+ const kept = conclusive.reduce((n, c) => n + c.survived, 0);
6208
+ const sent = conclusive.reduce((n, c) => n + c.sampled, 0);
6209
+ return { name, status: "ok", message: `${kept}/${sent} sampled paths survived the last ${conclusive.length} compaction(s)` };
6210
+ } catch (e) {
6211
+ return { name, status: "warn", message: `could not read compaction stats: ${extractErrorMessage(e)}` };
6212
+ }
6213
+ }
6067
6214
  function runDoctor(dataDir2, configPath2, rootDir, processes) {
6068
6215
  const results = [];
6069
6216
  const actualDataDir = dataDir2 || dataDir();
@@ -6075,8 +6222,10 @@ function runDoctor(dataDir2, configPath2, rootDir, processes) {
6075
6222
  results.push(checkSymbolBodySize(path11.join(actualDataDir, "global.db")));
6076
6223
  results.push(checkSymbolCount(path11.join(actualDataDir, "global.db"), rootDir));
6077
6224
  results.push(checkDirtyQueueHealth(actualDataDir));
6225
+ results.push(checkCompactionChannel(path11.join(actualDataDir, "global.db")));
6078
6226
  const actualConfigPath = configPath2 || configPath();
6079
6227
  results.push(checkConfigValid(actualConfigPath));
6228
+ results.push(checkEmbeddings(loadConfig(rootDir)));
6080
6229
  for (const result of checkSecurityPosture(loadConfig(rootDir), actualDataDir)) results.push(result);
6081
6230
  results.push(checkDiskSpace(actualDataDir));
6082
6231
  const copilotResult = checkCopilotCli(copilotCliConfigPath(), copilotCliScriptPath());
@@ -12408,8 +12557,8 @@ async function runMemoryCommand(opts = {}) {
12408
12557
  }
12409
12558
 
12410
12559
  // src/cli_waste.ts
12411
- import * as fs23 from "node:fs";
12412
- import * as path23 from "node:path";
12560
+ import * as fs24 from "node:fs";
12561
+ import * as path24 from "node:path";
12413
12562
 
12414
12563
  // src/waste.ts
12415
12564
  import * as fs22 from "node:fs";
@@ -12494,6 +12643,7 @@ function parseTranscript2(transcriptPath) {
12494
12643
  const calls = [];
12495
12644
  const resultTextById = /* @__PURE__ */ new Map();
12496
12645
  const assistantTurns = [];
12646
+ const resident = createResidentContextStats();
12497
12647
  let seq = 0;
12498
12648
  for (const line of raw.split("\n")) {
12499
12649
  const trimmed = line.trim();
@@ -12506,6 +12656,7 @@ function parseTranscript2(transcriptPath) {
12506
12656
  }
12507
12657
  if (obj === null || typeof obj !== "object") continue;
12508
12658
  const o = obj;
12659
+ accumulateResidentLine(resident, o, trimmed.length);
12509
12660
  const message = o["message"];
12510
12661
  if (message === null || typeof message !== "object") continue;
12511
12662
  const messageObj = message;
@@ -12545,7 +12696,7 @@ function parseTranscript2(transcriptPath) {
12545
12696
  }
12546
12697
  if (role === "assistant" && sawTextBlock) assistantTurns.push(turnTextTokens);
12547
12698
  }
12548
- return { calls, resultTextById, assistantTurns };
12699
+ return { calls, resultTextById, assistantTurns, resident };
12549
12700
  }
12550
12701
  function costPerCall(parsed) {
12551
12702
  return parsed.calls.map((c) => ({
@@ -12651,11 +12802,250 @@ async function buildWasteReport(transcriptPath, opts = {}) {
12651
12802
  topCalls: topExpensiveCalls(costs, topN).map((c) => ({ seq: c.seq, name: c.name, summary: c.summary, tokens: c.tokens })),
12652
12803
  neverTouchedAgain: neverTouchedAgain(costs),
12653
12804
  repeatedUncompressedBash: await repeatedUncompressedBashCommands(costs),
12654
- assistantOutput: assistantOutputCost(parsed.assistantTurns)
12805
+ assistantOutput: assistantOutputCost(parsed.assistantTurns),
12806
+ residentContext: summarizeResidentContext(parsed.resident)
12655
12807
  };
12656
12808
  }
12657
12809
 
12810
+ // src/copilot_waste.ts
12811
+ import * as fs23 from "node:fs";
12812
+ import * as path23 from "node:path";
12813
+ var HOOK_RECORD_TYPES = /* @__PURE__ */ new Set(["hook.start", "hook.end"]);
12814
+ function splitInjectedBlocks(transformed) {
12815
+ const out2 = [];
12816
+ const blockRe = /<([a-z_][a-z0-9_]*)>([\s\S]*?)<\/\1>/gi;
12817
+ let m;
12818
+ while ((m = blockRe.exec(transformed)) !== null) {
12819
+ const tag = (m[1] ?? "").toLowerCase();
12820
+ const body = m[2] ?? "";
12821
+ let kind = tag;
12822
+ if (tag === "system_reminder") {
12823
+ const inner = /^\s*<([a-z_][a-z0-9_]*)>/i.exec(body);
12824
+ kind = inner !== null ? `reminder:${(inner[1] ?? "").toLowerCase()}` : "reminder:text";
12825
+ }
12826
+ out2.push({ kind, body: m[0] });
12827
+ }
12828
+ return out2;
12829
+ }
12830
+ function findLatestCopilotSession() {
12831
+ const root = path23.join(copilotCliUserRoot(), "session-state");
12832
+ let entries;
12833
+ try {
12834
+ entries = fs23.readdirSync(root);
12835
+ } catch {
12836
+ return null;
12837
+ }
12838
+ let best = null;
12839
+ let bestMtime = -Infinity;
12840
+ for (const entry of entries) {
12841
+ const candidate = path23.join(root, entry, "events.jsonl");
12842
+ try {
12843
+ const st = fs23.statSync(candidate);
12844
+ if (!st.isFile()) continue;
12845
+ if (st.mtimeMs > bestMtime) {
12846
+ bestMtime = st.mtimeMs;
12847
+ best = candidate;
12848
+ }
12849
+ } catch {
12850
+ }
12851
+ }
12852
+ return best;
12853
+ }
12854
+ function readNumber(source, key) {
12855
+ const value = source[key];
12856
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
12857
+ }
12858
+ function buildCopilotWasteReport(eventsPath) {
12859
+ const raw = fs23.readFileSync(eventsPath, "utf-8");
12860
+ const report = {
12861
+ sessionPath: eventsPath,
12862
+ sessionId: path23.basename(path23.dirname(eventsPath)),
12863
+ turns: 0,
12864
+ tokens: null,
12865
+ blocks: [],
12866
+ compactions: [],
12867
+ hookRecordBytes: 0,
12868
+ totalEventBytes: Buffer.byteLength(raw, "utf-8"),
12869
+ // Read through the real resolution chain rather than passed in. Copilot's
12870
+ // own COPILOT_CACHE_HOME override is what tests point at a fixture, so the
12871
+ // shipping path is the tested path and there is no seam here that only a
12872
+ // test ever supplies.
12873
+ mcpTools: readCopilotMcpTools()
12874
+ };
12875
+ const classes = /* @__PURE__ */ new Map();
12876
+ const seen = /* @__PURE__ */ new Map();
12877
+ for (const line of raw.split("\n")) {
12878
+ const trimmed = line.trim();
12879
+ if (trimmed === "") continue;
12880
+ let event;
12881
+ try {
12882
+ event = JSON.parse(trimmed);
12883
+ } catch {
12884
+ continue;
12885
+ }
12886
+ const type = typeof event["type"] === "string" ? event["type"] : "";
12887
+ const data = event["data"] ?? {};
12888
+ if (HOOK_RECORD_TYPES.has(type)) {
12889
+ report.hookRecordBytes += Buffer.byteLength(trimmed, "utf-8");
12890
+ continue;
12891
+ }
12892
+ if (type === "session.shutdown") {
12893
+ report.tokens = {
12894
+ systemTokens: readNumber(data, "systemTokens"),
12895
+ toolDefinitionsTokens: readNumber(data, "toolDefinitionsTokens"),
12896
+ conversationTokens: readNumber(data, "conversationTokens"),
12897
+ currentTokens: readNumber(data, "currentTokens")
12898
+ };
12899
+ continue;
12900
+ }
12901
+ if (type === "session.compaction_complete") {
12902
+ const summary = typeof data["summaryContent"] === "string" ? data["summaryContent"] : "";
12903
+ report.compactions.push({
12904
+ trigger: typeof data["trigger"] === "string" ? data["trigger"] : "unknown",
12905
+ summaryBytes: Buffer.byteLength(summary, "utf-8"),
12906
+ preTokens: readNumber(data, "preCompactionTokens"),
12907
+ postTokens: readNumber(data, "postCompactionTokens")
12908
+ });
12909
+ continue;
12910
+ }
12911
+ if (type !== "user.message") continue;
12912
+ const transformed = typeof data["transformedContent"] === "string" ? data["transformedContent"] : "";
12913
+ if (transformed === "") continue;
12914
+ report.turns += 1;
12915
+ for (const block of splitInjectedBlocks(transformed)) {
12916
+ const bytes = Buffer.byteLength(block.body, "utf-8");
12917
+ let cls = classes.get(block.kind);
12918
+ if (cls === void 0) {
12919
+ cls = { kind: block.kind, count: 0, bytes: 0, repeatBytes: 0, repeatCount: 0 };
12920
+ classes.set(block.kind, cls);
12921
+ }
12922
+ cls.count += 1;
12923
+ cls.bytes += bytes;
12924
+ let bucket = seen.get(block.kind);
12925
+ if (bucket === void 0) {
12926
+ bucket = /* @__PURE__ */ new Set();
12927
+ seen.set(block.kind, bucket);
12928
+ }
12929
+ if (bucket.has(block.body)) {
12930
+ cls.repeatBytes += bytes;
12931
+ cls.repeatCount += 1;
12932
+ } else {
12933
+ bucket.add(block.body);
12934
+ }
12935
+ }
12936
+ }
12937
+ report.blocks = [...classes.values()].sort((a, b) => b.bytes - a.bytes);
12938
+ return report;
12939
+ }
12940
+
12658
12941
  // src/cli_waste.ts
12942
+ function printCopilotReport(report) {
12943
+ const w = (text) => {
12944
+ process.stdout.write(text);
12945
+ };
12946
+ w("\n# token-goat waste (Copilot CLI)\n");
12947
+ w(`Session: ${report.sessionId}
12948
+ `);
12949
+ w(` ${report.sessionPath}
12950
+ `);
12951
+ w("\n## Per-request fixed overhead (Copilot's own token counts)\n");
12952
+ if (report.tokens === null) {
12953
+ w(" No shutdown record yet, so Copilot has not published a token split for this session.\n");
12954
+ } else {
12955
+ const { systemTokens, toolDefinitionsTokens, conversationTokens } = report.tokens;
12956
+ const fixed = systemTokens + toolDefinitionsTokens;
12957
+ const total = fixed + conversationTokens;
12958
+ w(` System prompt: ${systemTokens.toLocaleString()} tok
12959
+ `);
12960
+ w(` Tool definitions: ${toolDefinitionsTokens.toLocaleString()} tok
12961
+ `);
12962
+ w(` Conversation: ${conversationTokens.toLocaleString()} tok
12963
+ `);
12964
+ if (total > 0) {
12965
+ const pct = (fixed / total * 100).toFixed(1);
12966
+ w(` ${fixed.toLocaleString()} tok of system prompt and tool definitions ships with every
12967
+ `);
12968
+ w(` request; at shutdown that was ${pct}% of the context. No hook can reach it: Copilot
12969
+ `);
12970
+ w(" assembles both natively, with nothing between assembly and send. The levers are all\n");
12971
+ w(" config: fewer MCP servers and custom tools, and --excluded-tools / --available-tools /\n");
12972
+ w(" --agent / --no-ask-user / --no-custom-instructions, whose effect on this number is\n");
12973
+ w(" entailed by how they are wired but has not been measured here. Copilot also ships\n");
12974
+ w(" built-in MCP servers, which --disable-builtin-mcps turns off wholesale. Note that\n");
12975
+ w(" --enable-all-github-mcp-tools moves this number the wrong way: its own help text calls\n");
12976
+ w(" the default a subset, so enabling everything adds definitions rather than removing\n");
12977
+ w(" them. --add-github-mcp-tool and --add-github-mcp-toolset replace that subset too.\n");
12978
+ }
12979
+ }
12980
+ w("\n## Tool definitions by MCP server (estimated)\n");
12981
+ const mcp = report.mcpTools;
12982
+ if (!mcp.cacheFound) {
12983
+ w(` No MCP tool cache at ${copilotCliMcpToolsDir()}.
12984
+ `);
12985
+ w(" That is not the same as having no MCP servers: it means nothing has been cached\n");
12986
+ w(" there yet, which is also what a Copilot that has never connected one looks like.\n");
12987
+ } else if (mcp.servers.length === 0) {
12988
+ w(" Cache is present but holds no servers, so none of the tool-definition budget\n");
12989
+ w(` above is MCP: it is all Copilot's own tools, which the flags above can narrow
12990
+ `);
12991
+ w(" but dropping a server cannot.\n");
12992
+ } else {
12993
+ for (const server of mcp.servers) {
12994
+ const tokens = server.estimatedTokens.toLocaleString();
12995
+ w(` ${server.serverName}: ${countNoun(server.toolCount, "tool")}, `);
12996
+ w(`${formatBytes(server.definitionBytes)}, ~${tokens} tok
12997
+ `);
12998
+ }
12999
+ const mcpTokens = mcp.servers.reduce((sum, server) => sum + server.estimatedTokens, 0);
13000
+ w(` ~${mcpTokens.toLocaleString()} tok estimated across `);
13001
+ w(`${countNoun(mcp.servers.length, "server")}, re-sent every request.
13002
+ `);
13003
+ if (report.tokens !== null && report.tokens.toolDefinitionsTokens > 0) {
13004
+ const share = (mcpTokens / report.tokens.toolDefinitionsTokens * 100).toFixed(1);
13005
+ w(` Copilot counted ${report.tokens.toolDefinitionsTokens.toLocaleString()} tok of tool `);
13006
+ w(`definitions in total, so this is roughly ${share}% of it.
13007
+ `);
13008
+ w(" The two are not a split of each other and come from different places: Copilot\n");
13009
+ w(" counted its own with its own tokeniser, while these are estimated from the bytes\n");
13010
+ w(` of the cached definitions. The remainder is mostly Copilot's own non-MCP tools,
13011
+ `);
13012
+ w(" which are not cached here: dropping a server does not touch them, though the\n");
13013
+ w(" tool-filter flags above still can.\n");
13014
+ }
13015
+ w(" Dropping a server saves its line above on every request for the rest of the session.\n");
13016
+ }
13017
+ if (mcp.unreadable > 0) {
13018
+ const verb = mcp.unreadable === 1 ? "is" : "are";
13019
+ w(` ${countNoun(mcp.unreadable, "cache file")} could not be read and ${verb} not counted above.
13020
+ `);
13021
+ }
13022
+ w("\n## Injected blocks in the assembled prompt\n");
13023
+ if (report.blocks.length === 0) {
13024
+ w(` none across ${countNoun(report.turns, "turn")}
13025
+ `);
13026
+ } else {
13027
+ for (const block of report.blocks) {
13028
+ const pct = block.bytes > 0 ? (block.repeatBytes / block.bytes * 100).toFixed(1) : "0.0";
13029
+ w(` ${block.kind}: ${countNoun(block.count, "injection")}, ${formatBytes(block.bytes)}`);
13030
+ w(block.repeatBytes > 0 ? `, ${formatBytes(block.repeatBytes)} re-sent verbatim (${pct}%)
13031
+ ` : "\n");
13032
+ }
13033
+ }
13034
+ w("\n## Compactions\n");
13035
+ if (report.compactions.length === 0) {
13036
+ w(` ${countNoun(report.compactions.length, "compaction")} recorded this session
13037
+ `);
13038
+ } else {
13039
+ for (const c of report.compactions) {
13040
+ w(` ${c.trigger}: summary ${formatBytes(c.summaryBytes)}, ${c.preTokens.toLocaleString()} -> ${c.postTokens.toLocaleString()} tok
13041
+ `);
13042
+ }
13043
+ }
13044
+ w("\n## On disk but never billed\n");
13045
+ w(` Hook records: ${formatBytes(report.hookRecordBytes)} of ${formatBytes(report.totalEventBytes)} in the event log.
13046
+ `);
13047
+ w(" hook.start/hook.end never reach the model, so this is log weight, not context weight.\n");
13048
+ }
12659
13049
  function printReport2(report) {
12660
13050
  const w = (text) => {
12661
13051
  process.stdout.write(text);
@@ -12708,10 +13098,71 @@ function printReport2(report) {
12708
13098
  w(` Re-send upper bound: ${ao.resendCeilingTokens} tok if every turn were resent at full price on every later request
12709
13099
  `);
12710
13100
  w(" Real cost is substantially lower: prompt caching bills resent conversation history at cache-read rates, not full input price.\n");
13101
+ printResidentContext(report.residentContext, w);
13102
+ }
13103
+ function printResidentContext(resident, w) {
13104
+ w("\n## Harness-injected context (never passes through a hook)\n");
13105
+ if (resident.attachmentClasses.length === 0) {
13106
+ w(" none\n");
13107
+ } else {
13108
+ w(` Total injected: ${formatBytes(resident.totalAttachmentBytes)} across ${countNoun(resident.attachmentClasses.length, "class", "classes")}
13109
+ `);
13110
+ for (const c of resident.attachmentClasses.slice(0, 8)) {
13111
+ w(` ${c.type}: ${countNoun(c.count, "injection")}, ${formatBytes(c.bytes)}
13112
+ `);
13113
+ }
13114
+ }
13115
+ const task = resident.latestTaskList;
13116
+ if (task !== null) {
13117
+ w("\n## Task list (re-injected in full whenever it changes)\n");
13118
+ w(` Latest: ${formatBytes(task.bytes)} (~${formatTokenEstimate(estimateTokensFromLength(task.bytes))} tok est), ${countNoun(task.itemCount, "item")}
13119
+ `);
13120
+ w(` ${task.completed} completed, ${task.inProgress} in progress, ${task.pending} pending
13121
+ `);
13122
+ w(` Descriptions: ${formatBytes(task.descriptionBytes)} total, ${formatBytes(task.completedDescriptionBytes)} of it on completed items
13123
+ `);
13124
+ w(` Injected ${countNoun(resident.taskReminderCount, "time")} this session, ${formatBytes(resident.taskReminderBytes)} cumulative
13125
+ `);
13126
+ }
13127
+ if (resident.repeatedSkillBodies.length > 0) {
13128
+ w("\n## Skill bodies injected more than once\n");
13129
+ w(" Slash expansion and the Skill tool both send the whole body every time; no hook sees either.\n");
13130
+ for (const s of resident.repeatedSkillBodies.slice(0, 8)) {
13131
+ w(` ${s.skill}: ${countNoun(s.count, "injection")}, ${formatBytes(s.bytes)} (~${formatTokenEstimate(estimateTokensFromLength(s.bytes))} tok est)
13132
+ `);
13133
+ }
13134
+ }
13135
+ w("\n## Compactions\n");
13136
+ w(` ${countNoun(resident.compactionCount, "compaction")} this session`);
13137
+ w(resident.compactionCount === 0 ? "\n" : ", each re-injecting the fixed preamble (CLAUDE.md, memory, skill and agent listings)\n");
12711
13138
  }
12712
13139
  async function runWasteCommand(opts = {}) {
13140
+ if (opts.copilot === true) {
13141
+ const eventsPath = opts.transcript !== void 0 ? path24.resolve(opts.transcript) : findLatestCopilotSession();
13142
+ if (eventsPath === null || !fs24.existsSync(eventsPath)) {
13143
+ const detail = eventsPath === null ? "no Copilot CLI session found under <copilot-home>/session-state" : `Copilot session event log not found: ${eventsPath}`;
13144
+ if (opts.json === true) {
13145
+ process.stdout.write(`${JSON.stringify({ error: detail })}
13146
+ `);
13147
+ } else {
13148
+ process.stdout.write("\n# token-goat waste (Copilot CLI)\n");
13149
+ process.stdout.write(`${detail}
13150
+ `);
13151
+ }
13152
+ process.exitCode = 1;
13153
+ return;
13154
+ }
13155
+ const copilotReport = buildCopilotWasteReport(eventsPath);
13156
+ if (opts.json === true) {
13157
+ process.stdout.write(`${JSON.stringify(copilotReport)}
13158
+ `);
13159
+ return;
13160
+ }
13161
+ printCopilotReport(copilotReport);
13162
+ return;
13163
+ }
12713
13164
  const projectRoot = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
12714
- const transcriptPath = opts.transcript !== void 0 ? path23.resolve(opts.transcript) : findLatestTranscript(projectRoot);
13165
+ const transcriptPath = opts.transcript !== void 0 ? path24.resolve(opts.transcript) : findLatestTranscript(projectRoot);
12715
13166
  if (transcriptPath === null) {
12716
13167
  if (opts.json === true) {
12717
13168
  process.stdout.write(`${JSON.stringify({ error: "no session transcript found", project: projectRoot })}
@@ -12725,7 +13176,7 @@ async function runWasteCommand(opts = {}) {
12725
13176
  process.exitCode = 1;
12726
13177
  return;
12727
13178
  }
12728
- if (!fs23.existsSync(transcriptPath)) {
13179
+ if (!fs24.existsSync(transcriptPath)) {
12729
13180
  process.stderr.write(`token-goat: transcript not found: ${transcriptPath}
12730
13181
  `);
12731
13182
  process.exitCode = 1;
@@ -12741,17 +13192,17 @@ async function runWasteCommand(opts = {}) {
12741
13192
  }
12742
13193
 
12743
13194
  // src/session_read.ts
12744
- import * as fs24 from "node:fs";
12745
- import * as path24 from "node:path";
13195
+ import * as fs25 from "node:fs";
13196
+ import * as path25 from "node:path";
12746
13197
  import * as readline2 from "node:readline";
12747
13198
  function resolveSessionTranscript(arg, opts = {}) {
12748
13199
  if (arg !== void 0 && arg !== "") {
12749
- if (fs24.existsSync(arg) && fs24.statSync(arg).isFile()) return arg;
13200
+ if (fs25.existsSync(arg) && fs25.statSync(arg).isFile()) return arg;
12750
13201
  const projectRoot2 = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
12751
13202
  const dir = projectTranscriptsDir(projectRoot2);
12752
13203
  const byId = arg.endsWith(".jsonl") ? arg : `${arg}.jsonl`;
12753
- const candidate = path24.join(dir, byId);
12754
- if (fs24.existsSync(candidate) && fs24.statSync(candidate).isFile()) return candidate;
13204
+ const candidate = path25.join(dir, byId);
13205
+ if (fs25.existsSync(candidate) && fs25.statSync(candidate).isFile()) return candidate;
12755
13206
  return null;
12756
13207
  }
12757
13208
  const projectRoot = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
@@ -12809,7 +13260,7 @@ function toolCallsForBlocks(blocks) {
12809
13260
  return blocks.filter((b) => b.type === "tool_use" && b.name !== void 0).map((b) => b.name);
12810
13261
  }
12811
13262
  async function* streamTurns(transcriptPath) {
12812
- const input = fs24.createReadStream(transcriptPath, { encoding: "utf8" });
13263
+ const input = fs25.createReadStream(transcriptPath, { encoding: "utf8" });
12813
13264
  const rl = readline2.createInterface({ input, crlfDelay: Infinity });
12814
13265
  try {
12815
13266
  let lineNumber = 0;
@@ -12907,15 +13358,15 @@ function formatSessionSlice(turns) {
12907
13358
  }
12908
13359
 
12909
13360
  // src/cli_mcp_audit.ts
12910
- import * as fs25 from "node:fs";
12911
- import * as path25 from "node:path";
13361
+ import * as fs26 from "node:fs";
13362
+ import * as path26 from "node:path";
12912
13363
  function readMcpConfig(projectRoot) {
12913
- const configPath2 = path25.join(projectRoot, ".mcp.json");
13364
+ const configPath2 = path26.join(projectRoot, ".mcp.json");
12914
13365
  try {
12915
- if (!fs25.existsSync(configPath2)) {
13366
+ if (!fs26.existsSync(configPath2)) {
12916
13367
  return null;
12917
13368
  }
12918
- const content = fs25.readFileSync(configPath2, "utf-8");
13369
+ const content = fs26.readFileSync(configPath2, "utf-8");
12919
13370
  const parsed = JSON.parse(content);
12920
13371
  return parsed.mcpServers || parsed;
12921
13372
  } catch {
@@ -13221,10 +13672,10 @@ function readBoundedText(text, file) {
13221
13672
  if (text === void 0 && file === void 0) throw new CliError("provide text or --file");
13222
13673
  let value;
13223
13674
  if (file !== void 0) {
13224
- if (fs26.statSync(file).size > CONTENT_MAX_INPUT_CHARS) {
13675
+ if (fs27.statSync(file).size > CONTENT_MAX_INPUT_CHARS) {
13225
13676
  throw new CliError(`file exceeds the ${CONTENT_MAX_INPUT_CHARS}-byte safety limit`);
13226
13677
  }
13227
- value = fs26.readFileSync(file, "utf8");
13678
+ value = fs27.readFileSync(file, "utf8");
13228
13679
  } else {
13229
13680
  if (text === void 0) throw new CliError("provide text or --file");
13230
13681
  value = text;
@@ -13361,7 +13812,7 @@ async function cmdIndex(pathArg, opts = {}) {
13361
13812
  continue;
13362
13813
  }
13363
13814
  const sha = fingerprintFile(key);
13364
- if (sha === null && !fs26.existsSync(key)) continue;
13815
+ if (sha === null && !fs27.existsSync(key)) continue;
13365
13816
  const entry = sha !== null ? getFileEntry(key, dbPath) : null;
13366
13817
  const spellingStale = entry !== null && indexedPathSpellingIsStale(entry.filePath, key);
13367
13818
  const parseUnchanged = !force && !spellingStale && sha !== null && entry?.sha === sha;
@@ -13387,7 +13838,7 @@ async function cmdIndex(pathArg, opts = {}) {
13387
13838
  }
13388
13839
  continue;
13389
13840
  }
13390
- if (!fs26.existsSync(key)) continue;
13841
+ if (!fs27.existsSync(key)) continue;
13391
13842
  }
13392
13843
  if (!embedUnchanged) {
13393
13844
  paintProgress("embedding");
@@ -13447,11 +13898,11 @@ async function cmdMcpServe() {
13447
13898
  let StdioServerTransport;
13448
13899
  try {
13449
13900
  ;
13450
- ({ createMcpServer } = await import("./token-goat-chunk-65BKISIS.mjs"));
13451
- ({ StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js"));
13901
+ ({ createMcpServer } = await import("./token-goat-chunk-FBGBTICM.mjs"));
13902
+ ({ StdioServerTransport } = await import("./token-goat-chunk-324QOJYZ.mjs"));
13452
13903
  } catch (err2) {
13453
13904
  process.stderr.write(
13454
- `token-goat: mcp-server unavailable (install @modelcontextprotocol/sdk to use this feature): ${String(err2)}
13905
+ `token-goat: mcp-server unavailable (could not load the MCP server modules): ${String(err2)}
13455
13906
  `
13456
13907
  );
13457
13908
  process.exitCode = 1;
@@ -13461,14 +13912,14 @@ async function cmdMcpServe() {
13461
13912
  const transport = new StdioServerTransport();
13462
13913
  await server.connect(transport);
13463
13914
  await new Promise((resolve12) => {
13464
- server.server.onclose = resolve12;
13915
+ server.onclose = resolve12;
13465
13916
  });
13466
13917
  }
13467
13918
  async function cmdHook(event, opts) {
13468
13919
  if (typeof opts.harness === "string" && opts.harness.length > 0) {
13469
13920
  process.env[ENV_KEYS.HARNESS_OVERRIDE] = opts.harness;
13470
13921
  }
13471
- const { relay } = await import("./token-goat-chunk-FRTBMRP7.mjs");
13922
+ const { relay } = await import("./token-goat-chunk-A62K4XW2.mjs");
13472
13923
  await relay(event);
13473
13924
  }
13474
13925
  async function cmdInstall(opts) {
@@ -13572,16 +14023,16 @@ async function cmdInstall(opts) {
13572
14023
  );
13573
14024
  }
13574
14025
  try {
13575
- const skillDir = path26.join(homedir11(), ".claude", "skills");
13576
- if (fs26.existsSync(skillDir)) {
13577
- const entries = fs26.readdirSync(skillDir, { withFileTypes: true });
14026
+ const skillDir = path27.join(homedir11(), ".claude", "skills");
14027
+ if (fs27.existsSync(skillDir)) {
14028
+ const entries = fs27.readdirSync(skillDir, { withFileTypes: true });
13578
14029
  const skillNames = [];
13579
14030
  const sessionId = getSessionId();
13580
14031
  for (const entry of entries) {
13581
14032
  if (!entry.isDirectory()) continue;
13582
- const skillFile = path26.join(skillDir, entry.name, "SKILL.md");
13583
- if (fs26.existsSync(skillFile)) {
13584
- const body = fs26.readFileSync(skillFile, "utf-8");
14033
+ const skillFile = path27.join(skillDir, entry.name, "SKILL.md");
14034
+ if (fs27.existsSync(skillFile)) {
14035
+ const body = fs27.readFileSync(skillFile, "utf-8");
13585
14036
  const compact = extractCompactFromMarker(body);
13586
14037
  if (compact === null) continue;
13587
14038
  const sourceSha = contentHash(body);
@@ -13591,10 +14042,10 @@ async function cmdInstall(opts) {
13591
14042
  }
13592
14043
  if (skillNames.length > 0) {
13593
14044
  const dir = skillOutputsDir();
13594
- await fs26.promises.mkdir(dir, { recursive: true });
13595
- const pregenPath = path26.join(dir, "pregen.json");
14045
+ await fs27.promises.mkdir(dir, { recursive: true });
14046
+ const pregenPath = path27.join(dir, "pregen.json");
13596
14047
  const pregenData = { ts: Date.now(), names: skillNames };
13597
- await fs26.promises.writeFile(pregenPath, JSON.stringify(pregenData, null, 2));
14048
+ await fs27.promises.writeFile(pregenPath, JSON.stringify(pregenData, null, 2));
13598
14049
  out(`Pre-generated ${skillNames.length} skill compacts.`);
13599
14050
  }
13600
14051
  }
@@ -13682,7 +14133,7 @@ function runPurge() {
13682
14133
  }
13683
14134
  const result = purgeDataDirectories();
13684
14135
  for (const root of result.absent) out(`Nothing to purge at ${root}.`);
13685
- for (const removed of result.removed) out(`Purged ${removed.path} (${formatBytes(removed.bytes)} reclaimed).`);
14136
+ for (const removed of result.removed) out(`Purged ${removed.path} (${formatBytes2(removed.bytes)} reclaimed).`);
13686
14137
  for (const failure of result.failed) err(`token-goat: could not purge ${failure.path}: ${failure.reason}`);
13687
14138
  }
13688
14139
  function cmdWorkerStart() {
@@ -13768,7 +14219,8 @@ function cmdWaste(opts = {}) {
13768
14219
  ...opts.project !== void 0 ? { project: opts.project } : {},
13769
14220
  ...opts.transcript !== void 0 ? { transcript: opts.transcript } : {},
13770
14221
  ...opts.json === true ? { json: true } : {},
13771
- ...opts.top !== void 0 ? { top: requireNonNegativeInt("--top", opts.top) } : {}
14222
+ ...opts.top !== void 0 ? { top: requireNonNegativeInt("--top", opts.top) } : {},
14223
+ ...opts.copilot === true ? { copilot: true } : {}
13772
14224
  });
13773
14225
  }
13774
14226
  async function cmdSessionOutline(sessionIdOrPath, opts = {}) {
@@ -13788,7 +14240,7 @@ ${formatSessionOutline(turns)}`;
13788
14240
  }
13789
14241
  function sessionTranscriptSize(transcriptPath) {
13790
14242
  try {
13791
- return fs26.statSync(transcriptPath).size;
14243
+ return fs27.statSync(transcriptPath).size;
13792
14244
  } catch {
13793
14245
  return 0;
13794
14246
  }
@@ -13913,7 +14365,7 @@ function _applyFiltersAndPrint(content, opts, fenceUntrusted = false, fenceTag =
13913
14365
  }
13914
14366
  function fileSizeOrZero(filePath) {
13915
14367
  try {
13916
- return fs26.statSync(filePath).size;
14368
+ return fs27.statSync(filePath).size;
13917
14369
  } catch {
13918
14370
  return 0;
13919
14371
  }
@@ -13928,11 +14380,11 @@ function cmdBashOutput(id, opts) {
13928
14380
  }
13929
14381
  let content;
13930
14382
  try {
13931
- const st = fs26.statSync(opts.file);
14383
+ const st = fs27.statSync(opts.file);
13932
14384
  if (st.isFIFO() || st.isSocket()) {
13933
14385
  throw new CliError(`--file '${opts.file}' is a special file (FIFO or socket) \u2014 only regular files are supported`);
13934
14386
  }
13935
- content = redactIfDotenv(opts.file, decodeSource(fs26.readFileSync(opts.file)));
14387
+ content = redactIfDotenv(opts.file, decodeSource(fs27.readFileSync(opts.file)));
13936
14388
  } catch (e) {
13937
14389
  if (e instanceof CliError) throw e;
13938
14390
  throw new CliError(`cannot read file: ${opts.file}`);
@@ -14325,7 +14777,7 @@ function emitExtraFileArgsNote(command, first, extras, opts = {}) {
14325
14777
  }
14326
14778
  async function cmdCompress(opts) {
14327
14779
  try {
14328
- const bashRunner = await import("./token-goat-chunk-CNDOJ3ZP.mjs");
14780
+ const bashRunner = await import("./token-goat-chunk-4KZILRZN.mjs");
14329
14781
  if (opts.compress === false) {
14330
14782
  process.exitCode = bashRunner.runRaw(opts.cmd, parseTimeout(opts.timeout, bashRunner.DEFAULT_TIMEOUT_SECONDS));
14331
14783
  return;
@@ -14351,7 +14803,7 @@ async function cmdSkillBody(name, opts) {
14351
14803
  if (filePath === null) {
14352
14804
  throw new CliError(`skill '${name}' not found`);
14353
14805
  }
14354
- const body = decodeSource(fs26.readFileSync(filePath));
14806
+ const body = decodeSource(fs27.readFileSync(filePath));
14355
14807
  if (opts.compact === true) {
14356
14808
  out(extractCompactFromMarker(body) ?? body);
14357
14809
  } else {
@@ -14368,7 +14820,7 @@ async function cmdSkillCompact(name, opts) {
14368
14820
  for (const skill of skills) {
14369
14821
  const filePath = await getSkillFilePath(skill.name);
14370
14822
  if (!filePath) continue;
14371
- const body2 = decodeSource(fs26.readFileSync(filePath));
14823
+ const body2 = decodeSource(fs27.readFileSync(filePath));
14372
14824
  const compact2 = extractCompactFromMarker(body2);
14373
14825
  if (compact2 === null) continue;
14374
14826
  const sourceSha2 = contentHash(body2);
@@ -14389,19 +14841,19 @@ async function cmdSkillCompact(name, opts) {
14389
14841
  if (!opts.path.trim()) {
14390
14842
  throw new CliError("--path cannot be empty");
14391
14843
  }
14392
- if (!fs26.existsSync(opts.path)) {
14844
+ if (!fs27.existsSync(opts.path)) {
14393
14845
  throw new CliError(`skill file not found: ${opts.path}`);
14394
14846
  }
14395
14847
  try {
14396
- body = fs26.readFileSync(opts.path, "utf-8");
14848
+ body = fs27.readFileSync(opts.path, "utf-8");
14397
14849
  } catch (e) {
14398
14850
  if (e.code === "ENOENT") {
14399
14851
  throw new CliError(`skill file not found: ${opts.path}`);
14400
14852
  }
14401
14853
  throw new CliError(`failed to read skill file '${opts.path}': ${extractErrorMessage(e)}`);
14402
14854
  }
14403
- cacheName = name ?? path26.basename(path26.dirname(path26.resolve(opts.path)));
14404
- sourcePath = path26.resolve(opts.path);
14855
+ cacheName = name ?? path27.basename(path27.dirname(path27.resolve(opts.path)));
14856
+ sourcePath = path27.resolve(opts.path);
14405
14857
  } else {
14406
14858
  if (name === void 0 || !name.trim()) {
14407
14859
  throw new CliError("skill-compact requires a <name> or --path <file>");
@@ -14410,7 +14862,7 @@ async function cmdSkillCompact(name, opts) {
14410
14862
  if (filePath === null) {
14411
14863
  throw new CliError(`skill '${name}' not found`);
14412
14864
  }
14413
- body = fs26.readFileSync(filePath, "utf-8");
14865
+ body = fs27.readFileSync(filePath, "utf-8");
14414
14866
  cacheName = name;
14415
14867
  sourcePath = filePath;
14416
14868
  }
@@ -14542,8 +14994,8 @@ async function cmdSkillDiff(name) {
14542
14994
  }
14543
14995
  const newer = versions[0];
14544
14996
  const older = versions[1];
14545
- const newerBody = await fs26.promises.readFile(path26.resolve(dir, `${newer.outputId}.txt`), "utf-8").catch(() => null);
14546
- const olderBody = await fs26.promises.readFile(path26.resolve(dir, `${older.outputId}.txt`), "utf-8").catch(() => null);
14997
+ const newerBody = await fs27.promises.readFile(path27.resolve(dir, `${newer.outputId}.txt`), "utf-8").catch(() => null);
14998
+ const olderBody = await fs27.promises.readFile(path27.resolve(dir, `${older.outputId}.txt`), "utf-8").catch(() => null);
14547
14999
  if (newerBody === null || olderBody === null) {
14548
15000
  out(`a cached version of '${name}' was evicted while diffing -- try again`);
14549
15001
  return;
@@ -14572,7 +15024,7 @@ async function cmdSkillSection(nameHeading, headingArg) {
14572
15024
  if (!filePath) {
14573
15025
  throw new CliError(`skill '${skillName}' not found`);
14574
15026
  }
14575
- const body = decodeSource(fs26.readFileSync(filePath));
15027
+ const body = decodeSource(fs27.readFileSync(filePath));
14576
15028
  const extracted = extractNamedSection(body, heading);
14577
15029
  if (!extracted) {
14578
15030
  process.exitCode = 1;
@@ -14582,7 +15034,7 @@ async function cmdSkillSection(nameHeading, headingArg) {
14582
15034
  }
14583
15035
  function atomicWriteBuffer(dest, data) {
14584
15036
  try {
14585
- if (fs26.statSync(dest).isDirectory()) {
15037
+ if (fs27.statSync(dest).isDirectory()) {
14586
15038
  const e = Object.assign(new Error(`EISDIR: illegal operation on a directory, open '${dest}'`), { code: "EISDIR", path: dest });
14587
15039
  throw e;
14588
15040
  }
@@ -14590,23 +15042,23 @@ function atomicWriteBuffer(dest, data) {
14590
15042
  if (e.code !== "ENOENT") throw e;
14591
15043
  }
14592
15044
  const rnd = Math.random().toString(36).slice(2, 8);
14593
- const tmp = path26.join(path26.dirname(path26.resolve(dest)), `.tmp.${process.pid}.${rnd}`);
15045
+ const tmp = path27.join(path27.dirname(path27.resolve(dest)), `.tmp.${process.pid}.${rnd}`);
14594
15046
  try {
14595
- fs26.writeFileSync(tmp, data, { mode: 384 });
15047
+ fs27.writeFileSync(tmp, data, { mode: 384 });
14596
15048
  try {
14597
- const destMode = fs26.statSync(dest).mode;
14598
- fs26.chmodSync(tmp, destMode);
15049
+ const destMode = fs27.statSync(dest).mode;
15050
+ fs27.chmodSync(tmp, destMode);
14599
15051
  } catch (e) {
14600
15052
  if (e.code !== "ENOENT") throw e;
14601
15053
  }
14602
15054
  withRetryOnLock(() => {
14603
15055
  try {
14604
- fs26.renameSync(tmp, dest);
15056
+ fs27.renameSync(tmp, dest);
14605
15057
  } catch (e) {
14606
15058
  if (e.code === "EXDEV") {
14607
- fs26.copyFileSync(tmp, dest);
15059
+ fs27.copyFileSync(tmp, dest);
14608
15060
  try {
14609
- fs26.unlinkSync(tmp);
15061
+ fs27.unlinkSync(tmp);
14610
15062
  } catch (ue) {
14611
15063
  process.stderr.write(`token-goat write-file: warning: could not remove temp file ${tmp}: ${ue.message}
14612
15064
  `);
@@ -14618,7 +15070,7 @@ function atomicWriteBuffer(dest, data) {
14618
15070
  });
14619
15071
  } catch (e) {
14620
15072
  try {
14621
- fs26.unlinkSync(tmp);
15073
+ fs27.unlinkSync(tmp);
14622
15074
  } catch {
14623
15075
  }
14624
15076
  throw e;
@@ -14628,9 +15080,9 @@ function mapFsError(e, src, dest, srcLabel = "source") {
14628
15080
  const fe = e;
14629
15081
  if (fe.code === "ENOENT") {
14630
15082
  const errPath = fe.path ?? "";
14631
- const isSource = src !== void 0 && path26.resolve(errPath) === path26.resolve(src);
15083
+ const isSource = src !== void 0 && path27.resolve(errPath) === path27.resolve(src);
14632
15084
  if (isSource) throw new CliError(`${srcLabel} file not found: ${src}`);
14633
- const destDir = dest ? path26.dirname(path26.resolve(dest)) : path26.dirname(path26.resolve(errPath || "."));
15085
+ const destDir = dest ? path27.dirname(path27.resolve(dest)) : path27.dirname(path27.resolve(errPath || "."));
14634
15086
  throw new CliError(`destination directory does not exist: ${destDir}`);
14635
15087
  }
14636
15088
  if (fe.code === "ENOTDIR") {
@@ -14641,7 +15093,7 @@ function mapFsError(e, src, dest, srcLabel = "source") {
14641
15093
  }
14642
15094
  if (fe.code === "EISDIR") {
14643
15095
  const errPath = fe.path ?? "";
14644
- const isSource = src !== void 0 && (errPath === "" || path26.resolve(errPath) === path26.resolve(src));
15096
+ const isSource = src !== void 0 && (errPath === "" || path27.resolve(errPath) === path27.resolve(src));
14645
15097
  if (isSource) throw new CliError(`source is a directory, not a file: ${src}`);
14646
15098
  throw new CliError(`destination is a directory, not a file: ${dest ?? (errPath || "(unknown)")}`);
14647
15099
  }
@@ -14710,7 +15162,7 @@ function validateWritablePath(dest, label) {
14710
15162
  throw new CliError(`${label} path contains a null byte`);
14711
15163
  }
14712
15164
  if (isWindows()) {
14713
- const base = path26.basename(dest);
15165
+ const base = path27.basename(dest);
14714
15166
  const stem = base.replace(/\.[^.]*$/, "").toUpperCase();
14715
15167
  if (WIN_RESERVED.has(stem)) {
14716
15168
  throw new CliError(`${label} '${base}' is a reserved Windows device name`);
@@ -14740,7 +15192,7 @@ function readFileBoundedRaw(filePath, label, allowStdIn = false) {
14740
15192
  throw new CliError(`${label} ${filePath} requires piped input; use ${altLabel} for interactive use`);
14741
15193
  }
14742
15194
  try {
14743
- const st = fs26.statSync(filePath);
15195
+ const st = fs27.statSync(filePath);
14744
15196
  if (st.isFIFO() || st.isSocket()) {
14745
15197
  throw new CliError(`${label} '${filePath}' is a special file (FIFO or socket) \u2014 only regular files are supported`);
14746
15198
  }
@@ -14748,7 +15200,7 @@ function readFileBoundedRaw(filePath, label, allowStdIn = false) {
14748
15200
  if (st.size > maxBytes) {
14749
15201
  throw new CliError(`${label} '${filePath}' exceeds size limit (${Math.round(st.size / 1024 / 1024)} MB); set TOKEN_GOAT_MAX_STDIN_MB to override`);
14750
15202
  }
14751
- return fs26.readFileSync(filePath);
15203
+ return fs27.readFileSync(filePath);
14752
15204
  } catch (e) {
14753
15205
  if (e instanceof CliError) throw e;
14754
15206
  mapFsError(e, filePath, void 0, label);
@@ -14792,7 +15244,7 @@ function cmdNoteAdd(file, opts) {
14792
15244
  throw new CliError("note content must be valid UTF-8 text");
14793
15245
  }
14794
15246
  const resolvedPath = resolveIndexPath(file);
14795
- if (!fs26.existsSync(resolvedPath)) {
15247
+ if (!fs27.existsSync(resolvedPath)) {
14796
15248
  throw new CliError(`File not found: '${resolvedPath}'`);
14797
15249
  }
14798
15250
  healStaleIndex(resolvedPath);
@@ -15006,14 +15458,14 @@ function writeReplacedBuffer(file, replacedBuf, preWriteStat) {
15006
15458
  const until = process.env["TOKEN_GOAT_TEST_REPLACE_DELAY_UNTIL"];
15007
15459
  const deadline = Date.now() + testDelayMs;
15008
15460
  if (until !== void 0 && until !== "") {
15009
- while (Date.now() < deadline && !fs26.existsSync(until)) sleepSync(5);
15461
+ while (Date.now() < deadline && !fs27.existsSync(until)) sleepSync(5);
15010
15462
  } else {
15011
15463
  sleepSync(testDelayMs);
15012
15464
  }
15013
15465
  }
15014
15466
  let preRenameStat;
15015
15467
  try {
15016
- preRenameStat = fs26.statSync(file);
15468
+ preRenameStat = fs27.statSync(file);
15017
15469
  } catch {
15018
15470
  }
15019
15471
  if (preRenameStat !== void 0 && (preRenameStat.mtimeMs !== preWriteStat.mtimeMs || preRenameStat.size !== preWriteStat.size)) {
@@ -15054,7 +15506,7 @@ function cmdReplace(file, opts) {
15054
15506
  const targetBuf = readFileBoundedRaw(file, "target file", true);
15055
15507
  let preWriteStat;
15056
15508
  try {
15057
- preWriteStat = fs26.statSync(file);
15509
+ preWriteStat = fs27.statSync(file);
15058
15510
  } catch {
15059
15511
  }
15060
15512
  const usingFrom = opts.oldFrom !== void 0 || opts.newFrom !== void 0;
@@ -15148,7 +15600,7 @@ function cmdInsertSection(file, opts) {
15148
15600
  }
15149
15601
  let preWriteStat;
15150
15602
  try {
15151
- preWriteStat = fs26.statSync(file);
15603
+ preWriteStat = fs27.statSync(file);
15152
15604
  } catch {
15153
15605
  }
15154
15606
  const result = readSection(file, opts.after);
@@ -15162,7 +15614,7 @@ function cmdInsertSection(file, opts) {
15162
15614
  }
15163
15615
  let rawBytes;
15164
15616
  try {
15165
- rawBytes = fs26.readFileSync(file);
15617
+ rawBytes = fs27.readFileSync(file);
15166
15618
  } catch (e) {
15167
15619
  mapFsError(e, void 0, file);
15168
15620
  }
@@ -15178,7 +15630,7 @@ function cmdInsertSection(file, opts) {
15178
15630
  if (preWriteStat !== void 0) {
15179
15631
  let preRenameStat;
15180
15632
  try {
15181
- preRenameStat = fs26.statSync(file);
15633
+ preRenameStat = fs27.statSync(file);
15182
15634
  } catch {
15183
15635
  }
15184
15636
  if (preRenameStat !== void 0 && (preRenameStat.mtimeMs !== preWriteStat.mtimeMs || preRenameStat.size !== preWriteStat.size)) {
@@ -15219,25 +15671,25 @@ ${content}`;
15219
15671
  }
15220
15672
  function expandGlobs(root, patterns, globFnOverride) {
15221
15673
  const out2 = [];
15222
- const globFn = globFnOverride ?? fs26["globSync"];
15674
+ const globFn = globFnOverride ?? fs27["globSync"];
15223
15675
  for (const p of patterns) {
15224
15676
  if (globFn !== void 0 && (p.includes("*") || p.includes("?") || p.includes("{"))) {
15225
15677
  try {
15226
15678
  const hits = globFn(p, { cwd: root });
15227
- for (const h of hits) out2.push(path26.isAbsolute(h) ? h : path26.join(root, h));
15679
+ for (const h of hits) out2.push(path27.isAbsolute(h) ? h : path27.join(root, h));
15228
15680
  continue;
15229
15681
  } catch {
15230
15682
  }
15231
15683
  }
15232
- out2.push(path26.isAbsolute(p) ? p : path26.join(root, p));
15684
+ out2.push(path27.isAbsolute(p) ? p : path27.join(root, p));
15233
15685
  }
15234
15686
  return out2;
15235
15687
  }
15236
15688
  function readIgnoreFile(root) {
15237
- const ignorePath = path26.join(root, ".tokengoatignore");
15689
+ const ignorePath = path27.join(root, ".tokengoatignore");
15238
15690
  let raw;
15239
15691
  try {
15240
- raw = fs26.readFileSync(ignorePath, "utf8");
15692
+ raw = fs27.readFileSync(ignorePath, "utf8");
15241
15693
  } catch {
15242
15694
  return void 0;
15243
15695
  }
@@ -15278,14 +15730,14 @@ function cmdPack(patterns, opts) {
15278
15730
  }
15279
15731
  let instruction;
15280
15732
  if (opts.instructionFile !== void 0) {
15281
- instruction = fs26.readFileSync(opts.instructionFile, "utf8");
15733
+ instruction = fs27.readFileSync(opts.instructionFile, "utf8");
15282
15734
  }
15283
15735
  const formatted = formatPack(result, style, {
15284
15736
  ...opts.lineNumbers === true ? { line_numbers: true } : {},
15285
15737
  ...instruction !== void 0 ? { instruction } : {}
15286
15738
  });
15287
15739
  if (opts.output !== void 0) {
15288
- fs26.writeFileSync(opts.output, formatted, "utf8");
15740
+ fs27.writeFileSync(opts.output, formatted, "utf8");
15289
15741
  } else {
15290
15742
  out(formatted);
15291
15743
  }
@@ -15303,7 +15755,7 @@ function cmdTokens(patterns, opts) {
15303
15755
  if (opts.tree === true) {
15304
15756
  const dirs = /* @__PURE__ */ new Map();
15305
15757
  for (const e of entries) {
15306
- const dir = path26.dirname(e.rel_path);
15758
+ const dir = path27.dirname(e.rel_path);
15307
15759
  if (!dirs.has(dir)) dirs.set(dir, []);
15308
15760
  dirs.get(dir).push(e);
15309
15761
  }
@@ -15313,7 +15765,7 @@ function cmdTokens(patterns, opts) {
15313
15765
  const pct = result.total_tokens > 0 ? Math.round(dirTokens / result.total_tokens * 100) : 0;
15314
15766
  lines2.push(`${dir}/ (${dirTokens} tokens, ${pct}%)`);
15315
15767
  for (const e of dirEntries) {
15316
- lines2.push(` ${path26.basename(e.rel_path).padEnd(30)} ${String(e.tokens).padStart(8)} tokens`);
15768
+ lines2.push(` ${path27.basename(e.rel_path).padEnd(30)} ${String(e.tokens).padStart(8)} tokens`);
15317
15769
  }
15318
15770
  }
15319
15771
  out(lines2.join("\n"));
@@ -15344,7 +15796,7 @@ function cmdBudget(patterns, opts) {
15344
15796
  }
15345
15797
  }
15346
15798
  function cmdFailures(src, opts) {
15347
- const text = src !== void 0 ? fs26.readFileSync(src, "utf8") : fs26.readFileSync(0, "utf8");
15799
+ const text = src !== void 0 ? fs27.readFileSync(src, "utf8") : fs27.readFileSync(0, "utf8");
15348
15800
  const result = extractFailures(text, opts.runner !== void 0 ? { runner: opts.runner } : {});
15349
15801
  if (opts.delta !== true) {
15350
15802
  out(opts.json === true ? formatFailuresJson(result) : formatFailuresText(result));
@@ -15474,39 +15926,34 @@ function buildProgram() {
15474
15926
  )
15475
15927
  );
15476
15928
  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)").action(guard(cmdSemantic));
15477
- program2.command("skeleton <file> [more...]").description('list all symbols in a file without bodies (also accepts a comma-separated file list "a,b,c" for one headed block per file)').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(
15478
- (file, more, opts) => runExitText(
15479
- () => noteExtraFileArgs(
15480
- "skeleton",
15481
- file,
15482
- more,
15483
- () => runSkeleton({
15929
+ const registerSymbolListing = (name, description, run2) => {
15930
+ 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(
15931
+ (file, more, opts) => runExitText(
15932
+ () => noteExtraFileArgs(
15933
+ name,
15484
15934
  file,
15485
- ...opts.json === true ? { json: true } : {},
15486
- ...opts.minLines !== void 0 ? { minLines: requireNonNegativeInt("--min-lines", opts.minLines) } : {},
15487
- ...opts.grep !== void 0 ? { grep: opts.grep } : {},
15488
- ...opts.forceRefresh === true ? { forceRefresh: true } : {},
15489
- ...opts.stats === true ? { stats: true } : {}
15490
- })
15935
+ more,
15936
+ () => run2({
15937
+ file,
15938
+ ...opts.json === true ? { json: true } : {},
15939
+ ...opts.minLines !== void 0 ? { minLines: requireNonNegativeInt("--min-lines", opts.minLines) } : {},
15940
+ ...opts.grep !== void 0 ? { grep: opts.grep } : {},
15941
+ ...opts.forceRefresh === true ? { forceRefresh: true } : {},
15942
+ ...opts.stats === true ? { stats: true } : {}
15943
+ })
15944
+ )
15491
15945
  )
15492
- )
15946
+ );
15947
+ };
15948
+ registerSymbolListing(
15949
+ "skeleton",
15950
+ 'list all symbols in a file without bodies (also accepts a comma-separated file list "a,b,c" for one headed block per file)',
15951
+ runSkeleton
15493
15952
  );
15494
- program2.command("outline <file> [more...]").description('list symbols with line ranges and docstrings (also accepts a comma-separated file list "a,b,c" for one headed block per file)').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(
15495
- (file, more, opts) => runExitText(
15496
- () => noteExtraFileArgs(
15497
- "outline",
15498
- file,
15499
- more,
15500
- () => runOutline({
15501
- file,
15502
- ...opts.json === true ? { json: true } : {},
15503
- ...opts.minLines !== void 0 ? { minLines: requireNonNegativeInt("--min-lines", opts.minLines) } : {},
15504
- ...opts.grep !== void 0 ? { grep: opts.grep } : {},
15505
- ...opts.forceRefresh === true ? { forceRefresh: true } : {},
15506
- ...opts.stats === true ? { stats: true } : {}
15507
- })
15508
- )
15509
- )
15953
+ registerSymbolListing(
15954
+ "outline",
15955
+ 'list symbols with line ranges and docstrings (also accepts a comma-separated file list "a,b,c" for one headed block per file)',
15956
+ runOutline
15510
15957
  );
15511
15958
  program2.command("refs <spec> [more...]").description("find references to one or more symbols (spec: file::symbol, symbol, or comma-separated a,b,c / file::a,b for a merged multi-symbol view; cross-file a.ts::x,b.ts::y is also supported). For an unambiguous TypeScript symbol, automatically type-resolves candidates via the TypeScript compiler API to drop same-named-different-symbol false positives; falls back to name-based matching when that is not possible.").option("--callers", "group references by their enclosing caller symbol").option("-l, --limit <n>", "max results").option(
15512
15959
  "--top <n>",
@@ -15548,7 +15995,7 @@ function buildProgram() {
15548
15995
  program2.command("context-stats").description("show context statistics").option("--project <path>", "project root to analyze").option("-j, --json", "output as JSON").option("--fix", "apply automatic fixes (confirm-gated; shows a diff before writing)").option("-y, --yes", "with --fix, apply without prompting (non-interactive / scripted use)").action(guard(cmdContextStats));
15549
15996
  program2.command("bootstrap-audit").description("audit Claude Code startup-context contributors without reading prompt bodies").option("--project <path>", "project root to analyze").option("--home <path>", "home directory override (for CI/testing)").option("--follow-links", "follow external symlink/junction roots and direct children").option("-j, --json", "output as JSON").option("--top <n>", "largest metadata entries to show (default 10)", "10").option("--warn-tokens <n>", "warn when total estimated startup tokens exceed n").option("--fail-tokens <n>", "fail when total estimated startup tokens exceed n").option("--warn-bytes <n>", "warn when agent/skill metadata bytes exceed n").option("--fail-bytes <n>", "fail when agent/skill metadata bytes exceed n").action(guard(cmdBootstrapAudit));
15550
15997
  program2.command("memory").description("analyze CLAUDE.md files for duplicate/overlapping content (--fix to apply safe mechanical fixes)").option("--project <path>", "project root to analyze").option("--analyze", "report-only analysis (default)").option("--fix", "remove exact-duplicate lines (confirm-gated; shows a diff before writing)").option("--yes", "apply --fix changes without prompting (non-interactive)").action(guard(cmdMemory));
15551
- program2.command("waste").description("session spend-ledger: token cost per tool/file from the current Claude Code session transcript, plus waste signals").option("--project <path>", "project root to analyze").option("--transcript <path>", "explicit transcript JSONL path (default: most-recently-modified transcript for this project)").option("--top <n>", "number of top expensive tool calls to show (default: 10)").option("--json", "output JSON").action(guard(cmdWaste));
15998
+ program2.command("waste").description("session spend-ledger: token cost per tool/file from the current Claude Code session transcript, plus waste signals").option("--project <path>", "project root to analyze").option("--transcript <path>", "explicit transcript JSONL path (default: most-recently-modified transcript for this project)").option("--top <n>", "number of top expensive tool calls to show (default: 10)").option("--copilot", "analyze a Copilot CLI session event log instead, reporting Copilot's own token split").option("--json", "output JSON").action(guard(cmdWaste));
15552
15999
  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));
15553
16000
  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));
15554
16001
  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));