token-goat 2.6.36 → 2.8.1
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 +36 -8
- package/SECURITY.md +46 -17
- package/dist/{token-goat-chunk-UFOVM7ZN.mjs → token-goat-chunk-2G6RAB4G.mjs} +1391 -378
- package/dist/token-goat-chunk-324QOJYZ.mjs +91 -0
- package/dist/token-goat-chunk-5CVKO3DA.mjs +185 -0
- package/dist/{token-goat-chunk-65BKISIS.mjs → token-goat-chunk-73MF6YWW.mjs} +4 -4
- package/dist/{token-goat-chunk-MGOUYAA2.mjs → token-goat-chunk-7JDXDERZ.mjs} +1 -1
- package/dist/{token-goat-hook-chunk-BDR6C6IE.mjs → token-goat-chunk-AOBZUFNJ.mjs} +318 -51
- package/dist/{token-goat-chunk-V465YKOR.mjs → token-goat-chunk-ELDJRLHZ.mjs} +492 -28
- package/dist/{token-goat-chunk-CNDOJ3ZP.mjs → token-goat-chunk-I6TOUPLP.mjs} +2 -2
- package/dist/token-goat-chunk-R4SR7MQY.mjs +486 -0
- package/dist/{token-goat-chunk-IYTVE6KN.mjs → token-goat-chunk-RMDQFTQD.mjs} +226 -150
- package/dist/{token-goat-chunk-KYFJC37X.mjs → token-goat-chunk-VBXBLGTO.mjs} +792 -144
- package/dist/{token-goat-chunk-DG53MVNJ.mjs → token-goat-chunk-WN5T5EW5.mjs} +212 -212
- package/dist/{token-goat-chunk-VYMGEVZS.mjs → token-goat-chunk-XEDQH5DA.mjs} +317 -11
- package/dist/{token-goat-chunk-LN6OUHTV.mjs → token-goat-chunk-Y2SYNH3S.mjs} +5 -5
- package/dist/token-goat-chunk-YUGNM3KL.mjs +23 -0
- package/dist/token-goat-hook.mjs +7 -7
- package/dist/token-goat.core.mjs +5 -5
- package/package.json +12 -8
- package/dist/token-goat-chunk-FRTBMRP7.mjs +0 -10048
- package/dist/token-goat-hook-chunk-3QYSN4QV.mjs +0 -14764
- package/dist/token-goat-hook-chunk-3ZDBWJDF.mjs +0 -13659
- package/dist/token-goat-hook-chunk-5UH54CW6.mjs +0 -912
- package/dist/token-goat-hook-chunk-6ODM3MP7.mjs +0 -706
- package/dist/token-goat-hook-chunk-A77A26A7.mjs +0 -18184
- package/dist/token-goat-hook-chunk-BUOCULAM.mjs +0 -29
- package/dist/token-goat-hook-chunk-C6GIABOX.mjs +0 -15971
- package/dist/token-goat-hook-chunk-E257IGSN.mjs +0 -153
- package/dist/token-goat-hook-chunk-MW5HPEGD.mjs +0 -10411
- package/dist/token-goat-hook-chunk-QSCYNJ2B.mjs +0 -23
- package/dist/token-goat-hook-chunk-RFRLWOQH.mjs +0 -11
- package/dist/token-goat-hook-chunk-XUMIVYEN.mjs +0 -109
- package/dist/token-goat-hook-chunk-Y2WHX2P3.mjs +0 -6463
|
@@ -2,6 +2,7 @@ import { createRequire as __cjsRequire } from 'node:module';
|
|
|
2
2
|
const require = __cjsRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
4
|
ALL_SYMBOLS_IN_FILE_LIMIT,
|
|
5
|
+
AMBIGUOUS_HEADING_LIMIT,
|
|
5
6
|
CONTENT_MAX_INPUT_CHARS,
|
|
6
7
|
WHOLE_FILE_NOTE_SYMBOL,
|
|
7
8
|
compressText,
|
|
@@ -16,7 +17,7 @@ import {
|
|
|
16
17
|
findSpecSeparator,
|
|
17
18
|
getTrackedFiles,
|
|
18
19
|
healStaleIndex,
|
|
19
|
-
isAvailable,
|
|
20
|
+
isAvailable as isAvailable2,
|
|
20
21
|
listSections,
|
|
21
22
|
load,
|
|
22
23
|
loadError,
|
|
@@ -60,6 +61,7 @@ import {
|
|
|
60
61
|
runOpenApiOutline,
|
|
61
62
|
runOutline,
|
|
62
63
|
runPdfExtractText,
|
|
64
|
+
runPdfLocate,
|
|
63
65
|
runPdfMeta,
|
|
64
66
|
runPdfOutline,
|
|
65
67
|
runPrSlice,
|
|
@@ -84,17 +86,21 @@ import {
|
|
|
84
86
|
runZipRead,
|
|
85
87
|
symbolNamesInFile,
|
|
86
88
|
upsertNote
|
|
87
|
-
} from "./token-goat-chunk-
|
|
89
|
+
} from "./token-goat-chunk-RMDQFTQD.mjs";
|
|
88
90
|
import {
|
|
89
91
|
BASH_OUTPUT_SUBDIR,
|
|
90
92
|
GEMINI_TOOL_NAME_MAP,
|
|
91
93
|
HOOK_EVENTS,
|
|
92
94
|
WEB_OUTPUT_SUBDIR,
|
|
95
|
+
accumulateResidentLine,
|
|
93
96
|
buildCommandManifest,
|
|
94
97
|
checkSymbolBodySize,
|
|
95
98
|
commandHash,
|
|
99
|
+
createResidentContextStats,
|
|
96
100
|
filterCommandManifest,
|
|
101
|
+
formatBytes,
|
|
97
102
|
formatCommandManifest,
|
|
103
|
+
formatTokenEstimate,
|
|
98
104
|
getBashOutput,
|
|
99
105
|
getWebOutput,
|
|
100
106
|
getWebOutputByUrlFromDisk,
|
|
@@ -102,10 +108,12 @@ import {
|
|
|
102
108
|
isRecallCacheType,
|
|
103
109
|
listRecentRecall,
|
|
104
110
|
normalizeCommandForCacheKey,
|
|
111
|
+
readCopilotMcpTools,
|
|
105
112
|
readStdinJson,
|
|
106
113
|
searchRecall,
|
|
107
|
-
storeWebOutput
|
|
108
|
-
|
|
114
|
+
storeWebOutput,
|
|
115
|
+
summarizeResidentContext
|
|
116
|
+
} from "./token-goat-chunk-XEDQH5DA.mjs";
|
|
109
117
|
import {
|
|
110
118
|
AGENT_SALT_MARKER,
|
|
111
119
|
CONTEXT_AUTOCOMPACT_TOKENS,
|
|
@@ -132,16 +140,20 @@ import {
|
|
|
132
140
|
confirmAndApply,
|
|
133
141
|
contentHash,
|
|
134
142
|
copilotCliConfigPath,
|
|
143
|
+
copilotCliMcpToolsDir,
|
|
135
144
|
copilotCliScriptPath,
|
|
145
|
+
copilotCliUserRoot,
|
|
136
146
|
detectWalkMode,
|
|
137
147
|
dirtyQueuePathFor,
|
|
138
148
|
docxOutline,
|
|
139
149
|
docxText,
|
|
140
150
|
drainHeartbeatPathFor,
|
|
151
|
+
embeddingBackendLoadError,
|
|
141
152
|
embeddingsDepsAvailable,
|
|
142
153
|
emptyIndexMessage,
|
|
143
154
|
enqueueDirtyPathSafe,
|
|
144
155
|
estimateTokens,
|
|
156
|
+
estimateTokensFromLength,
|
|
145
157
|
extractChecklistSection,
|
|
146
158
|
extractCompactFromMarker,
|
|
147
159
|
extractNamedSection,
|
|
@@ -182,6 +194,7 @@ import {
|
|
|
182
194
|
installKimi,
|
|
183
195
|
installSkill,
|
|
184
196
|
installVscode,
|
|
197
|
+
isAvailable,
|
|
185
198
|
isCodexInstalled,
|
|
186
199
|
isCompactFresh,
|
|
187
200
|
isCopilotCliInstalled,
|
|
@@ -243,7 +256,7 @@ import {
|
|
|
243
256
|
vscodeDecoderConfigured,
|
|
244
257
|
walkProject,
|
|
245
258
|
writeCompact
|
|
246
|
-
} from "./token-goat-chunk-
|
|
259
|
+
} from "./token-goat-chunk-2G6RAB4G.mjs";
|
|
247
260
|
import {
|
|
248
261
|
C,
|
|
249
262
|
CONFIG_KEY_ENV_OVERRIDES,
|
|
@@ -296,6 +309,7 @@ import {
|
|
|
296
309
|
parse,
|
|
297
310
|
pushAll,
|
|
298
311
|
readConfigSource,
|
|
312
|
+
readUnmappedTools,
|
|
299
313
|
recordStat,
|
|
300
314
|
redactIfDotenv,
|
|
301
315
|
redactUrlQuery,
|
|
@@ -324,7 +338,7 @@ import {
|
|
|
324
338
|
withFileLock,
|
|
325
339
|
withRetryOnLock,
|
|
326
340
|
writeJsonSettings
|
|
327
|
-
} from "./token-goat-chunk-
|
|
341
|
+
} from "./token-goat-chunk-ELDJRLHZ.mjs";
|
|
328
342
|
import {
|
|
329
343
|
__export
|
|
330
344
|
} from "./token-goat-chunk-AEX54RUZ.mjs";
|
|
@@ -3440,9 +3454,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3440
3454
|
* @param {string} [path]
|
|
3441
3455
|
* @return {(string|null|Command)}
|
|
3442
3456
|
*/
|
|
3443
|
-
executableDir(
|
|
3444
|
-
if (
|
|
3445
|
-
this._executableDir =
|
|
3457
|
+
executableDir(path28) {
|
|
3458
|
+
if (path28 === void 0) return this._executableDir;
|
|
3459
|
+
this._executableDir = path28;
|
|
3446
3460
|
return this;
|
|
3447
3461
|
}
|
|
3448
3462
|
/**
|
|
@@ -3741,8 +3755,8 @@ function attemptedCommandName(argv) {
|
|
|
3741
3755
|
}
|
|
3742
3756
|
|
|
3743
3757
|
// src/cli.ts
|
|
3744
|
-
import * as
|
|
3745
|
-
import * as
|
|
3758
|
+
import * as fs27 from "fs";
|
|
3759
|
+
import * as path27 from "path";
|
|
3746
3760
|
import { homedir as homedir11 } from "os";
|
|
3747
3761
|
|
|
3748
3762
|
// src/walk_index.ts
|
|
@@ -4998,20 +5012,42 @@ function isOpenclawInstalled() {
|
|
|
4998
5012
|
|
|
4999
5013
|
// src/bridges_status.ts
|
|
5000
5014
|
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";
|
|
5015
|
+
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";
|
|
5016
|
+
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";
|
|
5017
|
+
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";
|
|
5018
|
+
var VERIFICATION_RANK = {
|
|
5019
|
+
dogfooded: 0,
|
|
5020
|
+
sourced: 1,
|
|
5021
|
+
documented: 2
|
|
5022
|
+
};
|
|
5023
|
+
var VERIFICATION_BLURB = {
|
|
5024
|
+
dogfooded: "driven against the real harness binary",
|
|
5025
|
+
sourced: "wire format read from the harness's own source or declarations, never driven against it",
|
|
5026
|
+
documented: "wired from the harness's documentation only, never driven against it"
|
|
5027
|
+
};
|
|
5001
5028
|
var BRIDGE_CAPABILITY_MATRIX = [
|
|
5002
5029
|
{
|
|
5003
5030
|
harness: "claudecode",
|
|
5004
5031
|
label: "Claude Code",
|
|
5005
5032
|
sourceFile: "src/install.ts (HOOK_EVENT_MAP)",
|
|
5006
|
-
|
|
5007
|
-
|
|
5033
|
+
verification: "dogfooded",
|
|
5034
|
+
verificationNote: "The harness this repo is developed and released under; CLAUDE.md requires every CLI/hook change be run against the built binary here before it ships.",
|
|
5035
|
+
implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact", "post_compact", "user_prompt_submit", "subagent_stop", "session_start"]),
|
|
5036
|
+
reasons: [
|
|
5037
|
+
{ events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
|
|
5038
|
+
{ events: ["notification", "stop"], reason: NO_SERVER_HANDLER_REASON }
|
|
5039
|
+
]
|
|
5008
5040
|
},
|
|
5009
5041
|
{
|
|
5010
5042
|
harness: "codex",
|
|
5011
5043
|
label: "Codex CLI",
|
|
5012
5044
|
sourceFile: "src/bridges/codex_install.ts (CODEX_HOOK_EVENTS, CODEX_GLOBAL_HOOK_EVENTS)",
|
|
5045
|
+
verification: "sourced",
|
|
5046
|
+
verificationNote: "Wire format pinned to Codex 0.137.0+'s declared hookSpecificOutput schema (src/bridges/codex.ts); no run against a Codex binary is recorded here.",
|
|
5013
5047
|
implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact", "user_prompt_submit", "subagent_stop"]),
|
|
5014
5048
|
reasons: [
|
|
5049
|
+
{ events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
|
|
5050
|
+
{ events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
|
|
5015
5051
|
{ events: ["notification", "stop"], reason: NO_SERVER_HANDLER_REASON },
|
|
5016
5052
|
{
|
|
5017
5053
|
events: ["session_start"],
|
|
@@ -5023,8 +5059,12 @@ var BRIDGE_CAPABILITY_MATRIX = [
|
|
|
5023
5059
|
harness: "grok",
|
|
5024
5060
|
label: "Grok CLI",
|
|
5025
5061
|
sourceFile: "src/bridges/grok_install.ts (GROK_HOOK_EVENTS)",
|
|
5062
|
+
verification: "dogfooded",
|
|
5063
|
+
verificationNote: "grok 0.2.93, 2026-07-09: observed executing the global settings.json hooks config unmodified and setting GROK_SESSION_ID on every hook subprocess (src/bridges/registry.ts, src/hooks_cli.ts).",
|
|
5026
5064
|
implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact", "user_prompt_submit", "subagent_stop"]),
|
|
5027
5065
|
reasons: [
|
|
5066
|
+
{ events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
|
|
5067
|
+
{ events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
|
|
5028
5068
|
{ events: ["notification", "stop"], reason: NO_SERVER_HANDLER_REASON },
|
|
5029
5069
|
{
|
|
5030
5070
|
events: ["session_start"],
|
|
@@ -5032,10 +5072,61 @@ var BRIDGE_CAPABILITY_MATRIX = [
|
|
|
5032
5072
|
}
|
|
5033
5073
|
]
|
|
5034
5074
|
},
|
|
5075
|
+
/**
|
|
5076
|
+
* What Copilot CLI's wired events can actually carry, read from the shipping 1.0.80 bundle
|
|
5077
|
+
* rather than from its hooks reference page. A row's `implemented` set says an event is wired;
|
|
5078
|
+
* it deliberately says nothing about how much of that event's response the harness honors, and
|
|
5079
|
+
* for Copilot the difference matters:
|
|
5080
|
+
*
|
|
5081
|
+
* - `pre_tool_use` deny is real, and the reason text reaches the model. The native string table
|
|
5082
|
+
* (runtime.node offset 98057208) carries `permissionDecision`, `permissionDecisionReason`, the
|
|
5083
|
+
* default "No reason provided." and the formatter "Denied by preToolUse hook: " as distinct
|
|
5084
|
+
* literals, and app.js sets `{textResultForLlm: reason, resultType: "denied"}` per denial and
|
|
5085
|
+
* filters that call out of execution. This was previously only an inference drawn from an
|
|
5086
|
+
* incident comment; it is now confirmed. The fail-closed "(hook errored)" deny-all strings sit
|
|
5087
|
+
* beside these, so that incident was behavior layered on top of a working deny handler, not a
|
|
5088
|
+
* substitute for one.
|
|
5089
|
+
* - `post_tool_use` honors `modifiedResult`, so compression, injection fencing and image shrink
|
|
5090
|
+
* do reach the model: `postToolExecution` (app.js offset 2043150) assigns the returned
|
|
5091
|
+
* `toolResultJson` onto the tool result in place.
|
|
5092
|
+
* - `post_tool_use` drops `additionalContext` on the JS path -- no supplier for the
|
|
5093
|
+
* `onAdditionalContext` callback anywhere in the bundle, and no `additional_contexts` key in
|
|
5094
|
+
* that event's native return payload, unlike its pre-tool sibling. Whether native folds it
|
|
5095
|
+
* into the returned result instead was not verified.
|
|
5096
|
+
* - Failed tool calls never reach `post_tool_use` at all. Copilot routes them to a separate
|
|
5097
|
+
* `postToolUseFailure` event, which is handed only a stringified error and honors only
|
|
5098
|
+
* `additionalContext`; `modifiedResult` is documented as not honored there. So on Copilot the
|
|
5099
|
+
* output of a failed tool call reaches the model unfenced, uncompressed and unshrunk, and no
|
|
5100
|
+
* response shape token-goat's shim can emit changes that. Known gap, deliberately recorded.
|
|
5101
|
+
* What `additionalContext` does on that event is no longer doc-derived: app.js offset 2043380
|
|
5102
|
+
* reads the native processor's return and, when it carries `additionalContext`, either folds
|
|
5103
|
+
* it into `textResultForLlm` (if `appendFailureContextToToolResult` is set) or formats it and
|
|
5104
|
+
* pushes `{content, source: "system"}` onto `toolResult.newMessages`. So the channel is real
|
|
5105
|
+
* and model-visible -- it just carries advice alongside the failure, never a replacement for
|
|
5106
|
+
* it. It is wired now, as its own `post_tool_use_failure` event rather than a reuse of
|
|
5107
|
+
* `post_tool_use`: routing failures through the success event would let success-path handlers
|
|
5108
|
+
* mark a file as successfully read when the read failed. Because the channel spends tokens
|
|
5109
|
+
* instead of saving them, its handler (`src/hooks_tool_failure.ts`) stays silent on a first
|
|
5110
|
+
* failure and speaks only on an exact repeat.
|
|
5111
|
+
* - `pre_compact` is wired and fires, but nothing it returns can reach the model. Both dispatch
|
|
5112
|
+
* sites call it in statement position and drop the result: app.js offset 2467452 (the manual
|
|
5113
|
+
* `/compact` path) and offset 2571216 (`case "execute_pre_compact_hook"`). The contrast is
|
|
5114
|
+
* what makes this conclusive rather than an absence of evidence -- the same `event()` runner's
|
|
5115
|
+
* return IS read for other events, e.g. `notification` at offset 2296123 assigns it and
|
|
5116
|
+
* forwards `additionalContext` as a prepended system prompt. The compaction prompt is then
|
|
5117
|
+
* built from session history and the user's own focus text, with no hook channel into it.
|
|
5118
|
+
* So token-goat's `pre_compact` registration here is observe-only by construction, not merely
|
|
5119
|
+
* unused: a future attempt to inject a session manifest through it would silently do nothing.
|
|
5120
|
+
* Not verified: whether the native declarative runner applies `additionalContext` itself. The
|
|
5121
|
+
* trail ends in stripped Rust. That it would make the explicit JS handling for `notification`
|
|
5122
|
+
* and `userPromptSubmitted` redundant is an argument, not a proof.
|
|
5123
|
+
*/
|
|
5035
5124
|
{
|
|
5036
5125
|
harness: "copilot_cli",
|
|
5037
5126
|
label: "Copilot CLI",
|
|
5038
5127
|
sourceFile: "src/bridges/copilot_cli_install.ts (COPILOT_CLI_HOOK_EVENTS), src/bridges/copilot_cli.ts (COPILOT_TO_TG_EVENT)",
|
|
5128
|
+
verification: "dogfooded",
|
|
5129
|
+
verificationNote: "doctor's checkCopilotCli drives the installed shim end to end, and every hook payload field is cross-checked against Copilot's own declarations pinned at 1.0.80 (schemas/copilot_cli.hooks.json).",
|
|
5039
5130
|
implemented: /* @__PURE__ */ new Set([
|
|
5040
5131
|
"session_start",
|
|
5041
5132
|
"pre_tool_use",
|
|
@@ -5043,12 +5134,14 @@ var BRIDGE_CAPABILITY_MATRIX = [
|
|
|
5043
5134
|
"pre_compact",
|
|
5044
5135
|
"stop",
|
|
5045
5136
|
"subagent_stop",
|
|
5046
|
-
"user_prompt_submit"
|
|
5137
|
+
"user_prompt_submit",
|
|
5138
|
+
"post_tool_use_failure"
|
|
5047
5139
|
]),
|
|
5048
5140
|
reasons: [
|
|
5141
|
+
{ events: ["post_compact"], reason: COPILOT_NO_POST_COMPACT_REASON },
|
|
5049
5142
|
{
|
|
5050
5143
|
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/
|
|
5144
|
+
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
5145
|
}
|
|
5053
5146
|
]
|
|
5054
5147
|
},
|
|
@@ -5056,8 +5149,12 @@ var BRIDGE_CAPABILITY_MATRIX = [
|
|
|
5056
5149
|
harness: "gemini",
|
|
5057
5150
|
label: "Gemini CLI",
|
|
5058
5151
|
sourceFile: "src/bridges/gemini_install.ts (GEMINI_HOOK_EVENTS)",
|
|
5152
|
+
verification: "sourced",
|
|
5153
|
+
verificationNote: "Verified against Gemini CLI's published hooks reference (src/bridges/gemini_install.ts); no run against a Gemini binary is recorded here.",
|
|
5059
5154
|
implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact"]),
|
|
5060
5155
|
reasons: [
|
|
5156
|
+
{ events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
|
|
5157
|
+
{ events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
|
|
5061
5158
|
{
|
|
5062
5159
|
events: ["notification", "stop", "user_prompt_submit", "subagent_stop", "session_start"],
|
|
5063
5160
|
reason: `Gemini CLI's hooks integration only wires BeforeTool/AfterTool/PreCompress (README "Gemini CLI users")`
|
|
@@ -5068,8 +5165,12 @@ var BRIDGE_CAPABILITY_MATRIX = [
|
|
|
5068
5165
|
harness: "qwen",
|
|
5069
5166
|
label: "Qwen Code",
|
|
5070
5167
|
sourceFile: "src/bridges/qwen_install.ts (QWEN_HOOK_EVENTS)",
|
|
5168
|
+
verification: "documented",
|
|
5169
|
+
verificationNote: "Wired from QwenLM/qwen-code's hooks.md, which src/bridges/qwen_install.ts states was not live-tested against a running install.",
|
|
5071
5170
|
implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact", "user_prompt_submit", "subagent_stop"]),
|
|
5072
5171
|
reasons: [
|
|
5172
|
+
{ events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
|
|
5173
|
+
{ events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
|
|
5073
5174
|
{
|
|
5074
5175
|
events: ["notification"],
|
|
5075
5176
|
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)"
|
|
@@ -5085,15 +5186,25 @@ var BRIDGE_CAPABILITY_MATRIX = [
|
|
|
5085
5186
|
harness: "kimi",
|
|
5086
5187
|
label: "Kimi Code CLI",
|
|
5087
5188
|
sourceFile: "src/bridges/kimi_install.ts (KIMI_EVENT_ARG), src/bridges/kimi.ts (KIMI_HOOK_SCRIPT)",
|
|
5189
|
+
verification: "sourced",
|
|
5190
|
+
verificationNote: "Payload keys and response contract read from MoonshotAI/kimi-code's own source (externalHooksRunner/runner.ts, externalHooksService.ts); no run against a Kimi binary is recorded here.",
|
|
5088
5191
|
implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact", "user_prompt_submit", "subagent_stop", "session_start"]),
|
|
5089
|
-
reasons: [
|
|
5192
|
+
reasons: [
|
|
5193
|
+
{ events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
|
|
5194
|
+
{ events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
|
|
5195
|
+
{ events: ["notification", "stop"], reason: NO_SERVER_HANDLER_REASON }
|
|
5196
|
+
]
|
|
5090
5197
|
},
|
|
5091
5198
|
{
|
|
5092
5199
|
harness: "opencode",
|
|
5093
5200
|
label: "opencode",
|
|
5094
5201
|
sourceFile: "src/bridges/opencode.ts",
|
|
5202
|
+
verification: "sourced",
|
|
5203
|
+
verificationNote: "Tool ids, parameter keys and handler behavior read directly from opencode's own source (src/bridges/opencode.ts); no run against a running gateway is recorded here.",
|
|
5095
5204
|
implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact"]),
|
|
5096
5205
|
reasons: [
|
|
5206
|
+
{ events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
|
|
5207
|
+
{ events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
|
|
5097
5208
|
{
|
|
5098
5209
|
events: ["notification", "stop", "user_prompt_submit", "subagent_stop", "session_start"],
|
|
5099
5210
|
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,8 +5215,12 @@ var BRIDGE_CAPABILITY_MATRIX = [
|
|
|
5104
5215
|
harness: "openclaw",
|
|
5105
5216
|
label: "OpenClaw",
|
|
5106
5217
|
sourceFile: "src/bridges/openclaw.ts",
|
|
5218
|
+
verification: "sourced",
|
|
5219
|
+
verificationNote: "Verified against the live plugin docs and OpenClaw's aristotle-agent/types.ts; the bridge's own note lists what stays unverified without a live instance (built-in tool names, session id).",
|
|
5107
5220
|
implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact"]),
|
|
5108
5221
|
reasons: [
|
|
5222
|
+
{ events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
|
|
5223
|
+
{ events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
|
|
5109
5224
|
{
|
|
5110
5225
|
events: ["notification", "stop", "user_prompt_submit", "subagent_stop", "session_start"],
|
|
5111
5226
|
reason: "OpenClaw's in-process plugin API only exposes before_tool_call/after_tool_call/before_compaction as api.on() handlers relevant here"
|
|
@@ -5116,8 +5231,12 @@ var BRIDGE_CAPABILITY_MATRIX = [
|
|
|
5116
5231
|
harness: "pi",
|
|
5117
5232
|
label: "pi (pi-coding-agent)",
|
|
5118
5233
|
sourceFile: "src/bridges/pi.ts",
|
|
5234
|
+
verification: "sourced",
|
|
5235
|
+
verificationNote: "Response shape verified against pi's own TextContent/ImageContent types (src/bridges/pi.ts); no run against a pi binary is recorded here.",
|
|
5119
5236
|
implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact"]),
|
|
5120
5237
|
reasons: [
|
|
5238
|
+
{ events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
|
|
5239
|
+
{ events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
|
|
5121
5240
|
{
|
|
5122
5241
|
events: ["notification", "stop", "user_prompt_submit", "subagent_stop", "session_start"],
|
|
5123
5242
|
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()"
|
|
@@ -5137,7 +5256,15 @@ function bridgesStatusToJson(matrix = BRIDGE_CAPABILITY_MATRIX) {
|
|
|
5137
5256
|
reasons[event] = reason;
|
|
5138
5257
|
}
|
|
5139
5258
|
}
|
|
5140
|
-
return {
|
|
5259
|
+
return {
|
|
5260
|
+
harness: row.harness,
|
|
5261
|
+
label: row.label,
|
|
5262
|
+
sourceFile: row.sourceFile,
|
|
5263
|
+
verification: row.verification,
|
|
5264
|
+
verificationNote: row.verificationNote,
|
|
5265
|
+
events,
|
|
5266
|
+
reasons
|
|
5267
|
+
};
|
|
5141
5268
|
});
|
|
5142
5269
|
}
|
|
5143
5270
|
function formatBridgesStatus(matrix = BRIDGE_CAPABILITY_MATRIX) {
|
|
@@ -5148,7 +5275,8 @@ function formatBridgesStatus(matrix = BRIDGE_CAPABILITY_MATRIX) {
|
|
|
5148
5275
|
lines.push("Read-only static analysis of src/bridges/*_install.ts and *.ts -- never invokes a real harness binary.");
|
|
5149
5276
|
lines.push("");
|
|
5150
5277
|
const harnessWidth = Math.max(...matrix.map((r) => r.harness.length), "harness".length);
|
|
5151
|
-
const
|
|
5278
|
+
const verifyWidth = Math.max(...matrix.map((r) => r.verification.length), "verified".length);
|
|
5279
|
+
const header = ["harness".padEnd(harnessWidth), ...HOOK_EVENTS.map((e) => e), "score", "verified".padEnd(verifyWidth)].join(" ");
|
|
5152
5280
|
lines.push(header);
|
|
5153
5281
|
for (const row of matrix) {
|
|
5154
5282
|
const cells = HOOK_EVENTS.map((event) => {
|
|
@@ -5156,7 +5284,16 @@ function formatBridgesStatus(matrix = BRIDGE_CAPABILITY_MATRIX) {
|
|
|
5156
5284
|
return cell.padEnd(event.length);
|
|
5157
5285
|
});
|
|
5158
5286
|
const score = `${row.implemented.size}/${HOOK_EVENTS.length}`;
|
|
5159
|
-
lines.push([row.harness.padEnd(harnessWidth), ...cells, score].join(" "));
|
|
5287
|
+
lines.push([row.harness.padEnd(harnessWidth), ...cells, score, row.verification.padEnd(verifyWidth)].join(" "));
|
|
5288
|
+
}
|
|
5289
|
+
lines.push("");
|
|
5290
|
+
lines.push("## How each row was established");
|
|
5291
|
+
for (const level of Object.keys(VERIFICATION_RANK)) {
|
|
5292
|
+
lines.push(`- ${level}: ${VERIFICATION_BLURB[level]}`);
|
|
5293
|
+
}
|
|
5294
|
+
lines.push("");
|
|
5295
|
+
for (const row of [...matrix].sort((a, b) => VERIFICATION_RANK[a.verification] - VERIFICATION_RANK[b.verification])) {
|
|
5296
|
+
lines.push(`- ${row.harness} (${row.verification}): ${row.verificationNote}`);
|
|
5160
5297
|
}
|
|
5161
5298
|
const gapRows = matrix.filter((r) => r.reasons.length > 0);
|
|
5162
5299
|
if (gapRows.length > 0) {
|
|
@@ -5170,6 +5307,11 @@ function formatBridgesStatus(matrix = BRIDGE_CAPABILITY_MATRIX) {
|
|
|
5170
5307
|
}
|
|
5171
5308
|
return lines.join("\n");
|
|
5172
5309
|
}
|
|
5310
|
+
function installVerificationNotice(harness) {
|
|
5311
|
+
const row = BRIDGE_CAPABILITY_MATRIX.find((r) => r.harness === harness);
|
|
5312
|
+
if (row === void 0 || row.verification === "dogfooded") return null;
|
|
5313
|
+
return `NOTE: the ${row.label} bridge is ${row.verification} -- ${VERIFICATION_BLURB[row.verification]}. If a token-goat feature seems to do nothing here, run \`token-goat doctor\` (its "Tool names" check reports what ${row.label} actually sent) and please open an issue. Details: token-goat bridges-status.`;
|
|
5314
|
+
}
|
|
5173
5315
|
|
|
5174
5316
|
// src/sharepoint_resolve.ts
|
|
5175
5317
|
import * as fs8 from "node:fs";
|
|
@@ -5484,7 +5626,7 @@ function purgeDataDirectories() {
|
|
|
5484
5626
|
}
|
|
5485
5627
|
return result;
|
|
5486
5628
|
}
|
|
5487
|
-
function
|
|
5629
|
+
function formatBytes2(bytes) {
|
|
5488
5630
|
if (bytes < 1024) return `${bytes} B`;
|
|
5489
5631
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
5490
5632
|
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
@@ -5505,8 +5647,8 @@ function formatTopFiles(ranked) {
|
|
|
5505
5647
|
if (ranked.length === 0) return "";
|
|
5506
5648
|
const lines = ["Top files this session:"];
|
|
5507
5649
|
for (const { path: filePath, count } of ranked) {
|
|
5508
|
-
const
|
|
5509
|
-
lines.push(` ${count.toString().padStart(3)}x ${
|
|
5650
|
+
const basename8 = path10.basename(filePath);
|
|
5651
|
+
lines.push(` ${count.toString().padStart(3)}x ${basename8} (${filePath})`);
|
|
5510
5652
|
}
|
|
5511
5653
|
return lines.join("\n");
|
|
5512
5654
|
}
|
|
@@ -5587,6 +5729,7 @@ function runStats(opts = {}) {
|
|
|
5587
5729
|
by_project: summary.by_project,
|
|
5588
5730
|
by_command: summary.by_command,
|
|
5589
5731
|
by_source: summary.by_source,
|
|
5732
|
+
by_harness: summary.by_harness,
|
|
5590
5733
|
window_days: summary.window_days
|
|
5591
5734
|
};
|
|
5592
5735
|
process.stdout.write(JSON.stringify(out2) + "\n");
|
|
@@ -5681,7 +5824,9 @@ function checkMcpProcessHealth(processes) {
|
|
|
5681
5824
|
const nodeProcesses = processes.filter((process3) => process3.name.toLowerCase() === "node.exe");
|
|
5682
5825
|
const chromeLaunchers = nodeProcesses.filter((process3) => /npx-cli\.js.*chrome-devtools-mcp/i.test(process3.commandLine));
|
|
5683
5826
|
const playwrightLaunchers = nodeProcesses.filter((process3) => /npx-cli\.js.*@playwright[\\/]mcp/i.test(process3.commandLine));
|
|
5684
|
-
const orphanedNodeProcesses = nodeProcesses.filter(
|
|
5827
|
+
const orphanedNodeProcesses = nodeProcesses.filter(
|
|
5828
|
+
(process3) => !byPid.has(process3.parentProcessId) && !/--worker-daemon\b/.test(process3.commandLine)
|
|
5829
|
+
);
|
|
5685
5830
|
const launchers = chromeLaunchers.length + playwrightLaunchers.length;
|
|
5686
5831
|
if (launchers > 2 || orphanedNodeProcesses.length > 0) {
|
|
5687
5832
|
const details = [];
|
|
@@ -5856,7 +6001,7 @@ function checkInstall() {
|
|
|
5856
6001
|
}
|
|
5857
6002
|
}
|
|
5858
6003
|
function checkTsCompiler() {
|
|
5859
|
-
if (
|
|
6004
|
+
if (isAvailable2()) {
|
|
5860
6005
|
return { name: "TypeScript compiler", status: "ok", message: "available" };
|
|
5861
6006
|
}
|
|
5862
6007
|
const err2 = loadError();
|
|
@@ -5866,6 +6011,27 @@ function checkTsCompiler() {
|
|
|
5866
6011
|
message: err2 !== null ? `unavailable: ${extractErrorMessage(err2)}` : "unavailable (not attempted)"
|
|
5867
6012
|
};
|
|
5868
6013
|
}
|
|
6014
|
+
function checkEmbeddings(config) {
|
|
6015
|
+
const name = "Embeddings";
|
|
6016
|
+
if ((config.indexing?.embeddings_enabled ?? true) === false) {
|
|
6017
|
+
return { name, status: "ok", message: "disabled by config (indexing.embeddings_enabled)" };
|
|
6018
|
+
}
|
|
6019
|
+
if (isAvailable()) return { name, status: "ok", message: "available" };
|
|
6020
|
+
const err2 = embeddingBackendLoadError();
|
|
6021
|
+
const code = err2?.code;
|
|
6022
|
+
if (code === "MODULE_NOT_FOUND" || code === "ERR_MODULE_NOT_FOUND") {
|
|
6023
|
+
return {
|
|
6024
|
+
name,
|
|
6025
|
+
status: "warn",
|
|
6026
|
+
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)"
|
|
6027
|
+
};
|
|
6028
|
+
}
|
|
6029
|
+
return {
|
|
6030
|
+
name,
|
|
6031
|
+
status: "warn",
|
|
6032
|
+
message: err2 !== null ? `onnxruntime-node is installed but failed to load: ${extractErrorMessage(err2)}` : "unavailable (not attempted)"
|
|
6033
|
+
};
|
|
6034
|
+
}
|
|
5869
6035
|
function checkConfigValid(configPath2) {
|
|
5870
6036
|
if (!fs12.existsSync(configPath2)) {
|
|
5871
6037
|
return {
|
|
@@ -6064,6 +6230,78 @@ function dataDirPermissionResult(dataDirPath) {
|
|
|
6064
6230
|
return { name: "Security data dir", status: "warn", message: "could not be read, so its permissions are unknown" };
|
|
6065
6231
|
}
|
|
6066
6232
|
}
|
|
6233
|
+
var COMPACTION_CHANNEL_WINDOW = 5;
|
|
6234
|
+
function checkCompactionChannel(dbPath) {
|
|
6235
|
+
const name = "Compaction channel";
|
|
6236
|
+
if (!fs12.existsSync(dbPath)) {
|
|
6237
|
+
return { name, status: "ok", message: "no database yet" };
|
|
6238
|
+
}
|
|
6239
|
+
try {
|
|
6240
|
+
const db = getDb(dbPath);
|
|
6241
|
+
const present = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'stats'").get();
|
|
6242
|
+
if (present === void 0) {
|
|
6243
|
+
return { name, status: "ok", message: "no compaction has been measured yet" };
|
|
6244
|
+
}
|
|
6245
|
+
const rows = db.prepare("SELECT detail FROM stats WHERE kind = 'compact_summary' ORDER BY rowid DESC LIMIT ?").all(COMPACTION_CHANNEL_WINDOW * 4);
|
|
6246
|
+
const conclusive = [];
|
|
6247
|
+
for (const row of rows) {
|
|
6248
|
+
const m = /manifest_paths=(\d+)\/(\d+)/.exec(row.detail ?? "");
|
|
6249
|
+
if (m === null) continue;
|
|
6250
|
+
const sampled = Number(m[2]);
|
|
6251
|
+
if (sampled === 0) continue;
|
|
6252
|
+
conclusive.push({ survived: Number(m[1]), sampled });
|
|
6253
|
+
if (conclusive.length >= COMPACTION_CHANNEL_WINDOW) break;
|
|
6254
|
+
}
|
|
6255
|
+
if (conclusive.length === 0) {
|
|
6256
|
+
return { name, status: "ok", message: "no compaction has been measured yet" };
|
|
6257
|
+
}
|
|
6258
|
+
const dead = conclusive.filter((c) => c.survived === 0).length;
|
|
6259
|
+
if (conclusive.length >= COMPACTION_CHANNEL_WINDOW && dead === conclusive.length) {
|
|
6260
|
+
return {
|
|
6261
|
+
name,
|
|
6262
|
+
status: "warn",
|
|
6263
|
+
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`
|
|
6264
|
+
};
|
|
6265
|
+
}
|
|
6266
|
+
const kept = conclusive.reduce((n, c) => n + c.survived, 0);
|
|
6267
|
+
const sent = conclusive.reduce((n, c) => n + c.sampled, 0);
|
|
6268
|
+
return { name, status: "ok", message: `${kept}/${sent} sampled paths survived the last ${conclusive.length} compaction(s)` };
|
|
6269
|
+
} catch (e) {
|
|
6270
|
+
return { name, status: "warn", message: `could not read compaction stats: ${extractErrorMessage(e)}` };
|
|
6271
|
+
}
|
|
6272
|
+
}
|
|
6273
|
+
var UNMAPPED_TOOL_SAMPLE = 5;
|
|
6274
|
+
function checkUnmappedTools(dbPath) {
|
|
6275
|
+
const name = "Tool names";
|
|
6276
|
+
if (!fs12.existsSync(dbPath)) {
|
|
6277
|
+
return { name, status: "ok", message: "no database yet" };
|
|
6278
|
+
}
|
|
6279
|
+
try {
|
|
6280
|
+
const rows = readUnmappedTools(dbPath);
|
|
6281
|
+
if (rows.length === 0) {
|
|
6282
|
+
return { name, status: "ok", message: "every tool name seen so far reached a handler that wanted it" };
|
|
6283
|
+
}
|
|
6284
|
+
const nearMisses = rows.filter((r) => r.near_miss !== null && r.near_miss !== void 0);
|
|
6285
|
+
if (nearMisses.length > 0) {
|
|
6286
|
+
const shown2 = nearMisses.slice(0, UNMAPPED_TOOL_SAMPLE).map((r) => `${r.harness} sent "${r.tool_name}" where "${r.near_miss}" is handled (${r.event_name}, ${r.hits}x)`);
|
|
6287
|
+
const more2 = nearMisses.length > UNMAPPED_TOOL_SAMPLE ? ` (+${nearMisses.length - UNMAPPED_TOOL_SAMPLE} more)` : "";
|
|
6288
|
+
return {
|
|
6289
|
+
name,
|
|
6290
|
+
status: "warn",
|
|
6291
|
+
message: `${shown2.join("; ")}${more2} -- these differ only by case or separators, so that bridge's tool-rename step is very likely not being applied and every handler behind those names is inert`
|
|
6292
|
+
};
|
|
6293
|
+
}
|
|
6294
|
+
const shown = rows.slice(0, UNMAPPED_TOOL_SAMPLE).map((r) => `${r.tool_name} (${r.hits}x)`);
|
|
6295
|
+
const more = rows.length > UNMAPPED_TOOL_SAMPLE ? `, +${rows.length - UNMAPPED_TOOL_SAMPLE} more` : "";
|
|
6296
|
+
return {
|
|
6297
|
+
name,
|
|
6298
|
+
status: "ok",
|
|
6299
|
+
message: `${rows.length} tool name(s) seen with no handler, none resembling one token-goat handles: ${shown.join(", ")}${more}`
|
|
6300
|
+
};
|
|
6301
|
+
} catch (e) {
|
|
6302
|
+
return { name, status: "warn", message: `could not read the tool-name histogram: ${extractErrorMessage(e)}` };
|
|
6303
|
+
}
|
|
6304
|
+
}
|
|
6067
6305
|
function runDoctor(dataDir2, configPath2, rootDir, processes) {
|
|
6068
6306
|
const results = [];
|
|
6069
6307
|
const actualDataDir = dataDir2 || dataDir();
|
|
@@ -6075,8 +6313,11 @@ function runDoctor(dataDir2, configPath2, rootDir, processes) {
|
|
|
6075
6313
|
results.push(checkSymbolBodySize(path11.join(actualDataDir, "global.db")));
|
|
6076
6314
|
results.push(checkSymbolCount(path11.join(actualDataDir, "global.db"), rootDir));
|
|
6077
6315
|
results.push(checkDirtyQueueHealth(actualDataDir));
|
|
6316
|
+
results.push(checkCompactionChannel(path11.join(actualDataDir, "global.db")));
|
|
6317
|
+
results.push(checkUnmappedTools(path11.join(actualDataDir, "global.db")));
|
|
6078
6318
|
const actualConfigPath = configPath2 || configPath();
|
|
6079
6319
|
results.push(checkConfigValid(actualConfigPath));
|
|
6320
|
+
results.push(checkEmbeddings(loadConfig(rootDir)));
|
|
6080
6321
|
for (const result of checkSecurityPosture(loadConfig(rootDir), actualDataDir)) results.push(result);
|
|
6081
6322
|
results.push(checkDiskSpace(actualDataDir));
|
|
6082
6323
|
const copilotResult = checkCopilotCli(copilotCliConfigPath(), copilotCliScriptPath());
|
|
@@ -8969,6 +9210,10 @@ var GO_FAIL = /^--- FAIL:\s+(\S+)/;
|
|
|
8969
9210
|
var CARGO_FAIL = /^test (\S+) \.\.\. FAILED/;
|
|
8970
9211
|
var CARGO_SECTION = /^---- (\S+) (?:stdout|stderr) ----$/;
|
|
8971
9212
|
var CARGO_RESULT = /^test result:/;
|
|
9213
|
+
var VITEST_FAIL_HEADER = /^\s+FAIL\s+(.+?)\s*$/;
|
|
9214
|
+
var VITEST_BANNER = /⎯+\s*Failed Tests\s+\d+\s*⎯+/;
|
|
9215
|
+
var VITEST_SEPARATOR = /^⎯+.*⎯+$/;
|
|
9216
|
+
var VITEST_SUMMARY = /^\s*(Test Files|Tests|Start at|Duration)\s/;
|
|
8972
9217
|
function detectRunner(text) {
|
|
8973
9218
|
if (text.includes("=== FAILURES ===") || text.includes("=== ERRORS ===")) {
|
|
8974
9219
|
return "pytest";
|
|
@@ -8976,6 +9221,9 @@ function detectRunner(text) {
|
|
|
8976
9221
|
if (/\bFAILED\b.+::test_/.test(text) || /short test summary/i.test(text)) {
|
|
8977
9222
|
return "pytest";
|
|
8978
9223
|
}
|
|
9224
|
+
if (VITEST_BANNER.test(text) || /^\s*Test Files\s+/m.test(text) && /^\s*Tests\s+\d+\s+(failed|passed)/m.test(text)) {
|
|
9225
|
+
return "vitest";
|
|
9226
|
+
}
|
|
8979
9227
|
if (/^\s+●\s/m.test(text) || /^FAIL\s+\S+\.test\b/m.test(text)) {
|
|
8980
9228
|
return "jest";
|
|
8981
9229
|
}
|
|
@@ -9100,6 +9348,44 @@ function extractJest(lines) {
|
|
|
9100
9348
|
}
|
|
9101
9349
|
return result;
|
|
9102
9350
|
}
|
|
9351
|
+
function extractVitest(lines) {
|
|
9352
|
+
const result = { runner: "vitest", blocks: [], summaryLines: [], statsLine: "" };
|
|
9353
|
+
let inBlock = false;
|
|
9354
|
+
let currentName = "";
|
|
9355
|
+
let currentBody = [];
|
|
9356
|
+
const flush = () => {
|
|
9357
|
+
if (inBlock && currentName) result.blocks.push({ name: currentName, body: currentBody.join("\n").trimEnd() });
|
|
9358
|
+
inBlock = false;
|
|
9359
|
+
currentName = "";
|
|
9360
|
+
currentBody = [];
|
|
9361
|
+
};
|
|
9362
|
+
for (const line of lines) {
|
|
9363
|
+
const s = line.trimEnd();
|
|
9364
|
+
const header = VITEST_FAIL_HEADER.exec(s);
|
|
9365
|
+
if (header) {
|
|
9366
|
+
flush();
|
|
9367
|
+
currentName = header[1] ?? "";
|
|
9368
|
+
currentBody = [];
|
|
9369
|
+
inBlock = true;
|
|
9370
|
+
result.summaryLines.push(currentName);
|
|
9371
|
+
continue;
|
|
9372
|
+
}
|
|
9373
|
+
if (VITEST_SUMMARY.test(s)) {
|
|
9374
|
+
if (/^\s*Tests\s/.test(s)) result.statsLine = s.trim();
|
|
9375
|
+
flush();
|
|
9376
|
+
continue;
|
|
9377
|
+
}
|
|
9378
|
+
if (inBlock) {
|
|
9379
|
+
if (VITEST_SEPARATOR.test(s)) {
|
|
9380
|
+
flush();
|
|
9381
|
+
continue;
|
|
9382
|
+
}
|
|
9383
|
+
currentBody.push(s);
|
|
9384
|
+
}
|
|
9385
|
+
}
|
|
9386
|
+
flush();
|
|
9387
|
+
return result;
|
|
9388
|
+
}
|
|
9103
9389
|
function extractGo(lines) {
|
|
9104
9390
|
const result = {
|
|
9105
9391
|
runner: "go",
|
|
@@ -9222,6 +9508,9 @@ function extractFailures(text, options) {
|
|
|
9222
9508
|
if (detected === "pytest") {
|
|
9223
9509
|
return extractPytest(lines);
|
|
9224
9510
|
}
|
|
9511
|
+
if (detected === "vitest") {
|
|
9512
|
+
return extractVitest(lines);
|
|
9513
|
+
}
|
|
9225
9514
|
if (detected === "jest") {
|
|
9226
9515
|
return extractJest(lines);
|
|
9227
9516
|
}
|
|
@@ -12408,8 +12697,8 @@ async function runMemoryCommand(opts = {}) {
|
|
|
12408
12697
|
}
|
|
12409
12698
|
|
|
12410
12699
|
// src/cli_waste.ts
|
|
12411
|
-
import * as
|
|
12412
|
-
import * as
|
|
12700
|
+
import * as fs24 from "node:fs";
|
|
12701
|
+
import * as path24 from "node:path";
|
|
12413
12702
|
|
|
12414
12703
|
// src/waste.ts
|
|
12415
12704
|
import * as fs22 from "node:fs";
|
|
@@ -12494,6 +12783,7 @@ function parseTranscript2(transcriptPath) {
|
|
|
12494
12783
|
const calls = [];
|
|
12495
12784
|
const resultTextById = /* @__PURE__ */ new Map();
|
|
12496
12785
|
const assistantTurns = [];
|
|
12786
|
+
const resident = createResidentContextStats();
|
|
12497
12787
|
let seq = 0;
|
|
12498
12788
|
for (const line of raw.split("\n")) {
|
|
12499
12789
|
const trimmed = line.trim();
|
|
@@ -12506,6 +12796,7 @@ function parseTranscript2(transcriptPath) {
|
|
|
12506
12796
|
}
|
|
12507
12797
|
if (obj === null || typeof obj !== "object") continue;
|
|
12508
12798
|
const o = obj;
|
|
12799
|
+
accumulateResidentLine(resident, o, trimmed.length);
|
|
12509
12800
|
const message = o["message"];
|
|
12510
12801
|
if (message === null || typeof message !== "object") continue;
|
|
12511
12802
|
const messageObj = message;
|
|
@@ -12545,7 +12836,7 @@ function parseTranscript2(transcriptPath) {
|
|
|
12545
12836
|
}
|
|
12546
12837
|
if (role === "assistant" && sawTextBlock) assistantTurns.push(turnTextTokens);
|
|
12547
12838
|
}
|
|
12548
|
-
return { calls, resultTextById, assistantTurns };
|
|
12839
|
+
return { calls, resultTextById, assistantTurns, resident };
|
|
12549
12840
|
}
|
|
12550
12841
|
function costPerCall(parsed) {
|
|
12551
12842
|
return parsed.calls.map((c) => ({
|
|
@@ -12651,11 +12942,250 @@ async function buildWasteReport(transcriptPath, opts = {}) {
|
|
|
12651
12942
|
topCalls: topExpensiveCalls(costs, topN).map((c) => ({ seq: c.seq, name: c.name, summary: c.summary, tokens: c.tokens })),
|
|
12652
12943
|
neverTouchedAgain: neverTouchedAgain(costs),
|
|
12653
12944
|
repeatedUncompressedBash: await repeatedUncompressedBashCommands(costs),
|
|
12654
|
-
assistantOutput: assistantOutputCost(parsed.assistantTurns)
|
|
12945
|
+
assistantOutput: assistantOutputCost(parsed.assistantTurns),
|
|
12946
|
+
residentContext: summarizeResidentContext(parsed.resident)
|
|
12947
|
+
};
|
|
12948
|
+
}
|
|
12949
|
+
|
|
12950
|
+
// src/copilot_waste.ts
|
|
12951
|
+
import * as fs23 from "node:fs";
|
|
12952
|
+
import * as path23 from "node:path";
|
|
12953
|
+
var HOOK_RECORD_TYPES = /* @__PURE__ */ new Set(["hook.start", "hook.end"]);
|
|
12954
|
+
function splitInjectedBlocks(transformed) {
|
|
12955
|
+
const out2 = [];
|
|
12956
|
+
const blockRe = /<([a-z_][a-z0-9_]*)>([\s\S]*?)<\/\1>/gi;
|
|
12957
|
+
let m;
|
|
12958
|
+
while ((m = blockRe.exec(transformed)) !== null) {
|
|
12959
|
+
const tag = (m[1] ?? "").toLowerCase();
|
|
12960
|
+
const body = m[2] ?? "";
|
|
12961
|
+
let kind = tag;
|
|
12962
|
+
if (tag === "system_reminder") {
|
|
12963
|
+
const inner = /^\s*<([a-z_][a-z0-9_]*)>/i.exec(body);
|
|
12964
|
+
kind = inner !== null ? `reminder:${(inner[1] ?? "").toLowerCase()}` : "reminder:text";
|
|
12965
|
+
}
|
|
12966
|
+
out2.push({ kind, body: m[0] });
|
|
12967
|
+
}
|
|
12968
|
+
return out2;
|
|
12969
|
+
}
|
|
12970
|
+
function findLatestCopilotSession() {
|
|
12971
|
+
const root = path23.join(copilotCliUserRoot(), "session-state");
|
|
12972
|
+
let entries;
|
|
12973
|
+
try {
|
|
12974
|
+
entries = fs23.readdirSync(root);
|
|
12975
|
+
} catch {
|
|
12976
|
+
return null;
|
|
12977
|
+
}
|
|
12978
|
+
let best = null;
|
|
12979
|
+
let bestMtime = -Infinity;
|
|
12980
|
+
for (const entry of entries) {
|
|
12981
|
+
const candidate = path23.join(root, entry, "events.jsonl");
|
|
12982
|
+
try {
|
|
12983
|
+
const st = fs23.statSync(candidate);
|
|
12984
|
+
if (!st.isFile()) continue;
|
|
12985
|
+
if (st.mtimeMs > bestMtime) {
|
|
12986
|
+
bestMtime = st.mtimeMs;
|
|
12987
|
+
best = candidate;
|
|
12988
|
+
}
|
|
12989
|
+
} catch {
|
|
12990
|
+
}
|
|
12991
|
+
}
|
|
12992
|
+
return best;
|
|
12993
|
+
}
|
|
12994
|
+
function readNumber(source, key) {
|
|
12995
|
+
const value = source[key];
|
|
12996
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
12997
|
+
}
|
|
12998
|
+
function buildCopilotWasteReport(eventsPath) {
|
|
12999
|
+
const raw = fs23.readFileSync(eventsPath, "utf-8");
|
|
13000
|
+
const report = {
|
|
13001
|
+
sessionPath: eventsPath,
|
|
13002
|
+
sessionId: path23.basename(path23.dirname(eventsPath)),
|
|
13003
|
+
turns: 0,
|
|
13004
|
+
tokens: null,
|
|
13005
|
+
blocks: [],
|
|
13006
|
+
compactions: [],
|
|
13007
|
+
hookRecordBytes: 0,
|
|
13008
|
+
totalEventBytes: Buffer.byteLength(raw, "utf-8"),
|
|
13009
|
+
// Read through the real resolution chain rather than passed in. Copilot's
|
|
13010
|
+
// own COPILOT_CACHE_HOME override is what tests point at a fixture, so the
|
|
13011
|
+
// shipping path is the tested path and there is no seam here that only a
|
|
13012
|
+
// test ever supplies.
|
|
13013
|
+
mcpTools: readCopilotMcpTools()
|
|
12655
13014
|
};
|
|
13015
|
+
const classes = /* @__PURE__ */ new Map();
|
|
13016
|
+
const seen = /* @__PURE__ */ new Map();
|
|
13017
|
+
for (const line of raw.split("\n")) {
|
|
13018
|
+
const trimmed = line.trim();
|
|
13019
|
+
if (trimmed === "") continue;
|
|
13020
|
+
let event;
|
|
13021
|
+
try {
|
|
13022
|
+
event = JSON.parse(trimmed);
|
|
13023
|
+
} catch {
|
|
13024
|
+
continue;
|
|
13025
|
+
}
|
|
13026
|
+
const type = typeof event["type"] === "string" ? event["type"] : "";
|
|
13027
|
+
const data = event["data"] ?? {};
|
|
13028
|
+
if (HOOK_RECORD_TYPES.has(type)) {
|
|
13029
|
+
report.hookRecordBytes += Buffer.byteLength(trimmed, "utf-8");
|
|
13030
|
+
continue;
|
|
13031
|
+
}
|
|
13032
|
+
if (type === "session.shutdown") {
|
|
13033
|
+
report.tokens = {
|
|
13034
|
+
systemTokens: readNumber(data, "systemTokens"),
|
|
13035
|
+
toolDefinitionsTokens: readNumber(data, "toolDefinitionsTokens"),
|
|
13036
|
+
conversationTokens: readNumber(data, "conversationTokens"),
|
|
13037
|
+
currentTokens: readNumber(data, "currentTokens")
|
|
13038
|
+
};
|
|
13039
|
+
continue;
|
|
13040
|
+
}
|
|
13041
|
+
if (type === "session.compaction_complete") {
|
|
13042
|
+
const summary = typeof data["summaryContent"] === "string" ? data["summaryContent"] : "";
|
|
13043
|
+
report.compactions.push({
|
|
13044
|
+
trigger: typeof data["trigger"] === "string" ? data["trigger"] : "unknown",
|
|
13045
|
+
summaryBytes: Buffer.byteLength(summary, "utf-8"),
|
|
13046
|
+
preTokens: readNumber(data, "preCompactionTokens"),
|
|
13047
|
+
postTokens: readNumber(data, "postCompactionTokens")
|
|
13048
|
+
});
|
|
13049
|
+
continue;
|
|
13050
|
+
}
|
|
13051
|
+
if (type !== "user.message") continue;
|
|
13052
|
+
const transformed = typeof data["transformedContent"] === "string" ? data["transformedContent"] : "";
|
|
13053
|
+
if (transformed === "") continue;
|
|
13054
|
+
report.turns += 1;
|
|
13055
|
+
for (const block of splitInjectedBlocks(transformed)) {
|
|
13056
|
+
const bytes = Buffer.byteLength(block.body, "utf-8");
|
|
13057
|
+
let cls = classes.get(block.kind);
|
|
13058
|
+
if (cls === void 0) {
|
|
13059
|
+
cls = { kind: block.kind, count: 0, bytes: 0, repeatBytes: 0, repeatCount: 0 };
|
|
13060
|
+
classes.set(block.kind, cls);
|
|
13061
|
+
}
|
|
13062
|
+
cls.count += 1;
|
|
13063
|
+
cls.bytes += bytes;
|
|
13064
|
+
let bucket = seen.get(block.kind);
|
|
13065
|
+
if (bucket === void 0) {
|
|
13066
|
+
bucket = /* @__PURE__ */ new Set();
|
|
13067
|
+
seen.set(block.kind, bucket);
|
|
13068
|
+
}
|
|
13069
|
+
if (bucket.has(block.body)) {
|
|
13070
|
+
cls.repeatBytes += bytes;
|
|
13071
|
+
cls.repeatCount += 1;
|
|
13072
|
+
} else {
|
|
13073
|
+
bucket.add(block.body);
|
|
13074
|
+
}
|
|
13075
|
+
}
|
|
13076
|
+
}
|
|
13077
|
+
report.blocks = [...classes.values()].sort((a, b) => b.bytes - a.bytes);
|
|
13078
|
+
return report;
|
|
12656
13079
|
}
|
|
12657
13080
|
|
|
12658
13081
|
// src/cli_waste.ts
|
|
13082
|
+
function printCopilotReport(report) {
|
|
13083
|
+
const w = (text) => {
|
|
13084
|
+
process.stdout.write(text);
|
|
13085
|
+
};
|
|
13086
|
+
w("\n# token-goat waste (Copilot CLI)\n");
|
|
13087
|
+
w(`Session: ${report.sessionId}
|
|
13088
|
+
`);
|
|
13089
|
+
w(` ${report.sessionPath}
|
|
13090
|
+
`);
|
|
13091
|
+
w("\n## Per-request fixed overhead (Copilot's own token counts)\n");
|
|
13092
|
+
if (report.tokens === null) {
|
|
13093
|
+
w(" No shutdown record yet, so Copilot has not published a token split for this session.\n");
|
|
13094
|
+
} else {
|
|
13095
|
+
const { systemTokens, toolDefinitionsTokens, conversationTokens } = report.tokens;
|
|
13096
|
+
const fixed = systemTokens + toolDefinitionsTokens;
|
|
13097
|
+
const total = fixed + conversationTokens;
|
|
13098
|
+
w(` System prompt: ${systemTokens.toLocaleString()} tok
|
|
13099
|
+
`);
|
|
13100
|
+
w(` Tool definitions: ${toolDefinitionsTokens.toLocaleString()} tok
|
|
13101
|
+
`);
|
|
13102
|
+
w(` Conversation: ${conversationTokens.toLocaleString()} tok
|
|
13103
|
+
`);
|
|
13104
|
+
if (total > 0) {
|
|
13105
|
+
const pct = (fixed / total * 100).toFixed(1);
|
|
13106
|
+
w(` ${fixed.toLocaleString()} tok of system prompt and tool definitions ships with every
|
|
13107
|
+
`);
|
|
13108
|
+
w(` request; at shutdown that was ${pct}% of the context. No hook can reach it: Copilot
|
|
13109
|
+
`);
|
|
13110
|
+
w(" assembles both natively, with nothing between assembly and send. The levers are all\n");
|
|
13111
|
+
w(" config: fewer MCP servers and custom tools, and --excluded-tools / --available-tools /\n");
|
|
13112
|
+
w(" --agent / --no-ask-user / --no-custom-instructions, whose effect on this number is\n");
|
|
13113
|
+
w(" entailed by how they are wired but has not been measured here. Copilot also ships\n");
|
|
13114
|
+
w(" built-in MCP servers, which --disable-builtin-mcps turns off wholesale. Note that\n");
|
|
13115
|
+
w(" --enable-all-github-mcp-tools moves this number the wrong way: its own help text calls\n");
|
|
13116
|
+
w(" the default a subset, so enabling everything adds definitions rather than removing\n");
|
|
13117
|
+
w(" them. --add-github-mcp-tool and --add-github-mcp-toolset replace that subset too.\n");
|
|
13118
|
+
}
|
|
13119
|
+
}
|
|
13120
|
+
w("\n## Tool definitions by MCP server (estimated)\n");
|
|
13121
|
+
const mcp = report.mcpTools;
|
|
13122
|
+
if (!mcp.cacheFound) {
|
|
13123
|
+
w(` No MCP tool cache at ${copilotCliMcpToolsDir()}.
|
|
13124
|
+
`);
|
|
13125
|
+
w(" That is not the same as having no MCP servers: it means nothing has been cached\n");
|
|
13126
|
+
w(" there yet, which is also what a Copilot that has never connected one looks like.\n");
|
|
13127
|
+
} else if (mcp.servers.length === 0) {
|
|
13128
|
+
w(" Cache is present but holds no servers, so none of the tool-definition budget\n");
|
|
13129
|
+
w(` above is MCP: it is all Copilot's own tools, which the flags above can narrow
|
|
13130
|
+
`);
|
|
13131
|
+
w(" but dropping a server cannot.\n");
|
|
13132
|
+
} else {
|
|
13133
|
+
for (const server of mcp.servers) {
|
|
13134
|
+
const tokens = server.estimatedTokens.toLocaleString();
|
|
13135
|
+
w(` ${server.serverName}: ${countNoun(server.toolCount, "tool")}, `);
|
|
13136
|
+
w(`${formatBytes(server.definitionBytes)}, ~${tokens} tok
|
|
13137
|
+
`);
|
|
13138
|
+
}
|
|
13139
|
+
const mcpTokens = mcp.servers.reduce((sum, server) => sum + server.estimatedTokens, 0);
|
|
13140
|
+
w(` ~${mcpTokens.toLocaleString()} tok estimated across `);
|
|
13141
|
+
w(`${countNoun(mcp.servers.length, "server")}, re-sent every request.
|
|
13142
|
+
`);
|
|
13143
|
+
if (report.tokens !== null && report.tokens.toolDefinitionsTokens > 0) {
|
|
13144
|
+
const share = (mcpTokens / report.tokens.toolDefinitionsTokens * 100).toFixed(1);
|
|
13145
|
+
w(` Copilot counted ${report.tokens.toolDefinitionsTokens.toLocaleString()} tok of tool `);
|
|
13146
|
+
w(`definitions in total, so this is roughly ${share}% of it.
|
|
13147
|
+
`);
|
|
13148
|
+
w(" The two are not a split of each other and come from different places: Copilot\n");
|
|
13149
|
+
w(" counted its own with its own tokeniser, while these are estimated from the bytes\n");
|
|
13150
|
+
w(` of the cached definitions. The remainder is mostly Copilot's own non-MCP tools,
|
|
13151
|
+
`);
|
|
13152
|
+
w(" which are not cached here: dropping a server does not touch them, though the\n");
|
|
13153
|
+
w(" tool-filter flags above still can.\n");
|
|
13154
|
+
}
|
|
13155
|
+
w(" Dropping a server saves its line above on every request for the rest of the session.\n");
|
|
13156
|
+
}
|
|
13157
|
+
if (mcp.unreadable > 0) {
|
|
13158
|
+
const verb = mcp.unreadable === 1 ? "is" : "are";
|
|
13159
|
+
w(` ${countNoun(mcp.unreadable, "cache file")} could not be read and ${verb} not counted above.
|
|
13160
|
+
`);
|
|
13161
|
+
}
|
|
13162
|
+
w("\n## Injected blocks in the assembled prompt\n");
|
|
13163
|
+
if (report.blocks.length === 0) {
|
|
13164
|
+
w(` none across ${countNoun(report.turns, "turn")}
|
|
13165
|
+
`);
|
|
13166
|
+
} else {
|
|
13167
|
+
for (const block of report.blocks) {
|
|
13168
|
+
const pct = block.bytes > 0 ? (block.repeatBytes / block.bytes * 100).toFixed(1) : "0.0";
|
|
13169
|
+
w(` ${block.kind}: ${countNoun(block.count, "injection")}, ${formatBytes(block.bytes)}`);
|
|
13170
|
+
w(block.repeatBytes > 0 ? `, ${formatBytes(block.repeatBytes)} re-sent verbatim (${pct}%)
|
|
13171
|
+
` : "\n");
|
|
13172
|
+
}
|
|
13173
|
+
}
|
|
13174
|
+
w("\n## Compactions\n");
|
|
13175
|
+
if (report.compactions.length === 0) {
|
|
13176
|
+
w(` ${countNoun(report.compactions.length, "compaction")} recorded this session
|
|
13177
|
+
`);
|
|
13178
|
+
} else {
|
|
13179
|
+
for (const c of report.compactions) {
|
|
13180
|
+
w(` ${c.trigger}: summary ${formatBytes(c.summaryBytes)}, ${c.preTokens.toLocaleString()} -> ${c.postTokens.toLocaleString()} tok
|
|
13181
|
+
`);
|
|
13182
|
+
}
|
|
13183
|
+
}
|
|
13184
|
+
w("\n## On disk but never billed\n");
|
|
13185
|
+
w(` Hook records: ${formatBytes(report.hookRecordBytes)} of ${formatBytes(report.totalEventBytes)} in the event log.
|
|
13186
|
+
`);
|
|
13187
|
+
w(" hook.start/hook.end never reach the model, so this is log weight, not context weight.\n");
|
|
13188
|
+
}
|
|
12659
13189
|
function printReport2(report) {
|
|
12660
13190
|
const w = (text) => {
|
|
12661
13191
|
process.stdout.write(text);
|
|
@@ -12708,10 +13238,71 @@ function printReport2(report) {
|
|
|
12708
13238
|
w(` Re-send upper bound: ${ao.resendCeilingTokens} tok if every turn were resent at full price on every later request
|
|
12709
13239
|
`);
|
|
12710
13240
|
w(" Real cost is substantially lower: prompt caching bills resent conversation history at cache-read rates, not full input price.\n");
|
|
13241
|
+
printResidentContext(report.residentContext, w);
|
|
13242
|
+
}
|
|
13243
|
+
function printResidentContext(resident, w) {
|
|
13244
|
+
w("\n## Harness-injected context (never passes through a hook)\n");
|
|
13245
|
+
if (resident.attachmentClasses.length === 0) {
|
|
13246
|
+
w(" none\n");
|
|
13247
|
+
} else {
|
|
13248
|
+
w(` Total injected: ${formatBytes(resident.totalAttachmentBytes)} across ${countNoun(resident.attachmentClasses.length, "class", "classes")}
|
|
13249
|
+
`);
|
|
13250
|
+
for (const c of resident.attachmentClasses.slice(0, 8)) {
|
|
13251
|
+
w(` ${c.type}: ${countNoun(c.count, "injection")}, ${formatBytes(c.bytes)}
|
|
13252
|
+
`);
|
|
13253
|
+
}
|
|
13254
|
+
}
|
|
13255
|
+
const task = resident.latestTaskList;
|
|
13256
|
+
if (task !== null) {
|
|
13257
|
+
w("\n## Task list (re-injected in full whenever it changes)\n");
|
|
13258
|
+
w(` Latest: ${formatBytes(task.bytes)} (~${formatTokenEstimate(estimateTokensFromLength(task.bytes))} tok est), ${countNoun(task.itemCount, "item")}
|
|
13259
|
+
`);
|
|
13260
|
+
w(` ${task.completed} completed, ${task.inProgress} in progress, ${task.pending} pending
|
|
13261
|
+
`);
|
|
13262
|
+
w(` Descriptions: ${formatBytes(task.descriptionBytes)} total, ${formatBytes(task.completedDescriptionBytes)} of it on completed items
|
|
13263
|
+
`);
|
|
13264
|
+
w(` Injected ${countNoun(resident.taskReminderCount, "time")} this session, ${formatBytes(resident.taskReminderBytes)} cumulative
|
|
13265
|
+
`);
|
|
13266
|
+
}
|
|
13267
|
+
if (resident.repeatedSkillBodies.length > 0) {
|
|
13268
|
+
w("\n## Skill bodies injected more than once\n");
|
|
13269
|
+
w(" Slash expansion and the Skill tool both send the whole body every time; no hook sees either.\n");
|
|
13270
|
+
for (const s of resident.repeatedSkillBodies.slice(0, 8)) {
|
|
13271
|
+
w(` ${s.skill}: ${countNoun(s.count, "injection")}, ${formatBytes(s.bytes)} (~${formatTokenEstimate(estimateTokensFromLength(s.bytes))} tok est)
|
|
13272
|
+
`);
|
|
13273
|
+
}
|
|
13274
|
+
}
|
|
13275
|
+
w("\n## Compactions\n");
|
|
13276
|
+
w(` ${countNoun(resident.compactionCount, "compaction")} this session`);
|
|
13277
|
+
w(resident.compactionCount === 0 ? "\n" : ", each re-injecting the fixed preamble (CLAUDE.md, memory, skill and agent listings)\n");
|
|
12711
13278
|
}
|
|
12712
13279
|
async function runWasteCommand(opts = {}) {
|
|
13280
|
+
if (opts.copilot === true) {
|
|
13281
|
+
const eventsPath = opts.transcript !== void 0 ? path24.resolve(opts.transcript) : findLatestCopilotSession();
|
|
13282
|
+
if (eventsPath === null || !fs24.existsSync(eventsPath)) {
|
|
13283
|
+
const detail = eventsPath === null ? "no Copilot CLI session found under <copilot-home>/session-state" : `Copilot session event log not found: ${eventsPath}`;
|
|
13284
|
+
if (opts.json === true) {
|
|
13285
|
+
process.stdout.write(`${JSON.stringify({ error: detail })}
|
|
13286
|
+
`);
|
|
13287
|
+
} else {
|
|
13288
|
+
process.stdout.write("\n# token-goat waste (Copilot CLI)\n");
|
|
13289
|
+
process.stdout.write(`${detail}
|
|
13290
|
+
`);
|
|
13291
|
+
}
|
|
13292
|
+
process.exitCode = 1;
|
|
13293
|
+
return;
|
|
13294
|
+
}
|
|
13295
|
+
const copilotReport = buildCopilotWasteReport(eventsPath);
|
|
13296
|
+
if (opts.json === true) {
|
|
13297
|
+
process.stdout.write(`${JSON.stringify(copilotReport)}
|
|
13298
|
+
`);
|
|
13299
|
+
return;
|
|
13300
|
+
}
|
|
13301
|
+
printCopilotReport(copilotReport);
|
|
13302
|
+
return;
|
|
13303
|
+
}
|
|
12713
13304
|
const projectRoot = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
|
|
12714
|
-
const transcriptPath = opts.transcript !== void 0 ?
|
|
13305
|
+
const transcriptPath = opts.transcript !== void 0 ? path24.resolve(opts.transcript) : findLatestTranscript(projectRoot);
|
|
12715
13306
|
if (transcriptPath === null) {
|
|
12716
13307
|
if (opts.json === true) {
|
|
12717
13308
|
process.stdout.write(`${JSON.stringify({ error: "no session transcript found", project: projectRoot })}
|
|
@@ -12725,7 +13316,7 @@ async function runWasteCommand(opts = {}) {
|
|
|
12725
13316
|
process.exitCode = 1;
|
|
12726
13317
|
return;
|
|
12727
13318
|
}
|
|
12728
|
-
if (!
|
|
13319
|
+
if (!fs24.existsSync(transcriptPath)) {
|
|
12729
13320
|
process.stderr.write(`token-goat: transcript not found: ${transcriptPath}
|
|
12730
13321
|
`);
|
|
12731
13322
|
process.exitCode = 1;
|
|
@@ -12741,17 +13332,17 @@ async function runWasteCommand(opts = {}) {
|
|
|
12741
13332
|
}
|
|
12742
13333
|
|
|
12743
13334
|
// src/session_read.ts
|
|
12744
|
-
import * as
|
|
12745
|
-
import * as
|
|
13335
|
+
import * as fs25 from "node:fs";
|
|
13336
|
+
import * as path25 from "node:path";
|
|
12746
13337
|
import * as readline2 from "node:readline";
|
|
12747
13338
|
function resolveSessionTranscript(arg, opts = {}) {
|
|
12748
13339
|
if (arg !== void 0 && arg !== "") {
|
|
12749
|
-
if (
|
|
13340
|
+
if (fs25.existsSync(arg) && fs25.statSync(arg).isFile()) return arg;
|
|
12750
13341
|
const projectRoot2 = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
|
|
12751
13342
|
const dir = projectTranscriptsDir(projectRoot2);
|
|
12752
13343
|
const byId = arg.endsWith(".jsonl") ? arg : `${arg}.jsonl`;
|
|
12753
|
-
const candidate =
|
|
12754
|
-
if (
|
|
13344
|
+
const candidate = path25.join(dir, byId);
|
|
13345
|
+
if (fs25.existsSync(candidate) && fs25.statSync(candidate).isFile()) return candidate;
|
|
12755
13346
|
return null;
|
|
12756
13347
|
}
|
|
12757
13348
|
const projectRoot = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
|
|
@@ -12809,7 +13400,7 @@ function toolCallsForBlocks(blocks) {
|
|
|
12809
13400
|
return blocks.filter((b) => b.type === "tool_use" && b.name !== void 0).map((b) => b.name);
|
|
12810
13401
|
}
|
|
12811
13402
|
async function* streamTurns(transcriptPath) {
|
|
12812
|
-
const input =
|
|
13403
|
+
const input = fs25.createReadStream(transcriptPath, { encoding: "utf8" });
|
|
12813
13404
|
const rl = readline2.createInterface({ input, crlfDelay: Infinity });
|
|
12814
13405
|
try {
|
|
12815
13406
|
let lineNumber = 0;
|
|
@@ -12907,15 +13498,15 @@ function formatSessionSlice(turns) {
|
|
|
12907
13498
|
}
|
|
12908
13499
|
|
|
12909
13500
|
// src/cli_mcp_audit.ts
|
|
12910
|
-
import * as
|
|
12911
|
-
import * as
|
|
13501
|
+
import * as fs26 from "node:fs";
|
|
13502
|
+
import * as path26 from "node:path";
|
|
12912
13503
|
function readMcpConfig(projectRoot) {
|
|
12913
|
-
const configPath2 =
|
|
13504
|
+
const configPath2 = path26.join(projectRoot, ".mcp.json");
|
|
12914
13505
|
try {
|
|
12915
|
-
if (!
|
|
13506
|
+
if (!fs26.existsSync(configPath2)) {
|
|
12916
13507
|
return null;
|
|
12917
13508
|
}
|
|
12918
|
-
const content =
|
|
13509
|
+
const content = fs26.readFileSync(configPath2, "utf-8");
|
|
12919
13510
|
const parsed = JSON.parse(content);
|
|
12920
13511
|
return parsed.mcpServers || parsed;
|
|
12921
13512
|
} catch {
|
|
@@ -13221,10 +13812,10 @@ function readBoundedText(text, file) {
|
|
|
13221
13812
|
if (text === void 0 && file === void 0) throw new CliError("provide text or --file");
|
|
13222
13813
|
let value;
|
|
13223
13814
|
if (file !== void 0) {
|
|
13224
|
-
if (
|
|
13815
|
+
if (fs27.statSync(file).size > CONTENT_MAX_INPUT_CHARS) {
|
|
13225
13816
|
throw new CliError(`file exceeds the ${CONTENT_MAX_INPUT_CHARS}-byte safety limit`);
|
|
13226
13817
|
}
|
|
13227
|
-
value =
|
|
13818
|
+
value = fs27.readFileSync(file, "utf8");
|
|
13228
13819
|
} else {
|
|
13229
13820
|
if (text === void 0) throw new CliError("provide text or --file");
|
|
13230
13821
|
value = text;
|
|
@@ -13361,7 +13952,7 @@ async function cmdIndex(pathArg, opts = {}) {
|
|
|
13361
13952
|
continue;
|
|
13362
13953
|
}
|
|
13363
13954
|
const sha = fingerprintFile(key);
|
|
13364
|
-
if (sha === null && !
|
|
13955
|
+
if (sha === null && !fs27.existsSync(key)) continue;
|
|
13365
13956
|
const entry = sha !== null ? getFileEntry(key, dbPath) : null;
|
|
13366
13957
|
const spellingStale = entry !== null && indexedPathSpellingIsStale(entry.filePath, key);
|
|
13367
13958
|
const parseUnchanged = !force && !spellingStale && sha !== null && entry?.sha === sha;
|
|
@@ -13387,7 +13978,7 @@ async function cmdIndex(pathArg, opts = {}) {
|
|
|
13387
13978
|
}
|
|
13388
13979
|
continue;
|
|
13389
13980
|
}
|
|
13390
|
-
if (!
|
|
13981
|
+
if (!fs27.existsSync(key)) continue;
|
|
13391
13982
|
}
|
|
13392
13983
|
if (!embedUnchanged) {
|
|
13393
13984
|
paintProgress("embedding");
|
|
@@ -13447,11 +14038,11 @@ async function cmdMcpServe() {
|
|
|
13447
14038
|
let StdioServerTransport;
|
|
13448
14039
|
try {
|
|
13449
14040
|
;
|
|
13450
|
-
({ createMcpServer } = await import("./token-goat-chunk-
|
|
13451
|
-
({ StdioServerTransport } = await import("
|
|
14041
|
+
({ createMcpServer } = await import("./token-goat-chunk-73MF6YWW.mjs"));
|
|
14042
|
+
({ StdioServerTransport } = await import("./token-goat-chunk-324QOJYZ.mjs"));
|
|
13452
14043
|
} catch (err2) {
|
|
13453
14044
|
process.stderr.write(
|
|
13454
|
-
`token-goat: mcp-server unavailable (
|
|
14045
|
+
`token-goat: mcp-server unavailable (could not load the MCP server modules): ${String(err2)}
|
|
13455
14046
|
`
|
|
13456
14047
|
);
|
|
13457
14048
|
process.exitCode = 1;
|
|
@@ -13461,14 +14052,18 @@ async function cmdMcpServe() {
|
|
|
13461
14052
|
const transport = new StdioServerTransport();
|
|
13462
14053
|
await server.connect(transport);
|
|
13463
14054
|
await new Promise((resolve12) => {
|
|
13464
|
-
server.
|
|
14055
|
+
server.onclose = resolve12;
|
|
13465
14056
|
});
|
|
13466
14057
|
}
|
|
14058
|
+
function printBridgeVerificationNotice(harness) {
|
|
14059
|
+
const notice = installVerificationNotice(harness);
|
|
14060
|
+
if (notice !== null) out(notice);
|
|
14061
|
+
}
|
|
13467
14062
|
async function cmdHook(event, opts) {
|
|
13468
14063
|
if (typeof opts.harness === "string" && opts.harness.length > 0) {
|
|
13469
14064
|
process.env[ENV_KEYS.HARNESS_OVERRIDE] = opts.harness;
|
|
13470
14065
|
}
|
|
13471
|
-
const { relay } = await import("./token-goat-chunk-
|
|
14066
|
+
const { relay } = await import("./token-goat-chunk-YUGNM3KL.mjs");
|
|
13472
14067
|
await relay(event);
|
|
13473
14068
|
}
|
|
13474
14069
|
async function cmdInstall(opts) {
|
|
@@ -13495,6 +14090,7 @@ async function cmdInstall(opts) {
|
|
|
13495
14090
|
} else {
|
|
13496
14091
|
out(`Installed token-goat Codex CLI integration \u2192 ${codexResult.configPath}, ${codexResult.agentsPath}`);
|
|
13497
14092
|
}
|
|
14093
|
+
printBridgeVerificationNotice("codex");
|
|
13498
14094
|
}
|
|
13499
14095
|
if (opts.gemini === true) {
|
|
13500
14096
|
const geminiResult = installGemini();
|
|
@@ -13503,6 +14099,7 @@ async function cmdInstall(opts) {
|
|
|
13503
14099
|
} else {
|
|
13504
14100
|
out(`Installed token-goat Gemini CLI integration \u2192 ${geminiResult.settingsPath}`);
|
|
13505
14101
|
}
|
|
14102
|
+
printBridgeVerificationNotice("gemini");
|
|
13506
14103
|
}
|
|
13507
14104
|
if (opts.qwen === true) {
|
|
13508
14105
|
const qwenResult = installQwen();
|
|
@@ -13511,6 +14108,7 @@ async function cmdInstall(opts) {
|
|
|
13511
14108
|
} else {
|
|
13512
14109
|
out(`Installed token-goat Qwen Code integration \u2192 ${qwenResult.settingsPath}`);
|
|
13513
14110
|
}
|
|
14111
|
+
printBridgeVerificationNotice("qwen");
|
|
13514
14112
|
}
|
|
13515
14113
|
if (opts.kimi === true) {
|
|
13516
14114
|
const kimiResult = installKimi();
|
|
@@ -13519,6 +14117,7 @@ async function cmdInstall(opts) {
|
|
|
13519
14117
|
} else {
|
|
13520
14118
|
out(`Installed token-goat Kimi Code integration \u2192 ${kimiResult.configPath}, ${kimiResult.hookScriptPath}, ${kimiResult.agentsPath}, ${kimiResult.skillPath}`);
|
|
13521
14119
|
}
|
|
14120
|
+
printBridgeVerificationNotice("kimi");
|
|
13522
14121
|
}
|
|
13523
14122
|
if (opts.pi === true) {
|
|
13524
14123
|
const piResult = installPi({ local: opts.local === true });
|
|
@@ -13527,6 +14126,7 @@ async function cmdInstall(opts) {
|
|
|
13527
14126
|
} else {
|
|
13528
14127
|
out(`Installed token-goat pi extension \u2192 ${piResult.extensionPath}`);
|
|
13529
14128
|
}
|
|
14129
|
+
printBridgeVerificationNotice("pi");
|
|
13530
14130
|
}
|
|
13531
14131
|
if (opts.openclaw === true) {
|
|
13532
14132
|
const openclawResult = installOpenclaw();
|
|
@@ -13535,6 +14135,7 @@ async function cmdInstall(opts) {
|
|
|
13535
14135
|
} else {
|
|
13536
14136
|
out(`Installed token-goat OpenClaw integration \u2192 ${openclawResult.configPath}, ${openclawResult.pluginPath}`);
|
|
13537
14137
|
}
|
|
14138
|
+
printBridgeVerificationNotice("openclaw");
|
|
13538
14139
|
}
|
|
13539
14140
|
if (opts.copilot === true) {
|
|
13540
14141
|
const copilotResult = installCopilotCli({ local: opts.local === true });
|
|
@@ -13543,6 +14144,7 @@ async function cmdInstall(opts) {
|
|
|
13543
14144
|
} else {
|
|
13544
14145
|
out(`Installed token-goat Copilot CLI integration \u2192 ${copilotResult.configPath}, ${copilotResult.scriptPath}, ${copilotResult.instructionsPath}`);
|
|
13545
14146
|
}
|
|
14147
|
+
printBridgeVerificationNotice("copilot_cli");
|
|
13546
14148
|
}
|
|
13547
14149
|
if (opts.opencode === true) {
|
|
13548
14150
|
const opencodeResult = installOpencode();
|
|
@@ -13551,6 +14153,7 @@ async function cmdInstall(opts) {
|
|
|
13551
14153
|
} else {
|
|
13552
14154
|
out(`Installed token-goat opencode plugin \u2192 ${opencodeResult.pluginPath}`);
|
|
13553
14155
|
}
|
|
14156
|
+
printBridgeVerificationNotice("opencode");
|
|
13554
14157
|
}
|
|
13555
14158
|
if (opts.grok === true) {
|
|
13556
14159
|
const grokResult = installGrok();
|
|
@@ -13559,6 +14162,7 @@ async function cmdInstall(opts) {
|
|
|
13559
14162
|
} else {
|
|
13560
14163
|
out(`Installed token-goat Grok CLI integration \u2192 ${grokResult.configPath}, ${grokResult.hookScriptPath}`);
|
|
13561
14164
|
}
|
|
14165
|
+
printBridgeVerificationNotice("grok");
|
|
13562
14166
|
}
|
|
13563
14167
|
if (opts.vscode === true) {
|
|
13564
14168
|
const vscodeResult = installVscode({ project: opts.project === true });
|
|
@@ -13572,16 +14176,16 @@ async function cmdInstall(opts) {
|
|
|
13572
14176
|
);
|
|
13573
14177
|
}
|
|
13574
14178
|
try {
|
|
13575
|
-
const skillDir =
|
|
13576
|
-
if (
|
|
13577
|
-
const entries =
|
|
14179
|
+
const skillDir = path27.join(homedir11(), ".claude", "skills");
|
|
14180
|
+
if (fs27.existsSync(skillDir)) {
|
|
14181
|
+
const entries = fs27.readdirSync(skillDir, { withFileTypes: true });
|
|
13578
14182
|
const skillNames = [];
|
|
13579
14183
|
const sessionId = getSessionId();
|
|
13580
14184
|
for (const entry of entries) {
|
|
13581
14185
|
if (!entry.isDirectory()) continue;
|
|
13582
|
-
const skillFile =
|
|
13583
|
-
if (
|
|
13584
|
-
const body =
|
|
14186
|
+
const skillFile = path27.join(skillDir, entry.name, "SKILL.md");
|
|
14187
|
+
if (fs27.existsSync(skillFile)) {
|
|
14188
|
+
const body = fs27.readFileSync(skillFile, "utf-8");
|
|
13585
14189
|
const compact = extractCompactFromMarker(body);
|
|
13586
14190
|
if (compact === null) continue;
|
|
13587
14191
|
const sourceSha = contentHash(body);
|
|
@@ -13591,10 +14195,10 @@ async function cmdInstall(opts) {
|
|
|
13591
14195
|
}
|
|
13592
14196
|
if (skillNames.length > 0) {
|
|
13593
14197
|
const dir = skillOutputsDir();
|
|
13594
|
-
await
|
|
13595
|
-
const pregenPath =
|
|
14198
|
+
await fs27.promises.mkdir(dir, { recursive: true });
|
|
14199
|
+
const pregenPath = path27.join(dir, "pregen.json");
|
|
13596
14200
|
const pregenData = { ts: Date.now(), names: skillNames };
|
|
13597
|
-
await
|
|
14201
|
+
await fs27.promises.writeFile(pregenPath, JSON.stringify(pregenData, null, 2));
|
|
13598
14202
|
out(`Pre-generated ${skillNames.length} skill compacts.`);
|
|
13599
14203
|
}
|
|
13600
14204
|
}
|
|
@@ -13682,7 +14286,7 @@ function runPurge() {
|
|
|
13682
14286
|
}
|
|
13683
14287
|
const result = purgeDataDirectories();
|
|
13684
14288
|
for (const root of result.absent) out(`Nothing to purge at ${root}.`);
|
|
13685
|
-
for (const removed of result.removed) out(`Purged ${removed.path} (${
|
|
14289
|
+
for (const removed of result.removed) out(`Purged ${removed.path} (${formatBytes2(removed.bytes)} reclaimed).`);
|
|
13686
14290
|
for (const failure of result.failed) err(`token-goat: could not purge ${failure.path}: ${failure.reason}`);
|
|
13687
14291
|
}
|
|
13688
14292
|
function cmdWorkerStart() {
|
|
@@ -13768,7 +14372,8 @@ function cmdWaste(opts = {}) {
|
|
|
13768
14372
|
...opts.project !== void 0 ? { project: opts.project } : {},
|
|
13769
14373
|
...opts.transcript !== void 0 ? { transcript: opts.transcript } : {},
|
|
13770
14374
|
...opts.json === true ? { json: true } : {},
|
|
13771
|
-
...opts.top !== void 0 ? { top: requireNonNegativeInt("--top", opts.top) } : {}
|
|
14375
|
+
...opts.top !== void 0 ? { top: requireNonNegativeInt("--top", opts.top) } : {},
|
|
14376
|
+
...opts.copilot === true ? { copilot: true } : {}
|
|
13772
14377
|
});
|
|
13773
14378
|
}
|
|
13774
14379
|
async function cmdSessionOutline(sessionIdOrPath, opts = {}) {
|
|
@@ -13788,7 +14393,7 @@ ${formatSessionOutline(turns)}`;
|
|
|
13788
14393
|
}
|
|
13789
14394
|
function sessionTranscriptSize(transcriptPath) {
|
|
13790
14395
|
try {
|
|
13791
|
-
return
|
|
14396
|
+
return fs27.statSync(transcriptPath).size;
|
|
13792
14397
|
} catch {
|
|
13793
14398
|
return 0;
|
|
13794
14399
|
}
|
|
@@ -13892,10 +14497,11 @@ function _applyFiltersAndPrint(content, opts, fenceUntrusted = false, fenceTag =
|
|
|
13892
14497
|
content = [...matched.slice(0, cap), "[token-goat: showing first " + cap + " of " + matched.length + " matching lines; raise --max-matches for more]"].join("\n");
|
|
13893
14498
|
}
|
|
13894
14499
|
}
|
|
13895
|
-
const
|
|
14500
|
+
const rawLines = content.split(/\r?\n/);
|
|
13896
14501
|
if (opts.full === true) {
|
|
13897
|
-
return emit2(
|
|
14502
|
+
return emit2(rawLines.join("\n"));
|
|
13898
14503
|
}
|
|
14504
|
+
const lines = rawLines.length > 1 && rawLines[rawLines.length - 1] === "" ? rawLines.slice(0, -1) : rawLines;
|
|
13899
14505
|
const headN = opts.head !== void 0 ? requireNonNegativeInt("--head", opts.head) : 30;
|
|
13900
14506
|
const tailN = opts.tail !== void 0 ? requireNonNegativeInt("--tail", opts.tail) : 80;
|
|
13901
14507
|
const applyElision = (lines2, headN2, tailN2) => lines2.length > headN2 + tailN2 + 1 ? [...lines2.slice(0, headN2), "...(elided)...", ...lines2.slice(lines2.length - tailN2)] : lines2;
|
|
@@ -13913,7 +14519,7 @@ function _applyFiltersAndPrint(content, opts, fenceUntrusted = false, fenceTag =
|
|
|
13913
14519
|
}
|
|
13914
14520
|
function fileSizeOrZero(filePath) {
|
|
13915
14521
|
try {
|
|
13916
|
-
return
|
|
14522
|
+
return fs27.statSync(filePath).size;
|
|
13917
14523
|
} catch {
|
|
13918
14524
|
return 0;
|
|
13919
14525
|
}
|
|
@@ -13928,11 +14534,11 @@ function cmdBashOutput(id, opts) {
|
|
|
13928
14534
|
}
|
|
13929
14535
|
let content;
|
|
13930
14536
|
try {
|
|
13931
|
-
const st =
|
|
14537
|
+
const st = fs27.statSync(opts.file);
|
|
13932
14538
|
if (st.isFIFO() || st.isSocket()) {
|
|
13933
14539
|
throw new CliError(`--file '${opts.file}' is a special file (FIFO or socket) \u2014 only regular files are supported`);
|
|
13934
14540
|
}
|
|
13935
|
-
content = redactIfDotenv(opts.file, decodeSource(
|
|
14541
|
+
content = redactIfDotenv(opts.file, decodeSource(fs27.readFileSync(opts.file)));
|
|
13936
14542
|
} catch (e) {
|
|
13937
14543
|
if (e instanceof CliError) throw e;
|
|
13938
14544
|
throw new CliError(`cannot read file: ${opts.file}`);
|
|
@@ -13979,6 +14585,33 @@ async function cmdPdfExtract(file, opts) {
|
|
|
13979
14585
|
const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(printed, "utf8"));
|
|
13980
14586
|
recordStat("pdf_extract", bytesSaved, Math.round(bytesSaved / 4));
|
|
13981
14587
|
}
|
|
14588
|
+
async function cmdPdfLocate(file, pattern, opts) {
|
|
14589
|
+
const locateOpts = {
|
|
14590
|
+
ignoreCase: opts.ignoreCase === true
|
|
14591
|
+
};
|
|
14592
|
+
if (opts.maxMatches !== void 0) locateOpts.maxMatches = requirePositiveInt("--max-matches", opts.maxMatches);
|
|
14593
|
+
if (opts.context !== void 0) locateOpts.context = requirePositiveInt("--context", opts.context);
|
|
14594
|
+
if (opts.pages !== void 0) locateOpts.pages = opts.pages;
|
|
14595
|
+
const matches2 = await runPdfLocate(file, pattern, locateOpts);
|
|
14596
|
+
const pages = matches2.map((m) => m.page);
|
|
14597
|
+
let printed;
|
|
14598
|
+
if (opts.json === true) {
|
|
14599
|
+
printed = JSON.stringify({ file, pattern, matchCount: matches2.length, pages, matches: matches2 }, null, 2);
|
|
14600
|
+
out(printed);
|
|
14601
|
+
} else if (matches2.length === 0) {
|
|
14602
|
+
printed = "(no matches)";
|
|
14603
|
+
out(printed);
|
|
14604
|
+
} else {
|
|
14605
|
+
const lines = matches2.map((m) => `p${m.page}: ${m.snippet}`);
|
|
14606
|
+
printed = `${lines.join("\n")}
|
|
14607
|
+
|
|
14608
|
+
${countNoun(matches2.length, "match", "matches")} across ${countNoun(pages.length, "page")}`;
|
|
14609
|
+
out(printed);
|
|
14610
|
+
}
|
|
14611
|
+
const fullSourceBytes = fileSizeOrZero(file);
|
|
14612
|
+
const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(printed, "utf8"));
|
|
14613
|
+
recordStat("pdf_locate", bytesSaved, Math.round(bytesSaved / 4));
|
|
14614
|
+
}
|
|
13982
14615
|
async function cmdPdfOutline(file, opts) {
|
|
13983
14616
|
const entries = await runPdfOutline(file);
|
|
13984
14617
|
if (entries.length === 0) {
|
|
@@ -14325,7 +14958,7 @@ function emitExtraFileArgsNote(command, first, extras, opts = {}) {
|
|
|
14325
14958
|
}
|
|
14326
14959
|
async function cmdCompress(opts) {
|
|
14327
14960
|
try {
|
|
14328
|
-
const bashRunner = await import("./token-goat-chunk-
|
|
14961
|
+
const bashRunner = await import("./token-goat-chunk-I6TOUPLP.mjs");
|
|
14329
14962
|
if (opts.compress === false) {
|
|
14330
14963
|
process.exitCode = bashRunner.runRaw(opts.cmd, parseTimeout(opts.timeout, bashRunner.DEFAULT_TIMEOUT_SECONDS));
|
|
14331
14964
|
return;
|
|
@@ -14351,7 +14984,7 @@ async function cmdSkillBody(name, opts) {
|
|
|
14351
14984
|
if (filePath === null) {
|
|
14352
14985
|
throw new CliError(`skill '${name}' not found`);
|
|
14353
14986
|
}
|
|
14354
|
-
const body = decodeSource(
|
|
14987
|
+
const body = decodeSource(fs27.readFileSync(filePath));
|
|
14355
14988
|
if (opts.compact === true) {
|
|
14356
14989
|
out(extractCompactFromMarker(body) ?? body);
|
|
14357
14990
|
} else {
|
|
@@ -14368,7 +15001,7 @@ async function cmdSkillCompact(name, opts) {
|
|
|
14368
15001
|
for (const skill of skills) {
|
|
14369
15002
|
const filePath = await getSkillFilePath(skill.name);
|
|
14370
15003
|
if (!filePath) continue;
|
|
14371
|
-
const body2 = decodeSource(
|
|
15004
|
+
const body2 = decodeSource(fs27.readFileSync(filePath));
|
|
14372
15005
|
const compact2 = extractCompactFromMarker(body2);
|
|
14373
15006
|
if (compact2 === null) continue;
|
|
14374
15007
|
const sourceSha2 = contentHash(body2);
|
|
@@ -14389,19 +15022,19 @@ async function cmdSkillCompact(name, opts) {
|
|
|
14389
15022
|
if (!opts.path.trim()) {
|
|
14390
15023
|
throw new CliError("--path cannot be empty");
|
|
14391
15024
|
}
|
|
14392
|
-
if (!
|
|
15025
|
+
if (!fs27.existsSync(opts.path)) {
|
|
14393
15026
|
throw new CliError(`skill file not found: ${opts.path}`);
|
|
14394
15027
|
}
|
|
14395
15028
|
try {
|
|
14396
|
-
body =
|
|
15029
|
+
body = fs27.readFileSync(opts.path, "utf-8");
|
|
14397
15030
|
} catch (e) {
|
|
14398
15031
|
if (e.code === "ENOENT") {
|
|
14399
15032
|
throw new CliError(`skill file not found: ${opts.path}`);
|
|
14400
15033
|
}
|
|
14401
15034
|
throw new CliError(`failed to read skill file '${opts.path}': ${extractErrorMessage(e)}`);
|
|
14402
15035
|
}
|
|
14403
|
-
cacheName = name ??
|
|
14404
|
-
sourcePath =
|
|
15036
|
+
cacheName = name ?? path27.basename(path27.dirname(path27.resolve(opts.path)));
|
|
15037
|
+
sourcePath = path27.resolve(opts.path);
|
|
14405
15038
|
} else {
|
|
14406
15039
|
if (name === void 0 || !name.trim()) {
|
|
14407
15040
|
throw new CliError("skill-compact requires a <name> or --path <file>");
|
|
@@ -14410,7 +15043,7 @@ async function cmdSkillCompact(name, opts) {
|
|
|
14410
15043
|
if (filePath === null) {
|
|
14411
15044
|
throw new CliError(`skill '${name}' not found`);
|
|
14412
15045
|
}
|
|
14413
|
-
body =
|
|
15046
|
+
body = fs27.readFileSync(filePath, "utf-8");
|
|
14414
15047
|
cacheName = name;
|
|
14415
15048
|
sourcePath = filePath;
|
|
14416
15049
|
}
|
|
@@ -14542,8 +15175,8 @@ async function cmdSkillDiff(name) {
|
|
|
14542
15175
|
}
|
|
14543
15176
|
const newer = versions[0];
|
|
14544
15177
|
const older = versions[1];
|
|
14545
|
-
const newerBody = await
|
|
14546
|
-
const olderBody = await
|
|
15178
|
+
const newerBody = await fs27.promises.readFile(path27.resolve(dir, `${newer.outputId}.txt`), "utf-8").catch(() => null);
|
|
15179
|
+
const olderBody = await fs27.promises.readFile(path27.resolve(dir, `${older.outputId}.txt`), "utf-8").catch(() => null);
|
|
14547
15180
|
if (newerBody === null || olderBody === null) {
|
|
14548
15181
|
out(`a cached version of '${name}' was evicted while diffing -- try again`);
|
|
14549
15182
|
return;
|
|
@@ -14572,17 +15205,22 @@ async function cmdSkillSection(nameHeading, headingArg) {
|
|
|
14572
15205
|
if (!filePath) {
|
|
14573
15206
|
throw new CliError(`skill '${skillName}' not found`);
|
|
14574
15207
|
}
|
|
14575
|
-
const body = decodeSource(
|
|
15208
|
+
const body = decodeSource(fs27.readFileSync(filePath));
|
|
14576
15209
|
const extracted = extractNamedSection(body, heading);
|
|
14577
15210
|
if (!extracted) {
|
|
14578
|
-
|
|
14579
|
-
|
|
15211
|
+
const messages = [`Section '${heading}' not found in skill '${skillName}'`];
|
|
15212
|
+
const allHeadings = listSections(filePath);
|
|
15213
|
+
const available = filterSimilarHeadings(allHeadings, heading);
|
|
15214
|
+
if (available.length > 0) messages.push(didYouMean(available));
|
|
15215
|
+
else if (allHeadings.length === 0) messages.push(`skill '${skillName}' has no headings`);
|
|
15216
|
+
else messages.push(`Try: token-goat outline ${filePath}`);
|
|
15217
|
+
throw new CliError(messages.join("\n"));
|
|
14580
15218
|
}
|
|
14581
15219
|
out(extracted);
|
|
14582
15220
|
}
|
|
14583
15221
|
function atomicWriteBuffer(dest, data) {
|
|
14584
15222
|
try {
|
|
14585
|
-
if (
|
|
15223
|
+
if (fs27.statSync(dest).isDirectory()) {
|
|
14586
15224
|
const e = Object.assign(new Error(`EISDIR: illegal operation on a directory, open '${dest}'`), { code: "EISDIR", path: dest });
|
|
14587
15225
|
throw e;
|
|
14588
15226
|
}
|
|
@@ -14590,23 +15228,23 @@ function atomicWriteBuffer(dest, data) {
|
|
|
14590
15228
|
if (e.code !== "ENOENT") throw e;
|
|
14591
15229
|
}
|
|
14592
15230
|
const rnd = Math.random().toString(36).slice(2, 8);
|
|
14593
|
-
const tmp =
|
|
15231
|
+
const tmp = path27.join(path27.dirname(path27.resolve(dest)), `.tmp.${process.pid}.${rnd}`);
|
|
14594
15232
|
try {
|
|
14595
|
-
|
|
15233
|
+
fs27.writeFileSync(tmp, data, { mode: 384 });
|
|
14596
15234
|
try {
|
|
14597
|
-
const destMode =
|
|
14598
|
-
|
|
15235
|
+
const destMode = fs27.statSync(dest).mode;
|
|
15236
|
+
fs27.chmodSync(tmp, destMode);
|
|
14599
15237
|
} catch (e) {
|
|
14600
15238
|
if (e.code !== "ENOENT") throw e;
|
|
14601
15239
|
}
|
|
14602
15240
|
withRetryOnLock(() => {
|
|
14603
15241
|
try {
|
|
14604
|
-
|
|
15242
|
+
fs27.renameSync(tmp, dest);
|
|
14605
15243
|
} catch (e) {
|
|
14606
15244
|
if (e.code === "EXDEV") {
|
|
14607
|
-
|
|
15245
|
+
fs27.copyFileSync(tmp, dest);
|
|
14608
15246
|
try {
|
|
14609
|
-
|
|
15247
|
+
fs27.unlinkSync(tmp);
|
|
14610
15248
|
} catch (ue) {
|
|
14611
15249
|
process.stderr.write(`token-goat write-file: warning: could not remove temp file ${tmp}: ${ue.message}
|
|
14612
15250
|
`);
|
|
@@ -14618,7 +15256,7 @@ function atomicWriteBuffer(dest, data) {
|
|
|
14618
15256
|
});
|
|
14619
15257
|
} catch (e) {
|
|
14620
15258
|
try {
|
|
14621
|
-
|
|
15259
|
+
fs27.unlinkSync(tmp);
|
|
14622
15260
|
} catch {
|
|
14623
15261
|
}
|
|
14624
15262
|
throw e;
|
|
@@ -14628,9 +15266,9 @@ function mapFsError(e, src, dest, srcLabel = "source") {
|
|
|
14628
15266
|
const fe = e;
|
|
14629
15267
|
if (fe.code === "ENOENT") {
|
|
14630
15268
|
const errPath = fe.path ?? "";
|
|
14631
|
-
const isSource = src !== void 0 &&
|
|
14632
|
-
if (isSource) throw new CliError(`${srcLabel} file not found: ${src}`);
|
|
14633
|
-
const destDir = dest ?
|
|
15269
|
+
const isSource = src !== void 0 && path27.resolve(errPath) === path27.resolve(src);
|
|
15270
|
+
if (isSource) throw new CliError(`${/\bfile$/i.test(srcLabel) ? srcLabel : `${srcLabel} file`} not found: ${src}`);
|
|
15271
|
+
const destDir = dest ? path27.dirname(path27.resolve(dest)) : path27.dirname(path27.resolve(errPath || "."));
|
|
14634
15272
|
throw new CliError(`destination directory does not exist: ${destDir}`);
|
|
14635
15273
|
}
|
|
14636
15274
|
if (fe.code === "ENOTDIR") {
|
|
@@ -14641,7 +15279,7 @@ function mapFsError(e, src, dest, srcLabel = "source") {
|
|
|
14641
15279
|
}
|
|
14642
15280
|
if (fe.code === "EISDIR") {
|
|
14643
15281
|
const errPath = fe.path ?? "";
|
|
14644
|
-
const isSource = src !== void 0 && (errPath === "" ||
|
|
15282
|
+
const isSource = src !== void 0 && (errPath === "" || path27.resolve(errPath) === path27.resolve(src));
|
|
14645
15283
|
if (isSource) throw new CliError(`source is a directory, not a file: ${src}`);
|
|
14646
15284
|
throw new CliError(`destination is a directory, not a file: ${dest ?? (errPath || "(unknown)")}`);
|
|
14647
15285
|
}
|
|
@@ -14710,7 +15348,7 @@ function validateWritablePath(dest, label) {
|
|
|
14710
15348
|
throw new CliError(`${label} path contains a null byte`);
|
|
14711
15349
|
}
|
|
14712
15350
|
if (isWindows()) {
|
|
14713
|
-
const base =
|
|
15351
|
+
const base = path27.basename(dest);
|
|
14714
15352
|
const stem = base.replace(/\.[^.]*$/, "").toUpperCase();
|
|
14715
15353
|
if (WIN_RESERVED.has(stem)) {
|
|
14716
15354
|
throw new CliError(`${label} '${base}' is a reserved Windows device name`);
|
|
@@ -14740,7 +15378,7 @@ function readFileBoundedRaw(filePath, label, allowStdIn = false) {
|
|
|
14740
15378
|
throw new CliError(`${label} ${filePath} requires piped input; use ${altLabel} for interactive use`);
|
|
14741
15379
|
}
|
|
14742
15380
|
try {
|
|
14743
|
-
const st =
|
|
15381
|
+
const st = fs27.statSync(filePath);
|
|
14744
15382
|
if (st.isFIFO() || st.isSocket()) {
|
|
14745
15383
|
throw new CliError(`${label} '${filePath}' is a special file (FIFO or socket) \u2014 only regular files are supported`);
|
|
14746
15384
|
}
|
|
@@ -14748,7 +15386,7 @@ function readFileBoundedRaw(filePath, label, allowStdIn = false) {
|
|
|
14748
15386
|
if (st.size > maxBytes) {
|
|
14749
15387
|
throw new CliError(`${label} '${filePath}' exceeds size limit (${Math.round(st.size / 1024 / 1024)} MB); set TOKEN_GOAT_MAX_STDIN_MB to override`);
|
|
14750
15388
|
}
|
|
14751
|
-
return
|
|
15389
|
+
return fs27.readFileSync(filePath);
|
|
14752
15390
|
} catch (e) {
|
|
14753
15391
|
if (e instanceof CliError) throw e;
|
|
14754
15392
|
mapFsError(e, filePath, void 0, label);
|
|
@@ -14792,7 +15430,7 @@ function cmdNoteAdd(file, opts) {
|
|
|
14792
15430
|
throw new CliError("note content must be valid UTF-8 text");
|
|
14793
15431
|
}
|
|
14794
15432
|
const resolvedPath = resolveIndexPath(file);
|
|
14795
|
-
if (!
|
|
15433
|
+
if (!fs27.existsSync(resolvedPath)) {
|
|
14796
15434
|
throw new CliError(`File not found: '${resolvedPath}'`);
|
|
14797
15435
|
}
|
|
14798
15436
|
healStaleIndex(resolvedPath);
|
|
@@ -15006,14 +15644,14 @@ function writeReplacedBuffer(file, replacedBuf, preWriteStat) {
|
|
|
15006
15644
|
const until = process.env["TOKEN_GOAT_TEST_REPLACE_DELAY_UNTIL"];
|
|
15007
15645
|
const deadline = Date.now() + testDelayMs;
|
|
15008
15646
|
if (until !== void 0 && until !== "") {
|
|
15009
|
-
while (Date.now() < deadline && !
|
|
15647
|
+
while (Date.now() < deadline && !fs27.existsSync(until)) sleepSync(5);
|
|
15010
15648
|
} else {
|
|
15011
15649
|
sleepSync(testDelayMs);
|
|
15012
15650
|
}
|
|
15013
15651
|
}
|
|
15014
15652
|
let preRenameStat;
|
|
15015
15653
|
try {
|
|
15016
|
-
preRenameStat =
|
|
15654
|
+
preRenameStat = fs27.statSync(file);
|
|
15017
15655
|
} catch {
|
|
15018
15656
|
}
|
|
15019
15657
|
if (preRenameStat !== void 0 && (preRenameStat.mtimeMs !== preWriteStat.mtimeMs || preRenameStat.size !== preWriteStat.size)) {
|
|
@@ -15054,7 +15692,7 @@ function cmdReplace(file, opts) {
|
|
|
15054
15692
|
const targetBuf = readFileBoundedRaw(file, "target file", true);
|
|
15055
15693
|
let preWriteStat;
|
|
15056
15694
|
try {
|
|
15057
|
-
preWriteStat =
|
|
15695
|
+
preWriteStat = fs27.statSync(file);
|
|
15058
15696
|
} catch {
|
|
15059
15697
|
}
|
|
15060
15698
|
const usingFrom = opts.oldFrom !== void 0 || opts.newFrom !== void 0;
|
|
@@ -15148,7 +15786,7 @@ function cmdInsertSection(file, opts) {
|
|
|
15148
15786
|
}
|
|
15149
15787
|
let preWriteStat;
|
|
15150
15788
|
try {
|
|
15151
|
-
preWriteStat =
|
|
15789
|
+
preWriteStat = fs27.statSync(file);
|
|
15152
15790
|
} catch {
|
|
15153
15791
|
}
|
|
15154
15792
|
const result = readSection(file, opts.after);
|
|
@@ -15160,9 +15798,21 @@ function cmdInsertSection(file, opts) {
|
|
|
15160
15798
|
else if (allHeadings.length > 0) messages.push(`Try: token-goat outline ${file}`);
|
|
15161
15799
|
throw new CliError(messages.join("\n"));
|
|
15162
15800
|
}
|
|
15801
|
+
if (result.occurrences !== void 0) {
|
|
15802
|
+
const lines = [
|
|
15803
|
+
`Ambiguous heading '${opts.after}' in '${file}': ${countNoun(result.occurrences.length, "heading")} match. Retry with one of the qualified forms below to pick one:`
|
|
15804
|
+
];
|
|
15805
|
+
for (const [i, line] of result.occurrences.slice(0, AMBIGUOUS_HEADING_LIMIT).entries()) {
|
|
15806
|
+
lines.push(` - line ${line} -> --after "${opts.after}#${i + 1}"`);
|
|
15807
|
+
}
|
|
15808
|
+
if (result.occurrences.length > AMBIGUOUS_HEADING_LIMIT) {
|
|
15809
|
+
lines.push(` (${result.occurrences.length - AMBIGUOUS_HEADING_LIMIT} more not shown)`);
|
|
15810
|
+
}
|
|
15811
|
+
throw new CliError(lines.join("\n"));
|
|
15812
|
+
}
|
|
15163
15813
|
let rawBytes;
|
|
15164
15814
|
try {
|
|
15165
|
-
rawBytes =
|
|
15815
|
+
rawBytes = fs27.readFileSync(file);
|
|
15166
15816
|
} catch (e) {
|
|
15167
15817
|
mapFsError(e, void 0, file);
|
|
15168
15818
|
}
|
|
@@ -15178,7 +15828,7 @@ function cmdInsertSection(file, opts) {
|
|
|
15178
15828
|
if (preWriteStat !== void 0) {
|
|
15179
15829
|
let preRenameStat;
|
|
15180
15830
|
try {
|
|
15181
|
-
preRenameStat =
|
|
15831
|
+
preRenameStat = fs27.statSync(file);
|
|
15182
15832
|
} catch {
|
|
15183
15833
|
}
|
|
15184
15834
|
if (preRenameStat !== void 0 && (preRenameStat.mtimeMs !== preWriteStat.mtimeMs || preRenameStat.size !== preWriteStat.size)) {
|
|
@@ -15219,25 +15869,25 @@ ${content}`;
|
|
|
15219
15869
|
}
|
|
15220
15870
|
function expandGlobs(root, patterns, globFnOverride) {
|
|
15221
15871
|
const out2 = [];
|
|
15222
|
-
const globFn = globFnOverride ??
|
|
15872
|
+
const globFn = globFnOverride ?? fs27["globSync"];
|
|
15223
15873
|
for (const p of patterns) {
|
|
15224
15874
|
if (globFn !== void 0 && (p.includes("*") || p.includes("?") || p.includes("{"))) {
|
|
15225
15875
|
try {
|
|
15226
15876
|
const hits = globFn(p, { cwd: root });
|
|
15227
|
-
for (const h of hits) out2.push(
|
|
15877
|
+
for (const h of hits) out2.push(path27.isAbsolute(h) ? h : path27.join(root, h));
|
|
15228
15878
|
continue;
|
|
15229
15879
|
} catch {
|
|
15230
15880
|
}
|
|
15231
15881
|
}
|
|
15232
|
-
out2.push(
|
|
15882
|
+
out2.push(path27.isAbsolute(p) ? p : path27.join(root, p));
|
|
15233
15883
|
}
|
|
15234
15884
|
return out2;
|
|
15235
15885
|
}
|
|
15236
15886
|
function readIgnoreFile(root) {
|
|
15237
|
-
const ignorePath =
|
|
15887
|
+
const ignorePath = path27.join(root, ".tokengoatignore");
|
|
15238
15888
|
let raw;
|
|
15239
15889
|
try {
|
|
15240
|
-
raw =
|
|
15890
|
+
raw = fs27.readFileSync(ignorePath, "utf8");
|
|
15241
15891
|
} catch {
|
|
15242
15892
|
return void 0;
|
|
15243
15893
|
}
|
|
@@ -15278,14 +15928,14 @@ function cmdPack(patterns, opts) {
|
|
|
15278
15928
|
}
|
|
15279
15929
|
let instruction;
|
|
15280
15930
|
if (opts.instructionFile !== void 0) {
|
|
15281
|
-
instruction =
|
|
15931
|
+
instruction = fs27.readFileSync(opts.instructionFile, "utf8");
|
|
15282
15932
|
}
|
|
15283
15933
|
const formatted = formatPack(result, style, {
|
|
15284
15934
|
...opts.lineNumbers === true ? { line_numbers: true } : {},
|
|
15285
15935
|
...instruction !== void 0 ? { instruction } : {}
|
|
15286
15936
|
});
|
|
15287
15937
|
if (opts.output !== void 0) {
|
|
15288
|
-
|
|
15938
|
+
fs27.writeFileSync(opts.output, formatted, "utf8");
|
|
15289
15939
|
} else {
|
|
15290
15940
|
out(formatted);
|
|
15291
15941
|
}
|
|
@@ -15303,7 +15953,7 @@ function cmdTokens(patterns, opts) {
|
|
|
15303
15953
|
if (opts.tree === true) {
|
|
15304
15954
|
const dirs = /* @__PURE__ */ new Map();
|
|
15305
15955
|
for (const e of entries) {
|
|
15306
|
-
const dir =
|
|
15956
|
+
const dir = path27.dirname(e.rel_path);
|
|
15307
15957
|
if (!dirs.has(dir)) dirs.set(dir, []);
|
|
15308
15958
|
dirs.get(dir).push(e);
|
|
15309
15959
|
}
|
|
@@ -15313,7 +15963,7 @@ function cmdTokens(patterns, opts) {
|
|
|
15313
15963
|
const pct = result.total_tokens > 0 ? Math.round(dirTokens / result.total_tokens * 100) : 0;
|
|
15314
15964
|
lines2.push(`${dir}/ (${dirTokens} tokens, ${pct}%)`);
|
|
15315
15965
|
for (const e of dirEntries) {
|
|
15316
|
-
lines2.push(` ${
|
|
15966
|
+
lines2.push(` ${path27.basename(e.rel_path).padEnd(30)} ${String(e.tokens).padStart(8)} tokens`);
|
|
15317
15967
|
}
|
|
15318
15968
|
}
|
|
15319
15969
|
out(lines2.join("\n"));
|
|
@@ -15344,7 +15994,7 @@ function cmdBudget(patterns, opts) {
|
|
|
15344
15994
|
}
|
|
15345
15995
|
}
|
|
15346
15996
|
function cmdFailures(src, opts) {
|
|
15347
|
-
const text = src !== void 0 ?
|
|
15997
|
+
const text = src !== void 0 ? fs27.readFileSync(src, "utf8") : fs27.readFileSync(0, "utf8");
|
|
15348
15998
|
const result = extractFailures(text, opts.runner !== void 0 ? { runner: opts.runner } : {});
|
|
15349
15999
|
if (opts.delta !== true) {
|
|
15350
16000
|
out(opts.json === true ? formatFailuresJson(result) : formatFailuresText(result));
|
|
@@ -15474,39 +16124,34 @@ function buildProgram() {
|
|
|
15474
16124
|
)
|
|
15475
16125
|
);
|
|
15476
16126
|
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
|
-
|
|
15478
|
-
(file
|
|
15479
|
-
() =>
|
|
15480
|
-
|
|
15481
|
-
|
|
15482
|
-
more,
|
|
15483
|
-
() => runSkeleton({
|
|
16127
|
+
const registerSymbolListing = (name, description, run2) => {
|
|
16128
|
+
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(
|
|
16129
|
+
(file, more, opts) => runExitText(
|
|
16130
|
+
() => noteExtraFileArgs(
|
|
16131
|
+
name,
|
|
15484
16132
|
file,
|
|
15485
|
-
|
|
15486
|
-
|
|
15487
|
-
|
|
15488
|
-
|
|
15489
|
-
|
|
15490
|
-
|
|
16133
|
+
more,
|
|
16134
|
+
() => run2({
|
|
16135
|
+
file,
|
|
16136
|
+
...opts.json === true ? { json: true } : {},
|
|
16137
|
+
...opts.minLines !== void 0 ? { minLines: requireNonNegativeInt("--min-lines", opts.minLines) } : {},
|
|
16138
|
+
...opts.grep !== void 0 ? { grep: opts.grep } : {},
|
|
16139
|
+
...opts.forceRefresh === true ? { forceRefresh: true } : {},
|
|
16140
|
+
...opts.stats === true ? { stats: true } : {}
|
|
16141
|
+
})
|
|
16142
|
+
)
|
|
15491
16143
|
)
|
|
15492
|
-
)
|
|
16144
|
+
);
|
|
16145
|
+
};
|
|
16146
|
+
registerSymbolListing(
|
|
16147
|
+
"skeleton",
|
|
16148
|
+
'list all symbols in a file without bodies (also accepts a comma-separated file list "a,b,c" for one headed block per file)',
|
|
16149
|
+
runSkeleton
|
|
15493
16150
|
);
|
|
15494
|
-
|
|
15495
|
-
|
|
15496
|
-
|
|
15497
|
-
|
|
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
|
-
)
|
|
16151
|
+
registerSymbolListing(
|
|
16152
|
+
"outline",
|
|
16153
|
+
'list symbols with line ranges and docstrings (also accepts a comma-separated file list "a,b,c" for one headed block per file)',
|
|
16154
|
+
runOutline
|
|
15510
16155
|
);
|
|
15511
16156
|
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
16157
|
"--top <n>",
|
|
@@ -15548,7 +16193,7 @@ function buildProgram() {
|
|
|
15548
16193
|
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
16194
|
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
16195
|
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));
|
|
16196
|
+
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
16197
|
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
16198
|
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
16199
|
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));
|
|
@@ -15819,6 +16464,9 @@ function buildProgram() {
|
|
|
15819
16464
|
);
|
|
15820
16465
|
program2.command("config-get <file> <key>").description("read one value from a config file (TOML/JSON/YAML/INI)").action((file, key) => runExit(() => runConfigGet({ file, key })));
|
|
15821
16466
|
program2.command("pdf-extract <file>").description("extract plain text from a PDF (optionally --pages N or N-M) instead of a raw Read").option("--pages <spec>", "page range to extract, e.g. 1-5 or 3 (default: all pages)").option("--layout", "heuristic column-aware reading-order reconstruction from text-item coordinates (imperfect on rotated/overlapping text)").option("--head <n>", "show only the first N lines").option("--tail <n>", "show only the last N lines").option("--grep <pattern>", "filter to lines matching this regex").option("--section <heading>", "extract one markdown section by heading").option("--max-matches <n>", "cap the number of --grep matches shown").action(guard(cmdPdfExtract));
|
|
16467
|
+
program2.command("pdf-locate <file> <pattern>").description(
|
|
16468
|
+
"find which pages of a PDF match a regex, with a snippet per match, so you can pdf-extract only those pages instead of the whole document"
|
|
16469
|
+
).option("-i, --ignore-case", "case-insensitive matching").option("--max-matches <n>", "stop after this many page matches (default: 50)").option("--context <n>", "snippet length in characters around each match (default: 80)").option("--pages <spec>", "page range to scan, e.g. 1-5 or 3 (default: all pages)").option("-j, --json", "output as JSON").action(guard(cmdPdfLocate));
|
|
15822
16470
|
program2.command("pdf-outline <file>").description("list a PDF's bookmark/outline tree with page numbers instead of a raw Read").option("-j, --json", "output as JSON").action(guard(cmdPdfOutline));
|
|
15823
16471
|
program2.command("pdf-meta <file>").description("page count, title/author, and whether a PDF has an extractable text layer").option("-j, --json", "output as JSON").action(guard(cmdPdfMeta));
|
|
15824
16472
|
program2.command("image-meta <file>").description("dimensions, byte size, format, and what a shrink would cost -- sharp metadata only, never runs OCR").option("-j, --json", "output as JSON").action(guard(cmdImageMeta));
|