token-goat 2.6.35 → 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.
- package/README.md +35 -7
- package/SECURITY.md +47 -16
- package/dist/{token-goat-chunk-I2KCEGIP.mjs → token-goat-chunk-2F6TFBZE.mjs} +1355 -408
- package/dist/token-goat-chunk-324QOJYZ.mjs +91 -0
- package/dist/{token-goat-chunk-K7W3P2TJ.mjs → token-goat-chunk-44Y77VHR.mjs} +425 -39
- package/dist/{token-goat-chunk-UIZVX3HN.mjs → token-goat-chunk-4KZILRZN.mjs} +2 -2
- package/dist/token-goat-chunk-5CVKO3DA.mjs +185 -0
- package/dist/token-goat-chunk-A62K4XW2.mjs +23 -0
- package/dist/{token-goat-chunk-SIHYQCTM.mjs → token-goat-chunk-AO6MFFTW.mjs} +331 -13
- package/dist/{token-goat-chunk-DRAIYVUI.mjs → token-goat-chunk-FBGBTICM.mjs} +4 -4
- package/dist/{token-goat-chunk-YF67EWRH.mjs → token-goat-chunk-J35AKWEQ.mjs} +5 -5
- package/dist/{token-goat-hook-chunk-4POU6OHK.mjs → token-goat-chunk-LVCBDJVE.mjs} +318 -51
- package/dist/token-goat-chunk-R4SR7MQY.mjs +486 -0
- package/dist/{token-goat-chunk-D5IZEGN2.mjs → token-goat-chunk-SRAR6DOK.mjs} +144 -147
- package/dist/{token-goat-hook-chunk-NIPXIAPA.mjs → token-goat-chunk-TUPJRK7R.mjs} +1 -1
- package/dist/{token-goat-chunk-HW4VUKJ5.mjs → token-goat-chunk-VXSYZGBA.mjs} +589 -141
- package/dist/{token-goat-chunk-DG53MVNJ.mjs → token-goat-chunk-WN5T5EW5.mjs} +212 -212
- package/dist/token-goat-hook.mjs +7 -7
- package/dist/token-goat.core.mjs +5 -5
- package/package.json +18 -15
- package/scripts/install-git-hooks.mjs +55 -0
- package/dist/token-goat-chunk-DQIT5JIF.mjs +0 -13659
- package/dist/token-goat-chunk-Y2NH6DGZ.mjs +0 -10048
- package/dist/token-goat-hook-chunk-2O7P4Z6Q.mjs +0 -6419
- package/dist/token-goat-hook-chunk-3AAH72SO.mjs +0 -15970
- package/dist/token-goat-hook-chunk-3QYSN4QV.mjs +0 -14764
- package/dist/token-goat-hook-chunk-BUOCULAM.mjs +0 -29
- package/dist/token-goat-hook-chunk-CRBMPT74.mjs +0 -900
- package/dist/token-goat-hook-chunk-D6UJEFHX.mjs +0 -153
- package/dist/token-goat-hook-chunk-J4HKWUWZ.mjs +0 -23
- package/dist/token-goat-hook-chunk-NXVT4F3S.mjs +0 -10406
- package/dist/token-goat-hook-chunk-RFRLWOQH.mjs +0 -11
- package/dist/token-goat-hook-chunk-VU6VGZBM.mjs +0 -706
- package/dist/token-goat-hook-chunk-XM4W3WCO.mjs +0 -18144
- package/dist/token-goat-hook-chunk-XUMIVYEN.mjs +0 -109
|
@@ -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-
|
|
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
|
-
|
|
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-
|
|
257
|
+
} from "./token-goat-chunk-2F6TFBZE.mjs";
|
|
247
258
|
import {
|
|
248
259
|
C,
|
|
249
260
|
CONFIG_KEY_ENV_OVERRIDES,
|
|
@@ -294,6 +305,8 @@ import {
|
|
|
294
305
|
normalizePath,
|
|
295
306
|
pad,
|
|
296
307
|
parse,
|
|
308
|
+
pushAll,
|
|
309
|
+
readConfigSource,
|
|
297
310
|
recordStat,
|
|
298
311
|
redactIfDotenv,
|
|
299
312
|
redactUrlQuery,
|
|
@@ -322,7 +335,7 @@ import {
|
|
|
322
335
|
withFileLock,
|
|
323
336
|
withRetryOnLock,
|
|
324
337
|
writeJsonSettings
|
|
325
|
-
} from "./token-goat-chunk-
|
|
338
|
+
} from "./token-goat-chunk-44Y77VHR.mjs";
|
|
326
339
|
import {
|
|
327
340
|
__export
|
|
328
341
|
} from "./token-goat-chunk-AEX54RUZ.mjs";
|
|
@@ -3438,9 +3451,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3438
3451
|
* @param {string} [path]
|
|
3439
3452
|
* @return {(string|null|Command)}
|
|
3440
3453
|
*/
|
|
3441
|
-
executableDir(
|
|
3442
|
-
if (
|
|
3443
|
-
this._executableDir =
|
|
3454
|
+
executableDir(path28) {
|
|
3455
|
+
if (path28 === void 0) return this._executableDir;
|
|
3456
|
+
this._executableDir = path28;
|
|
3444
3457
|
return this;
|
|
3445
3458
|
}
|
|
3446
3459
|
/**
|
|
@@ -3739,8 +3752,8 @@ function attemptedCommandName(argv) {
|
|
|
3739
3752
|
}
|
|
3740
3753
|
|
|
3741
3754
|
// src/cli.ts
|
|
3742
|
-
import * as
|
|
3743
|
-
import * as
|
|
3755
|
+
import * as fs27 from "fs";
|
|
3756
|
+
import * as path27 from "path";
|
|
3744
3757
|
import { homedir as homedir11 } from "os";
|
|
3745
3758
|
|
|
3746
3759
|
// src/walk_index.ts
|
|
@@ -4996,13 +5009,19 @@ function isOpenclawInstalled() {
|
|
|
4996
5009
|
|
|
4997
5010
|
// src/bridges_status.ts
|
|
4998
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";
|
|
4999
5015
|
var BRIDGE_CAPABILITY_MATRIX = [
|
|
5000
5016
|
{
|
|
5001
5017
|
harness: "claudecode",
|
|
5002
5018
|
label: "Claude Code",
|
|
5003
5019
|
sourceFile: "src/install.ts (HOOK_EVENT_MAP)",
|
|
5004
|
-
implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact", "user_prompt_submit", "subagent_stop", "session_start"]),
|
|
5005
|
-
reasons: [
|
|
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
|
+
]
|
|
5006
5025
|
},
|
|
5007
5026
|
{
|
|
5008
5027
|
harness: "codex",
|
|
@@ -5010,6 +5029,8 @@ var BRIDGE_CAPABILITY_MATRIX = [
|
|
|
5010
5029
|
sourceFile: "src/bridges/codex_install.ts (CODEX_HOOK_EVENTS, CODEX_GLOBAL_HOOK_EVENTS)",
|
|
5011
5030
|
implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact", "user_prompt_submit", "subagent_stop"]),
|
|
5012
5031
|
reasons: [
|
|
5032
|
+
{ events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
|
|
5033
|
+
{ events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
|
|
5013
5034
|
{ events: ["notification", "stop"], reason: NO_SERVER_HANDLER_REASON },
|
|
5014
5035
|
{
|
|
5015
5036
|
events: ["session_start"],
|
|
@@ -5023,6 +5044,8 @@ var BRIDGE_CAPABILITY_MATRIX = [
|
|
|
5023
5044
|
sourceFile: "src/bridges/grok_install.ts (GROK_HOOK_EVENTS)",
|
|
5024
5045
|
implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact", "user_prompt_submit", "subagent_stop"]),
|
|
5025
5046
|
reasons: [
|
|
5047
|
+
{ events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
|
|
5048
|
+
{ events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
|
|
5026
5049
|
{ events: ["notification", "stop"], reason: NO_SERVER_HANDLER_REASON },
|
|
5027
5050
|
{
|
|
5028
5051
|
events: ["session_start"],
|
|
@@ -5030,6 +5053,55 @@ var BRIDGE_CAPABILITY_MATRIX = [
|
|
|
5030
5053
|
}
|
|
5031
5054
|
]
|
|
5032
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
|
+
*/
|
|
5033
5105
|
{
|
|
5034
5106
|
harness: "copilot_cli",
|
|
5035
5107
|
label: "Copilot CLI",
|
|
@@ -5041,12 +5113,14 @@ var BRIDGE_CAPABILITY_MATRIX = [
|
|
|
5041
5113
|
"pre_compact",
|
|
5042
5114
|
"stop",
|
|
5043
5115
|
"subagent_stop",
|
|
5044
|
-
"user_prompt_submit"
|
|
5116
|
+
"user_prompt_submit",
|
|
5117
|
+
"post_tool_use_failure"
|
|
5045
5118
|
]),
|
|
5046
5119
|
reasons: [
|
|
5120
|
+
{ events: ["post_compact"], reason: COPILOT_NO_POST_COMPACT_REASON },
|
|
5047
5121
|
{
|
|
5048
5122
|
events: ["notification"],
|
|
5049
|
-
reason: "Copilot CLI has a real 'notification' hook event, but copilot_cli.ts's COPILOT_TO_TG_EVENT deliberately leaves it (and sessionEnd/
|
|
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"
|
|
5050
5124
|
}
|
|
5051
5125
|
]
|
|
5052
5126
|
},
|
|
@@ -5056,6 +5130,8 @@ var BRIDGE_CAPABILITY_MATRIX = [
|
|
|
5056
5130
|
sourceFile: "src/bridges/gemini_install.ts (GEMINI_HOOK_EVENTS)",
|
|
5057
5131
|
implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact"]),
|
|
5058
5132
|
reasons: [
|
|
5133
|
+
{ events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
|
|
5134
|
+
{ events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
|
|
5059
5135
|
{
|
|
5060
5136
|
events: ["notification", "stop", "user_prompt_submit", "subagent_stop", "session_start"],
|
|
5061
5137
|
reason: `Gemini CLI's hooks integration only wires BeforeTool/AfterTool/PreCompress (README "Gemini CLI users")`
|
|
@@ -5068,6 +5144,8 @@ var BRIDGE_CAPABILITY_MATRIX = [
|
|
|
5068
5144
|
sourceFile: "src/bridges/qwen_install.ts (QWEN_HOOK_EVENTS)",
|
|
5069
5145
|
implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact", "user_prompt_submit", "subagent_stop"]),
|
|
5070
5146
|
reasons: [
|
|
5147
|
+
{ events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
|
|
5148
|
+
{ events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
|
|
5071
5149
|
{
|
|
5072
5150
|
events: ["notification"],
|
|
5073
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)"
|
|
@@ -5084,7 +5162,11 @@ var BRIDGE_CAPABILITY_MATRIX = [
|
|
|
5084
5162
|
label: "Kimi Code CLI",
|
|
5085
5163
|
sourceFile: "src/bridges/kimi_install.ts (KIMI_EVENT_ARG), src/bridges/kimi.ts (KIMI_HOOK_SCRIPT)",
|
|
5086
5164
|
implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact", "user_prompt_submit", "subagent_stop", "session_start"]),
|
|
5087
|
-
reasons: [
|
|
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
|
+
]
|
|
5088
5170
|
},
|
|
5089
5171
|
{
|
|
5090
5172
|
harness: "opencode",
|
|
@@ -5092,6 +5174,8 @@ var BRIDGE_CAPABILITY_MATRIX = [
|
|
|
5092
5174
|
sourceFile: "src/bridges/opencode.ts",
|
|
5093
5175
|
implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact"]),
|
|
5094
5176
|
reasons: [
|
|
5177
|
+
{ events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
|
|
5178
|
+
{ events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
|
|
5095
5179
|
{
|
|
5096
5180
|
events: ["notification", "stop", "user_prompt_submit", "subagent_stop", "session_start"],
|
|
5097
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)"
|
|
@@ -5104,6 +5188,8 @@ var BRIDGE_CAPABILITY_MATRIX = [
|
|
|
5104
5188
|
sourceFile: "src/bridges/openclaw.ts",
|
|
5105
5189
|
implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact"]),
|
|
5106
5190
|
reasons: [
|
|
5191
|
+
{ events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
|
|
5192
|
+
{ events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
|
|
5107
5193
|
{
|
|
5108
5194
|
events: ["notification", "stop", "user_prompt_submit", "subagent_stop", "session_start"],
|
|
5109
5195
|
reason: "OpenClaw's in-process plugin API only exposes before_tool_call/after_tool_call/before_compaction as api.on() handlers relevant here"
|
|
@@ -5116,6 +5202,8 @@ var BRIDGE_CAPABILITY_MATRIX = [
|
|
|
5116
5202
|
sourceFile: "src/bridges/pi.ts",
|
|
5117
5203
|
implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact"]),
|
|
5118
5204
|
reasons: [
|
|
5205
|
+
{ events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
|
|
5206
|
+
{ events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
|
|
5119
5207
|
{
|
|
5120
5208
|
events: ["notification", "stop", "user_prompt_submit", "subagent_stop", "session_start"],
|
|
5121
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()"
|
|
@@ -5482,7 +5570,7 @@ function purgeDataDirectories() {
|
|
|
5482
5570
|
}
|
|
5483
5571
|
return result;
|
|
5484
5572
|
}
|
|
5485
|
-
function
|
|
5573
|
+
function formatBytes2(bytes) {
|
|
5486
5574
|
if (bytes < 1024) return `${bytes} B`;
|
|
5487
5575
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
5488
5576
|
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
@@ -5503,8 +5591,8 @@ function formatTopFiles(ranked) {
|
|
|
5503
5591
|
if (ranked.length === 0) return "";
|
|
5504
5592
|
const lines = ["Top files this session:"];
|
|
5505
5593
|
for (const { path: filePath, count } of ranked) {
|
|
5506
|
-
const
|
|
5507
|
-
lines.push(` ${count.toString().padStart(3)}x ${
|
|
5594
|
+
const basename8 = path10.basename(filePath);
|
|
5595
|
+
lines.push(` ${count.toString().padStart(3)}x ${basename8} (${filePath})`);
|
|
5508
5596
|
}
|
|
5509
5597
|
return lines.join("\n");
|
|
5510
5598
|
}
|
|
@@ -5854,7 +5942,7 @@ function checkInstall() {
|
|
|
5854
5942
|
}
|
|
5855
5943
|
}
|
|
5856
5944
|
function checkTsCompiler() {
|
|
5857
|
-
if (
|
|
5945
|
+
if (isAvailable2()) {
|
|
5858
5946
|
return { name: "TypeScript compiler", status: "ok", message: "available" };
|
|
5859
5947
|
}
|
|
5860
5948
|
const err2 = loadError();
|
|
@@ -5864,6 +5952,27 @@ function checkTsCompiler() {
|
|
|
5864
5952
|
message: err2 !== null ? `unavailable: ${extractErrorMessage(err2)}` : "unavailable (not attempted)"
|
|
5865
5953
|
};
|
|
5866
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
|
+
}
|
|
5867
5976
|
function checkConfigValid(configPath2) {
|
|
5868
5977
|
if (!fs12.existsSync(configPath2)) {
|
|
5869
5978
|
return {
|
|
@@ -5873,7 +5982,7 @@ function checkConfigValid(configPath2) {
|
|
|
5873
5982
|
};
|
|
5874
5983
|
}
|
|
5875
5984
|
try {
|
|
5876
|
-
const content =
|
|
5985
|
+
const content = readConfigSource(configPath2);
|
|
5877
5986
|
parse(content);
|
|
5878
5987
|
return {
|
|
5879
5988
|
name: "Config",
|
|
@@ -6062,6 +6171,46 @@ function dataDirPermissionResult(dataDirPath) {
|
|
|
6062
6171
|
return { name: "Security data dir", status: "warn", message: "could not be read, so its permissions are unknown" };
|
|
6063
6172
|
}
|
|
6064
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
|
+
}
|
|
6065
6214
|
function runDoctor(dataDir2, configPath2, rootDir, processes) {
|
|
6066
6215
|
const results = [];
|
|
6067
6216
|
const actualDataDir = dataDir2 || dataDir();
|
|
@@ -6073,8 +6222,10 @@ function runDoctor(dataDir2, configPath2, rootDir, processes) {
|
|
|
6073
6222
|
results.push(checkSymbolBodySize(path11.join(actualDataDir, "global.db")));
|
|
6074
6223
|
results.push(checkSymbolCount(path11.join(actualDataDir, "global.db"), rootDir));
|
|
6075
6224
|
results.push(checkDirtyQueueHealth(actualDataDir));
|
|
6225
|
+
results.push(checkCompactionChannel(path11.join(actualDataDir, "global.db")));
|
|
6076
6226
|
const actualConfigPath = configPath2 || configPath();
|
|
6077
6227
|
results.push(checkConfigValid(actualConfigPath));
|
|
6228
|
+
results.push(checkEmbeddings(loadConfig(rootDir)));
|
|
6078
6229
|
for (const result of checkSecurityPosture(loadConfig(rootDir), actualDataDir)) results.push(result);
|
|
6079
6230
|
results.push(checkDiskSpace(actualDataDir));
|
|
6080
6231
|
const copilotResult = checkCopilotCli(copilotCliConfigPath(), copilotCliScriptPath());
|
|
@@ -9561,7 +9712,7 @@ function collectTodoFiles(patterns) {
|
|
|
9561
9712
|
try {
|
|
9562
9713
|
const stat = fs16.statSync(abs);
|
|
9563
9714
|
if (stat.isDirectory()) {
|
|
9564
|
-
results
|
|
9715
|
+
pushAll(results, walkProject(abs).files);
|
|
9565
9716
|
} else {
|
|
9566
9717
|
results.push(abs);
|
|
9567
9718
|
}
|
|
@@ -9579,7 +9730,7 @@ function cmdTodo(patterns, opts) {
|
|
|
9579
9730
|
const files = collectTodoFiles(patterns);
|
|
9580
9731
|
const items = [];
|
|
9581
9732
|
for (const f of files) {
|
|
9582
|
-
items
|
|
9733
|
+
pushAll(items, scanFileForTodos(f, kindSet));
|
|
9583
9734
|
}
|
|
9584
9735
|
if (opts.json === true) {
|
|
9585
9736
|
process.stdout.write(JSON.stringify({ items }, null, 2) + "\n");
|
|
@@ -11332,7 +11483,7 @@ function reclaimIndex(dbPath, opts = {}) {
|
|
|
11332
11483
|
db.prepare(`DELETE FROM "${table}"`).run();
|
|
11333
11484
|
dropped[table] = before;
|
|
11334
11485
|
}
|
|
11335
|
-
})();
|
|
11486
|
+
}).immediate();
|
|
11336
11487
|
if (tableExists(db, "symbols_fts")) {
|
|
11337
11488
|
try {
|
|
11338
11489
|
db.prepare(`INSERT INTO symbols_fts(symbols_fts) VALUES('rebuild')`).run();
|
|
@@ -11701,8 +11852,7 @@ function cmdConfig(opts) {
|
|
|
11701
11852
|
let raw = {};
|
|
11702
11853
|
let parseErr = null;
|
|
11703
11854
|
try {
|
|
11704
|
-
|
|
11705
|
-
raw = parse(text);
|
|
11855
|
+
raw = parse(readConfigSource(cfgFile));
|
|
11706
11856
|
} catch (e) {
|
|
11707
11857
|
const code = e.code;
|
|
11708
11858
|
if (code !== "ENOENT") {
|
|
@@ -12407,8 +12557,8 @@ async function runMemoryCommand(opts = {}) {
|
|
|
12407
12557
|
}
|
|
12408
12558
|
|
|
12409
12559
|
// src/cli_waste.ts
|
|
12410
|
-
import * as
|
|
12411
|
-
import * as
|
|
12560
|
+
import * as fs24 from "node:fs";
|
|
12561
|
+
import * as path24 from "node:path";
|
|
12412
12562
|
|
|
12413
12563
|
// src/waste.ts
|
|
12414
12564
|
import * as fs22 from "node:fs";
|
|
@@ -12493,6 +12643,7 @@ function parseTranscript2(transcriptPath) {
|
|
|
12493
12643
|
const calls = [];
|
|
12494
12644
|
const resultTextById = /* @__PURE__ */ new Map();
|
|
12495
12645
|
const assistantTurns = [];
|
|
12646
|
+
const resident = createResidentContextStats();
|
|
12496
12647
|
let seq = 0;
|
|
12497
12648
|
for (const line of raw.split("\n")) {
|
|
12498
12649
|
const trimmed = line.trim();
|
|
@@ -12505,6 +12656,7 @@ function parseTranscript2(transcriptPath) {
|
|
|
12505
12656
|
}
|
|
12506
12657
|
if (obj === null || typeof obj !== "object") continue;
|
|
12507
12658
|
const o = obj;
|
|
12659
|
+
accumulateResidentLine(resident, o, trimmed.length);
|
|
12508
12660
|
const message = o["message"];
|
|
12509
12661
|
if (message === null || typeof message !== "object") continue;
|
|
12510
12662
|
const messageObj = message;
|
|
@@ -12544,7 +12696,7 @@ function parseTranscript2(transcriptPath) {
|
|
|
12544
12696
|
}
|
|
12545
12697
|
if (role === "assistant" && sawTextBlock) assistantTurns.push(turnTextTokens);
|
|
12546
12698
|
}
|
|
12547
|
-
return { calls, resultTextById, assistantTurns };
|
|
12699
|
+
return { calls, resultTextById, assistantTurns, resident };
|
|
12548
12700
|
}
|
|
12549
12701
|
function costPerCall(parsed) {
|
|
12550
12702
|
return parsed.calls.map((c) => ({
|
|
@@ -12650,11 +12802,250 @@ async function buildWasteReport(transcriptPath, opts = {}) {
|
|
|
12650
12802
|
topCalls: topExpensiveCalls(costs, topN).map((c) => ({ seq: c.seq, name: c.name, summary: c.summary, tokens: c.tokens })),
|
|
12651
12803
|
neverTouchedAgain: neverTouchedAgain(costs),
|
|
12652
12804
|
repeatedUncompressedBash: await repeatedUncompressedBashCommands(costs),
|
|
12653
|
-
assistantOutput: assistantOutputCost(parsed.assistantTurns)
|
|
12805
|
+
assistantOutput: assistantOutputCost(parsed.assistantTurns),
|
|
12806
|
+
residentContext: summarizeResidentContext(parsed.resident)
|
|
12654
12807
|
};
|
|
12655
12808
|
}
|
|
12656
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
|
+
|
|
12657
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
|
+
}
|
|
12658
13049
|
function printReport2(report) {
|
|
12659
13050
|
const w = (text) => {
|
|
12660
13051
|
process.stdout.write(text);
|
|
@@ -12707,10 +13098,71 @@ function printReport2(report) {
|
|
|
12707
13098
|
w(` Re-send upper bound: ${ao.resendCeilingTokens} tok if every turn were resent at full price on every later request
|
|
12708
13099
|
`);
|
|
12709
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");
|
|
12710
13138
|
}
|
|
12711
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
|
+
}
|
|
12712
13164
|
const projectRoot = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
|
|
12713
|
-
const transcriptPath = opts.transcript !== void 0 ?
|
|
13165
|
+
const transcriptPath = opts.transcript !== void 0 ? path24.resolve(opts.transcript) : findLatestTranscript(projectRoot);
|
|
12714
13166
|
if (transcriptPath === null) {
|
|
12715
13167
|
if (opts.json === true) {
|
|
12716
13168
|
process.stdout.write(`${JSON.stringify({ error: "no session transcript found", project: projectRoot })}
|
|
@@ -12724,7 +13176,7 @@ async function runWasteCommand(opts = {}) {
|
|
|
12724
13176
|
process.exitCode = 1;
|
|
12725
13177
|
return;
|
|
12726
13178
|
}
|
|
12727
|
-
if (!
|
|
13179
|
+
if (!fs24.existsSync(transcriptPath)) {
|
|
12728
13180
|
process.stderr.write(`token-goat: transcript not found: ${transcriptPath}
|
|
12729
13181
|
`);
|
|
12730
13182
|
process.exitCode = 1;
|
|
@@ -12740,17 +13192,17 @@ async function runWasteCommand(opts = {}) {
|
|
|
12740
13192
|
}
|
|
12741
13193
|
|
|
12742
13194
|
// src/session_read.ts
|
|
12743
|
-
import * as
|
|
12744
|
-
import * as
|
|
13195
|
+
import * as fs25 from "node:fs";
|
|
13196
|
+
import * as path25 from "node:path";
|
|
12745
13197
|
import * as readline2 from "node:readline";
|
|
12746
13198
|
function resolveSessionTranscript(arg, opts = {}) {
|
|
12747
13199
|
if (arg !== void 0 && arg !== "") {
|
|
12748
|
-
if (
|
|
13200
|
+
if (fs25.existsSync(arg) && fs25.statSync(arg).isFile()) return arg;
|
|
12749
13201
|
const projectRoot2 = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
|
|
12750
13202
|
const dir = projectTranscriptsDir(projectRoot2);
|
|
12751
13203
|
const byId = arg.endsWith(".jsonl") ? arg : `${arg}.jsonl`;
|
|
12752
|
-
const candidate =
|
|
12753
|
-
if (
|
|
13204
|
+
const candidate = path25.join(dir, byId);
|
|
13205
|
+
if (fs25.existsSync(candidate) && fs25.statSync(candidate).isFile()) return candidate;
|
|
12754
13206
|
return null;
|
|
12755
13207
|
}
|
|
12756
13208
|
const projectRoot = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
|
|
@@ -12808,7 +13260,7 @@ function toolCallsForBlocks(blocks) {
|
|
|
12808
13260
|
return blocks.filter((b) => b.type === "tool_use" && b.name !== void 0).map((b) => b.name);
|
|
12809
13261
|
}
|
|
12810
13262
|
async function* streamTurns(transcriptPath) {
|
|
12811
|
-
const input =
|
|
13263
|
+
const input = fs25.createReadStream(transcriptPath, { encoding: "utf8" });
|
|
12812
13264
|
const rl = readline2.createInterface({ input, crlfDelay: Infinity });
|
|
12813
13265
|
try {
|
|
12814
13266
|
let lineNumber = 0;
|
|
@@ -12906,15 +13358,15 @@ function formatSessionSlice(turns) {
|
|
|
12906
13358
|
}
|
|
12907
13359
|
|
|
12908
13360
|
// src/cli_mcp_audit.ts
|
|
12909
|
-
import * as
|
|
12910
|
-
import * as
|
|
13361
|
+
import * as fs26 from "node:fs";
|
|
13362
|
+
import * as path26 from "node:path";
|
|
12911
13363
|
function readMcpConfig(projectRoot) {
|
|
12912
|
-
const configPath2 =
|
|
13364
|
+
const configPath2 = path26.join(projectRoot, ".mcp.json");
|
|
12913
13365
|
try {
|
|
12914
|
-
if (!
|
|
13366
|
+
if (!fs26.existsSync(configPath2)) {
|
|
12915
13367
|
return null;
|
|
12916
13368
|
}
|
|
12917
|
-
const content =
|
|
13369
|
+
const content = fs26.readFileSync(configPath2, "utf-8");
|
|
12918
13370
|
const parsed = JSON.parse(content);
|
|
12919
13371
|
return parsed.mcpServers || parsed;
|
|
12920
13372
|
} catch {
|
|
@@ -13220,10 +13672,10 @@ function readBoundedText(text, file) {
|
|
|
13220
13672
|
if (text === void 0 && file === void 0) throw new CliError("provide text or --file");
|
|
13221
13673
|
let value;
|
|
13222
13674
|
if (file !== void 0) {
|
|
13223
|
-
if (
|
|
13675
|
+
if (fs27.statSync(file).size > CONTENT_MAX_INPUT_CHARS) {
|
|
13224
13676
|
throw new CliError(`file exceeds the ${CONTENT_MAX_INPUT_CHARS}-byte safety limit`);
|
|
13225
13677
|
}
|
|
13226
|
-
value =
|
|
13678
|
+
value = fs27.readFileSync(file, "utf8");
|
|
13227
13679
|
} else {
|
|
13228
13680
|
if (text === void 0) throw new CliError("provide text or --file");
|
|
13229
13681
|
value = text;
|
|
@@ -13360,7 +13812,7 @@ async function cmdIndex(pathArg, opts = {}) {
|
|
|
13360
13812
|
continue;
|
|
13361
13813
|
}
|
|
13362
13814
|
const sha = fingerprintFile(key);
|
|
13363
|
-
if (sha === null && !
|
|
13815
|
+
if (sha === null && !fs27.existsSync(key)) continue;
|
|
13364
13816
|
const entry = sha !== null ? getFileEntry(key, dbPath) : null;
|
|
13365
13817
|
const spellingStale = entry !== null && indexedPathSpellingIsStale(entry.filePath, key);
|
|
13366
13818
|
const parseUnchanged = !force && !spellingStale && sha !== null && entry?.sha === sha;
|
|
@@ -13386,7 +13838,7 @@ async function cmdIndex(pathArg, opts = {}) {
|
|
|
13386
13838
|
}
|
|
13387
13839
|
continue;
|
|
13388
13840
|
}
|
|
13389
|
-
if (!
|
|
13841
|
+
if (!fs27.existsSync(key)) continue;
|
|
13390
13842
|
}
|
|
13391
13843
|
if (!embedUnchanged) {
|
|
13392
13844
|
paintProgress("embedding");
|
|
@@ -13446,11 +13898,11 @@ async function cmdMcpServe() {
|
|
|
13446
13898
|
let StdioServerTransport;
|
|
13447
13899
|
try {
|
|
13448
13900
|
;
|
|
13449
|
-
({ createMcpServer } = await import("./token-goat-chunk-
|
|
13450
|
-
({ StdioServerTransport } = await import("
|
|
13901
|
+
({ createMcpServer } = await import("./token-goat-chunk-FBGBTICM.mjs"));
|
|
13902
|
+
({ StdioServerTransport } = await import("./token-goat-chunk-324QOJYZ.mjs"));
|
|
13451
13903
|
} catch (err2) {
|
|
13452
13904
|
process.stderr.write(
|
|
13453
|
-
`token-goat: mcp-server unavailable (
|
|
13905
|
+
`token-goat: mcp-server unavailable (could not load the MCP server modules): ${String(err2)}
|
|
13454
13906
|
`
|
|
13455
13907
|
);
|
|
13456
13908
|
process.exitCode = 1;
|
|
@@ -13460,14 +13912,14 @@ async function cmdMcpServe() {
|
|
|
13460
13912
|
const transport = new StdioServerTransport();
|
|
13461
13913
|
await server.connect(transport);
|
|
13462
13914
|
await new Promise((resolve12) => {
|
|
13463
|
-
server.
|
|
13915
|
+
server.onclose = resolve12;
|
|
13464
13916
|
});
|
|
13465
13917
|
}
|
|
13466
13918
|
async function cmdHook(event, opts) {
|
|
13467
13919
|
if (typeof opts.harness === "string" && opts.harness.length > 0) {
|
|
13468
13920
|
process.env[ENV_KEYS.HARNESS_OVERRIDE] = opts.harness;
|
|
13469
13921
|
}
|
|
13470
|
-
const { relay } = await import("./token-goat-chunk-
|
|
13922
|
+
const { relay } = await import("./token-goat-chunk-A62K4XW2.mjs");
|
|
13471
13923
|
await relay(event);
|
|
13472
13924
|
}
|
|
13473
13925
|
async function cmdInstall(opts) {
|
|
@@ -13571,16 +14023,16 @@ async function cmdInstall(opts) {
|
|
|
13571
14023
|
);
|
|
13572
14024
|
}
|
|
13573
14025
|
try {
|
|
13574
|
-
const skillDir =
|
|
13575
|
-
if (
|
|
13576
|
-
const entries =
|
|
14026
|
+
const skillDir = path27.join(homedir11(), ".claude", "skills");
|
|
14027
|
+
if (fs27.existsSync(skillDir)) {
|
|
14028
|
+
const entries = fs27.readdirSync(skillDir, { withFileTypes: true });
|
|
13577
14029
|
const skillNames = [];
|
|
13578
14030
|
const sessionId = getSessionId();
|
|
13579
14031
|
for (const entry of entries) {
|
|
13580
14032
|
if (!entry.isDirectory()) continue;
|
|
13581
|
-
const skillFile =
|
|
13582
|
-
if (
|
|
13583
|
-
const body =
|
|
14033
|
+
const skillFile = path27.join(skillDir, entry.name, "SKILL.md");
|
|
14034
|
+
if (fs27.existsSync(skillFile)) {
|
|
14035
|
+
const body = fs27.readFileSync(skillFile, "utf-8");
|
|
13584
14036
|
const compact = extractCompactFromMarker(body);
|
|
13585
14037
|
if (compact === null) continue;
|
|
13586
14038
|
const sourceSha = contentHash(body);
|
|
@@ -13590,10 +14042,10 @@ async function cmdInstall(opts) {
|
|
|
13590
14042
|
}
|
|
13591
14043
|
if (skillNames.length > 0) {
|
|
13592
14044
|
const dir = skillOutputsDir();
|
|
13593
|
-
await
|
|
13594
|
-
const pregenPath =
|
|
14045
|
+
await fs27.promises.mkdir(dir, { recursive: true });
|
|
14046
|
+
const pregenPath = path27.join(dir, "pregen.json");
|
|
13595
14047
|
const pregenData = { ts: Date.now(), names: skillNames };
|
|
13596
|
-
await
|
|
14048
|
+
await fs27.promises.writeFile(pregenPath, JSON.stringify(pregenData, null, 2));
|
|
13597
14049
|
out(`Pre-generated ${skillNames.length} skill compacts.`);
|
|
13598
14050
|
}
|
|
13599
14051
|
}
|
|
@@ -13681,7 +14133,7 @@ function runPurge() {
|
|
|
13681
14133
|
}
|
|
13682
14134
|
const result = purgeDataDirectories();
|
|
13683
14135
|
for (const root of result.absent) out(`Nothing to purge at ${root}.`);
|
|
13684
|
-
for (const removed of result.removed) out(`Purged ${removed.path} (${
|
|
14136
|
+
for (const removed of result.removed) out(`Purged ${removed.path} (${formatBytes2(removed.bytes)} reclaimed).`);
|
|
13685
14137
|
for (const failure of result.failed) err(`token-goat: could not purge ${failure.path}: ${failure.reason}`);
|
|
13686
14138
|
}
|
|
13687
14139
|
function cmdWorkerStart() {
|
|
@@ -13767,7 +14219,8 @@ function cmdWaste(opts = {}) {
|
|
|
13767
14219
|
...opts.project !== void 0 ? { project: opts.project } : {},
|
|
13768
14220
|
...opts.transcript !== void 0 ? { transcript: opts.transcript } : {},
|
|
13769
14221
|
...opts.json === true ? { json: true } : {},
|
|
13770
|
-
...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 } : {}
|
|
13771
14224
|
});
|
|
13772
14225
|
}
|
|
13773
14226
|
async function cmdSessionOutline(sessionIdOrPath, opts = {}) {
|
|
@@ -13787,7 +14240,7 @@ ${formatSessionOutline(turns)}`;
|
|
|
13787
14240
|
}
|
|
13788
14241
|
function sessionTranscriptSize(transcriptPath) {
|
|
13789
14242
|
try {
|
|
13790
|
-
return
|
|
14243
|
+
return fs27.statSync(transcriptPath).size;
|
|
13791
14244
|
} catch {
|
|
13792
14245
|
return 0;
|
|
13793
14246
|
}
|
|
@@ -13912,7 +14365,7 @@ function _applyFiltersAndPrint(content, opts, fenceUntrusted = false, fenceTag =
|
|
|
13912
14365
|
}
|
|
13913
14366
|
function fileSizeOrZero(filePath) {
|
|
13914
14367
|
try {
|
|
13915
|
-
return
|
|
14368
|
+
return fs27.statSync(filePath).size;
|
|
13916
14369
|
} catch {
|
|
13917
14370
|
return 0;
|
|
13918
14371
|
}
|
|
@@ -13927,11 +14380,11 @@ function cmdBashOutput(id, opts) {
|
|
|
13927
14380
|
}
|
|
13928
14381
|
let content;
|
|
13929
14382
|
try {
|
|
13930
|
-
const st =
|
|
14383
|
+
const st = fs27.statSync(opts.file);
|
|
13931
14384
|
if (st.isFIFO() || st.isSocket()) {
|
|
13932
14385
|
throw new CliError(`--file '${opts.file}' is a special file (FIFO or socket) \u2014 only regular files are supported`);
|
|
13933
14386
|
}
|
|
13934
|
-
content = redactIfDotenv(opts.file, decodeSource(
|
|
14387
|
+
content = redactIfDotenv(opts.file, decodeSource(fs27.readFileSync(opts.file)));
|
|
13935
14388
|
} catch (e) {
|
|
13936
14389
|
if (e instanceof CliError) throw e;
|
|
13937
14390
|
throw new CliError(`cannot read file: ${opts.file}`);
|
|
@@ -14324,7 +14777,7 @@ function emitExtraFileArgsNote(command, first, extras, opts = {}) {
|
|
|
14324
14777
|
}
|
|
14325
14778
|
async function cmdCompress(opts) {
|
|
14326
14779
|
try {
|
|
14327
|
-
const bashRunner = await import("./token-goat-chunk-
|
|
14780
|
+
const bashRunner = await import("./token-goat-chunk-4KZILRZN.mjs");
|
|
14328
14781
|
if (opts.compress === false) {
|
|
14329
14782
|
process.exitCode = bashRunner.runRaw(opts.cmd, parseTimeout(opts.timeout, bashRunner.DEFAULT_TIMEOUT_SECONDS));
|
|
14330
14783
|
return;
|
|
@@ -14350,7 +14803,7 @@ async function cmdSkillBody(name, opts) {
|
|
|
14350
14803
|
if (filePath === null) {
|
|
14351
14804
|
throw new CliError(`skill '${name}' not found`);
|
|
14352
14805
|
}
|
|
14353
|
-
const body = decodeSource(
|
|
14806
|
+
const body = decodeSource(fs27.readFileSync(filePath));
|
|
14354
14807
|
if (opts.compact === true) {
|
|
14355
14808
|
out(extractCompactFromMarker(body) ?? body);
|
|
14356
14809
|
} else {
|
|
@@ -14367,7 +14820,7 @@ async function cmdSkillCompact(name, opts) {
|
|
|
14367
14820
|
for (const skill of skills) {
|
|
14368
14821
|
const filePath = await getSkillFilePath(skill.name);
|
|
14369
14822
|
if (!filePath) continue;
|
|
14370
|
-
const body2 = decodeSource(
|
|
14823
|
+
const body2 = decodeSource(fs27.readFileSync(filePath));
|
|
14371
14824
|
const compact2 = extractCompactFromMarker(body2);
|
|
14372
14825
|
if (compact2 === null) continue;
|
|
14373
14826
|
const sourceSha2 = contentHash(body2);
|
|
@@ -14388,19 +14841,19 @@ async function cmdSkillCompact(name, opts) {
|
|
|
14388
14841
|
if (!opts.path.trim()) {
|
|
14389
14842
|
throw new CliError("--path cannot be empty");
|
|
14390
14843
|
}
|
|
14391
|
-
if (!
|
|
14844
|
+
if (!fs27.existsSync(opts.path)) {
|
|
14392
14845
|
throw new CliError(`skill file not found: ${opts.path}`);
|
|
14393
14846
|
}
|
|
14394
14847
|
try {
|
|
14395
|
-
body =
|
|
14848
|
+
body = fs27.readFileSync(opts.path, "utf-8");
|
|
14396
14849
|
} catch (e) {
|
|
14397
14850
|
if (e.code === "ENOENT") {
|
|
14398
14851
|
throw new CliError(`skill file not found: ${opts.path}`);
|
|
14399
14852
|
}
|
|
14400
14853
|
throw new CliError(`failed to read skill file '${opts.path}': ${extractErrorMessage(e)}`);
|
|
14401
14854
|
}
|
|
14402
|
-
cacheName = name ??
|
|
14403
|
-
sourcePath =
|
|
14855
|
+
cacheName = name ?? path27.basename(path27.dirname(path27.resolve(opts.path)));
|
|
14856
|
+
sourcePath = path27.resolve(opts.path);
|
|
14404
14857
|
} else {
|
|
14405
14858
|
if (name === void 0 || !name.trim()) {
|
|
14406
14859
|
throw new CliError("skill-compact requires a <name> or --path <file>");
|
|
@@ -14409,7 +14862,7 @@ async function cmdSkillCompact(name, opts) {
|
|
|
14409
14862
|
if (filePath === null) {
|
|
14410
14863
|
throw new CliError(`skill '${name}' not found`);
|
|
14411
14864
|
}
|
|
14412
|
-
body =
|
|
14865
|
+
body = fs27.readFileSync(filePath, "utf-8");
|
|
14413
14866
|
cacheName = name;
|
|
14414
14867
|
sourcePath = filePath;
|
|
14415
14868
|
}
|
|
@@ -14541,8 +14994,8 @@ async function cmdSkillDiff(name) {
|
|
|
14541
14994
|
}
|
|
14542
14995
|
const newer = versions[0];
|
|
14543
14996
|
const older = versions[1];
|
|
14544
|
-
const newerBody = await
|
|
14545
|
-
const olderBody = await
|
|
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);
|
|
14546
14999
|
if (newerBody === null || olderBody === null) {
|
|
14547
15000
|
out(`a cached version of '${name}' was evicted while diffing -- try again`);
|
|
14548
15001
|
return;
|
|
@@ -14571,7 +15024,7 @@ async function cmdSkillSection(nameHeading, headingArg) {
|
|
|
14571
15024
|
if (!filePath) {
|
|
14572
15025
|
throw new CliError(`skill '${skillName}' not found`);
|
|
14573
15026
|
}
|
|
14574
|
-
const body = decodeSource(
|
|
15027
|
+
const body = decodeSource(fs27.readFileSync(filePath));
|
|
14575
15028
|
const extracted = extractNamedSection(body, heading);
|
|
14576
15029
|
if (!extracted) {
|
|
14577
15030
|
process.exitCode = 1;
|
|
@@ -14581,7 +15034,7 @@ async function cmdSkillSection(nameHeading, headingArg) {
|
|
|
14581
15034
|
}
|
|
14582
15035
|
function atomicWriteBuffer(dest, data) {
|
|
14583
15036
|
try {
|
|
14584
|
-
if (
|
|
15037
|
+
if (fs27.statSync(dest).isDirectory()) {
|
|
14585
15038
|
const e = Object.assign(new Error(`EISDIR: illegal operation on a directory, open '${dest}'`), { code: "EISDIR", path: dest });
|
|
14586
15039
|
throw e;
|
|
14587
15040
|
}
|
|
@@ -14589,23 +15042,23 @@ function atomicWriteBuffer(dest, data) {
|
|
|
14589
15042
|
if (e.code !== "ENOENT") throw e;
|
|
14590
15043
|
}
|
|
14591
15044
|
const rnd = Math.random().toString(36).slice(2, 8);
|
|
14592
|
-
const tmp =
|
|
15045
|
+
const tmp = path27.join(path27.dirname(path27.resolve(dest)), `.tmp.${process.pid}.${rnd}`);
|
|
14593
15046
|
try {
|
|
14594
|
-
|
|
15047
|
+
fs27.writeFileSync(tmp, data, { mode: 384 });
|
|
14595
15048
|
try {
|
|
14596
|
-
const destMode =
|
|
14597
|
-
|
|
15049
|
+
const destMode = fs27.statSync(dest).mode;
|
|
15050
|
+
fs27.chmodSync(tmp, destMode);
|
|
14598
15051
|
} catch (e) {
|
|
14599
15052
|
if (e.code !== "ENOENT") throw e;
|
|
14600
15053
|
}
|
|
14601
15054
|
withRetryOnLock(() => {
|
|
14602
15055
|
try {
|
|
14603
|
-
|
|
15056
|
+
fs27.renameSync(tmp, dest);
|
|
14604
15057
|
} catch (e) {
|
|
14605
15058
|
if (e.code === "EXDEV") {
|
|
14606
|
-
|
|
15059
|
+
fs27.copyFileSync(tmp, dest);
|
|
14607
15060
|
try {
|
|
14608
|
-
|
|
15061
|
+
fs27.unlinkSync(tmp);
|
|
14609
15062
|
} catch (ue) {
|
|
14610
15063
|
process.stderr.write(`token-goat write-file: warning: could not remove temp file ${tmp}: ${ue.message}
|
|
14611
15064
|
`);
|
|
@@ -14617,7 +15070,7 @@ function atomicWriteBuffer(dest, data) {
|
|
|
14617
15070
|
});
|
|
14618
15071
|
} catch (e) {
|
|
14619
15072
|
try {
|
|
14620
|
-
|
|
15073
|
+
fs27.unlinkSync(tmp);
|
|
14621
15074
|
} catch {
|
|
14622
15075
|
}
|
|
14623
15076
|
throw e;
|
|
@@ -14627,9 +15080,9 @@ function mapFsError(e, src, dest, srcLabel = "source") {
|
|
|
14627
15080
|
const fe = e;
|
|
14628
15081
|
if (fe.code === "ENOENT") {
|
|
14629
15082
|
const errPath = fe.path ?? "";
|
|
14630
|
-
const isSource = src !== void 0 &&
|
|
15083
|
+
const isSource = src !== void 0 && path27.resolve(errPath) === path27.resolve(src);
|
|
14631
15084
|
if (isSource) throw new CliError(`${srcLabel} file not found: ${src}`);
|
|
14632
|
-
const destDir = dest ?
|
|
15085
|
+
const destDir = dest ? path27.dirname(path27.resolve(dest)) : path27.dirname(path27.resolve(errPath || "."));
|
|
14633
15086
|
throw new CliError(`destination directory does not exist: ${destDir}`);
|
|
14634
15087
|
}
|
|
14635
15088
|
if (fe.code === "ENOTDIR") {
|
|
@@ -14640,7 +15093,7 @@ function mapFsError(e, src, dest, srcLabel = "source") {
|
|
|
14640
15093
|
}
|
|
14641
15094
|
if (fe.code === "EISDIR") {
|
|
14642
15095
|
const errPath = fe.path ?? "";
|
|
14643
|
-
const isSource = src !== void 0 && (errPath === "" ||
|
|
15096
|
+
const isSource = src !== void 0 && (errPath === "" || path27.resolve(errPath) === path27.resolve(src));
|
|
14644
15097
|
if (isSource) throw new CliError(`source is a directory, not a file: ${src}`);
|
|
14645
15098
|
throw new CliError(`destination is a directory, not a file: ${dest ?? (errPath || "(unknown)")}`);
|
|
14646
15099
|
}
|
|
@@ -14709,7 +15162,7 @@ function validateWritablePath(dest, label) {
|
|
|
14709
15162
|
throw new CliError(`${label} path contains a null byte`);
|
|
14710
15163
|
}
|
|
14711
15164
|
if (isWindows()) {
|
|
14712
|
-
const base =
|
|
15165
|
+
const base = path27.basename(dest);
|
|
14713
15166
|
const stem = base.replace(/\.[^.]*$/, "").toUpperCase();
|
|
14714
15167
|
if (WIN_RESERVED.has(stem)) {
|
|
14715
15168
|
throw new CliError(`${label} '${base}' is a reserved Windows device name`);
|
|
@@ -14739,7 +15192,7 @@ function readFileBoundedRaw(filePath, label, allowStdIn = false) {
|
|
|
14739
15192
|
throw new CliError(`${label} ${filePath} requires piped input; use ${altLabel} for interactive use`);
|
|
14740
15193
|
}
|
|
14741
15194
|
try {
|
|
14742
|
-
const st =
|
|
15195
|
+
const st = fs27.statSync(filePath);
|
|
14743
15196
|
if (st.isFIFO() || st.isSocket()) {
|
|
14744
15197
|
throw new CliError(`${label} '${filePath}' is a special file (FIFO or socket) \u2014 only regular files are supported`);
|
|
14745
15198
|
}
|
|
@@ -14747,7 +15200,7 @@ function readFileBoundedRaw(filePath, label, allowStdIn = false) {
|
|
|
14747
15200
|
if (st.size > maxBytes) {
|
|
14748
15201
|
throw new CliError(`${label} '${filePath}' exceeds size limit (${Math.round(st.size / 1024 / 1024)} MB); set TOKEN_GOAT_MAX_STDIN_MB to override`);
|
|
14749
15202
|
}
|
|
14750
|
-
return
|
|
15203
|
+
return fs27.readFileSync(filePath);
|
|
14751
15204
|
} catch (e) {
|
|
14752
15205
|
if (e instanceof CliError) throw e;
|
|
14753
15206
|
mapFsError(e, filePath, void 0, label);
|
|
@@ -14791,7 +15244,7 @@ function cmdNoteAdd(file, opts) {
|
|
|
14791
15244
|
throw new CliError("note content must be valid UTF-8 text");
|
|
14792
15245
|
}
|
|
14793
15246
|
const resolvedPath = resolveIndexPath(file);
|
|
14794
|
-
if (!
|
|
15247
|
+
if (!fs27.existsSync(resolvedPath)) {
|
|
14795
15248
|
throw new CliError(`File not found: '${resolvedPath}'`);
|
|
14796
15249
|
}
|
|
14797
15250
|
healStaleIndex(resolvedPath);
|
|
@@ -15005,14 +15458,14 @@ function writeReplacedBuffer(file, replacedBuf, preWriteStat) {
|
|
|
15005
15458
|
const until = process.env["TOKEN_GOAT_TEST_REPLACE_DELAY_UNTIL"];
|
|
15006
15459
|
const deadline = Date.now() + testDelayMs;
|
|
15007
15460
|
if (until !== void 0 && until !== "") {
|
|
15008
|
-
while (Date.now() < deadline && !
|
|
15461
|
+
while (Date.now() < deadline && !fs27.existsSync(until)) sleepSync(5);
|
|
15009
15462
|
} else {
|
|
15010
15463
|
sleepSync(testDelayMs);
|
|
15011
15464
|
}
|
|
15012
15465
|
}
|
|
15013
15466
|
let preRenameStat;
|
|
15014
15467
|
try {
|
|
15015
|
-
preRenameStat =
|
|
15468
|
+
preRenameStat = fs27.statSync(file);
|
|
15016
15469
|
} catch {
|
|
15017
15470
|
}
|
|
15018
15471
|
if (preRenameStat !== void 0 && (preRenameStat.mtimeMs !== preWriteStat.mtimeMs || preRenameStat.size !== preWriteStat.size)) {
|
|
@@ -15053,7 +15506,7 @@ function cmdReplace(file, opts) {
|
|
|
15053
15506
|
const targetBuf = readFileBoundedRaw(file, "target file", true);
|
|
15054
15507
|
let preWriteStat;
|
|
15055
15508
|
try {
|
|
15056
|
-
preWriteStat =
|
|
15509
|
+
preWriteStat = fs27.statSync(file);
|
|
15057
15510
|
} catch {
|
|
15058
15511
|
}
|
|
15059
15512
|
const usingFrom = opts.oldFrom !== void 0 || opts.newFrom !== void 0;
|
|
@@ -15147,7 +15600,7 @@ function cmdInsertSection(file, opts) {
|
|
|
15147
15600
|
}
|
|
15148
15601
|
let preWriteStat;
|
|
15149
15602
|
try {
|
|
15150
|
-
preWriteStat =
|
|
15603
|
+
preWriteStat = fs27.statSync(file);
|
|
15151
15604
|
} catch {
|
|
15152
15605
|
}
|
|
15153
15606
|
const result = readSection(file, opts.after);
|
|
@@ -15161,7 +15614,7 @@ function cmdInsertSection(file, opts) {
|
|
|
15161
15614
|
}
|
|
15162
15615
|
let rawBytes;
|
|
15163
15616
|
try {
|
|
15164
|
-
rawBytes =
|
|
15617
|
+
rawBytes = fs27.readFileSync(file);
|
|
15165
15618
|
} catch (e) {
|
|
15166
15619
|
mapFsError(e, void 0, file);
|
|
15167
15620
|
}
|
|
@@ -15177,7 +15630,7 @@ function cmdInsertSection(file, opts) {
|
|
|
15177
15630
|
if (preWriteStat !== void 0) {
|
|
15178
15631
|
let preRenameStat;
|
|
15179
15632
|
try {
|
|
15180
|
-
preRenameStat =
|
|
15633
|
+
preRenameStat = fs27.statSync(file);
|
|
15181
15634
|
} catch {
|
|
15182
15635
|
}
|
|
15183
15636
|
if (preRenameStat !== void 0 && (preRenameStat.mtimeMs !== preWriteStat.mtimeMs || preRenameStat.size !== preWriteStat.size)) {
|
|
@@ -15218,25 +15671,25 @@ ${content}`;
|
|
|
15218
15671
|
}
|
|
15219
15672
|
function expandGlobs(root, patterns, globFnOverride) {
|
|
15220
15673
|
const out2 = [];
|
|
15221
|
-
const globFn = globFnOverride ??
|
|
15674
|
+
const globFn = globFnOverride ?? fs27["globSync"];
|
|
15222
15675
|
for (const p of patterns) {
|
|
15223
15676
|
if (globFn !== void 0 && (p.includes("*") || p.includes("?") || p.includes("{"))) {
|
|
15224
15677
|
try {
|
|
15225
15678
|
const hits = globFn(p, { cwd: root });
|
|
15226
|
-
for (const h of hits) out2.push(
|
|
15679
|
+
for (const h of hits) out2.push(path27.isAbsolute(h) ? h : path27.join(root, h));
|
|
15227
15680
|
continue;
|
|
15228
15681
|
} catch {
|
|
15229
15682
|
}
|
|
15230
15683
|
}
|
|
15231
|
-
out2.push(
|
|
15684
|
+
out2.push(path27.isAbsolute(p) ? p : path27.join(root, p));
|
|
15232
15685
|
}
|
|
15233
15686
|
return out2;
|
|
15234
15687
|
}
|
|
15235
15688
|
function readIgnoreFile(root) {
|
|
15236
|
-
const ignorePath =
|
|
15689
|
+
const ignorePath = path27.join(root, ".tokengoatignore");
|
|
15237
15690
|
let raw;
|
|
15238
15691
|
try {
|
|
15239
|
-
raw =
|
|
15692
|
+
raw = fs27.readFileSync(ignorePath, "utf8");
|
|
15240
15693
|
} catch {
|
|
15241
15694
|
return void 0;
|
|
15242
15695
|
}
|
|
@@ -15277,14 +15730,14 @@ function cmdPack(patterns, opts) {
|
|
|
15277
15730
|
}
|
|
15278
15731
|
let instruction;
|
|
15279
15732
|
if (opts.instructionFile !== void 0) {
|
|
15280
|
-
instruction =
|
|
15733
|
+
instruction = fs27.readFileSync(opts.instructionFile, "utf8");
|
|
15281
15734
|
}
|
|
15282
15735
|
const formatted = formatPack(result, style, {
|
|
15283
15736
|
...opts.lineNumbers === true ? { line_numbers: true } : {},
|
|
15284
15737
|
...instruction !== void 0 ? { instruction } : {}
|
|
15285
15738
|
});
|
|
15286
15739
|
if (opts.output !== void 0) {
|
|
15287
|
-
|
|
15740
|
+
fs27.writeFileSync(opts.output, formatted, "utf8");
|
|
15288
15741
|
} else {
|
|
15289
15742
|
out(formatted);
|
|
15290
15743
|
}
|
|
@@ -15302,7 +15755,7 @@ function cmdTokens(patterns, opts) {
|
|
|
15302
15755
|
if (opts.tree === true) {
|
|
15303
15756
|
const dirs = /* @__PURE__ */ new Map();
|
|
15304
15757
|
for (const e of entries) {
|
|
15305
|
-
const dir =
|
|
15758
|
+
const dir = path27.dirname(e.rel_path);
|
|
15306
15759
|
if (!dirs.has(dir)) dirs.set(dir, []);
|
|
15307
15760
|
dirs.get(dir).push(e);
|
|
15308
15761
|
}
|
|
@@ -15312,7 +15765,7 @@ function cmdTokens(patterns, opts) {
|
|
|
15312
15765
|
const pct = result.total_tokens > 0 ? Math.round(dirTokens / result.total_tokens * 100) : 0;
|
|
15313
15766
|
lines2.push(`${dir}/ (${dirTokens} tokens, ${pct}%)`);
|
|
15314
15767
|
for (const e of dirEntries) {
|
|
15315
|
-
lines2.push(` ${
|
|
15768
|
+
lines2.push(` ${path27.basename(e.rel_path).padEnd(30)} ${String(e.tokens).padStart(8)} tokens`);
|
|
15316
15769
|
}
|
|
15317
15770
|
}
|
|
15318
15771
|
out(lines2.join("\n"));
|
|
@@ -15343,7 +15796,7 @@ function cmdBudget(patterns, opts) {
|
|
|
15343
15796
|
}
|
|
15344
15797
|
}
|
|
15345
15798
|
function cmdFailures(src, opts) {
|
|
15346
|
-
const text = src !== void 0 ?
|
|
15799
|
+
const text = src !== void 0 ? fs27.readFileSync(src, "utf8") : fs27.readFileSync(0, "utf8");
|
|
15347
15800
|
const result = extractFailures(text, opts.runner !== void 0 ? { runner: opts.runner } : {});
|
|
15348
15801
|
if (opts.delta !== true) {
|
|
15349
15802
|
out(opts.json === true ? formatFailuresJson(result) : formatFailuresText(result));
|
|
@@ -15473,39 +15926,34 @@ function buildProgram() {
|
|
|
15473
15926
|
)
|
|
15474
15927
|
);
|
|
15475
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));
|
|
15476
|
-
|
|
15477
|
-
(file
|
|
15478
|
-
() =>
|
|
15479
|
-
|
|
15480
|
-
|
|
15481
|
-
more,
|
|
15482
|
-
() => 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,
|
|
15483
15934
|
file,
|
|
15484
|
-
|
|
15485
|
-
|
|
15486
|
-
|
|
15487
|
-
|
|
15488
|
-
|
|
15489
|
-
|
|
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
|
+
)
|
|
15490
15945
|
)
|
|
15491
|
-
)
|
|
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
|
|
15492
15952
|
);
|
|
15493
|
-
|
|
15494
|
-
|
|
15495
|
-
|
|
15496
|
-
|
|
15497
|
-
file,
|
|
15498
|
-
more,
|
|
15499
|
-
() => runOutline({
|
|
15500
|
-
file,
|
|
15501
|
-
...opts.json === true ? { json: true } : {},
|
|
15502
|
-
...opts.minLines !== void 0 ? { minLines: requireNonNegativeInt("--min-lines", opts.minLines) } : {},
|
|
15503
|
-
...opts.grep !== void 0 ? { grep: opts.grep } : {},
|
|
15504
|
-
...opts.forceRefresh === true ? { forceRefresh: true } : {},
|
|
15505
|
-
...opts.stats === true ? { stats: true } : {}
|
|
15506
|
-
})
|
|
15507
|
-
)
|
|
15508
|
-
)
|
|
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
|
|
15509
15957
|
);
|
|
15510
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(
|
|
15511
15959
|
"--top <n>",
|
|
@@ -15547,7 +15995,7 @@ function buildProgram() {
|
|
|
15547
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));
|
|
15548
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));
|
|
15549
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));
|
|
15550
|
-
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));
|
|
15551
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));
|
|
15552
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));
|
|
15553
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));
|