token-goat 2.9.18 → 2.9.20
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 +5 -1
- package/dist/token-goat-chunk-2CLWWRFR.mjs +26 -0
- package/dist/{token-goat-chunk-LTYWLKAG.mjs → token-goat-chunk-2Q3N5KUQ.mjs} +1283 -1012
- package/dist/{token-goat-chunk-6LGVCCMY.mjs → token-goat-chunk-3OGZOFLS.mjs} +26 -16
- package/dist/{token-goat-chunk-QML7ITHJ.mjs → token-goat-chunk-3VTJC7KU.mjs} +693 -438
- package/dist/{token-goat-chunk-QIDLJGXZ.mjs → token-goat-chunk-6LJNM4ZZ.mjs} +29 -5
- package/dist/{token-goat-chunk-CA6V36KW.mjs → token-goat-chunk-74ADIZZX.mjs} +68 -50
- package/dist/{token-goat-chunk-52IMXJ2L.mjs → token-goat-chunk-ASTBLE2R.mjs} +4 -4
- package/dist/token-goat-chunk-AU2K2QHR.mjs +20 -0
- package/dist/token-goat-chunk-BQIC3AVL.mjs +292 -0
- package/dist/token-goat-chunk-CBXLE54Q.mjs +30 -0
- package/dist/{token-goat-chunk-LMFO66YD.mjs → token-goat-chunk-DA43OE7L.mjs} +1 -1
- package/dist/{token-goat-chunk-ARNRFR6M.mjs → token-goat-chunk-DNAF26L4.mjs} +1 -1
- package/dist/{token-goat-chunk-BFEWOMA3.mjs → token-goat-chunk-DPHJU3P7.mjs} +1186 -4573
- package/dist/{token-goat-chunk-OWSJJY5R.mjs → token-goat-chunk-DW4VKU7D.mjs} +5 -5
- package/dist/token-goat-chunk-DYUBZZNP.mjs +198 -0
- package/dist/{token-goat-chunk-W3E65QOW.mjs → token-goat-chunk-ERGHDBC6.mjs} +310 -47
- package/dist/token-goat-chunk-EUZKG7ZO.mjs +304 -0
- package/dist/{token-goat-chunk-5MMJAJF3.mjs → token-goat-chunk-FWYZVS3E.mjs} +324 -31
- package/dist/token-goat-chunk-G6SYQQO7.mjs +41 -0
- package/dist/{token-goat-chunk-YQZE5NS6.mjs → token-goat-chunk-GIPMOGOI.mjs} +6 -6
- package/dist/{token-goat-chunk-BWCRC23L.mjs → token-goat-chunk-H4VC5TR6.mjs} +29 -20
- package/dist/{token-goat-chunk-V3ZWBEHM.mjs → token-goat-chunk-I5PQRHFE.mjs} +103 -24
- package/dist/token-goat-chunk-ITWYDOKP.mjs +341 -0
- package/dist/{token-goat-chunk-I67CPTDD.mjs → token-goat-chunk-K3ZNBN4U.mjs} +19 -14
- package/dist/{token-goat-chunk-HPOFR6QX.mjs → token-goat-chunk-K5XPIUVB.mjs} +222 -61
- package/dist/token-goat-chunk-KT75DZK4.mjs +1989 -0
- package/dist/{token-goat-chunk-H3FONFBO.mjs → token-goat-chunk-MNJWG2ZO.mjs} +3 -3
- package/dist/{token-goat-chunk-GRI6HWX3.mjs → token-goat-chunk-MUJV7BR3.mjs} +1 -1
- package/dist/{token-goat-chunk-ERTXEKB6.mjs → token-goat-chunk-NMTKNYGF.mjs} +2 -0
- package/dist/{token-goat-chunk-LA3O4JGD.mjs → token-goat-chunk-ODF7H7WS.mjs} +3 -3
- package/dist/{token-goat-chunk-K4DG6EEF.mjs → token-goat-chunk-ONMU7BON.mjs} +7 -8
- package/dist/{token-goat-chunk-4EXFN2AW.mjs → token-goat-chunk-OZRSREKR.mjs} +2 -2
- package/dist/{token-goat-chunk-EPXNOKIV.mjs → token-goat-chunk-P4XBHZXU.mjs} +2 -2
- package/dist/{token-goat-chunk-CTOYJ232.mjs → token-goat-chunk-PAVWB4L4.mjs} +173 -1840
- package/dist/{token-goat-chunk-UENEL6KX.mjs → token-goat-chunk-PTW22MRC.mjs} +2 -2
- package/dist/token-goat-chunk-SL454EN5.mjs +1816 -0
- package/dist/token-goat-chunk-SP5QR2HN.mjs +1379 -0
- package/dist/{token-goat-chunk-7VLWNQDQ.mjs → token-goat-chunk-T46UKRFM.mjs} +1 -1
- package/dist/{token-goat-chunk-L7VCX33I.mjs → token-goat-chunk-T63VHOL3.mjs} +40 -10
- package/dist/token-goat-chunk-VSJGPEBB.mjs +248 -0
- package/dist/{token-goat-chunk-HIZXQVNU.mjs → token-goat-chunk-VYXGHGUY.mjs} +4 -4
- package/dist/token-goat-chunk-W6QYCWAT.mjs +128 -0
- package/dist/{token-goat-chunk-WFX2V3OF.mjs → token-goat-chunk-Z4ALQADF.mjs} +579 -422
- package/dist/token-goat-chunk-ZHFM4DHJ.mjs +324 -0
- package/dist/token-goat-hook.mjs +18 -12
- package/dist/token-goat.core.mjs +29 -20
- package/docs/C4_RUNTIME_ARCHITECTURE.md +1 -1
- package/docs/cli.md +15 -13
- package/docs/install.md +1 -1
- package/docs/security.md +2 -2
- package/package.json +3 -2
- package/dist/token-goat-chunk-2KTZ6J7T.mjs +0 -173
- package/dist/token-goat-chunk-34OUJ2IE.mjs +0 -35
- package/dist/token-goat-chunk-RHHFEXKF.mjs +0 -93
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import { createRequire as __cjsRequire } from 'node:module';
|
|
2
2
|
const require = __cjsRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
compareSemver
|
|
5
|
+
} from "./token-goat-chunk-ONMU7BON.mjs";
|
|
6
|
+
import {
|
|
7
|
+
buildCopilotWasteReport,
|
|
8
|
+
findLatestCopilotSession,
|
|
9
|
+
findProjectSession,
|
|
10
|
+
isCopilotTranscript
|
|
11
|
+
} from "./token-goat-chunk-EUZKG7ZO.mjs";
|
|
12
|
+
import {
|
|
13
|
+
buildResumePacket
|
|
14
|
+
} from "./token-goat-chunk-W6QYCWAT.mjs";
|
|
3
15
|
import {
|
|
4
16
|
ALL_SYMBOLS_IN_FILE_LIMIT,
|
|
5
17
|
AMBIGUOUS_HEADING_LIMIT,
|
|
@@ -10,6 +22,8 @@ import {
|
|
|
10
22
|
computeFileFingerprint,
|
|
11
23
|
createHandoff,
|
|
12
24
|
didYouMean,
|
|
25
|
+
emit,
|
|
26
|
+
emitErr,
|
|
13
27
|
enclosingSymbol,
|
|
14
28
|
extraFileArgsNote,
|
|
15
29
|
extractTranscriptText,
|
|
@@ -20,8 +34,10 @@ import {
|
|
|
20
34
|
isAvailable as isAvailable2,
|
|
21
35
|
load,
|
|
22
36
|
loadError,
|
|
37
|
+
namedSpecsMergeable,
|
|
23
38
|
queryJson,
|
|
24
39
|
rankSimilarNames,
|
|
40
|
+
readSpecsMergeable,
|
|
25
41
|
resolveBody,
|
|
26
42
|
resolveHandoff,
|
|
27
43
|
resolveSymbolMatch,
|
|
@@ -91,10 +107,11 @@ import {
|
|
|
91
107
|
symbolNamesInFile,
|
|
92
108
|
upsertNote,
|
|
93
109
|
warnIfFilesStale
|
|
94
|
-
} from "./token-goat-chunk-
|
|
110
|
+
} from "./token-goat-chunk-3VTJC7KU.mjs";
|
|
95
111
|
import {
|
|
96
112
|
DEFAULT_RECONCILE_BUDGET_MS,
|
|
97
113
|
HOOK_EVENTS,
|
|
114
|
+
MCP_TOOL_PATTERN,
|
|
98
115
|
WEB_OUTPUT_SUBDIR,
|
|
99
116
|
buildCommandManifest,
|
|
100
117
|
checkSymbolBodySize,
|
|
@@ -106,197 +123,219 @@ import {
|
|
|
106
123
|
getWebOutputByUrlFromDisk,
|
|
107
124
|
getWebOutputRaw,
|
|
108
125
|
readStdinJson,
|
|
126
|
+
runRecallCommand,
|
|
109
127
|
runReconcile,
|
|
110
128
|
storeWebOutput
|
|
111
|
-
} from "./token-goat-chunk-
|
|
129
|
+
} from "./token-goat-chunk-I5PQRHFE.mjs";
|
|
130
|
+
import {
|
|
131
|
+
buildWasteReport,
|
|
132
|
+
extractResultText,
|
|
133
|
+
findLatestTranscript,
|
|
134
|
+
projectTranscriptsDir,
|
|
135
|
+
safeStringify
|
|
136
|
+
} from "./token-goat-chunk-BQIC3AVL.mjs";
|
|
137
|
+
import {
|
|
138
|
+
buildManifest
|
|
139
|
+
} from "./token-goat-chunk-VSJGPEBB.mjs";
|
|
112
140
|
import {
|
|
113
|
-
AGENT_SALT_MARKER,
|
|
114
|
-
CONTEXT_AUTOCOMPACT_TOKENS,
|
|
115
141
|
GEMINI_TOOL_NAME_MAP,
|
|
116
|
-
HOOKS_SCRIPT_FILE,
|
|
117
|
-
MATERIALIZE_SHRUNK_IMAGE_JS,
|
|
118
142
|
MAX_FILES_SCANNED,
|
|
119
|
-
SESSIONS_SUBDIR,
|
|
120
|
-
SKILLS_OUTPUT_SUBDIR,
|
|
121
143
|
SKIP_DIRS,
|
|
122
144
|
WORKER_HEARTBEAT_STALE_MS,
|
|
123
145
|
WorkerAlreadyRunningError,
|
|
124
|
-
accumulateResidentLine,
|
|
125
146
|
applyIndexingPriority,
|
|
126
147
|
auditClaudeMd,
|
|
127
148
|
buildExtractiveCompact,
|
|
128
|
-
buildGuidanceBlock,
|
|
129
|
-
buildGuidanceBody,
|
|
130
149
|
buildLineDiff,
|
|
131
|
-
buildManifestWithCount,
|
|
132
150
|
buildProjectMap,
|
|
133
151
|
buildStats,
|
|
134
|
-
bundledCliPath,
|
|
135
|
-
capJsonRows,
|
|
136
152
|
cmdReclaimIndex,
|
|
137
153
|
compactDoc,
|
|
138
154
|
compactPathFor,
|
|
139
155
|
confirmAndApply,
|
|
140
|
-
contentHash,
|
|
141
|
-
copilotCliConfigPath,
|
|
142
|
-
copilotCliMcpToolsDir,
|
|
143
|
-
copilotCliScriptPath,
|
|
144
|
-
copilotCliUserRoot,
|
|
145
|
-
copilotHooksFilePaths,
|
|
146
|
-
createResidentContextStats,
|
|
147
156
|
detectWalkMode,
|
|
148
157
|
dirtyQueuePathFor,
|
|
149
158
|
drainHeartbeatPathFor,
|
|
150
|
-
dropEmptyServers,
|
|
151
159
|
emptyIndexMessage,
|
|
152
160
|
enqueueDirtyPathSafe,
|
|
153
|
-
estimateTokens,
|
|
154
|
-
estimateTokensFromLength,
|
|
155
|
-
extractChecklistSection,
|
|
156
|
-
extractCompactFromMarker,
|
|
157
|
-
extractNamedSection,
|
|
158
161
|
fenceUntrusted,
|
|
159
162
|
findClaudeMdFiles,
|
|
160
163
|
findContentDuplicates,
|
|
161
|
-
findLatestSessionId,
|
|
162
164
|
findMemSuggestionCandidates,
|
|
163
165
|
findMemoryMd,
|
|
164
166
|
findOrphanedChunkPaths,
|
|
165
167
|
findSystemTempFiles,
|
|
166
|
-
|
|
167
|
-
formatBytes,
|
|
168
|
+
findTopIndexedProjects,
|
|
168
169
|
formatMemSuggestions,
|
|
169
170
|
formatProjectMap,
|
|
170
171
|
formatSymbolLocation,
|
|
171
|
-
formatTokenEstimate,
|
|
172
|
-
getContextPressure,
|
|
173
172
|
getDirtyPathsFor,
|
|
174
173
|
getEmbeddingCoverage,
|
|
175
174
|
getHintStatsSummary,
|
|
176
175
|
getHintStatsTotals,
|
|
177
176
|
getParserFreshness,
|
|
178
177
|
getProjectIndexCounts,
|
|
179
|
-
getSessionFiles,
|
|
180
|
-
getSessionId,
|
|
181
|
-
getSkillFilePath,
|
|
182
178
|
getTrackedFiles,
|
|
183
|
-
|
|
184
|
-
incrementSkillHit,
|
|
179
|
+
indexSizeBytes,
|
|
185
180
|
injectionFencingEnabled,
|
|
186
|
-
installCopilotCli,
|
|
187
|
-
installCopilotHooksFile,
|
|
188
|
-
installVisualStudio,
|
|
189
181
|
isCompactFresh,
|
|
190
|
-
isCopilotCliInstalled,
|
|
191
182
|
isHintCategory,
|
|
192
|
-
|
|
183
|
+
isIgnoredIndexPath,
|
|
193
184
|
isPrivateIpv4Octets,
|
|
194
185
|
isPrivateIpv6Groups,
|
|
195
186
|
isSuppressionCategory,
|
|
196
|
-
isVisualStudioInstalled,
|
|
197
187
|
isWorkerRunning,
|
|
198
|
-
jsonc,
|
|
199
|
-
listOutputs,
|
|
200
|
-
listSkills,
|
|
201
|
-
loadSessionCache,
|
|
202
|
-
managedServer,
|
|
203
188
|
mapLookupBytesSaved,
|
|
204
189
|
markCategoryEffective,
|
|
205
190
|
markCategoryIneffective,
|
|
206
191
|
pruneBlockedRoot,
|
|
207
192
|
pruneDeletedFiles,
|
|
208
193
|
pruneOrphanedChunks,
|
|
209
|
-
|
|
194
|
+
pruneOrphanedVectors,
|
|
210
195
|
pruneSystemTempFiles,
|
|
196
|
+
pruneUnembeddableChunks,
|
|
211
197
|
readCompactBody,
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
readServersJson,
|
|
215
|
-
releaseCopilotHooksFile,
|
|
198
|
+
reclaimIndex,
|
|
199
|
+
recordKnownRootThrottled,
|
|
216
200
|
removeFileFromIndex,
|
|
217
201
|
resetHintStats,
|
|
218
202
|
runContextStats,
|
|
219
203
|
runDetachedWorkerDaemon,
|
|
220
204
|
scanAndRecord,
|
|
221
|
-
serversOf,
|
|
222
|
-
setTokenGoatServer,
|
|
223
205
|
shrinkImage,
|
|
224
|
-
skillDescriptionLine,
|
|
225
|
-
skillOutputsDir,
|
|
226
206
|
startDetachedWorker,
|
|
227
207
|
stopWorker,
|
|
228
|
-
storeCompact,
|
|
229
|
-
storeOutput,
|
|
230
|
-
summarizeResidentContext,
|
|
231
|
-
syncVisualStudioProjectGuidance,
|
|
232
208
|
toolMatcherFor,
|
|
233
|
-
trimToBudget,
|
|
234
|
-
uninstallCopilotCli,
|
|
235
|
-
uninstallVisualStudio,
|
|
236
209
|
urlPolicyDenialReason,
|
|
237
210
|
visionTokensSavedByText,
|
|
211
|
+
walkProject,
|
|
212
|
+
writeCompact
|
|
213
|
+
} from "./token-goat-chunk-DPHJU3P7.mjs";
|
|
214
|
+
import {
|
|
215
|
+
CompressedOutput,
|
|
216
|
+
TOOL_FILTERS,
|
|
217
|
+
ToolFilter,
|
|
218
|
+
deliverCompressed,
|
|
219
|
+
detectFromCommand
|
|
220
|
+
} from "./token-goat-chunk-PAVWB4L4.mjs";
|
|
221
|
+
import {
|
|
222
|
+
HOOKS_SCRIPT_FILE,
|
|
223
|
+
MATERIALIZE_SHRUNK_IMAGE_JS,
|
|
224
|
+
buildGuidanceBlock,
|
|
225
|
+
buildGuidanceBody,
|
|
226
|
+
bundledCliPath,
|
|
227
|
+
copilotCliConfigPath,
|
|
228
|
+
copilotCliMcpToolsDir,
|
|
229
|
+
copilotCliScriptPath,
|
|
230
|
+
copilotHooksFilePaths,
|
|
231
|
+
dropEmptyServers,
|
|
232
|
+
hasManagedServer,
|
|
233
|
+
installCopilotCli,
|
|
234
|
+
installCopilotHooksFile,
|
|
235
|
+
installVisualStudio,
|
|
236
|
+
isCopilotCliInstalled,
|
|
237
|
+
isManagedServer,
|
|
238
|
+
isResidueServersJson,
|
|
239
|
+
isVisualStudioInstalled,
|
|
240
|
+
jsonc,
|
|
241
|
+
managedServer,
|
|
242
|
+
readCopilotHooksOwners,
|
|
243
|
+
readServersJson,
|
|
244
|
+
releaseCopilotHooksFile,
|
|
245
|
+
serversOf,
|
|
246
|
+
setTokenGoatServer,
|
|
247
|
+
skillDescriptionLine,
|
|
248
|
+
syncVisualStudioProjectGuidance,
|
|
249
|
+
uninstallCopilotCli,
|
|
250
|
+
uninstallVisualStudio,
|
|
238
251
|
visualStudioDuplicateNote,
|
|
239
252
|
visualStudioManagedEntry,
|
|
240
253
|
visualStudioMcpStatus,
|
|
241
254
|
visualStudioOtherScopeHasManagedServer,
|
|
242
255
|
visualStudioProjectMcpPath,
|
|
243
256
|
visualStudioSolutionVscodeMcpPath,
|
|
244
|
-
visualStudioUserMcpPath
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
257
|
+
visualStudioUserMcpPath
|
|
258
|
+
} from "./token-goat-chunk-SP5QR2HN.mjs";
|
|
259
|
+
import {
|
|
260
|
+
AGENT_SALT_MARKER,
|
|
261
|
+
CONTEXT_AUTOCOMPACT_TOKENS,
|
|
262
|
+
SESSIONS_SUBDIR,
|
|
263
|
+
SKILLS_OUTPUT_SUBDIR,
|
|
264
|
+
contentHash,
|
|
265
|
+
eventCount,
|
|
266
|
+
extractCompactFromMarker,
|
|
267
|
+
extractNamedSection,
|
|
268
|
+
findLatestSessionId,
|
|
269
|
+
formatAge,
|
|
270
|
+
getContextPressure,
|
|
271
|
+
getSessionFiles,
|
|
272
|
+
getSessionId,
|
|
273
|
+
getSkillFilePath,
|
|
274
|
+
incrementSkillHit,
|
|
275
|
+
listOutputs,
|
|
276
|
+
listSkills,
|
|
277
|
+
loadSessionCache,
|
|
278
|
+
loadSessionState,
|
|
279
|
+
pruneSkillOutputs,
|
|
280
|
+
sessionsDir,
|
|
281
|
+
skillOutputsDir,
|
|
282
|
+
storeCompact,
|
|
283
|
+
storeOutput
|
|
284
|
+
} from "./token-goat-chunk-KT75DZK4.mjs";
|
|
285
|
+
import {
|
|
286
|
+
capJsonRows,
|
|
287
|
+
claudeConfigDir,
|
|
288
|
+
estimateTokens,
|
|
289
|
+
estimateTokensFromLength,
|
|
290
|
+
formatBytes,
|
|
291
|
+
formatTokenEstimate,
|
|
292
|
+
trimToBudget
|
|
293
|
+
} from "./token-goat-chunk-ZHFM4DHJ.mjs";
|
|
248
294
|
import {
|
|
249
295
|
BASH_OUTPUT_SUBDIR,
|
|
250
|
-
CompressedOutput,
|
|
251
|
-
FILTERS,
|
|
252
|
-
RECALL_DEFAULT_LIMIT,
|
|
253
|
-
TOOL_FILTERS,
|
|
254
|
-
ToolFilter,
|
|
255
296
|
clipLongMatchLine,
|
|
256
297
|
combineStreams,
|
|
257
|
-
commandHash,
|
|
258
|
-
deliverCompressed,
|
|
259
|
-
detectFromCommand,
|
|
260
298
|
getBashOutput,
|
|
261
|
-
isRecallCacheType
|
|
262
|
-
|
|
263
|
-
normalizeCommandForCacheKey,
|
|
264
|
-
searchRecall
|
|
265
|
-
} from "./token-goat-chunk-CTOYJ232.mjs";
|
|
299
|
+
isRecallCacheType
|
|
300
|
+
} from "./token-goat-chunk-SL454EN5.mjs";
|
|
266
301
|
import {
|
|
267
302
|
C,
|
|
268
303
|
DEFAULT_MAX_AGE_MS,
|
|
269
304
|
DEFAULT_MAX_COUNT,
|
|
305
|
+
HOOK_STATS_RETENTION_DAYS,
|
|
270
306
|
RESET,
|
|
271
307
|
_useRichStats,
|
|
272
308
|
colorStdout,
|
|
273
309
|
compileCustomPatterns,
|
|
274
310
|
fg,
|
|
275
311
|
formatLocalTimestamp,
|
|
312
|
+
getGlobalDb,
|
|
276
313
|
listBlobs,
|
|
277
|
-
loadBlob,
|
|
278
314
|
pruneBlobs,
|
|
315
|
+
pruneStalePatternCoveredUnmappedTools,
|
|
279
316
|
readUnmappedTools,
|
|
280
317
|
recordStat,
|
|
281
318
|
redactSecrets,
|
|
282
319
|
renderShortStats,
|
|
283
320
|
renderStats,
|
|
284
321
|
savedTokensFromBytes,
|
|
285
|
-
|
|
322
|
+
statsHasDurationColumn,
|
|
323
|
+
statsHasHarnessColumn,
|
|
324
|
+
stripAnsiEscapes,
|
|
286
325
|
summarize
|
|
287
|
-
} from "./token-goat-chunk-
|
|
326
|
+
} from "./token-goat-chunk-K5XPIUVB.mjs";
|
|
288
327
|
import {
|
|
289
328
|
docxOutline,
|
|
290
329
|
docxTables,
|
|
291
330
|
docxText,
|
|
292
331
|
formatDocxTables
|
|
293
|
-
} from "./token-goat-chunk-
|
|
332
|
+
} from "./token-goat-chunk-PTW22MRC.mjs";
|
|
294
333
|
import {
|
|
295
334
|
pptxNotesText,
|
|
296
335
|
pptxOutline,
|
|
297
336
|
pptxSlideText,
|
|
298
337
|
pptxTextGrep
|
|
299
|
-
} from "./token-goat-chunk-
|
|
338
|
+
} from "./token-goat-chunk-DNAF26L4.mjs";
|
|
300
339
|
import {
|
|
301
340
|
formatXlsxColumns,
|
|
302
341
|
formatXlsxRange,
|
|
@@ -305,13 +344,14 @@ import {
|
|
|
305
344
|
querySheet,
|
|
306
345
|
rangeSheet,
|
|
307
346
|
xlsxColumns
|
|
308
|
-
} from "./token-goat-chunk-
|
|
347
|
+
} from "./token-goat-chunk-ODF7H7WS.mjs";
|
|
309
348
|
import {
|
|
310
349
|
formatCsvTable,
|
|
311
350
|
parseWhereSpecs
|
|
312
|
-
} from "./token-goat-chunk-
|
|
351
|
+
} from "./token-goat-chunk-P4XBHZXU.mjs";
|
|
313
352
|
import {
|
|
314
|
-
|
|
353
|
+
assetEmbedSha,
|
|
354
|
+
deleteFileEmbeddings,
|
|
315
355
|
embeddingBackendLoadError,
|
|
316
356
|
embeddingsDepsAvailable,
|
|
317
357
|
ensureEmbeddingProvenance,
|
|
@@ -327,16 +367,19 @@ import {
|
|
|
327
367
|
isTreeSitterAvailable,
|
|
328
368
|
listSections,
|
|
329
369
|
loadRegexExtractors,
|
|
370
|
+
maxChunksEmbedSha,
|
|
330
371
|
missingTreeSitterGrammarPackages,
|
|
331
372
|
modelFilesPresent,
|
|
373
|
+
parserFingerprintForLanguage,
|
|
332
374
|
readSection,
|
|
333
375
|
treeSitterCoreAvailable,
|
|
334
376
|
treeSitterCoreLoadError
|
|
335
|
-
} from "./token-goat-chunk-
|
|
377
|
+
} from "./token-goat-chunk-ERGHDBC6.mjs";
|
|
336
378
|
import {
|
|
337
379
|
getFileEntry,
|
|
380
|
+
getProjectFileEntries,
|
|
338
381
|
querySymbols
|
|
339
|
-
} from "./token-goat-chunk-
|
|
382
|
+
} from "./token-goat-chunk-K3ZNBN4U.mjs";
|
|
340
383
|
import {
|
|
341
384
|
TREE_SITTER_LANGUAGES,
|
|
342
385
|
detectLanguage,
|
|
@@ -347,7 +390,7 @@ import {
|
|
|
347
390
|
matchGroovySlashy,
|
|
348
391
|
nonTreeSitterLanguageCount,
|
|
349
392
|
redactIfDotenv
|
|
350
|
-
} from "./token-goat-chunk-
|
|
393
|
+
} from "./token-goat-chunk-T63VHOL3.mjs";
|
|
351
394
|
import {
|
|
352
395
|
CONFIG_KEY_ENV_OVERRIDES,
|
|
353
396
|
PROJECT_LOCKED_KEYS,
|
|
@@ -376,7 +419,7 @@ import {
|
|
|
376
419
|
stringify,
|
|
377
420
|
validateEnumField,
|
|
378
421
|
validateNumericField
|
|
379
|
-
} from "./token-goat-chunk-
|
|
422
|
+
} from "./token-goat-chunk-74ADIZZX.mjs";
|
|
380
423
|
import {
|
|
381
424
|
ENV_KEYS,
|
|
382
425
|
LOCK_WAIT_MS_HARDENED,
|
|
@@ -402,6 +445,7 @@ import {
|
|
|
402
445
|
foldPath,
|
|
403
446
|
globalDbPath,
|
|
404
447
|
hookCommandFor,
|
|
448
|
+
hookExecPartsFor,
|
|
405
449
|
installSingleFilePlugin,
|
|
406
450
|
isTestFile,
|
|
407
451
|
isUnderBlockedRoot,
|
|
@@ -414,12 +458,12 @@ import {
|
|
|
414
458
|
recordCreatedConfig,
|
|
415
459
|
redactUrlQuery,
|
|
416
460
|
removeCreatedBackups,
|
|
461
|
+
removeEmptyDirInScope,
|
|
417
462
|
removeFileInScope,
|
|
418
463
|
requireNonNegativeStrictInt,
|
|
419
464
|
requirePositiveStrictInt,
|
|
420
465
|
resolveOnPath,
|
|
421
466
|
runGit,
|
|
422
|
-
safeSlice,
|
|
423
467
|
sleepSync,
|
|
424
468
|
stripDelimitedBlock,
|
|
425
469
|
stripLower,
|
|
@@ -436,7 +480,7 @@ import {
|
|
|
436
480
|
withRetryOnLock,
|
|
437
481
|
writeIfDifferent,
|
|
438
482
|
writeJsonSettings
|
|
439
|
-
} from "./token-goat-chunk-
|
|
483
|
+
} from "./token-goat-chunk-6LJNM4ZZ.mjs";
|
|
440
484
|
import {
|
|
441
485
|
UNTRUSTED_FILE_TAG,
|
|
442
486
|
UNTRUSTED_TOOL_TAG,
|
|
@@ -450,7 +494,7 @@ import {
|
|
|
450
494
|
normalizePath,
|
|
451
495
|
resolveIndexPath,
|
|
452
496
|
toDisplayPath
|
|
453
|
-
} from "./token-goat-chunk-
|
|
497
|
+
} from "./token-goat-chunk-NMTKNYGF.mjs";
|
|
454
498
|
import {
|
|
455
499
|
compileGuardedRegex
|
|
456
500
|
} from "./token-goat-chunk-GMOUBOX4.mjs";
|
|
@@ -3584,9 +3628,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3584
3628
|
* @param {string} [path]
|
|
3585
3629
|
* @return {(string|null|Command)}
|
|
3586
3630
|
*/
|
|
3587
|
-
executableDir(
|
|
3588
|
-
if (
|
|
3589
|
-
this._executableDir =
|
|
3631
|
+
executableDir(path42) {
|
|
3632
|
+
if (path42 === void 0) return this._executableDir;
|
|
3633
|
+
this._executableDir = path42;
|
|
3590
3634
|
return this;
|
|
3591
3635
|
}
|
|
3592
3636
|
/**
|
|
@@ -3886,9 +3930,8 @@ function attemptedCommandName(argv) {
|
|
|
3886
3930
|
}
|
|
3887
3931
|
|
|
3888
3932
|
// src/cli.ts
|
|
3889
|
-
import * as
|
|
3890
|
-
import * as
|
|
3891
|
-
import { homedir as homedir20 } from "os";
|
|
3933
|
+
import * as fs45 from "fs";
|
|
3934
|
+
import * as path41 from "path";
|
|
3892
3935
|
|
|
3893
3936
|
// src/walk_index.ts
|
|
3894
3937
|
init_define_import_meta_env();
|
|
@@ -3941,8 +3984,8 @@ function collectWalkIndexFiles(root, opts = {}) {
|
|
|
3941
3984
|
|
|
3942
3985
|
// src/install.ts
|
|
3943
3986
|
init_define_import_meta_env();
|
|
3987
|
+
import { spawnSync } from "node:child_process";
|
|
3944
3988
|
import * as fs3 from "node:fs";
|
|
3945
|
-
import * as os2 from "node:os";
|
|
3946
3989
|
import * as path3 from "node:path";
|
|
3947
3990
|
|
|
3948
3991
|
// src/bridges/claudecode.ts
|
|
@@ -3974,15 +4017,18 @@ var SHIM_TRY_IN_PROCESS = `// Attempts the in-process hook call: import()s dist/
|
|
|
3974
4017
|
// loaded) and calls its exported relayInProcess() directly, avoiding a second node
|
|
3975
4018
|
// process spawn entirely. Returns undefined (triggering the spawnSync fallback below)
|
|
3976
4019
|
// when entryPath is absent, the sibling file doesn't exist (an older install predating
|
|
3977
|
-
// this file), or anything else goes wrong -- this must never throw.
|
|
3978
|
-
|
|
4020
|
+
// this file), or anything else goes wrong -- this must never throw. harnessWaitMs, when
|
|
4021
|
+
// the caller has one (Claude Code's async-detach marker time -- see SHIM_ASYNC_DETACH),
|
|
4022
|
+
// is forwarded straight through: this runs in the same process as the caller, so its
|
|
4023
|
+
// performance.now() reading shares the same origin and needs no conversion.
|
|
4024
|
+
async function tryInProcess(entryPath, eventName, input, harnessWaitMs) {
|
|
3979
4025
|
if (!entryPath) return undefined
|
|
3980
4026
|
try {
|
|
3981
4027
|
const hookLibPath = path.join(path.dirname(entryPath), 'token-goat-hook.mjs')
|
|
3982
4028
|
if (!require('node:fs').existsSync(hookLibPath)) return undefined
|
|
3983
4029
|
const mod = await import(pathToFileURL(hookLibPath).href)
|
|
3984
4030
|
const payload = JSON.parse(input)
|
|
3985
|
-
return await mod.relayInProcess(eventName, payload)
|
|
4031
|
+
return await mod.relayInProcess(eventName, payload, harnessWaitMs)
|
|
3986
4032
|
} catch {
|
|
3987
4033
|
return undefined
|
|
3988
4034
|
}
|
|
@@ -3996,6 +4042,52 @@ var SHIM_SPAWN_LADDER = ` const res = entryPath
|
|
|
3996
4042
|
maxBuffer: SHIM_MAX_BUFFER_BYTES,
|
|
3997
4043
|
})`;
|
|
3998
4044
|
var SHIM_MAX_BUFFER_CONST = `const SHIM_MAX_BUFFER_BYTES = 32 * 1024 * 1024`;
|
|
4045
|
+
var SHIM_ASYNC_DETACH = `const ASYNC_DETACH_TOOLS = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit'])
|
|
4046
|
+
const ASYNC_DETACH_SKIP_EXT_RE = /\\.(md|mdx|markdown|rst)$/i
|
|
4047
|
+
const ASYNC_DETACH_BASH_MAX_BYTES = 200
|
|
4048
|
+
// Same key order as OUTPUT_FIRST_TOOL_RESPONSE_KEYS in hooks_common.ts.
|
|
4049
|
+
const ASYNC_DETACH_BASH_TEXT_KEYS = ['output', 'content', 'text', 'body', 'stdout', 'stderr']
|
|
4050
|
+
function bashResultByteLength(resp) {
|
|
4051
|
+
if (typeof resp === 'string') return Buffer.byteLength(resp, 'utf8')
|
|
4052
|
+
if (resp && typeof resp === 'object') {
|
|
4053
|
+
if (typeof resp['persistedOutputSize'] === 'number') return resp['persistedOutputSize']
|
|
4054
|
+
for (const key of ASYNC_DETACH_BASH_TEXT_KEYS) {
|
|
4055
|
+
if (typeof resp[key] === 'string' && resp[key] !== '') return Buffer.byteLength(resp[key], 'utf8')
|
|
4056
|
+
}
|
|
4057
|
+
return Buffer.byteLength(JSON.stringify(resp), 'utf8')
|
|
4058
|
+
}
|
|
4059
|
+
return 0
|
|
4060
|
+
}
|
|
4061
|
+
function isAsyncDetachEligible(eventName, input) {
|
|
4062
|
+
if (eventName === 'subagent_stop') return true
|
|
4063
|
+
if (eventName !== 'post_tool_use') return false
|
|
4064
|
+
try {
|
|
4065
|
+
const payload = JSON.parse(input)
|
|
4066
|
+
const toolName = payload['tool_name']
|
|
4067
|
+
if (ASYNC_DETACH_TOOLS.has(toolName)) {
|
|
4068
|
+
const toolInput = payload['tool_input'] || {}
|
|
4069
|
+
const filePath =
|
|
4070
|
+
typeof toolInput['file_path'] === 'string'
|
|
4071
|
+
? toolInput['file_path']
|
|
4072
|
+
: typeof toolInput['notebook_path'] === 'string'
|
|
4073
|
+
? toolInput['notebook_path']
|
|
4074
|
+
: ''
|
|
4075
|
+
return !ASYNC_DETACH_SKIP_EXT_RE.test(filePath)
|
|
4076
|
+
}
|
|
4077
|
+
if (toolName === 'Bash') return bashResultByteLength(payload['tool_response']) < ASYNC_DETACH_BASH_MAX_BYTES
|
|
4078
|
+
return false
|
|
4079
|
+
} catch {
|
|
4080
|
+
return false
|
|
4081
|
+
}
|
|
4082
|
+
}`;
|
|
4083
|
+
var SHIM_OWN_COMMAND_BYPASS = `const TG_CD_PREFIX_RE = /^(?:cd\\s+(?:"[^"]*"|'[^']*'|\\S+)[ \\t]*(?:&&|;|\\r?\\n)\\s*)+/
|
|
4084
|
+
const TG_OWN_COMMAND_RE = /^(?:[A-Za-z_][A-Za-z0-9_]*=\\S+\\s+)*(?:token-goat\\b|node(?:\\.exe)?\\s+["']?(?:\\S*[\\\\/])?token-goat(?:\\.mjs)?["']?\\b)/i
|
|
4085
|
+
function isOwnTokenGoatCommand(cmd) {
|
|
4086
|
+
if (typeof cmd !== 'string' || cmd.trim() === '') return false
|
|
4087
|
+
const body = (cmd.replace(TG_CD_PREFIX_RE, '') || cmd).trim()
|
|
4088
|
+
if (/[;&|\`]|\\$\\(/.test(body)) return false
|
|
4089
|
+
return TG_OWN_COMMAND_RE.test(body)
|
|
4090
|
+
}`;
|
|
3999
4091
|
|
|
4000
4092
|
// src/bridges/claudecode.ts
|
|
4001
4093
|
var CLAUDECODE_HOOK_SCRIPT = `#!/usr/bin/env node
|
|
@@ -4025,6 +4117,10 @@ ${SHIM_TRY_IN_PROCESS}
|
|
|
4025
4117
|
|
|
4026
4118
|
${SHIM_MAX_BUFFER_CONST}
|
|
4027
4119
|
|
|
4120
|
+
${SHIM_ASYNC_DETACH}
|
|
4121
|
+
|
|
4122
|
+
${SHIM_OWN_COMMAND_BYPASS}
|
|
4123
|
+
|
|
4028
4124
|
async function main() {
|
|
4029
4125
|
const eventName = process.argv[2] || ''
|
|
4030
4126
|
if (!VALID_HOOK_EVENTS.has(eventName)) {
|
|
@@ -4038,6 +4134,32 @@ async function main() {
|
|
|
4038
4134
|
process.stdout.write('{}')
|
|
4039
4135
|
return
|
|
4040
4136
|
}
|
|
4137
|
+
// A pre_tool_use Bash call that is token-goat's own CLI has nothing for the pipeline below to
|
|
4138
|
+
// say, so this skips the round trip entirely rather than backgrounding it -- see
|
|
4139
|
+
// isOwnTokenGoatCommand's docstring in shim_common.ts for the measured emission rate this
|
|
4140
|
+
// forfeits.
|
|
4141
|
+
if (eventName === 'pre_tool_use') {
|
|
4142
|
+
try {
|
|
4143
|
+
const payload = JSON.parse(input)
|
|
4144
|
+
if (payload['tool_name'] === 'Bash' && isOwnTokenGoatCommand(payload['tool_input'] && payload['tool_input']['command'])) {
|
|
4145
|
+
process.stdout.write('{}')
|
|
4146
|
+
return
|
|
4147
|
+
}
|
|
4148
|
+
} catch {
|
|
4149
|
+
// Fall through to normal handling on unparseable stdin.
|
|
4150
|
+
}
|
|
4151
|
+
}
|
|
4152
|
+
// Print this before the in-process/spawn round trip below, not after: the harness detaches on
|
|
4153
|
+
// the first stdout line it sees, and the whole point is not waiting out that round trip for a
|
|
4154
|
+
// call that was always going to answer '{}'. asyncDetachAtMs, taken at this exact print, is the
|
|
4155
|
+
// last instant this process's elapsed time and the harness's own wait are the same number --
|
|
4156
|
+
// record it now so the finally block in relayInProcess (relay.ts) can report what Claude Code
|
|
4157
|
+
// actually waited on instead of this call's full lifetime.
|
|
4158
|
+
let asyncDetachAtMs
|
|
4159
|
+
if (isAsyncDetachEligible(eventName, input)) {
|
|
4160
|
+
asyncDetachAtMs = performance.now()
|
|
4161
|
+
process.stdout.write('{"async":true}\\n')
|
|
4162
|
+
}
|
|
4041
4163
|
// process.argv[3], when present, is the absolute path to the running token-goat CLI
|
|
4042
4164
|
// entry (mirrors the same argv[3]-baked-entry convention used by the Codex/Copilot
|
|
4043
4165
|
// CLI shims). Try the in-process hook lib first (tryInProcess above), which avoids
|
|
@@ -4048,8 +4170,15 @@ async function main() {
|
|
|
4048
4170
|
// timeout/killSignal on both spawnSync fallbacks means token-goat degrades to its own
|
|
4049
4171
|
// fail-open '{}' rather than being force-killed by Claude Code's own hook timeout.
|
|
4050
4172
|
const entryPath = process.argv[3]
|
|
4051
|
-
let stdout = await tryInProcess(entryPath, eventName, input)
|
|
4173
|
+
let stdout = await tryInProcess(entryPath, eventName, input, asyncDetachAtMs)
|
|
4052
4174
|
if (stdout === undefined) {
|
|
4175
|
+
// Reaching here with asyncDetachAtMs set means tryInProcess failed after
|
|
4176
|
+
// isAsyncDetachEligible had already parsed this same input successfully, so the cause is an
|
|
4177
|
+
// install missing its dist/token-goat-hook.mjs sibling or a broken import -- never a bad
|
|
4178
|
+
// payload. asyncDetachAtMs is this process's own elapsed time and has no meaning in the
|
|
4179
|
+
// spawned child's clock, so it is deliberately not threaded across that boundary; the child
|
|
4180
|
+
// falls back to timing its own lifetime, the same already-approximate number every hook got
|
|
4181
|
+
// on this fallback path before async-detach existed.
|
|
4053
4182
|
${SHIM_SPAWN_LADDER}
|
|
4054
4183
|
: spawnSync('token-goat hook ' + eventName, {
|
|
4055
4184
|
input,
|
|
@@ -4073,6 +4202,12 @@ main().catch(() => {
|
|
|
4073
4202
|
})
|
|
4074
4203
|
`;
|
|
4075
4204
|
|
|
4205
|
+
// src/canonical_skill.ts
|
|
4206
|
+
init_define_import_meta_env();
|
|
4207
|
+
import { gunzipSync } from "node:zlib";
|
|
4208
|
+
var CANONICAL_SKILL_GZIP_BASE64 = "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";
|
|
4209
|
+
var CANONICAL_SKILL_MD = gunzipSync(Buffer.from(CANONICAL_SKILL_GZIP_BASE64, "base64")).toString("utf8");
|
|
4210
|
+
|
|
4076
4211
|
// src/install.ts
|
|
4077
4212
|
var HOOK_EVENT_MAP = [
|
|
4078
4213
|
["PreToolUse", "pre_tool_use"],
|
|
@@ -4091,18 +4226,56 @@ function anchoredMarkerPattern(marker) {
|
|
|
4091
4226
|
return new RegExp(`(?<![a-zA-Z0-9_-])${escaped}(?![a-zA-Z0-9_-])`);
|
|
4092
4227
|
}
|
|
4093
4228
|
var HOOK_MARKER_PATTERNS = [SHIM_COMMAND_MARKER, COMMAND_MARKER, ...LEGACY_COMMAND_MARKERS].map(anchoredMarkerPattern);
|
|
4094
|
-
function isTokenGoatHookCommand(command) {
|
|
4095
|
-
|
|
4229
|
+
function isTokenGoatHookCommand(command, args) {
|
|
4230
|
+
const haystack = args !== void 0 && args.length > 0 ? `${command} ${args.join(" ")}` : command;
|
|
4231
|
+
return HOOK_MARKER_PATTERNS.some((pattern) => pattern.test(haystack));
|
|
4232
|
+
}
|
|
4233
|
+
var CLAUDE_EXEC_FORM_MIN_VERSION = "2.1.139";
|
|
4234
|
+
var _claudeExecFormHooksSupported;
|
|
4235
|
+
function claudeExecFormHooksSupported() {
|
|
4236
|
+
const override = process.env["TOKEN_GOAT_CLAUDE_EXEC_FORM_HOOKS"];
|
|
4237
|
+
if (override === "1") return true;
|
|
4238
|
+
if (override === "0") return false;
|
|
4239
|
+
if (_claudeExecFormHooksSupported !== void 0) return _claudeExecFormHooksSupported;
|
|
4240
|
+
_claudeExecFormHooksSupported = probeClaudeExecFormHooksSupported();
|
|
4241
|
+
return _claudeExecFormHooksSupported;
|
|
4242
|
+
}
|
|
4243
|
+
function probeClaudeExecFormHooksSupported() {
|
|
4244
|
+
const resolved = resolveOnPath("claude");
|
|
4245
|
+
if (resolved === null) return false;
|
|
4246
|
+
try {
|
|
4247
|
+
const isBatch = /\.(?:cmd|bat)$/i.test(resolved);
|
|
4248
|
+
const comspec = path3.join(process.env["SystemRoot"] ?? process.env["windir"] ?? "C:\\Windows", "System32", "cmd.exe");
|
|
4249
|
+
const result = isBatch ? spawnSync(fs3.existsSync(comspec) ? comspec : "cmd.exe", ["/d", "/s", "/c", resolved, "--version"], { encoding: "utf-8", timeout: 5e3, windowsHide: true }) : spawnSync(resolved, ["--version"], { encoding: "utf-8", timeout: 5e3, windowsHide: true });
|
|
4250
|
+
if (result.status !== 0) return false;
|
|
4251
|
+
return execFormSupportedForVersionOutput(result.stdout ?? "");
|
|
4252
|
+
} catch {
|
|
4253
|
+
return false;
|
|
4254
|
+
}
|
|
4255
|
+
}
|
|
4256
|
+
function execFormSupportedForVersionOutput(stdout) {
|
|
4257
|
+
const m = /(\d+\.\d+\.\d+)/.exec(stdout);
|
|
4258
|
+
if (!m) return false;
|
|
4259
|
+
return compareSemver(m[1], CLAUDE_EXEC_FORM_MIN_VERSION) >= 0;
|
|
4260
|
+
}
|
|
4261
|
+
function expectedHookEntryFor(scriptPath, event) {
|
|
4262
|
+
return claudeExecFormHooksSupported() ? hookExecPartsFor(scriptPath, event) : { command: hookCommandFor(scriptPath, event) };
|
|
4263
|
+
}
|
|
4264
|
+
function hookEntryMatches(command, args, expected) {
|
|
4265
|
+
if (command !== expected.command) return false;
|
|
4266
|
+
const a = args ?? [];
|
|
4267
|
+
const b = expected.args ?? [];
|
|
4268
|
+
return a.length === b.length && a.every((v, i) => v === b[i]);
|
|
4096
4269
|
}
|
|
4097
4270
|
function claudeHookScriptPath() {
|
|
4098
|
-
return path3.join(
|
|
4271
|
+
return path3.join(claudeConfigDir(), "hooks", "token-goat-shim.js");
|
|
4099
4272
|
}
|
|
4100
4273
|
function anyScopeReferencesShim(scriptPath, currentScope, alreadyStripped) {
|
|
4101
4274
|
const referencesIn = (map) => {
|
|
4102
4275
|
for (const groups of Object.values(map ?? {})) {
|
|
4103
4276
|
for (const group of groups) {
|
|
4104
4277
|
for (const h of group.hooks ?? []) {
|
|
4105
|
-
if (h.command.includes(scriptPath)) return true;
|
|
4278
|
+
if (h.command.includes(scriptPath) || (h.args ?? []).some((a) => a.includes(scriptPath))) return true;
|
|
4106
4279
|
}
|
|
4107
4280
|
}
|
|
4108
4281
|
}
|
|
@@ -4116,8 +4289,7 @@ function anyScopeReferencesShim(scriptPath, currentScope, alreadyStripped) {
|
|
|
4116
4289
|
return false;
|
|
4117
4290
|
}
|
|
4118
4291
|
function settingsPath(scope) {
|
|
4119
|
-
const
|
|
4120
|
-
const base = path3.join(root, ".claude");
|
|
4292
|
+
const base = scope === "user" ? claudeConfigDir() : path3.join(normalizeDarwinSystemAlias(process.cwd()), ".claude");
|
|
4121
4293
|
return path3.join(base, "settings.json");
|
|
4122
4294
|
}
|
|
4123
4295
|
var SettingsParseError = class extends Error {
|
|
@@ -4154,7 +4326,7 @@ function groupHasTokenGoat(groups, predicate) {
|
|
|
4154
4326
|
if (groups === void 0) return false;
|
|
4155
4327
|
for (const group of groups) {
|
|
4156
4328
|
for (const h of group.hooks ?? []) {
|
|
4157
|
-
if (predicate(h.command)) return true;
|
|
4329
|
+
if (predicate(h.command, h.args)) return true;
|
|
4158
4330
|
}
|
|
4159
4331
|
}
|
|
4160
4332
|
return false;
|
|
@@ -4176,13 +4348,13 @@ function installHooksScoped(scope) {
|
|
|
4176
4348
|
});
|
|
4177
4349
|
let settingsChanged = false;
|
|
4178
4350
|
for (const [eventKey, eventArg] of HOOK_EVENT_MAP) {
|
|
4179
|
-
const
|
|
4351
|
+
const expected = expectedHookEntryFor(scriptPath, eventArg);
|
|
4180
4352
|
const existingGroups = hooks[eventKey] ?? [];
|
|
4181
4353
|
const groups = [];
|
|
4182
4354
|
let strippedStale = false;
|
|
4183
4355
|
for (const group of existingGroups) {
|
|
4184
4356
|
const keptHooks = (group.hooks ?? []).filter((h) => {
|
|
4185
|
-
const isStale = isTokenGoatHookCommand(h.command) && h.command
|
|
4357
|
+
const isStale = isTokenGoatHookCommand(h.command, h.args) && !hookEntryMatches(h.command, h.args, expected);
|
|
4186
4358
|
if (isStale) strippedStale = true;
|
|
4187
4359
|
return !isStale;
|
|
4188
4360
|
});
|
|
@@ -4192,7 +4364,7 @@ function installHooksScoped(scope) {
|
|
|
4192
4364
|
groups.push(group);
|
|
4193
4365
|
}
|
|
4194
4366
|
}
|
|
4195
|
-
const isOurs = (command) => command
|
|
4367
|
+
const isOurs = (command, args) => hookEntryMatches(command, args, expected);
|
|
4196
4368
|
if (groupHasTokenGoat(groups, isOurs)) {
|
|
4197
4369
|
const narrowed = toolMatcherFor(eventArg);
|
|
4198
4370
|
let renarrowed = false;
|
|
@@ -4201,7 +4373,7 @@ function installHooksScoped(scope) {
|
|
|
4201
4373
|
const group = groups[i];
|
|
4202
4374
|
if (group === void 0) continue;
|
|
4203
4375
|
const ownHooks = group.hooks ?? [];
|
|
4204
|
-
const isOwnGroup = ownHooks.length > 0 && ownHooks.every((h) => isOurs(h.command));
|
|
4376
|
+
const isOwnGroup = ownHooks.length > 0 && ownHooks.every((h) => isOurs(h.command, h.args));
|
|
4205
4377
|
if (isOwnGroup && group.matcher !== narrowed) {
|
|
4206
4378
|
groups[i] = { ...group, matcher: narrowed };
|
|
4207
4379
|
renarrowed = true;
|
|
@@ -4215,7 +4387,7 @@ function installHooksScoped(scope) {
|
|
|
4215
4387
|
continue;
|
|
4216
4388
|
}
|
|
4217
4389
|
const matcher = toolMatcherFor(eventArg) ?? "";
|
|
4218
|
-
groups.push({ matcher, hooks: [{ type: "command", command:
|
|
4390
|
+
groups.push({ matcher, hooks: [{ type: "command", command: expected.command, ...expected.args !== void 0 ? { args: expected.args } : {} }] });
|
|
4219
4391
|
hooks[eventKey] = groups;
|
|
4220
4392
|
settingsChanged = true;
|
|
4221
4393
|
}
|
|
@@ -4259,15 +4431,15 @@ function isInstalled(scope = "user") {
|
|
|
4259
4431
|
const scriptPath = claudeHookScriptPath();
|
|
4260
4432
|
if (!fs3.existsSync(scriptPath)) return false;
|
|
4261
4433
|
for (const [eventKey, eventArg] of HOOK_EVENT_MAP) {
|
|
4262
|
-
const
|
|
4263
|
-
if (!groupHasTokenGoat(hooks[eventKey], (c) => c
|
|
4434
|
+
const expected = expectedHookEntryFor(scriptPath, eventArg);
|
|
4435
|
+
if (!groupHasTokenGoat(hooks[eventKey], (c, a) => hookEntryMatches(c, a, expected))) return false;
|
|
4264
4436
|
}
|
|
4265
4437
|
return true;
|
|
4266
4438
|
}
|
|
4267
4439
|
var CLAUDE_MD_BEGIN = "<!-- token-goat-begin -->";
|
|
4268
4440
|
var CLAUDE_MD_END = "<!-- token-goat-end -->";
|
|
4269
4441
|
function claudeMdPath() {
|
|
4270
|
-
return path3.join(
|
|
4442
|
+
return path3.join(claudeConfigDir(), "CLAUDE.md");
|
|
4271
4443
|
}
|
|
4272
4444
|
function buildClaudeMdBlock() {
|
|
4273
4445
|
return buildGuidanceBlock({
|
|
@@ -4292,7 +4464,7 @@ function uninstallClaudeMd() {
|
|
|
4292
4464
|
return stripClaudeMdBlock(claudeMdPath());
|
|
4293
4465
|
}
|
|
4294
4466
|
function findStrayClaudeMdBlocks(searchRoot) {
|
|
4295
|
-
const root = searchRoot ??
|
|
4467
|
+
const root = searchRoot ?? claudeConfigDir();
|
|
4296
4468
|
const canonical = path3.resolve(claudeMdPath());
|
|
4297
4469
|
const found = [];
|
|
4298
4470
|
if (!fs3.existsSync(root)) return found;
|
|
@@ -4335,26 +4507,11 @@ function findStrayClaudeMdBlocks(searchRoot) {
|
|
|
4335
4507
|
walk(root, 0);
|
|
4336
4508
|
return found.sort();
|
|
4337
4509
|
}
|
|
4338
|
-
function skillMdFrontmatter(gdrive) {
|
|
4339
|
-
return `---
|
|
4340
|
-
name: token-goat
|
|
4341
|
-
${skillDescriptionLine(gdrive)}
|
|
4342
|
-
allowed-tools:
|
|
4343
|
-
- Bash
|
|
4344
|
-
- Read
|
|
4345
|
-
- Grep
|
|
4346
|
-
- Glob
|
|
4347
|
-
---`;
|
|
4348
|
-
}
|
|
4349
4510
|
function skillMdContent() {
|
|
4350
|
-
|
|
4351
|
-
return `${skillMdFrontmatter(gdrive)}
|
|
4352
|
-
|
|
4353
|
-
${buildGuidanceBody("Claude Code's own Read, Grep, and Glob preference rules", { gdrive })}
|
|
4354
|
-
`;
|
|
4511
|
+
return CANONICAL_SKILL_MD;
|
|
4355
4512
|
}
|
|
4356
4513
|
function skillDir() {
|
|
4357
|
-
return path3.join(
|
|
4514
|
+
return path3.join(claudeConfigDir(), "skills", "token-goat");
|
|
4358
4515
|
}
|
|
4359
4516
|
function skillPath() {
|
|
4360
4517
|
return path3.join(skillDir(), "SKILL.md");
|
|
@@ -4386,7 +4543,7 @@ function uninstallSkill() {
|
|
|
4386
4543
|
init_define_import_meta_env();
|
|
4387
4544
|
import * as crypto from "node:crypto";
|
|
4388
4545
|
import * as fs4 from "node:fs";
|
|
4389
|
-
import * as
|
|
4546
|
+
import * as os2 from "node:os";
|
|
4390
4547
|
import * as path4 from "node:path";
|
|
4391
4548
|
|
|
4392
4549
|
// src/bridges/codex.ts
|
|
@@ -4398,6 +4555,9 @@ ${SHIM_REQUIRES}
|
|
|
4398
4555
|
|
|
4399
4556
|
${SHIM_VALID_HOOK_EVENTS}
|
|
4400
4557
|
|
|
4558
|
+
// Codex sets no env var of its own identifying a hook subprocess; see this module's docblock.
|
|
4559
|
+
process.env.TOKEN_GOAT_HARNESS_OVERRIDE = 'codex'
|
|
4560
|
+
|
|
4401
4561
|
// Keep in sync with CLAUDE_CODE_EVENT_NAMES in src/hook_registry.ts -- the
|
|
4402
4562
|
// live/wired 'token-goat hook <event>' path (src/relay.ts) always emits this
|
|
4403
4563
|
// PascalCase spelling for hookSpecificOutput.hookEventName, never the raw
|
|
@@ -4526,7 +4686,7 @@ function groupHasTokenGoat2(groups, matcher, predicate) {
|
|
|
4526
4686
|
|
|
4527
4687
|
// src/bridges/codex_install.ts
|
|
4528
4688
|
var CODEX_COMMAND_MARKER = "token-goat-shim";
|
|
4529
|
-
var CODEX_MATCHERS = ["view_image|shell|bash", "apply_patch", "web_search"];
|
|
4689
|
+
var CODEX_MATCHERS = ["view_image|Bash|exec|shell|bash", "apply_patch", "web_search"];
|
|
4530
4690
|
var CODEX_HOOK_EVENTS = ["PreToolUse", "PostToolUse"];
|
|
4531
4691
|
var CODEX_EVENT_ARG = {
|
|
4532
4692
|
PreToolUse: "pre_tool_use",
|
|
@@ -4541,13 +4701,13 @@ var CODEX_GLOBAL_EVENT_ARG = {
|
|
|
4541
4701
|
var CodexConfigParseError = class extends Error {
|
|
4542
4702
|
};
|
|
4543
4703
|
function codexConfigPath() {
|
|
4544
|
-
return path4.join(
|
|
4704
|
+
return path4.join(os2.homedir(), ".codex", "config.toml");
|
|
4545
4705
|
}
|
|
4546
4706
|
function codexAgentsPath() {
|
|
4547
|
-
return path4.join(
|
|
4707
|
+
return path4.join(os2.homedir(), ".codex", "AGENTS.md");
|
|
4548
4708
|
}
|
|
4549
4709
|
function codexHookScriptPath() {
|
|
4550
|
-
return path4.join(
|
|
4710
|
+
return path4.join(os2.homedir(), ".codex", "hooks", "token-goat-shim.js");
|
|
4551
4711
|
}
|
|
4552
4712
|
function readCodexConfig(p, opts = {}) {
|
|
4553
4713
|
let raw;
|
|
@@ -4809,7 +4969,7 @@ function buildAgentsBlock() {
|
|
|
4809
4969
|
return buildGuidanceBlock({
|
|
4810
4970
|
beginMarker: AGENTS_BEGIN,
|
|
4811
4971
|
endMarker: AGENTS_END,
|
|
4812
|
-
fallbackToolClause: "Codex's native `
|
|
4972
|
+
fallbackToolClause: "Codex's native `exec`, `apply_patch`, and `view_image` tools (shell commands like `cat`/`type` run inside `exec`)",
|
|
4813
4973
|
gdrive: loadConfig().gdrive.enabled
|
|
4814
4974
|
});
|
|
4815
4975
|
}
|
|
@@ -4823,7 +4983,7 @@ function stripAgentsBlock(p) {
|
|
|
4823
4983
|
// src/bridges/gemini_install.ts
|
|
4824
4984
|
init_define_import_meta_env();
|
|
4825
4985
|
import * as fs5 from "node:fs";
|
|
4826
|
-
import * as
|
|
4986
|
+
import * as os3 from "node:os";
|
|
4827
4987
|
import * as path5 from "node:path";
|
|
4828
4988
|
var GEMINI_LEGACY_COMMAND_MARKER = "token-goat hook";
|
|
4829
4989
|
var GEMINI_HOOK_EVENTS = ["BeforeTool", "AfterTool", "PreCompress"];
|
|
@@ -4845,7 +5005,7 @@ function registeredInternalTools(eventName) {
|
|
|
4845
5005
|
var GeminiSettingsParseError = class extends Error {
|
|
4846
5006
|
};
|
|
4847
5007
|
function geminiSettingsPath() {
|
|
4848
|
-
return path5.join(
|
|
5008
|
+
return path5.join(os3.homedir(), ".gemini", "settings.json");
|
|
4849
5009
|
}
|
|
4850
5010
|
function readGeminiSettings(p, opts = {}) {
|
|
4851
5011
|
let raw;
|
|
@@ -4976,7 +5136,7 @@ function isGeminiInstalled() {
|
|
|
4976
5136
|
// src/bridges/qwen_install.ts
|
|
4977
5137
|
init_define_import_meta_env();
|
|
4978
5138
|
import * as fs6 from "node:fs";
|
|
4979
|
-
import * as
|
|
5139
|
+
import * as os4 from "node:os";
|
|
4980
5140
|
import * as path6 from "node:path";
|
|
4981
5141
|
var QWEN_HOOK_EVENTS = ["PreToolUse", "PostToolUse", "PreCompact", "UserPromptSubmit", "SubagentStop"];
|
|
4982
5142
|
var QWEN_EVENT_ARG = {
|
|
@@ -4989,7 +5149,7 @@ var QWEN_EVENT_ARG = {
|
|
|
4989
5149
|
var QwenSettingsParseError = class extends Error {
|
|
4990
5150
|
};
|
|
4991
5151
|
function qwenSettingsPath() {
|
|
4992
|
-
return path6.join(
|
|
5152
|
+
return path6.join(os4.homedir(), ".qwen", "settings.json");
|
|
4993
5153
|
}
|
|
4994
5154
|
function readQwenSettings(p, opts = {}) {
|
|
4995
5155
|
let raw;
|
|
@@ -5001,10 +5161,10 @@ function readQwenSettings(p, opts = {}) {
|
|
|
5001
5161
|
let parsed;
|
|
5002
5162
|
try {
|
|
5003
5163
|
parsed = JSON.parse(raw);
|
|
5004
|
-
} catch {
|
|
5164
|
+
} catch (e) {
|
|
5005
5165
|
if (opts.strict === true) {
|
|
5006
5166
|
throw new QwenSettingsParseError(
|
|
5007
|
-
`Qwen Code settings file '${p}' exists but contains invalid JSON. Fix or back up the file before running install
|
|
5167
|
+
`Qwen Code settings file '${p}' exists but contains invalid JSON. Fix or back up the file before running install. (${extractErrorMessage(e)})`
|
|
5008
5168
|
);
|
|
5009
5169
|
}
|
|
5010
5170
|
return {};
|
|
@@ -5093,7 +5253,7 @@ function isQwenInstalled() {
|
|
|
5093
5253
|
// src/bridges/kimi_install.ts
|
|
5094
5254
|
init_define_import_meta_env();
|
|
5095
5255
|
import * as fs7 from "node:fs";
|
|
5096
|
-
import * as
|
|
5256
|
+
import * as os5 from "node:os";
|
|
5097
5257
|
import * as path7 from "node:path";
|
|
5098
5258
|
|
|
5099
5259
|
// src/bridges/kimi.ts
|
|
@@ -5202,7 +5362,7 @@ var KimiConfigParseError = class extends Error {
|
|
|
5202
5362
|
function kimiHome() {
|
|
5203
5363
|
const override = process.env["KIMI_CODE_HOME"];
|
|
5204
5364
|
if (override !== void 0 && override.trim() !== "") return path7.resolve(override);
|
|
5205
|
-
return path7.join(
|
|
5365
|
+
return path7.join(os5.homedir(), ".kimi-code");
|
|
5206
5366
|
}
|
|
5207
5367
|
function kimiConfigPath() {
|
|
5208
5368
|
return path7.join(kimiHome(), "config.toml");
|
|
@@ -5356,7 +5516,7 @@ function isKimiInstalled() {
|
|
|
5356
5516
|
// src/bridges/pi_install.ts
|
|
5357
5517
|
init_define_import_meta_env();
|
|
5358
5518
|
import * as fs8 from "node:fs";
|
|
5359
|
-
import * as
|
|
5519
|
+
import * as os6 from "node:os";
|
|
5360
5520
|
import * as path8 from "node:path";
|
|
5361
5521
|
|
|
5362
5522
|
// src/bridges/pi.ts
|
|
@@ -5722,7 +5882,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
5722
5882
|
|
|
5723
5883
|
// src/bridges/pi_install.ts
|
|
5724
5884
|
function piGlobalExtensionPath() {
|
|
5725
|
-
return path8.join(
|
|
5885
|
+
return path8.join(os6.homedir(), ".pi", "agent", "extensions", "token-goat.ts");
|
|
5726
5886
|
}
|
|
5727
5887
|
function piLocalExtensionPath() {
|
|
5728
5888
|
return path8.join(process.cwd(), ".pi", "extensions", "token-goat.ts");
|
|
@@ -5758,7 +5918,7 @@ function isPiInstalled(opts = {}) {
|
|
|
5758
5918
|
// src/bridges/opencode_install.ts
|
|
5759
5919
|
init_define_import_meta_env();
|
|
5760
5920
|
import * as fs9 from "node:fs";
|
|
5761
|
-
import * as
|
|
5921
|
+
import * as os7 from "node:os";
|
|
5762
5922
|
import * as path9 from "node:path";
|
|
5763
5923
|
|
|
5764
5924
|
// src/bridges/opencode.ts
|
|
@@ -6032,11 +6192,11 @@ function opencodeGlobalConfigDir() {
|
|
|
6032
6192
|
if (process.platform === "win32") {
|
|
6033
6193
|
const appData = process.env["APPDATA"];
|
|
6034
6194
|
if (appData !== void 0 && appData.trim() !== "") return appData;
|
|
6035
|
-
return path9.join(
|
|
6195
|
+
return path9.join(os7.homedir(), "AppData", "Roaming");
|
|
6036
6196
|
}
|
|
6037
6197
|
const xdgConfigHome = process.env["XDG_CONFIG_HOME"];
|
|
6038
6198
|
if (xdgConfigHome !== void 0 && xdgConfigHome.trim() !== "") return xdgConfigHome;
|
|
6039
|
-
return path9.join(
|
|
6199
|
+
return path9.join(os7.homedir(), ".config");
|
|
6040
6200
|
}
|
|
6041
6201
|
function opencodePluginPath() {
|
|
6042
6202
|
return path9.join(opencodeGlobalConfigDir(), "opencode", "plugins", "token-goat.ts");
|
|
@@ -6059,7 +6219,7 @@ function isOpencodeInstalled() {
|
|
|
6059
6219
|
// src/bridges/openclaw_install.ts
|
|
6060
6220
|
init_define_import_meta_env();
|
|
6061
6221
|
import * as fs10 from "node:fs";
|
|
6062
|
-
import * as
|
|
6222
|
+
import * as os8 from "node:os";
|
|
6063
6223
|
import * as path10 from "node:path";
|
|
6064
6224
|
|
|
6065
6225
|
// src/bridges/openclaw.ts
|
|
@@ -6221,7 +6381,7 @@ var OpenclawConfigParseError = class extends Error {
|
|
|
6221
6381
|
};
|
|
6222
6382
|
var OPENCLAW_PLUGIN_ID = "token-goat";
|
|
6223
6383
|
function openclawHomeDir() {
|
|
6224
|
-
return path10.join(
|
|
6384
|
+
return path10.join(os8.homedir(), ".openclaw");
|
|
6225
6385
|
}
|
|
6226
6386
|
function openclawConfigPath() {
|
|
6227
6387
|
return path10.join(openclawHomeDir(), "openclaw.json");
|
|
@@ -6373,7 +6533,7 @@ function isOpenclawInstalled() {
|
|
|
6373
6533
|
// src/bridges/grok_install.ts
|
|
6374
6534
|
init_define_import_meta_env();
|
|
6375
6535
|
import * as fs11 from "node:fs";
|
|
6376
|
-
import * as
|
|
6536
|
+
import * as os9 from "node:os";
|
|
6377
6537
|
import * as path11 from "node:path";
|
|
6378
6538
|
|
|
6379
6539
|
// src/bridges/grok.ts
|
|
@@ -6485,7 +6645,7 @@ var GROK_EVENT_ARG = {
|
|
|
6485
6645
|
SubagentStop: "subagent_stop"
|
|
6486
6646
|
};
|
|
6487
6647
|
function grokHooksDir() {
|
|
6488
|
-
return path11.join(
|
|
6648
|
+
return path11.join(os9.homedir(), ".grok", "hooks");
|
|
6489
6649
|
}
|
|
6490
6650
|
function grokConfigPath() {
|
|
6491
6651
|
return path11.join(grokHooksDir(), "token-goat.json");
|
|
@@ -6535,7 +6695,7 @@ function isGrokInstalled() {
|
|
|
6535
6695
|
// src/bridges/vscode_install.ts
|
|
6536
6696
|
init_define_import_meta_env();
|
|
6537
6697
|
import * as fs12 from "node:fs";
|
|
6538
|
-
import * as
|
|
6698
|
+
import * as os10 from "node:os";
|
|
6539
6699
|
import * as path12 from "node:path";
|
|
6540
6700
|
var VSCODE_GUIDANCE_BEGIN = "<!-- token-goat-vscode-begin -->";
|
|
6541
6701
|
var VSCODE_GUIDANCE_END = "<!-- token-goat-vscode-end -->";
|
|
@@ -6544,13 +6704,13 @@ var END = VSCODE_GUIDANCE_END;
|
|
|
6544
6704
|
function vscodeUserConfigDir() {
|
|
6545
6705
|
if (process.platform === "win32") {
|
|
6546
6706
|
const appData = process.env["APPDATA"];
|
|
6547
|
-
const base = appData !== void 0 && appData.trim() !== "" ? appData : path12.join(
|
|
6707
|
+
const base = appData !== void 0 && appData.trim() !== "" ? appData : path12.join(os10.homedir(), "AppData", "Roaming");
|
|
6548
6708
|
return path12.join(base, "Code", "User");
|
|
6549
6709
|
}
|
|
6550
6710
|
if (process.platform === "darwin") {
|
|
6551
|
-
return path12.join(
|
|
6711
|
+
return path12.join(os10.homedir(), "Library", "Application Support", "Code", "User");
|
|
6552
6712
|
}
|
|
6553
|
-
return path12.join(
|
|
6713
|
+
return path12.join(os10.homedir(), ".config", "Code", "User");
|
|
6554
6714
|
}
|
|
6555
6715
|
function vscodeUserMcpPath() {
|
|
6556
6716
|
return path12.join(vscodeUserConfigDir(), "mcp.json");
|
|
@@ -6565,11 +6725,11 @@ function otherScopeMcpPath(opts) {
|
|
|
6565
6725
|
return opts.project === true ? vscodeUserMcpPath() : vscodeProjectMcpPath(opts.projectRoot);
|
|
6566
6726
|
}
|
|
6567
6727
|
function vscodeInstructionsPath(opts = {}) {
|
|
6568
|
-
return opts.project === true ? path12.join(path12.resolve(opts.projectRoot ?? process.cwd()), ".github", "copilot-instructions.md") : path12.join(
|
|
6728
|
+
return opts.project === true ? path12.join(path12.resolve(opts.projectRoot ?? process.cwd()), ".github", "copilot-instructions.md") : path12.join(os10.homedir(), ".copilot", "instructions", "token-goat.instructions.md");
|
|
6569
6729
|
}
|
|
6570
6730
|
var USER_INSTRUCTIONS_FRONTMATTER = "---\ndescription: 'token-goat: when to use its MCP tools and CLI instead of reading whole files'\napplyTo: '**'\n---\n";
|
|
6571
6731
|
function vscodeHooksDir(opts = {}) {
|
|
6572
|
-
return opts.project === true ? path12.join(path12.resolve(opts.projectRoot ?? process.cwd()), ".github", "hooks") : path12.join(
|
|
6732
|
+
return opts.project === true ? path12.join(path12.resolve(opts.projectRoot ?? process.cwd()), ".github", "hooks") : path12.join(os10.homedir(), ".copilot", "hooks");
|
|
6573
6733
|
}
|
|
6574
6734
|
function vscodeScopeFromFlags(flags) {
|
|
6575
6735
|
return { project: flags.user !== true };
|
|
@@ -6715,8 +6875,12 @@ function uninstallVscodeScoped(opts) {
|
|
|
6715
6875
|
}
|
|
6716
6876
|
if (servers && isManagedServer(servers["token-goat"])) {
|
|
6717
6877
|
const next = dropEmptyServers(updateConfig(config.text, void 0));
|
|
6718
|
-
if (/^\s*\{\s*\}\s*$/.test(next) && takeCreatedConfig(mcpPath))
|
|
6719
|
-
|
|
6878
|
+
if (/^\s*\{\s*\}\s*$/.test(next) && takeCreatedConfig(mcpPath)) {
|
|
6879
|
+
removeFileInScope(mcpPath);
|
|
6880
|
+
if (opts.project === true) {
|
|
6881
|
+
removeEmptyDirInScope(path12.dirname(mcpPath));
|
|
6882
|
+
}
|
|
6883
|
+
} else {
|
|
6720
6884
|
backupFile(mcpPath);
|
|
6721
6885
|
atomicWriteText(mcpPath, next);
|
|
6722
6886
|
}
|
|
@@ -6733,16 +6897,31 @@ function uninstallVscodeScoped(opts) {
|
|
|
6733
6897
|
if (releaseCopilotHooksFile(vscodeHooksDir(opts), "vscode", opts.keepBackups === true)) removed = true;
|
|
6734
6898
|
return removed;
|
|
6735
6899
|
}
|
|
6900
|
+
function cleanupDeprecatedVscodeProjectMcp(projectRoot) {
|
|
6901
|
+
const root = path12.resolve(projectRoot ?? process.cwd());
|
|
6902
|
+
return withInstallScope(root, () => {
|
|
6903
|
+
const projectMcp = path12.join(root, ".vscode", "mcp.json");
|
|
6904
|
+
if (!fs12.existsSync(projectMcp)) return false;
|
|
6905
|
+
const text = fs12.readFileSync(projectMcp, "utf8");
|
|
6906
|
+
if (isResidueServersJson(text) && takeCreatedConfig(projectMcp)) {
|
|
6907
|
+
removeFileInScope(projectMcp);
|
|
6908
|
+
removeEmptyDirInScope(path12.dirname(projectMcp));
|
|
6909
|
+
removeCreatedBackups(projectMcp);
|
|
6910
|
+
return true;
|
|
6911
|
+
}
|
|
6912
|
+
return false;
|
|
6913
|
+
});
|
|
6914
|
+
}
|
|
6736
6915
|
|
|
6737
6916
|
// src/bridges/cursor_install.ts
|
|
6738
6917
|
init_define_import_meta_env();
|
|
6739
6918
|
import * as fs13 from "node:fs";
|
|
6740
|
-
import * as
|
|
6919
|
+
import * as os11 from "node:os";
|
|
6741
6920
|
import * as path13 from "node:path";
|
|
6742
6921
|
var MCP_SERVERS_KEY = "mcpServers";
|
|
6743
6922
|
var TOKEN_GOAT_ENTRY_KEY = "token-goat";
|
|
6744
6923
|
function cursorUserConfigDir() {
|
|
6745
|
-
return path13.join(
|
|
6924
|
+
return path13.join(os11.homedir(), ".cursor");
|
|
6746
6925
|
}
|
|
6747
6926
|
function cursorMcpPath(opts = {}) {
|
|
6748
6927
|
if (opts.project === true) {
|
|
@@ -6817,7 +6996,7 @@ function cursorManagedEntry(mcpPath) {
|
|
|
6817
6996
|
// src/bridges/zed_install.ts
|
|
6818
6997
|
init_define_import_meta_env();
|
|
6819
6998
|
import * as fs14 from "node:fs";
|
|
6820
|
-
import * as
|
|
6999
|
+
import * as os12 from "node:os";
|
|
6821
7000
|
import * as path14 from "node:path";
|
|
6822
7001
|
var CONTEXT_SERVERS_KEY = "context_servers";
|
|
6823
7002
|
var TOKEN_GOAT_ENTRY_KEY2 = "token-goat";
|
|
@@ -6825,11 +7004,11 @@ var ZED_TIMEOUT_MS = 3e4;
|
|
|
6825
7004
|
function zedConfigDir() {
|
|
6826
7005
|
if (process.platform === "win32") {
|
|
6827
7006
|
const appData = process.env["APPDATA"];
|
|
6828
|
-
const base2 = appData !== void 0 && appData.trim() !== "" ? appData : path14.join(
|
|
7007
|
+
const base2 = appData !== void 0 && appData.trim() !== "" ? appData : path14.join(os12.homedir(), "AppData", "Roaming");
|
|
6829
7008
|
return path14.join(base2, "Zed");
|
|
6830
7009
|
}
|
|
6831
7010
|
const xdgConfig = process.env["XDG_CONFIG_HOME"];
|
|
6832
|
-
const base = xdgConfig !== void 0 && xdgConfig.trim() !== "" ? xdgConfig : path14.join(
|
|
7011
|
+
const base = xdgConfig !== void 0 && xdgConfig.trim() !== "" ? xdgConfig : path14.join(os12.homedir(), ".config");
|
|
6833
7012
|
return path14.join(base, "zed");
|
|
6834
7013
|
}
|
|
6835
7014
|
function zedSettingsPath() {
|
|
@@ -6919,13 +7098,88 @@ function zedManagedEntry(settingsPath2) {
|
|
|
6919
7098
|
init_define_import_meta_env();
|
|
6920
7099
|
import * as fs18 from "fs";
|
|
6921
7100
|
import * as path17 from "path";
|
|
6922
|
-
import { spawnSync as
|
|
7101
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
7102
|
+
|
|
7103
|
+
// src/hook_latency.ts
|
|
7104
|
+
init_define_import_meta_env();
|
|
7105
|
+
function percentile(sorted, p) {
|
|
7106
|
+
if (sorted.length === 0) return 0;
|
|
7107
|
+
const idx = Math.min(sorted.length - 1, Math.max(0, Math.ceil(p / 100 * sorted.length) - 1));
|
|
7108
|
+
return sorted[idx];
|
|
7109
|
+
}
|
|
7110
|
+
function formatAgeSeconds(seconds) {
|
|
7111
|
+
if (seconds < 60) return `${seconds}s ago`;
|
|
7112
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
|
|
7113
|
+
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
|
|
7114
|
+
return `${Math.floor(seconds / 86400)}d ago`;
|
|
7115
|
+
}
|
|
7116
|
+
function hookLatencyBreakdown(testDb, homeDir) {
|
|
7117
|
+
try {
|
|
7118
|
+
const db = testDb ?? getGlobalDb(homeDir);
|
|
7119
|
+
if (!statsHasDurationColumn(db)) return [];
|
|
7120
|
+
const harnessExpr = statsHasHarnessColumn(db) ? "COALESCE(harness, '')" : "''";
|
|
7121
|
+
const rows = db.prepare(
|
|
7122
|
+
`SELECT kind, ${harnessExpr} AS harness, duration_ms, ts FROM stats
|
|
7123
|
+
WHERE kind LIKE 'hook:%' AND duration_ms IS NOT NULL ORDER BY ts DESC`
|
|
7124
|
+
).all();
|
|
7125
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
7126
|
+
for (const row of rows) {
|
|
7127
|
+
const event = row.kind.slice("hook:".length);
|
|
7128
|
+
const key = `${event}::${row.harness}`;
|
|
7129
|
+
let g = grouped.get(key);
|
|
7130
|
+
if (g === void 0) {
|
|
7131
|
+
g = { event, harness: row.harness, durations: [], newest_ts: row.ts };
|
|
7132
|
+
grouped.set(key, g);
|
|
7133
|
+
}
|
|
7134
|
+
g.durations.push(row.duration_ms);
|
|
7135
|
+
if (row.ts > g.newest_ts) g.newest_ts = row.ts;
|
|
7136
|
+
}
|
|
7137
|
+
return [...grouped.values()].map((g) => {
|
|
7138
|
+
const sorted = [...g.durations].sort((a, b) => a - b);
|
|
7139
|
+
return {
|
|
7140
|
+
event: g.event,
|
|
7141
|
+
harness: g.harness,
|
|
7142
|
+
count: sorted.length,
|
|
7143
|
+
median_ms: percentile(sorted, 50),
|
|
7144
|
+
p95_ms: percentile(sorted, 95),
|
|
7145
|
+
max_ms: sorted[sorted.length - 1],
|
|
7146
|
+
newest_ts: g.newest_ts
|
|
7147
|
+
};
|
|
7148
|
+
}).sort((a, b) => b.p95_ms - a.p95_ms);
|
|
7149
|
+
} catch {
|
|
7150
|
+
return [];
|
|
7151
|
+
}
|
|
7152
|
+
}
|
|
7153
|
+
function renderHookLatencyStats(testDb, homeDir) {
|
|
7154
|
+
const rows = hookLatencyBreakdown(testDb, homeDir);
|
|
7155
|
+
if (rows.length === 0) {
|
|
7156
|
+
console.log(`No hook latency recorded in the last ${HOOK_STATS_RETENTION_DAYS} day(s) (or this database predates the duration_ms column).`);
|
|
7157
|
+
return;
|
|
7158
|
+
}
|
|
7159
|
+
const nowTs = Math.floor(Date.now() / 1e3);
|
|
7160
|
+
console.log(`Hook latency, last ${HOOK_STATS_RETENTION_DAYS} day(s) (worst p95 first):`);
|
|
7161
|
+
for (const r of rows) {
|
|
7162
|
+
const age = formatAgeSeconds(Math.max(0, nowTs - r.newest_ts));
|
|
7163
|
+
console.log(
|
|
7164
|
+
` ${r.event.padEnd(22)} ${(r.harness || "unrecorded").padEnd(12)} n=${String(r.count).padEnd(5)} median ${String(r.median_ms).padStart(5)}ms p95 ${String(r.p95_ms).padStart(5)}ms max ${String(r.max_ms).padStart(6)}ms last seen ${age}`
|
|
7165
|
+
);
|
|
7166
|
+
}
|
|
7167
|
+
}
|
|
6923
7168
|
|
|
6924
7169
|
// src/cli_doctor_process.ts
|
|
6925
7170
|
init_define_import_meta_env();
|
|
6926
|
-
import { spawnSync } from "node:child_process";
|
|
7171
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
6927
7172
|
import * as fs15 from "node:fs";
|
|
6928
7173
|
import * as path15 from "node:path";
|
|
7174
|
+
var INTERPRETER_BASENAMES = /* @__PURE__ */ new Set(["node.exe", "python.exe", "python3.exe", "pwsh.exe", "powershell.exe", "cmd.exe"]);
|
|
7175
|
+
function describeProcess(commandLine) {
|
|
7176
|
+
for (const m of commandLine.matchAll(/[^\s"]+\.(?:js|mjs|cjs|ts|py|exe)\b/gi)) {
|
|
7177
|
+
const basename10 = m[0].replace(/\\/g, "/").split("/").pop() ?? m[0];
|
|
7178
|
+
if (!INTERPRETER_BASENAMES.has(basename10.toLowerCase())) return basename10;
|
|
7179
|
+
}
|
|
7180
|
+
const trimmed = commandLine.trim();
|
|
7181
|
+
return trimmed.length > 0 ? trimmed.length > 60 ? trimmed.slice(0, 60) + "\u2026" : trimmed : "(no command line)";
|
|
7182
|
+
}
|
|
6929
7183
|
function checkMcpProcessHealth(processes) {
|
|
6930
7184
|
if (processes === null) {
|
|
6931
7185
|
return {
|
|
@@ -6947,13 +7201,13 @@ function checkMcpProcessHealth(processes) {
|
|
|
6947
7201
|
if (chromeLaunchers.length > 1) details.push(`${chromeLaunchers.length} Chrome DevTools MCP launchers (PIDs: ${chromeLaunchers.map((p) => p.processId).join(", ")})`);
|
|
6948
7202
|
if (playwrightLaunchers.length > 1) details.push(`${playwrightLaunchers.length} Playwright MCP launchers (PIDs: ${playwrightLaunchers.map((p) => p.processId).join(", ")})`);
|
|
6949
7203
|
if (orphanedNodeProcesses.length > 0) {
|
|
6950
|
-
const
|
|
6951
|
-
details.push(`${orphanedNodeProcesses.length} orphaned Node process${orphanedNodeProcesses.length === 1 ? "" : "es"}
|
|
7204
|
+
const described = orphanedNodeProcesses.map((p) => `${p.processId} (${describeProcess(p.commandLine)})`).join(", ");
|
|
7205
|
+
details.push(`${orphanedNodeProcesses.length} orphaned Node process${orphanedNodeProcesses.length === 1 ? "" : "es"}: ${described}`);
|
|
6952
7206
|
}
|
|
6953
7207
|
return {
|
|
6954
7208
|
name: "MCP process health",
|
|
6955
7209
|
status: "warn",
|
|
6956
|
-
message: `${details.join("; ")} detected.
|
|
7210
|
+
message: `${details.join("; ")} detected. None of these are token-goat's own processes -- they are host-managed Node/MCP processes left by closed terminal or CLI sessions; confirm what each one is (the name shown above is a starting point) before stopping it with Stop-Process -Id <PID>.`
|
|
6957
7211
|
};
|
|
6958
7212
|
}
|
|
6959
7213
|
return { name: "MCP process health", status: "ok", message: "no duplicate MCP launchers or orphaned Node processes detected" };
|
|
@@ -6962,7 +7216,7 @@ function runProcessListCommand() {
|
|
|
6962
7216
|
const command = "Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,Name,CommandLine | ConvertTo-Json -Compress";
|
|
6963
7217
|
const systemRoot = process.env["SystemRoot"] ?? process.env["windir"] ?? "C:\\Windows";
|
|
6964
7218
|
const shell = path15.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
6965
|
-
const result =
|
|
7219
|
+
const result = spawnSync2(fs15.existsSync(shell) ? shell : "powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", command], {
|
|
6966
7220
|
encoding: "utf8",
|
|
6967
7221
|
timeout: 2e4,
|
|
6968
7222
|
maxBuffer: 10 * 1024 * 1024,
|
|
@@ -7000,11 +7254,11 @@ function checkWorkerRunning(dataDir2) {
|
|
|
7000
7254
|
// src/cli_doctor_platforms.ts
|
|
7001
7255
|
init_define_import_meta_env();
|
|
7002
7256
|
import * as fs16 from "node:fs";
|
|
7003
|
-
import * as
|
|
7257
|
+
import * as os13 from "node:os";
|
|
7004
7258
|
import * as path16 from "node:path";
|
|
7005
7259
|
function globalMcpConfigPath() {
|
|
7006
7260
|
const copilotHome = process.env["COPILOT_HOME"];
|
|
7007
|
-
const root = copilotHome !== void 0 && copilotHome.trim() !== "" ? path16.resolve(copilotHome) : path16.join(
|
|
7261
|
+
const root = copilotHome !== void 0 && copilotHome.trim() !== "" ? path16.resolve(copilotHome) : path16.join(os13.homedir(), ".copilot");
|
|
7008
7262
|
return path16.join(root, "mcp-config.json");
|
|
7009
7263
|
}
|
|
7010
7264
|
function checkGlobalMcpConfig(configPath2 = globalMcpConfigPath()) {
|
|
@@ -7161,6 +7415,28 @@ function checkStrayClaudeMdBlocks(searchRoot) {
|
|
|
7161
7415
|
message: `${strays.length} stray cop${strays.length === 1 ? "y" : "ies"} outside CLAUDE.md (never refreshed by install, never removed by uninstall, will go stale): ${strays.join(", ")}`
|
|
7162
7416
|
};
|
|
7163
7417
|
}
|
|
7418
|
+
function checkVscodeProjectMcp(projectRoot = process.cwd()) {
|
|
7419
|
+
const mcpPath = path16.join(path16.resolve(projectRoot), ".vscode", "mcp.json");
|
|
7420
|
+
if (!fs16.existsSync(mcpPath)) return null;
|
|
7421
|
+
if (hasManagedServer(mcpPath, "VS Code")) return null;
|
|
7422
|
+
try {
|
|
7423
|
+
const content = fs16.readFileSync(mcpPath, "utf8");
|
|
7424
|
+
if (isResidueServersJson(content)) {
|
|
7425
|
+
return {
|
|
7426
|
+
name: "VS Code project MCP",
|
|
7427
|
+
status: "warn",
|
|
7428
|
+
message: `empty residue file at ${displaySafeText(mcpPath)} triggers Copilot CLI deprecation warnings; delete it or run 'token-goat uninstall --vscode'`
|
|
7429
|
+
};
|
|
7430
|
+
}
|
|
7431
|
+
return {
|
|
7432
|
+
name: "VS Code project MCP",
|
|
7433
|
+
status: "warn",
|
|
7434
|
+
message: `${displaySafeText(mcpPath)} is deprecated by Copilot CLI v1.0.84+; migrate configured servers to .mcp.json or .github/mcp.json (https://gh.io/copilotcli-mcpmigrate)`
|
|
7435
|
+
};
|
|
7436
|
+
} catch {
|
|
7437
|
+
return null;
|
|
7438
|
+
}
|
|
7439
|
+
}
|
|
7164
7440
|
|
|
7165
7441
|
// src/cli_doctor_security.ts
|
|
7166
7442
|
init_define_import_meta_env();
|
|
@@ -7285,22 +7561,61 @@ function checkSecurityPosture(cfg, dataDirPath) {
|
|
|
7285
7561
|
}
|
|
7286
7562
|
|
|
7287
7563
|
// src/cli_doctor.ts
|
|
7288
|
-
var DB_SIZE_WARN_BYTES =
|
|
7564
|
+
var DB_SIZE_WARN_BYTES = 1500 * 1024 * 1024;
|
|
7289
7565
|
function freelistBytes(header) {
|
|
7290
7566
|
if (header.length < 40) return 0;
|
|
7291
7567
|
const rawPageSize = header.readUInt16BE(16);
|
|
7292
7568
|
return (rawPageSize === 1 ? 65536 : rawPageSize) * header.readUInt32BE(36);
|
|
7293
7569
|
}
|
|
7294
|
-
|
|
7570
|
+
var CATEGORY_COLUMNS = [
|
|
7571
|
+
{ name: "symbol bodies", table: "symbols", column: "body", command: "'token-goat reclaim-index --rebuild' drops and re-derives them under the current parser's size cap" },
|
|
7572
|
+
{ name: "refs", table: "refs", column: "context", command: "'token-goat reclaim-index --rebuild' drops and re-derives them" },
|
|
7573
|
+
{ name: "chunk text", table: "chunks", column: "text", command: "'token-goat reclaim-index --rebuild' drops and re-derives them" },
|
|
7574
|
+
{ name: "stats detail", table: "stats", column: "detail", command: "ages out on its own (180-day retention) -- no manual command needed" }
|
|
7575
|
+
];
|
|
7576
|
+
var VECTOR_BYTES_PER_ROW = 384 * 4;
|
|
7577
|
+
function dbCategoryBreakdown(dbPath) {
|
|
7578
|
+
const db = getDb(dbPath);
|
|
7579
|
+
const shares = [];
|
|
7580
|
+
for (const cat of CATEGORY_COLUMNS) {
|
|
7581
|
+
try {
|
|
7582
|
+
const row = db.prepare(`SELECT COALESCE(SUM(LENGTH(${cat.column})), 0) as bytes FROM ${cat.table}`).get();
|
|
7583
|
+
if (row.bytes > 0) shares.push({ name: cat.name, bytes: row.bytes, command: cat.command });
|
|
7584
|
+
} catch {
|
|
7585
|
+
}
|
|
7586
|
+
}
|
|
7587
|
+
try {
|
|
7588
|
+
const row = db.prepare("SELECT COUNT(*) as n FROM chunk_vectors").get();
|
|
7589
|
+
if (row.n > 0) {
|
|
7590
|
+
shares.push({
|
|
7591
|
+
name: "embedding vectors",
|
|
7592
|
+
bytes: row.n * VECTOR_BYTES_PER_ROW,
|
|
7593
|
+
command: "'token-goat reclaim-index --rebuild' drops them (set indexing.embeddings_enabled = false first so they don't regrow)"
|
|
7594
|
+
});
|
|
7595
|
+
}
|
|
7596
|
+
} catch {
|
|
7597
|
+
}
|
|
7598
|
+
return shares.sort((a, b) => b.bytes - a.bytes);
|
|
7599
|
+
}
|
|
7600
|
+
function oversizeDbMessage(dbPath, sizeBytes, freeBytes, tempRows, categories = [], topConsumers = [], thresholdMb = 1500, autoReclaimEmbeddings = false) {
|
|
7295
7601
|
const mb = (bytes) => Math.round(bytes / (1024 * 1024));
|
|
7296
7602
|
const advice = [];
|
|
7297
7603
|
if (freeBytes >= sizeBytes / 10) advice.push(`'token-goat reclaim-index' returns the ${mb(freeBytes)} MB of it that is free pages`);
|
|
7298
7604
|
if (tempRows > 0) advice.push(`'token-goat project prune' removes ${countNoun(tempRows, "scratch file")} indexed under the OS temp dir`);
|
|
7299
|
-
|
|
7300
|
-
|
|
7301
|
-
|
|
7605
|
+
if (autoReclaimEmbeddings) {
|
|
7606
|
+
advice.push(`'token-goat doctor --repair' will automatically reclaim embedding vectors and compact global.db`);
|
|
7607
|
+
}
|
|
7608
|
+
const head = `global.db is ${mb(sizeBytes)} MB at ${displaySafeText(dbPath)} (larger than threshold of ${thresholdMb} MB). `;
|
|
7609
|
+
const totalCategoryBytes = categories.reduce((sum, c) => sum + c.bytes, 0);
|
|
7610
|
+
const breakdown = totalCategoryBytes > 0 ? ` Where it went: ${categories.slice(0, 3).map((c) => `${c.name} ${mb(c.bytes)} MB (${Math.round(c.bytes / totalCategoryBytes * 100)}%) -- ${c.command}`).join("; ")}.` : "";
|
|
7611
|
+
let base = advice.length > 0 ? `${head}${advice.join("; ")}.${breakdown}` : `${head}Only ${mb(freeBytes)} MB of it is free pages and none of it is temp-dir scratch, so it is live index data that neither 'reclaim-index' nor 'project prune' will shrink.${breakdown}`;
|
|
7612
|
+
if (topConsumers.length > 0) {
|
|
7613
|
+
const list = topConsumers.map((c) => `${path17.basename(c.root) || c.root} (${countNoun(c.fileCount, "file")})`).join(", ");
|
|
7614
|
+
base += ` Top index consumers: ${list}.`;
|
|
7615
|
+
}
|
|
7616
|
+
return base;
|
|
7302
7617
|
}
|
|
7303
|
-
function checkDbExists(dataDir2) {
|
|
7618
|
+
function checkDbExists(dataDir2, maxDbSizeMb) {
|
|
7304
7619
|
const dbPath = path17.join(dataDir2, "global.db");
|
|
7305
7620
|
if (!fs18.existsSync(dbPath)) {
|
|
7306
7621
|
return {
|
|
@@ -7309,7 +7624,7 @@ function checkDbExists(dataDir2) {
|
|
|
7309
7624
|
message: `global.db not found at ${dbPath}`
|
|
7310
7625
|
};
|
|
7311
7626
|
}
|
|
7312
|
-
const sizeBytes =
|
|
7627
|
+
const sizeBytes = indexSizeBytes(dbPath);
|
|
7313
7628
|
const SQLITE_HEADER = "SQLite format 3\0";
|
|
7314
7629
|
let header = "";
|
|
7315
7630
|
let headerBytes = Buffer.alloc(0);
|
|
@@ -7332,13 +7647,35 @@ function checkDbExists(dataDir2) {
|
|
|
7332
7647
|
message: `global.db at ${dbPath} is not a valid SQLite file (${sizeBytes} bytes) \u2014 likely truncated or corrupt`
|
|
7333
7648
|
};
|
|
7334
7649
|
}
|
|
7335
|
-
|
|
7650
|
+
const cfg = loadConfig();
|
|
7651
|
+
const thresholdMb = maxDbSizeMb ?? cfg.indexing.max_db_size_mb ?? 1500;
|
|
7652
|
+
const warnBytes = thresholdMb * 1024 * 1024;
|
|
7653
|
+
if (sizeBytes > warnBytes) {
|
|
7336
7654
|
let tempRows = 0;
|
|
7337
7655
|
try {
|
|
7338
7656
|
tempRows = findSystemTempFiles(dbPath).length;
|
|
7339
7657
|
} catch {
|
|
7340
7658
|
}
|
|
7341
|
-
|
|
7659
|
+
let categories = [];
|
|
7660
|
+
try {
|
|
7661
|
+
categories = dbCategoryBreakdown(dbPath);
|
|
7662
|
+
} catch {
|
|
7663
|
+
}
|
|
7664
|
+
const topConsumers = findTopIndexedProjects(dbPath, 3);
|
|
7665
|
+
return {
|
|
7666
|
+
name: "Database",
|
|
7667
|
+
status: "warn",
|
|
7668
|
+
message: oversizeDbMessage(
|
|
7669
|
+
dbPath,
|
|
7670
|
+
sizeBytes,
|
|
7671
|
+
freelistBytes(headerBytes),
|
|
7672
|
+
tempRows,
|
|
7673
|
+
categories,
|
|
7674
|
+
topConsumers,
|
|
7675
|
+
thresholdMb,
|
|
7676
|
+
cfg.indexing.auto_reclaim_embeddings
|
|
7677
|
+
)
|
|
7678
|
+
};
|
|
7342
7679
|
}
|
|
7343
7680
|
return {
|
|
7344
7681
|
name: "Database",
|
|
@@ -7421,7 +7758,7 @@ function checkParserFreshness(dbPath, rootDir) {
|
|
|
7421
7758
|
return { name: "Parser freshness", status: "ok", message: "no database yet" };
|
|
7422
7759
|
}
|
|
7423
7760
|
try {
|
|
7424
|
-
const { indexedFiles, currentFiles } = getParserFreshness(dbPath,
|
|
7761
|
+
const { indexedFiles, currentFiles } = getParserFreshness(dbPath, parserFingerprintForLanguage, rootDir);
|
|
7425
7762
|
if (indexedFiles === 0) {
|
|
7426
7763
|
return { name: "Parser freshness", status: "ok", message: "no indexed files yet" };
|
|
7427
7764
|
}
|
|
@@ -7484,7 +7821,7 @@ function checkInstall() {
|
|
|
7484
7821
|
if (resolved !== null) {
|
|
7485
7822
|
const isBatch = /\.(?:cmd|bat)$/i.test(resolved);
|
|
7486
7823
|
const comspec = path17.join(process.env["SystemRoot"] ?? process.env["windir"] ?? "C:\\Windows", "System32", "cmd.exe");
|
|
7487
|
-
const result = isBatch ?
|
|
7824
|
+
const result = isBatch ? spawnSync3(fs18.existsSync(comspec) ? comspec : "cmd.exe", ["/d", "/s", "/c", resolved, "--version"], { encoding: "utf-8", timeout: 15e3, windowsHide: true }) : spawnSync3(resolved, ["--version"], { encoding: "utf-8", timeout: 15e3, windowsHide: true });
|
|
7488
7825
|
if (result.status === 0) return { name: "Installation", status: "ok", message: (result.stdout ?? "").trim() };
|
|
7489
7826
|
}
|
|
7490
7827
|
return { name: "Installation", status: "fail", message: `token-goat command not found; run: npm install -g ${PACKAGE_NAME}` };
|
|
@@ -7633,7 +7970,7 @@ function checkDiskSpace(dataDir2) {
|
|
|
7633
7970
|
}
|
|
7634
7971
|
if (process.platform !== "win32") {
|
|
7635
7972
|
try {
|
|
7636
|
-
const result =
|
|
7973
|
+
const result = spawnSync3("df", ["-Pk", dataDir2], { encoding: "utf-8" });
|
|
7637
7974
|
const stdout = typeof result.stdout === "string" ? result.stdout : "";
|
|
7638
7975
|
if (result.error === void 0 && result.status === 0 && stdout) {
|
|
7639
7976
|
const lines = stdout.trim().split("\n");
|
|
@@ -7689,7 +8026,7 @@ function checkCopilotCli(configPath2, scriptPath) {
|
|
|
7689
8026
|
toolName: "view",
|
|
7690
8027
|
toolArgs: { path: "doctor-check.txt" }
|
|
7691
8028
|
});
|
|
7692
|
-
const res =
|
|
8029
|
+
const res = spawnSync3(preToolUseCommand, {
|
|
7693
8030
|
input: synthetic,
|
|
7694
8031
|
encoding: "utf-8",
|
|
7695
8032
|
shell: true,
|
|
@@ -7724,6 +8061,7 @@ function checkCopilotCli(configPath2, scriptPath) {
|
|
|
7724
8061
|
var COMPACTION_CHANNEL_WINDOW = 5;
|
|
7725
8062
|
var COMPACTION_STATS_BATCH_SIZE = 50;
|
|
7726
8063
|
var COMPACTION_STATS_SCAN_CEILING = 2e3;
|
|
8064
|
+
var COMPACTION_MANIFEST_FIX_VERSION = "2.9.18";
|
|
7727
8065
|
function checkCompactionChannel(dbPath) {
|
|
7728
8066
|
const name = "Compaction channel";
|
|
7729
8067
|
if (!fs18.existsSync(dbPath)) {
|
|
@@ -7738,8 +8076,9 @@ function checkCompactionChannel(dbPath) {
|
|
|
7738
8076
|
const conclusive = [];
|
|
7739
8077
|
let offset = 0;
|
|
7740
8078
|
let scanned = 0;
|
|
8079
|
+
let preFixConclusive = 0;
|
|
7741
8080
|
for (; ; ) {
|
|
7742
|
-
const batch = db.prepare("SELECT detail FROM stats WHERE kind = 'compact_summary' ORDER BY rowid DESC LIMIT ? OFFSET ?").all(COMPACTION_STATS_BATCH_SIZE, offset);
|
|
8081
|
+
const batch = db.prepare("SELECT detail, tg_version FROM stats WHERE kind = 'compact_summary' ORDER BY rowid DESC LIMIT ? OFFSET ?").all(COMPACTION_STATS_BATCH_SIZE, offset);
|
|
7743
8082
|
if (batch.length === 0) break;
|
|
7744
8083
|
offset += batch.length;
|
|
7745
8084
|
scanned += batch.length;
|
|
@@ -7748,6 +8087,10 @@ function checkCompactionChannel(dbPath) {
|
|
|
7748
8087
|
if (m === null) continue;
|
|
7749
8088
|
const sampled = Number(m[2]);
|
|
7750
8089
|
if (sampled === 0) continue;
|
|
8090
|
+
if (row.tg_version === null || compareSemver(row.tg_version, COMPACTION_MANIFEST_FIX_VERSION) < 0) {
|
|
8091
|
+
preFixConclusive++;
|
|
8092
|
+
continue;
|
|
8093
|
+
}
|
|
7751
8094
|
conclusive.push({ survived: Number(m[1]), sampled });
|
|
7752
8095
|
if (conclusive.length >= COMPACTION_CHANNEL_WINDOW) break;
|
|
7753
8096
|
}
|
|
@@ -7755,6 +8098,9 @@ function checkCompactionChannel(dbPath) {
|
|
|
7755
8098
|
if (scanned >= COMPACTION_STATS_SCAN_CEILING) break;
|
|
7756
8099
|
}
|
|
7757
8100
|
if (conclusive.length === 0) {
|
|
8101
|
+
if (preFixConclusive > 0) {
|
|
8102
|
+
return { name, status: "ok", message: `no post-fix compaction evidence yet -- ${preFixConclusive} compaction(s) found predate ${COMPACTION_MANIFEST_FIX_VERSION}'s manifest-survival fix and are not counted` };
|
|
8103
|
+
}
|
|
7758
8104
|
return { name, status: "ok", message: "no compaction has been measured yet" };
|
|
7759
8105
|
}
|
|
7760
8106
|
const dead = conclusive.filter((c) => c.survived === 0).length;
|
|
@@ -7772,20 +8118,58 @@ function checkCompactionChannel(dbPath) {
|
|
|
7772
8118
|
return { name, status: "warn", message: `could not read compaction stats: ${extractErrorMessage(e)}` };
|
|
7773
8119
|
}
|
|
7774
8120
|
}
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
const name = "Tool names";
|
|
8121
|
+
function checkHookLatency(dbPath) {
|
|
8122
|
+
const name = "Hook latency";
|
|
7778
8123
|
if (!fs18.existsSync(dbPath)) {
|
|
7779
8124
|
return { name, status: "ok", message: "no database yet" };
|
|
7780
8125
|
}
|
|
7781
8126
|
try {
|
|
7782
|
-
const
|
|
8127
|
+
const db = getDb(dbPath);
|
|
8128
|
+
const present = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'stats'").get();
|
|
8129
|
+
if (present === void 0) {
|
|
8130
|
+
return { name, status: "ok", message: "no hooks measured yet" };
|
|
8131
|
+
}
|
|
8132
|
+
const rows = hookLatencyBreakdown(db);
|
|
7783
8133
|
if (rows.length === 0) {
|
|
7784
|
-
return { name, status: "ok", message: "
|
|
8134
|
+
return { name, status: "ok", message: "no hook latency recorded yet (or this database predates the duration_ms column)" };
|
|
7785
8135
|
}
|
|
7786
|
-
const
|
|
7787
|
-
|
|
7788
|
-
)
|
|
8136
|
+
const worst = rows[0];
|
|
8137
|
+
const totalCount = rows.reduce((n, r) => n + r.count, 0);
|
|
8138
|
+
const worstLabel = `${worst.event} (${worst.harness || "unrecorded harness"})`;
|
|
8139
|
+
const budgetMs = loadConfig().hooks.latency_budget_ms;
|
|
8140
|
+
if (worst.p95_ms > budgetMs) {
|
|
8141
|
+
return {
|
|
8142
|
+
name,
|
|
8143
|
+
status: "warn",
|
|
8144
|
+
message: `${worstLabel} is running a p95 of ${worst.p95_ms}ms across ${totalCount} recent hook(s); see 'token-goat stats --hooks' for the full breakdown`
|
|
8145
|
+
};
|
|
8146
|
+
}
|
|
8147
|
+
return { name, status: "ok", message: `worst p95 ${worst.p95_ms}ms (${worstLabel}) across ${totalCount} recent hook(s)` };
|
|
8148
|
+
} catch (e) {
|
|
8149
|
+
return { name, status: "warn", message: `could not read hook latency stats: ${extractErrorMessage(e)}` };
|
|
8150
|
+
}
|
|
8151
|
+
}
|
|
8152
|
+
var UNMAPPED_TOOL_SAMPLE = 5;
|
|
8153
|
+
var UNMAPPED_TOOL_MAX_AGE_DAYS = 7;
|
|
8154
|
+
function checkUnmappedTools(dbPath, options) {
|
|
8155
|
+
const name = "Tool names";
|
|
8156
|
+
if (!fs18.existsSync(dbPath)) {
|
|
8157
|
+
return { name, status: "ok", message: "no database yet" };
|
|
8158
|
+
}
|
|
8159
|
+
try {
|
|
8160
|
+
pruneStalePatternCoveredUnmappedTools(getDb(dbPath), [MCP_TOOL_PATTERN]);
|
|
8161
|
+
const rows = readUnmappedTools(dbPath);
|
|
8162
|
+
if (rows.length === 0) {
|
|
8163
|
+
return { name, status: "ok", message: "every tool name seen so far reached a handler that wanted it" };
|
|
8164
|
+
}
|
|
8165
|
+
const maxAgeDays = options?.maxAgeDays ?? UNMAPPED_TOOL_MAX_AGE_DAYS;
|
|
8166
|
+
const nowSecs = options?.nowSecs ?? Math.floor(Date.now() / 1e3);
|
|
8167
|
+
const maxAgeSecs = maxAgeDays * 86400;
|
|
8168
|
+
const nearMisses = rows.filter((r) => {
|
|
8169
|
+
if (r.near_miss === null || r.near_miss === void 0 || r.near_miss === r.tool_name) return false;
|
|
8170
|
+
if (r.last_seen > 0 && nowSecs - r.last_seen > maxAgeSecs) return false;
|
|
8171
|
+
return true;
|
|
8172
|
+
});
|
|
7789
8173
|
if (nearMisses.length > 0) {
|
|
7790
8174
|
const shown2 = nearMisses.slice(0, UNMAPPED_TOOL_SAMPLE).map((r) => `${r.harness} sent "${r.tool_name}" where "${r.near_miss}" is handled (${r.event_name}, ${r.hits}x)`);
|
|
7791
8175
|
const more2 = nearMisses.length > UNMAPPED_TOOL_SAMPLE ? ` (+${nearMisses.length - UNMAPPED_TOOL_SAMPLE} more)` : "";
|
|
@@ -7817,11 +8201,13 @@ function runDoctor(dataDir2, configPath2, rootDir, processes) {
|
|
|
7817
8201
|
results.push(checkTreeSitter());
|
|
7818
8202
|
results.push(checkStrayClaudeMdBlocks());
|
|
7819
8203
|
results.push(checkWorkerRunning(actualDataDir) ? { name: "Worker", status: "ok", message: "running" } : { name: "Worker", status: "warn", message: "not running" });
|
|
7820
|
-
|
|
8204
|
+
const cfg = loadConfig(rootDir);
|
|
8205
|
+
results.push(checkDbExists(actualDataDir, cfg.indexing.max_db_size_mb));
|
|
7821
8206
|
results.push(checkSymbolBodySize(path17.join(actualDataDir, "global.db")));
|
|
7822
8207
|
results.push(checkSymbolCount(path17.join(actualDataDir, "global.db"), rootDir));
|
|
7823
8208
|
results.push(checkDirtyQueueHealth(actualDataDir));
|
|
7824
8209
|
results.push(checkCompactionChannel(path17.join(actualDataDir, "global.db")));
|
|
8210
|
+
results.push(checkHookLatency(path17.join(actualDataDir, "global.db")));
|
|
7825
8211
|
results.push(checkUnmappedTools(path17.join(actualDataDir, "global.db")));
|
|
7826
8212
|
const actualConfigPath = configPath2 || configPath();
|
|
7827
8213
|
results.push(checkConfigValid(actualConfigPath));
|
|
@@ -7841,6 +8227,8 @@ function runDoctor(dataDir2, configPath2, rootDir, processes) {
|
|
|
7841
8227
|
if (vscodeHooksResult) results.push(vscodeHooksResult);
|
|
7842
8228
|
const vscodeScopeResult = checkVscodeUserScopeHooks(vscodeHooksInstalled(), vscodeHooksInstalled({ project: true }));
|
|
7843
8229
|
if (vscodeScopeResult) results.push(vscodeScopeResult);
|
|
8230
|
+
const vscodeProjectMcpResult = checkVscodeProjectMcp(rootDir);
|
|
8231
|
+
if (vscodeProjectMcpResult) results.push(vscodeProjectMcpResult);
|
|
7844
8232
|
const visualStudioResult = checkVisualStudio([visualStudioUserMcpPath(), visualStudioProjectMcpPath()], [visualStudioSolutionVscodeMcpPath()]);
|
|
7845
8233
|
if (visualStudioResult) results.push(visualStudioResult);
|
|
7846
8234
|
const zedResult = checkZed(zedSettingsPath());
|
|
@@ -7897,6 +8285,31 @@ async function runDoctorRepair(opts) {
|
|
|
7897
8285
|
errors.push(`Failed to download embedding model: ${extractErrorMessage(e)}`);
|
|
7898
8286
|
}
|
|
7899
8287
|
}
|
|
8288
|
+
const rootDir = path17.resolve(opts?.rootDir ?? process.cwd());
|
|
8289
|
+
const projectMcp = path17.join(rootDir, ".vscode", "mcp.json");
|
|
8290
|
+
try {
|
|
8291
|
+
if (cleanupDeprecatedVscodeProjectMcp(rootDir)) {
|
|
8292
|
+
repairs.push(`Removed empty deprecated VS Code MCP residue file at ${displaySafeText(projectMcp)}`);
|
|
8293
|
+
}
|
|
8294
|
+
} catch (e) {
|
|
8295
|
+
errors.push(`Failed to clean up ${displaySafeText(projectMcp)}: ${extractErrorMessage(e)}`);
|
|
8296
|
+
}
|
|
8297
|
+
const actualDataDir = opts?.dataDir ?? dataDir();
|
|
8298
|
+
const dbPath = path17.join(actualDataDir, "global.db");
|
|
8299
|
+
if (cfg.indexing.auto_reclaim_embeddings && fs18.existsSync(dbPath)) {
|
|
8300
|
+
try {
|
|
8301
|
+
const sizeBytes = indexSizeBytes(dbPath);
|
|
8302
|
+
const thresholdMb = cfg.indexing.max_db_size_mb ?? 1500;
|
|
8303
|
+
const maxBytes = thresholdMb * 1024 * 1024;
|
|
8304
|
+
if (sizeBytes > maxBytes) {
|
|
8305
|
+
const res = reclaimIndex(dbPath, { embeddingsOnly: true });
|
|
8306
|
+
const savedMb = Math.round((res.beforeBytes - res.afterBytes) / (1024 * 1024));
|
|
8307
|
+
repairs.push(`Auto-reclaimed embeddings and compacted global.db (${savedMb > 0 ? `reclaimed ${savedMb} MB` : "compacted"})`);
|
|
8308
|
+
}
|
|
8309
|
+
} catch (e) {
|
|
8310
|
+
errors.push(`Failed to auto-reclaim embeddings from global.db: ${extractErrorMessage(e)}`);
|
|
8311
|
+
}
|
|
8312
|
+
}
|
|
7900
8313
|
return { repairs, errors };
|
|
7901
8314
|
}
|
|
7902
8315
|
function printDoctorResults(results) {
|
|
@@ -8058,7 +8471,7 @@ async function runDoctorAndExit(opts) {
|
|
|
8058
8471
|
}
|
|
8059
8472
|
}
|
|
8060
8473
|
try {
|
|
8061
|
-
const { checkUpdateStatus } = await import("./token-goat-chunk-
|
|
8474
|
+
const { checkUpdateStatus } = await import("./token-goat-chunk-AU2K2QHR.mjs");
|
|
8062
8475
|
const update = await checkUpdateStatus(1500);
|
|
8063
8476
|
if (update.updateAvailable && update.latest) {
|
|
8064
8477
|
console.log(`
|
|
@@ -8150,6 +8563,7 @@ function generateCompactHelp() {
|
|
|
8150
8563
|
"Options:",
|
|
8151
8564
|
" -v, --version print the token-goat version",
|
|
8152
8565
|
" --cwd <path> run as if invoked from this directory",
|
|
8566
|
+
" --notice <text> print this line before the command's own output",
|
|
8153
8567
|
" -h, --help display help for command",
|
|
8154
8568
|
"",
|
|
8155
8569
|
"Reads: symbol, read, brief, section, semantic, skeleton, outline, refs, scope,",
|
|
@@ -8164,7 +8578,7 @@ function generateCompactHelp() {
|
|
|
8164
8578
|
" html-lint, docx-outline, docx-tables, docx-text, pptx-outline, pptx-slide, pptx-text,",
|
|
8165
8579
|
" pptx-notes",
|
|
8166
8580
|
"",
|
|
8167
|
-
"Index & Search: index, map, reconcile, doctor, commands, ask, pack, tokens,",
|
|
8581
|
+
"Index & Search: index, map, reconcile, doctor, commands, ask, answer, pack, tokens,",
|
|
8168
8582
|
" budget, failures, todo, trace, logfold, lockdeps, dep-docs, recent, hot,",
|
|
8169
8583
|
" snapshot-snapshot, baseline, cost, coverage-report-gaps, clean-cache,",
|
|
8170
8584
|
" cache-audit, reclaim-index, project",
|
|
@@ -8206,7 +8620,7 @@ init_define_import_meta_env();
|
|
|
8206
8620
|
// src/config_commands.ts
|
|
8207
8621
|
init_define_import_meta_env();
|
|
8208
8622
|
import * as fs20 from "node:fs";
|
|
8209
|
-
import * as
|
|
8623
|
+
import * as os14 from "node:os";
|
|
8210
8624
|
import * as path18 from "node:path";
|
|
8211
8625
|
|
|
8212
8626
|
// src/webfetch.ts
|
|
@@ -8215,7 +8629,7 @@ import { existsSync as existsSync14, readdirSync as readdirSync3, statSync as st
|
|
|
8215
8629
|
import * as http from "http";
|
|
8216
8630
|
import * as https from "https";
|
|
8217
8631
|
import { isIPv4, isIPv6 } from "net";
|
|
8218
|
-
import { resolve as
|
|
8632
|
+
import { resolve as resolve7, join as join16 } from "path";
|
|
8219
8633
|
import { URL as URL2 } from "url";
|
|
8220
8634
|
import { promisify } from "util";
|
|
8221
8635
|
import { lookup as dnsLookup } from "dns";
|
|
@@ -8356,7 +8770,7 @@ function cleanupStaleDownloads() {
|
|
|
8356
8770
|
const files = readdirSync3(cacheDir);
|
|
8357
8771
|
for (const file of files) {
|
|
8358
8772
|
if (file.endsWith(".tmp")) {
|
|
8359
|
-
const filePath =
|
|
8773
|
+
const filePath = resolve7(cacheDir, file);
|
|
8360
8774
|
try {
|
|
8361
8775
|
const stat = statSync3(filePath);
|
|
8362
8776
|
if (Date.now() - stat.mtimeMs < STALE_DOWNLOAD_AGE_MS) continue;
|
|
@@ -8492,13 +8906,6 @@ function performHttpFetch(targetUrl, opts) {
|
|
|
8492
8906
|
}
|
|
8493
8907
|
|
|
8494
8908
|
// src/config_commands.ts
|
|
8495
|
-
function emit(text) {
|
|
8496
|
-
const payload = colorStdout() ? text : stripAnsi(text);
|
|
8497
|
-
process.stdout.write(ensureNewline(payload));
|
|
8498
|
-
}
|
|
8499
|
-
function emitErr(text) {
|
|
8500
|
-
process.stderr.write(ensureNewline(text));
|
|
8501
|
-
}
|
|
8502
8909
|
function saveConfigSafe(cfg) {
|
|
8503
8910
|
ensureDirSync(path18.dirname(configPath()));
|
|
8504
8911
|
saveConfig(cfg);
|
|
@@ -8948,13 +9355,15 @@ function cmdProject(opts) {
|
|
|
8948
9355
|
invalidateConfigCache();
|
|
8949
9356
|
const prunedTempFiles = pruneSystemTempFiles();
|
|
8950
9357
|
const prunedOrphanChunks = pruneOrphanedChunks();
|
|
9358
|
+
const prunedOrphanVectors = pruneOrphanedVectors();
|
|
8951
9359
|
if (opts.json === true) {
|
|
8952
|
-
emit(displaySafeJson({ pruned: removed, blocked_roots: after, prunedTempFiles: prunedTempFiles.length, prunedOrphanChunkFiles: prunedOrphanChunks.length }));
|
|
9360
|
+
emit(displaySafeJson({ pruned: removed, blocked_roots: after, prunedTempFiles: prunedTempFiles.length, prunedOrphanChunkFiles: prunedOrphanChunks.length, prunedOrphanVectors }));
|
|
8953
9361
|
return;
|
|
8954
9362
|
}
|
|
8955
9363
|
emit(`Pruned ${removed} stale root(s). Remaining: ${after.length}`);
|
|
8956
9364
|
emit(`Pruned ${prunedTempFiles.length} stale indexed temp-dir file(s).`);
|
|
8957
9365
|
emit(`Pruned orphaned embedding chunks for ${prunedOrphanChunks.length} file(s).`);
|
|
9366
|
+
emit(`Pruned ${prunedOrphanVectors} orphaned embedding vector(s).`);
|
|
8958
9367
|
return;
|
|
8959
9368
|
}
|
|
8960
9369
|
emitErr(`project: unknown action '${action}'. Use list, exclude, or prune.`);
|
|
@@ -9072,7 +9481,7 @@ async function cmdFetchImage(opts) {
|
|
|
9072
9481
|
throw new Error(`fetch failed: ${opts.url}`, { cause: e });
|
|
9073
9482
|
}
|
|
9074
9483
|
const buf = fetched.body;
|
|
9075
|
-
const outPath = opts.out ?? path18.join(
|
|
9484
|
+
const outPath = opts.out ?? path18.join(os14.tmpdir(), `tg-fetch-${Date.now()}${extensionForContentType(fetched.contentType)}`);
|
|
9076
9485
|
const originalBytes = buf.length;
|
|
9077
9486
|
let shrunkBytes;
|
|
9078
9487
|
let outData;
|
|
@@ -11852,7 +12261,7 @@ function extractGeneric(lines) {
|
|
|
11852
12261
|
return result;
|
|
11853
12262
|
}
|
|
11854
12263
|
function extractFailures(text, options) {
|
|
11855
|
-
const cleaned =
|
|
12264
|
+
const cleaned = stripAnsiEscapes(text);
|
|
11856
12265
|
const lines = cleaned.split("\n");
|
|
11857
12266
|
const detected = options?.runner ?? detectRunner(cleaned);
|
|
11858
12267
|
if (detected === "pytest") {
|
|
@@ -12027,7 +12436,8 @@ function cmdConflicts(targetPath, opts) {
|
|
|
12027
12436
|
process.exitCode = runConflicts({
|
|
12028
12437
|
...targetPath !== void 0 ? { path: targetPath } : {},
|
|
12029
12438
|
...opts.json === true ? { json: true } : {},
|
|
12030
|
-
...opts.summary === true ? { summary: true } : {}
|
|
12439
|
+
...opts.summary === true ? { summary: true } : {},
|
|
12440
|
+
...opts.context !== void 0 ? { context: parseInt(opts.context, 10) } : {}
|
|
12031
12441
|
});
|
|
12032
12442
|
}
|
|
12033
12443
|
async function cmdScreenshot(url, destPath, opts) {
|
|
@@ -12075,7 +12485,7 @@ function cmdPack(patterns, opts) {
|
|
|
12075
12485
|
}
|
|
12076
12486
|
const result = expandedList.length > 0 ? collectFiles(root, expandedList, collectOpts) : collectFromStdin(root, collectOpts);
|
|
12077
12487
|
if (opts.budget !== void 0) {
|
|
12078
|
-
const budgetN =
|
|
12488
|
+
const budgetN = requireInt("--budget", opts.budget);
|
|
12079
12489
|
if (result.total_tokens > budgetN) {
|
|
12080
12490
|
err(`token-goat: pack: token count ${result.total_tokens} exceeds budget ${budgetN}`);
|
|
12081
12491
|
process.exitCode = 3;
|
|
@@ -12112,7 +12522,7 @@ function cmdTokens(patterns, opts) {
|
|
|
12112
12522
|
let entries = [...result.entries];
|
|
12113
12523
|
if (opts.asc === true) entries.reverse();
|
|
12114
12524
|
const eligibleCount = entries.length;
|
|
12115
|
-
if (opts.top !== void 0) entries = entries.slice(0,
|
|
12525
|
+
if (opts.top !== void 0) entries = entries.slice(0, requireNonNegativeInt("--top", opts.top));
|
|
12116
12526
|
const truncated = entries.length < eligibleCount;
|
|
12117
12527
|
if (opts.json === true) {
|
|
12118
12528
|
out(displaySafeJson({ entries, truncated, totalCount: eligibleCount, total_tokens: result.total_tokens, total_lines: result.total_lines }));
|
|
@@ -12160,7 +12570,7 @@ function cmdBudget(patterns, opts) {
|
|
|
12160
12570
|
if (opts.json === true) {
|
|
12161
12571
|
out(displaySafeJson(result));
|
|
12162
12572
|
} else {
|
|
12163
|
-
const contextK = opts.context !== void 0 ?
|
|
12573
|
+
const contextK = opts.context !== void 0 ? requirePositiveInt("--context", opts.context) : Math.round(loadConfig().context.model_window_tokens / 1e3);
|
|
12164
12574
|
out(formatBudgetText(result, contextK));
|
|
12165
12575
|
}
|
|
12166
12576
|
}
|
|
@@ -12190,7 +12600,7 @@ import * as fs26 from "node:fs";
|
|
|
12190
12600
|
// src/sharepoint_resolve.ts
|
|
12191
12601
|
init_define_import_meta_env();
|
|
12192
12602
|
import * as fs24 from "node:fs";
|
|
12193
|
-
import * as
|
|
12603
|
+
import * as os15 from "node:os";
|
|
12194
12604
|
import * as path23 from "node:path";
|
|
12195
12605
|
var TENANT_HOST_RE = /^([a-z0-9-]+?)(-my)?\.sharepoint\.com$/i;
|
|
12196
12606
|
function parseShareUrl(url) {
|
|
@@ -12263,7 +12673,7 @@ function tryLibraryPaths(root, libSegments, triedPaths) {
|
|
|
12263
12673
|
}
|
|
12264
12674
|
return null;
|
|
12265
12675
|
}
|
|
12266
|
-
function resolveLocalPath(parsed, env = process.env, home =
|
|
12676
|
+
function resolveLocalPath(parsed, env = process.env, home = os15.homedir()) {
|
|
12267
12677
|
const roots = candidateRoots(env, home);
|
|
12268
12678
|
const libSegments = parsed.libraryPath.split(/[/\\]+/).filter((s) => s.length > 0 && s !== "." && s !== "..");
|
|
12269
12679
|
const triedPaths = [];
|
|
@@ -12423,12 +12833,12 @@ function formatCues(cues) {
|
|
|
12423
12833
|
|
|
12424
12834
|
// src/video_chapters.ts
|
|
12425
12835
|
init_define_import_meta_env();
|
|
12426
|
-
import { spawnSync as
|
|
12836
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
12427
12837
|
var _ffprobeAvailable;
|
|
12428
12838
|
function isFfprobeAvailable() {
|
|
12429
12839
|
if (_ffprobeAvailable !== void 0) return _ffprobeAvailable;
|
|
12430
12840
|
try {
|
|
12431
|
-
const res =
|
|
12841
|
+
const res = spawnSync4("ffprobe", ["-version"], { stdio: "ignore" });
|
|
12432
12842
|
_ffprobeAvailable = res.status === 0;
|
|
12433
12843
|
} catch {
|
|
12434
12844
|
_ffprobeAvailable = false;
|
|
@@ -12439,7 +12849,7 @@ function extractVideoChapters(file) {
|
|
|
12439
12849
|
if (!isFfprobeAvailable()) {
|
|
12440
12850
|
throw new Error("ffprobe not found on PATH; video-chapters requires ffmpeg (install ffmpeg and ensure ffprobe is on PATH)");
|
|
12441
12851
|
}
|
|
12442
|
-
const res =
|
|
12852
|
+
const res = spawnSync4(
|
|
12443
12853
|
"ffprobe",
|
|
12444
12854
|
["-v", "error", "-print_format", "json", "-show_chapters", "-show_streams", file],
|
|
12445
12855
|
{ encoding: "utf8", maxBuffer: 64 * 1024 * 1024 }
|
|
@@ -12507,8 +12917,8 @@ async function cmdPdfLocate(file, pattern, opts) {
|
|
|
12507
12917
|
const locateOpts = {
|
|
12508
12918
|
ignoreCase: opts.ignoreCase === true
|
|
12509
12919
|
};
|
|
12510
|
-
if (opts.maxMatches !== void 0) locateOpts.maxMatches =
|
|
12511
|
-
if (opts.context !== void 0) locateOpts.context =
|
|
12920
|
+
if (opts.maxMatches !== void 0) locateOpts.maxMatches = requirePositiveInt("--max-matches", opts.maxMatches);
|
|
12921
|
+
if (opts.context !== void 0) locateOpts.context = requirePositiveInt("--context", opts.context);
|
|
12512
12922
|
if (opts.pages !== void 0) locateOpts.pages = opts.pages;
|
|
12513
12923
|
const { matches: matches2, truncated } = await runPdfLocate(file, pattern, locateOpts);
|
|
12514
12924
|
const pages = matches2.map((m) => m.page);
|
|
@@ -12618,14 +13028,14 @@ async function cmdXlsxSheets(file, opts = {}) {
|
|
|
12618
13028
|
recordXlsxStat("xlsx_sheets", file, text);
|
|
12619
13029
|
}
|
|
12620
13030
|
async function cmdXlsxHead(file, opts) {
|
|
12621
|
-
const rows = opts.rows !== void 0 ?
|
|
13031
|
+
const rows = opts.rows !== void 0 ? requireNonNegativeInt("--rows", opts.rows) : 20;
|
|
12622
13032
|
const columns = opts.columns ? opts.columns.split(",").map((c) => c.trim()).filter(Boolean) : void 0;
|
|
12623
13033
|
const text = fenceFileText(await headSheet(file, opts.sheet, rows, columns));
|
|
12624
13034
|
out(text);
|
|
12625
13035
|
recordXlsxStat("xlsx_head", file, text);
|
|
12626
13036
|
}
|
|
12627
13037
|
async function cmdXlsxColumns(file, opts = {}) {
|
|
12628
|
-
const maxRows = opts.head !== void 0 ?
|
|
13038
|
+
const maxRows = opts.head !== void 0 ? requireNonNegativeInt("--head", opts.head) : 100;
|
|
12629
13039
|
const result = await xlsxColumns(file, opts.sheet, maxRows);
|
|
12630
13040
|
if (opts.json === true) {
|
|
12631
13041
|
const text = displaySafeJson({
|
|
@@ -12653,7 +13063,7 @@ async function cmdXlsxQuery(file, opts) {
|
|
|
12653
13063
|
const result = await querySheet(file, opts.sheet, {
|
|
12654
13064
|
...columns !== void 0 ? { columns } : {},
|
|
12655
13065
|
...wheres !== void 0 ? { wheres } : {},
|
|
12656
|
-
...opts.head !== void 0 ? { head:
|
|
13066
|
+
...opts.head !== void 0 ? { head: requireNonNegativeInt("--head", opts.head) } : {}
|
|
12657
13067
|
});
|
|
12658
13068
|
if (opts.json === true) {
|
|
12659
13069
|
const rowsJson = result.rows.map((r) => Object.fromEntries(result.header.map((h, i) => [h, fenceFileFieldIfMatched(r[i] ?? "")])));
|
|
@@ -12685,13 +13095,13 @@ async function cmdPptxOutline(file, opts) {
|
|
|
12685
13095
|
recordDocStat("pptx_outline", file, text);
|
|
12686
13096
|
}
|
|
12687
13097
|
async function cmdPptxSlide(file, opts) {
|
|
12688
|
-
const n =
|
|
13098
|
+
const n = requireNonNegativeInt("--slide", opts.slide);
|
|
12689
13099
|
const text = fenceFileText(await pptxSlideText(file, n, opts.notes === true));
|
|
12690
13100
|
out(text);
|
|
12691
13101
|
recordDocStat("pptx_slide", file, text);
|
|
12692
13102
|
}
|
|
12693
13103
|
async function cmdPptxNotes(file, opts) {
|
|
12694
|
-
const n = opts.slide !== void 0 ?
|
|
13104
|
+
const n = opts.slide !== void 0 ? requireNonNegativeInt("--slide", opts.slide) : void 0;
|
|
12695
13105
|
const text = await pptxNotesText(file, n);
|
|
12696
13106
|
const printed = text.length > 0 ? fenceFileText(text) : "no speaker notes found";
|
|
12697
13107
|
out(printed);
|
|
@@ -12820,7 +13230,7 @@ function cmdXmlOutline(file, opts) {
|
|
|
12820
13230
|
process.exitCode = runXmlOutline({
|
|
12821
13231
|
file,
|
|
12822
13232
|
...opts.json === true ? { json: true } : {},
|
|
12823
|
-
...depthStr !== void 0 ? { maxDepth:
|
|
13233
|
+
...depthStr !== void 0 ? { maxDepth: requireNonNegativeInt("--max-depth", depthStr) } : {}
|
|
12824
13234
|
});
|
|
12825
13235
|
}
|
|
12826
13236
|
function cmdXmlQuery(file, pathOrOpts, optsMaybe) {
|
|
@@ -13629,17 +14039,236 @@ function registerFormatCommands(program2, guard) {
|
|
|
13629
14039
|
// src/cli_cmd_analysis.ts
|
|
13630
14040
|
init_define_import_meta_env();
|
|
13631
14041
|
|
|
14042
|
+
// src/answer_router.ts
|
|
14043
|
+
init_define_import_meta_env();
|
|
14044
|
+
function refusal(why, suggestion) {
|
|
14045
|
+
return `cannot answer deterministically: ${why}; try: ${suggestion}`;
|
|
14046
|
+
}
|
|
14047
|
+
var JUDGEMENT_PATTERNS = [
|
|
14048
|
+
/\bwhy\b/i,
|
|
14049
|
+
/\bshould (?:i|we|it|this|that)\b/i,
|
|
14050
|
+
/^(?:is|are|does|do|did|can|could|would|will|has|have)\b/i,
|
|
14051
|
+
/^(?:explain|describe|summari[sz]e|review|assess|evaluate|compare|analy[sz]e)\b/i,
|
|
14052
|
+
/^how\b/i,
|
|
14053
|
+
/\bwhat(?:'s| is)? (?:the )?(?:bug|issue|problem|point|difference)\b/i,
|
|
14054
|
+
/\bwhat(?:'s| is) wrong\b/i,
|
|
14055
|
+
/\bat runtime\b/i,
|
|
14056
|
+
/\bwhat value\b/i,
|
|
14057
|
+
/\breturns? when\b/i,
|
|
14058
|
+
/\b(?:correct|incorrect|buggy|broken|safe|unsafe|better|worse|best)\b/i
|
|
14059
|
+
];
|
|
14060
|
+
var INTENT_RULES = [
|
|
14061
|
+
{ intent: "callers", re: /^who calls (.+)$/i },
|
|
14062
|
+
{ intent: "callers", re: /^what calls (.+)$/i },
|
|
14063
|
+
{ intent: "callers", re: /^callers of (.+)$/i },
|
|
14064
|
+
{ intent: "callers", re: /^call ?-?sites of (.+)$/i },
|
|
14065
|
+
{ intent: "callers", re: /^(?:all )?(.+) call ?-?sites$/i },
|
|
14066
|
+
{ intent: "tests", re: /^wh(?:at|ich) tests? (?:covers?|exercises?|tests?|touch(?:es)?) (.+)$/i },
|
|
14067
|
+
{ intent: "tests", re: /^tests? for (.+)$/i },
|
|
14068
|
+
{ intent: "tests", re: /^test coverage (?:for|of) (.+)$/i },
|
|
14069
|
+
{ intent: "exports", re: /^what does (.+) export$/i },
|
|
14070
|
+
{ intent: "exports", re: /^exports of (.+)$/i },
|
|
14071
|
+
{ intent: "exports", re: /^(.+) exports$/i },
|
|
14072
|
+
{ intent: "imports", re: /^what does (.+) import$/i },
|
|
14073
|
+
{ intent: "imports", re: /^imports of (.+)$/i },
|
|
14074
|
+
// The bare form is the commoner of the pair in the captured corpus (34 hits vs 15 for `X exports`). It is only safe alongside file-only subject resolution for this intent: with a symbol fallback, the corpus lines `Add imports` and `Update imports` resolve `Add`/`Update` to whatever same-named symbol sorts first.
|
|
14075
|
+
{ intent: "imports", re: /^(.+) imports$/i },
|
|
14076
|
+
{ intent: "impact", re: /^what breaks if (.+) changes?$/i },
|
|
14077
|
+
{ intent: "impact", re: /^what breaks if (?:i|we|you) chang(?:e|ed) (.+)$/i },
|
|
14078
|
+
{ intent: "impact", re: /^what breaks when (?:i|we|you) chang(?:e|ed) (.+)$/i },
|
|
14079
|
+
{ intent: "impact", re: /^what breaks when (.+) chang(?:es|ed)$/i },
|
|
14080
|
+
{ intent: "impact", re: /^what depends on (.+)$/i },
|
|
14081
|
+
{ intent: "impact", re: /^what(?:'s| is) impacted by (?:changing )?(.+)$/i },
|
|
14082
|
+
{ intent: "impact", re: /^blast radius of (.+)$/i },
|
|
14083
|
+
{ intent: "impact", re: /^impact of (?:changing )?(.+)$/i },
|
|
14084
|
+
{ intent: "where", re: /^where is (.+) (?:defined|declared)$/i },
|
|
14085
|
+
{ intent: "where", re: /^where is (.+)$/i },
|
|
14086
|
+
{ intent: "where", re: /^where does (.+) live$/i }
|
|
14087
|
+
];
|
|
14088
|
+
function normalizeQuestion(question) {
|
|
14089
|
+
const collapsed = question.replace(/\s+/g, " ").trim().replace(/[?\s]+$/, "");
|
|
14090
|
+
return collapsed.replace(/^(?:check|show|list|find|get|print|inspect|read|locate|verify|measure|view|trace|identify) (?:the |a |an )?/i, "");
|
|
14091
|
+
}
|
|
14092
|
+
function isJudgementQuestion(rawQuestion) {
|
|
14093
|
+
const question = normalizeQuestion(rawQuestion);
|
|
14094
|
+
return JUDGEMENT_PATTERNS.some((p) => p.test(question));
|
|
14095
|
+
}
|
|
14096
|
+
function classify(rawQuestion) {
|
|
14097
|
+
const question = normalizeQuestion(rawQuestion);
|
|
14098
|
+
if (isJudgementQuestion(question)) return null;
|
|
14099
|
+
for (const rule of INTENT_RULES) {
|
|
14100
|
+
const m = rule.re.exec(question);
|
|
14101
|
+
if (m && m[1] !== void 0) {
|
|
14102
|
+
const subject = normalizeSubject(m[1]);
|
|
14103
|
+
if (subject.length > 0) return { intent: rule.intent, subject };
|
|
14104
|
+
}
|
|
14105
|
+
}
|
|
14106
|
+
return null;
|
|
14107
|
+
}
|
|
14108
|
+
function normalizeSubject(raw) {
|
|
14109
|
+
let s = raw.trim();
|
|
14110
|
+
for (; ; ) {
|
|
14111
|
+
const next = s.replace(/^[`'"]+/, "").replace(/[`'".,;:!]+$/, "").trim();
|
|
14112
|
+
if (next === s) break;
|
|
14113
|
+
s = next;
|
|
14114
|
+
}
|
|
14115
|
+
s = s.replace(/^(?:the|a|an) /i, "");
|
|
14116
|
+
return s.trim();
|
|
14117
|
+
}
|
|
14118
|
+
var SYMBOL_SCAN_PAGE = 200;
|
|
14119
|
+
function resolveSymbolHit(subject, rootDir) {
|
|
14120
|
+
for (let offset = 0; ; offset += SYMBOL_SCAN_PAGE) {
|
|
14121
|
+
const rows = querySymbols({ name: subject, rootDir, limit: SYMBOL_SCAN_PAGE, offset });
|
|
14122
|
+
if (rows.length === 0) return null;
|
|
14123
|
+
const hit = rows.find((r) => !isIgnoredIndexPath(r.filePath));
|
|
14124
|
+
if (hit) return { name: hit.name, file: hit.filePath };
|
|
14125
|
+
if (rows.length < SYMBOL_SCAN_PAGE) return null;
|
|
14126
|
+
}
|
|
14127
|
+
}
|
|
14128
|
+
function resolveFileHit(subject, rootDir) {
|
|
14129
|
+
const entry = getFileEntry(resolveIndexPath(subject));
|
|
14130
|
+
if (entry && !isIgnoredIndexPath(entry.filePath)) return { kind: "file", path: entry.filePath };
|
|
14131
|
+
if (subject.includes("/") || subject.includes("\\")) return null;
|
|
14132
|
+
const want = foldPath(subject);
|
|
14133
|
+
const matches2 = [];
|
|
14134
|
+
for (const [folded, indexed] of getProjectFileEntries(rootDir)) {
|
|
14135
|
+
if (isIgnoredIndexPath(folded)) continue;
|
|
14136
|
+
const base = folded.slice(Math.max(folded.lastIndexOf("/"), folded.lastIndexOf("\\")) + 1);
|
|
14137
|
+
const dot = base.lastIndexOf(".");
|
|
14138
|
+
if (base === want || dot > 0 && base.slice(0, dot) === want) matches2.push(indexed.filePath);
|
|
14139
|
+
}
|
|
14140
|
+
const paths = [...new Set(matches2)].sort();
|
|
14141
|
+
if (paths.length === 0) return null;
|
|
14142
|
+
if (paths.length === 1 && paths[0] !== void 0) return { kind: "file", path: paths[0] };
|
|
14143
|
+
return { kind: "ambiguous", candidates: paths };
|
|
14144
|
+
}
|
|
14145
|
+
function resolveSubject(subject, mode = "symbol-first") {
|
|
14146
|
+
const rootDir = resolveProjectRoot({ project: process.cwd() });
|
|
14147
|
+
const sep5 = subject.lastIndexOf("::");
|
|
14148
|
+
if (sep5 > 0 && sep5 + 2 < subject.length) {
|
|
14149
|
+
const file2 = resolveIndexPath(subject.slice(0, sep5));
|
|
14150
|
+
const hit2 = querySymbols({ filePath: file2, name: subject.slice(sep5 + 2), rootDir, limit: 1 })[0];
|
|
14151
|
+
if (hit2 && !isIgnoredIndexPath(hit2.filePath)) return { kind: "symbol", name: hit2.name, file: hit2.filePath };
|
|
14152
|
+
return null;
|
|
14153
|
+
}
|
|
14154
|
+
if (/\s/.test(subject)) return null;
|
|
14155
|
+
if (mode === "symbol-first") {
|
|
14156
|
+
const hit2 = resolveSymbolHit(subject, rootDir);
|
|
14157
|
+
if (hit2) return { kind: "symbol", name: hit2.name, file: hit2.file };
|
|
14158
|
+
return resolveFileHit(subject, rootDir);
|
|
14159
|
+
}
|
|
14160
|
+
const file = resolveFileHit(subject, rootDir);
|
|
14161
|
+
if (file) return file;
|
|
14162
|
+
const hit = resolveSymbolHit(subject, rootDir);
|
|
14163
|
+
if (!hit) return null;
|
|
14164
|
+
return mode === "file-first" ? { kind: "symbol", name: hit.name, file: hit.file } : { kind: "symbol-only", name: hit.name, file: hit.file };
|
|
14165
|
+
}
|
|
14166
|
+
var FILE_INTENTS = /* @__PURE__ */ new Set(["tests", "exports", "imports"]);
|
|
14167
|
+
var ANSWER_DELEGATE_LIMIT = 20;
|
|
14168
|
+
function subjectModeFor(intent) {
|
|
14169
|
+
if (intent === "exports" || intent === "imports") return "file-only";
|
|
14170
|
+
return intent === "tests" ? "file-first" : "symbol-first";
|
|
14171
|
+
}
|
|
14172
|
+
function runAnswer(opts) {
|
|
14173
|
+
const question = opts.question.trim();
|
|
14174
|
+
if (question.length === 0) {
|
|
14175
|
+
emitErr(refusal("the question is empty", 'token-goat answer "who calls <symbol>"'));
|
|
14176
|
+
return 1;
|
|
14177
|
+
}
|
|
14178
|
+
if (isJudgementQuestion(question)) {
|
|
14179
|
+
emitErr(
|
|
14180
|
+
refusal(
|
|
14181
|
+
"that asks for judgement, intent, or runtime behaviour, which the index cannot answer -- it would need the code read and reasoned over",
|
|
14182
|
+
`token-goat semantic "${question}"`
|
|
14183
|
+
)
|
|
14184
|
+
);
|
|
14185
|
+
return 1;
|
|
14186
|
+
}
|
|
14187
|
+
const cls = classify(question);
|
|
14188
|
+
if (cls === null) {
|
|
14189
|
+
emitErr(
|
|
14190
|
+
refusal(
|
|
14191
|
+
"no intent matched -- this router only answers where/who-calls/what-tests-cover/what-exports/what-imports/what-breaks questions about a named symbol or file",
|
|
14192
|
+
`token-goat semantic "${question}"`
|
|
14193
|
+
)
|
|
14194
|
+
);
|
|
14195
|
+
return 1;
|
|
14196
|
+
}
|
|
14197
|
+
const resolved = resolveSubject(cls.subject, subjectModeFor(cls.intent));
|
|
14198
|
+
if (resolved === null) {
|
|
14199
|
+
emitErr(
|
|
14200
|
+
refusal(
|
|
14201
|
+
`'${cls.subject}' is not an indexed symbol or file`,
|
|
14202
|
+
`token-goat semantic "${question}"`
|
|
14203
|
+
)
|
|
14204
|
+
);
|
|
14205
|
+
return 1;
|
|
14206
|
+
}
|
|
14207
|
+
const rootDir = resolveProjectRoot({ project: process.cwd() });
|
|
14208
|
+
if (resolved.kind === "ambiguous") {
|
|
14209
|
+
const shown = resolved.candidates.slice(0, 5).map((c) => toDisplayPath(rootDir, c));
|
|
14210
|
+
const more = resolved.candidates.length - shown.length;
|
|
14211
|
+
const first = shown[0] ?? "";
|
|
14212
|
+
const next = FILE_INTENTS.has(cls.intent) ? `token-goat answer "${cls.intent === "tests" ? "tests for" : cls.intent === "imports" ? "imports of" : "exports of"} ${first}"` : `token-goat outline ${first}`;
|
|
14213
|
+
emitErr(
|
|
14214
|
+
refusal(
|
|
14215
|
+
`'${cls.subject}' names ${resolved.candidates.length} files in this project (${shown.join(", ")}${more > 0 ? `, +${more} more` : ""})`,
|
|
14216
|
+
next
|
|
14217
|
+
)
|
|
14218
|
+
);
|
|
14219
|
+
return 1;
|
|
14220
|
+
}
|
|
14221
|
+
if (resolved.kind === "symbol-only") {
|
|
14222
|
+
emitErr(
|
|
14223
|
+
refusal(
|
|
14224
|
+
`'${cls.subject}' is a symbol; exports/imports are file-level`,
|
|
14225
|
+
`token-goat ${cls.intent} ${toDisplayPath(rootDir, resolved.file)}`
|
|
14226
|
+
)
|
|
14227
|
+
);
|
|
14228
|
+
return 1;
|
|
14229
|
+
}
|
|
14230
|
+
if (FILE_INTENTS.has(cls.intent)) {
|
|
14231
|
+
const file = resolved.kind === "symbol" ? resolved.file : resolved.path;
|
|
14232
|
+
const display = toDisplayPath(rootDir, file);
|
|
14233
|
+
if (cls.intent === "tests") {
|
|
14234
|
+
emit(`via: token-goat test-for ${display}`);
|
|
14235
|
+
return runTestFor({ file });
|
|
14236
|
+
}
|
|
14237
|
+
if (cls.intent === "exports") {
|
|
14238
|
+
emit(`via: token-goat exports ${display}`);
|
|
14239
|
+
return runExports({ file });
|
|
14240
|
+
}
|
|
14241
|
+
emit(`via: token-goat imports ${display}`);
|
|
14242
|
+
return runImports({ file });
|
|
14243
|
+
}
|
|
14244
|
+
if (resolved.kind === "file") {
|
|
14245
|
+
const display = toDisplayPath(rootDir, resolved.path);
|
|
14246
|
+
emitErr(
|
|
14247
|
+
refusal(
|
|
14248
|
+
`'${cls.subject}' is a file, and ${cls.intent === "where" ? "where" : cls.intent} needs a symbol`,
|
|
14249
|
+
`token-goat outline ${display}`
|
|
14250
|
+
)
|
|
14251
|
+
);
|
|
14252
|
+
return 1;
|
|
14253
|
+
}
|
|
14254
|
+
if (cls.intent === "callers") {
|
|
14255
|
+
emit(`via: token-goat callers ${displaySafeText(resolved.name)} --limit ${ANSWER_DELEGATE_LIMIT}`);
|
|
14256
|
+
return runCallers({ symbol: resolved.name, limit: ANSWER_DELEGATE_LIMIT });
|
|
14257
|
+
}
|
|
14258
|
+
if (cls.intent === "impact") {
|
|
14259
|
+
emit(`via: token-goat impact ${displaySafeText(resolved.name)} --top ${ANSWER_DELEGATE_LIMIT}`);
|
|
14260
|
+
return runImpact({ symbol: resolved.name, top: ANSWER_DELEGATE_LIMIT });
|
|
14261
|
+
}
|
|
14262
|
+
emit(`via: token-goat symbol ${displaySafeText(resolved.name)} -p --exclude-vendored`);
|
|
14263
|
+
const r = runSymbol({ name: resolved.name, projectRoot: rootDir, limit: ANSWER_DELEGATE_LIMIT, excludeVendored: true });
|
|
14264
|
+
if (r.text.length > 0) emit(r.text);
|
|
14265
|
+
return r.code;
|
|
14266
|
+
}
|
|
14267
|
+
|
|
13632
14268
|
// src/affected.ts
|
|
13633
14269
|
init_define_import_meta_env();
|
|
13634
14270
|
import * as path25 from "node:path";
|
|
13635
14271
|
var DEFAULT_AFFECTED_DEPTH = 5;
|
|
13636
|
-
function emit2(text) {
|
|
13637
|
-
const payload = colorStdout() ? text : stripAnsi(text);
|
|
13638
|
-
process.stdout.write(ensureNewline(payload));
|
|
13639
|
-
}
|
|
13640
|
-
function emitErr2(text) {
|
|
13641
|
-
process.stderr.write(ensureNewline(text));
|
|
13642
|
-
}
|
|
13643
14272
|
function guardedFilter(filter2) {
|
|
13644
14273
|
const guarded = compileGuardedRegex(filter2);
|
|
13645
14274
|
if (!guarded.ok) throw new Error(`--filter is not usable as a regular expression: ${guarded.reason}`);
|
|
@@ -13694,43 +14323,43 @@ function computeAffected(opts) {
|
|
|
13694
14323
|
}
|
|
13695
14324
|
function runAffected(opts) {
|
|
13696
14325
|
if (opts.depth !== void 0 && opts.depth <= 0) {
|
|
13697
|
-
|
|
14326
|
+
emitErr(`--depth must be a positive number, got: ${opts.depth}`);
|
|
13698
14327
|
return 1;
|
|
13699
14328
|
}
|
|
13700
14329
|
if (opts.files.length === 0) {
|
|
13701
|
-
|
|
14330
|
+
emitErr("No changed files given. Pass paths as arguments, or pipe them in with --stdin.");
|
|
13702
14331
|
return 1;
|
|
13703
14332
|
}
|
|
13704
14333
|
if (opts.filter !== void 0) {
|
|
13705
14334
|
const guarded = compileGuardedRegex(opts.filter);
|
|
13706
14335
|
if (!guarded.ok) {
|
|
13707
|
-
|
|
14336
|
+
emitErr(`--filter is not usable as a regular expression: ${guarded.reason}`);
|
|
13708
14337
|
return 1;
|
|
13709
14338
|
}
|
|
13710
14339
|
}
|
|
13711
14340
|
const result = computeAffected(opts);
|
|
13712
14341
|
if (opts.json === true) {
|
|
13713
|
-
|
|
14342
|
+
emit(displaySafeJson(result));
|
|
13714
14343
|
return 0;
|
|
13715
14344
|
}
|
|
13716
14345
|
if (opts.quiet === true) {
|
|
13717
|
-
for (const f of result.testFiles)
|
|
14346
|
+
for (const f of result.testFiles) emit(f);
|
|
13718
14347
|
} else if (result.testFiles.length === 0) {
|
|
13719
14348
|
const traced = opts.files.length - result.unknownSeeds.length;
|
|
13720
14349
|
if (traced === 0) {
|
|
13721
|
-
|
|
14350
|
+
emit(`Nothing could be traced: ${result.unknownSeeds.length === 1 ? "the file given is not tracked" : "none of the files given are tracked"} in this project.`);
|
|
13722
14351
|
} else {
|
|
13723
|
-
|
|
14352
|
+
emit(`No test files import ${countNoun(traced, "changed file")} (within ${countNoun(result.depth, "hop")}).`);
|
|
13724
14353
|
}
|
|
13725
14354
|
} else {
|
|
13726
|
-
|
|
13727
|
-
for (const f of result.testFiles)
|
|
14355
|
+
emit(`${countNoun(result.testFiles.length, "affected test file")} (${countNoun(result.reachedCount, "file")} reached within ${countNoun(result.depth, "hop")}):`);
|
|
14356
|
+
for (const f of result.testFiles) emit(` ${f}`);
|
|
13728
14357
|
}
|
|
13729
14358
|
if (result.unknownSeeds.length > 0) {
|
|
13730
|
-
|
|
14359
|
+
emitErr(`Not tracked in this project, so nothing was traced from ${result.unknownSeeds.length === 1 ? "it" : "them"}: ${result.unknownSeeds.join(", ")}`);
|
|
13731
14360
|
}
|
|
13732
14361
|
if (result.depthLimited) {
|
|
13733
|
-
|
|
14362
|
+
emitErr(`Stopped at --depth ${result.depth} with ${countNoun(result.unexploredAtFrontier, "file")} still unexplored; more tests may be affected (raise --depth).`);
|
|
13734
14363
|
}
|
|
13735
14364
|
return 0;
|
|
13736
14365
|
}
|
|
@@ -14332,6 +14961,69 @@ function cmdTrace(src, opts) {
|
|
|
14332
14961
|
// src/text_logfold.ts
|
|
14333
14962
|
init_define_import_meta_env();
|
|
14334
14963
|
import * as fs31 from "fs";
|
|
14964
|
+
|
|
14965
|
+
// src/filters.ts
|
|
14966
|
+
init_define_import_meta_env();
|
|
14967
|
+
var DROP = () => null;
|
|
14968
|
+
var FILTERS = [
|
|
14969
|
+
{
|
|
14970
|
+
// git fetch/clone progress: "remote: Counting objects: 73% (8/11)", "Receiving objects: 100% (11/11), done.", "Resolving deltas: ...".
|
|
14971
|
+
name: "git-progress",
|
|
14972
|
+
pattern: /^(remote:\s+)?(Counting objects|Compressing objects|Receiving objects|Resolving deltas|Unpacking objects|Enumerating objects|Writing objects):/i,
|
|
14973
|
+
replacer: DROP
|
|
14974
|
+
},
|
|
14975
|
+
{
|
|
14976
|
+
// npm install summary chatter: "added 142 packages in 3s", "changed 5 packages", "audited 200 packages in 1s".
|
|
14977
|
+
name: "npm-summary",
|
|
14978
|
+
pattern: /^\s*(added|removed|changed|audited)\s+\d+\s+packages?\b/i,
|
|
14979
|
+
replacer: DROP
|
|
14980
|
+
},
|
|
14981
|
+
{
|
|
14982
|
+
// npm/yarn deprecation + funding noise that adds nothing actionable.
|
|
14983
|
+
name: "npm-funding",
|
|
14984
|
+
pattern: /^\s*\d+\s+packages? are looking for funding\b/i,
|
|
14985
|
+
replacer: DROP
|
|
14986
|
+
},
|
|
14987
|
+
{
|
|
14988
|
+
// pip download progress: "Downloading foo-1.2.3-py3-none-any.whl (1.2 MB)" and the " |████████| 1.2 MB 5.0 MB/s" meter lines.
|
|
14989
|
+
name: "pip-download",
|
|
14990
|
+
pattern: /^\s*(Downloading|Collecting|Using cached|Requirement already satisfied)\b/i,
|
|
14991
|
+
replacer: DROP
|
|
14992
|
+
},
|
|
14993
|
+
{
|
|
14994
|
+
// A progress meter line built from Unicode box-drawing/block glyphs, e.g. "████████░░░░ 60%". Restricted to those glyphs (not ASCII #/=/-) so an ASCII final state like "Building [####] 100%" is preserved as real output.
|
|
14995
|
+
name: "progress-bar",
|
|
14996
|
+
pattern: /[█▉▊▋▌▍▎▏▓▒░]{3,}/,
|
|
14997
|
+
replacer: DROP
|
|
14998
|
+
},
|
|
14999
|
+
{
|
|
15000
|
+
// Docker layer pull progress: "abc123: Pulling fs layer", "abc123: Download complete", "abc123: Pull complete".
|
|
15001
|
+
name: "docker-pull",
|
|
15002
|
+
pattern: /^[0-9a-f]{6,}:\s+(Pulling|Waiting|Downloading|Verifying|Extracting|Download complete|Pull complete|Already exists)\b/i,
|
|
15003
|
+
replacer: DROP
|
|
15004
|
+
},
|
|
15005
|
+
{
|
|
15006
|
+
// cargo build chatter: " Compiling foo v0.1.0", " Downloading crates ...".
|
|
15007
|
+
name: "cargo-progress",
|
|
15008
|
+
pattern: /^\s*(Compiling|Downloading|Downloaded|Updating|Fetching)\s+\S/,
|
|
15009
|
+
replacer: DROP
|
|
15010
|
+
},
|
|
15011
|
+
{
|
|
15012
|
+
// Webpack/bundler asset rows are voluminous; keep the build result lines (errors, warnings, "compiled") by only dropping plain asset size rows.
|
|
15013
|
+
name: "webpack-asset",
|
|
15014
|
+
pattern: /^\s*(asset|chunk)\s+\S+\s+[\d.]+\s*(KiB|MiB|bytes)\b/i,
|
|
15015
|
+
replacer: DROP
|
|
15016
|
+
},
|
|
15017
|
+
{
|
|
15018
|
+
// A binary-content marker line: any line carrying a NUL byte is treated as non-text and collapsed to a single readable marker.
|
|
15019
|
+
name: "binary-content",
|
|
15020
|
+
// eslint-disable-next-line no-control-regex -- intentionally matches the NUL control byte that signals binary content
|
|
15021
|
+
pattern: /\x00/,
|
|
15022
|
+
replacer: () => "[binary content elided by token-goat]"
|
|
15023
|
+
}
|
|
15024
|
+
];
|
|
15025
|
+
|
|
15026
|
+
// src/text_logfold.ts
|
|
14335
15027
|
var VOLATILE_SUBS = [
|
|
14336
15028
|
{ re: /\b\d{1,2}:\d{2}:\d{2}(?:\.\d+)?\b/g, placeholder: "[HH:MM:SS]" },
|
|
14337
15029
|
{ re: /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, placeholder: "[UUID]" },
|
|
@@ -14863,17 +15555,17 @@ function cmdNote(action, key, value, opts) {
|
|
|
14863
15555
|
throw new Error(`Unknown note action: ${action}. Use: set, get, unset, list, clear`);
|
|
14864
15556
|
}
|
|
14865
15557
|
function loadAllSessionReadCounts() {
|
|
14866
|
-
const
|
|
15558
|
+
const sessionsDir2 = path29.join(tokenGoatHome(), "sessions");
|
|
14867
15559
|
const totals = /* @__PURE__ */ new Map();
|
|
14868
15560
|
let entries;
|
|
14869
15561
|
try {
|
|
14870
|
-
entries = fs32.readdirSync(
|
|
15562
|
+
entries = fs32.readdirSync(sessionsDir2, { withFileTypes: true });
|
|
14871
15563
|
} catch {
|
|
14872
15564
|
return totals;
|
|
14873
15565
|
}
|
|
14874
15566
|
for (const entry of entries) {
|
|
14875
15567
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
14876
|
-
const filePath = path29.join(
|
|
15568
|
+
const filePath = path29.join(sessionsDir2, entry.name);
|
|
14877
15569
|
let raw;
|
|
14878
15570
|
try {
|
|
14879
15571
|
raw = JSON.parse(fs32.readFileSync(filePath, "utf8"));
|
|
@@ -15476,6 +16168,7 @@ function registerAnalysisCommands(program2, guard) {
|
|
|
15476
16168
|
})
|
|
15477
16169
|
)
|
|
15478
16170
|
);
|
|
16171
|
+
program2.command("answer <question>").description("answer a plain-English question from the index alone, or refuse with a reason (no model call, no inference)").action((question) => runExit(() => runAnswer({ question })));
|
|
15479
16172
|
program2.command("pack [patterns...]").description("bundle matched files into a single LLM-ready output (Markdown, XML, or plain text)").option("--format <style>", "output style: md (default), xml, or text", "md").option("--line-numbers", "prefix each line with its line number").option("--instruction-file <path>", "append a task prompt from a file").option("--output <path>", "write output to a file instead of stdout").option("--no-ignore", "bypass .tokengoatignore patterns").option("--strip-comments", "remove language-appropriate comments before packing").option("--scan-secrets", "scan for credentials; exit 2 if any are found").option("--budget <n>", "exit 3 if the estimated token count exceeds n").action(guard(cmdPack));
|
|
15480
16173
|
program2.command("tokens [patterns...]").description("per-file token footprint table, sorted largest-first").option("--tree", "group by directory with subtotals and percentage of total").option("--top <n>", "limit to the N biggest files").option("--asc", "reverse order (ascending)").option("-j, --json", "output as JSON").action(guard(cmdTokens));
|
|
15481
16174
|
program2.command("budget <patterns...>").description("estimate the total token cost of a file set").option("--context <n>", "context window in thousands of tokens (shows % fill)").option("-j, --json", "output as JSON").action(guard(cmdBudget));
|
|
@@ -15510,7 +16203,7 @@ function registerAnalysisCommands(program2, guard) {
|
|
|
15510
16203
|
).option("--file <path>", "narrow to one source file's gaps (matched exact or as a path suffix against the report's own file keys)").option("--json", "emit the gap report as JSON instead of text").action(guard(cmdCoverageReportGaps));
|
|
15511
16204
|
program2.command("conflicts [path]").description(
|
|
15512
16205
|
"unresolved git merge-conflict markers (<<<<<<< / ||||||| / ======= / >>>>>>>, two-way or diff3 three-way) instead of a raw Read or grep\n\npath may be a single file, a directory (scanned recursively), or omitted entirely (scans the whole project from the current directory); only files with at least one conflict region or malformed-marker warning are reported"
|
|
15513
|
-
).option("--summary", "line ranges and ours/base/theirs labels only, omitting the conflict content").option("--json", "emit the results as JSON instead of text").action(guard(cmdConflicts));
|
|
16206
|
+
).option("-C, --context <n>", "lines of surrounding context to include before and after each conflict region (default: 3)").option("--summary", "line ranges and ours/base/theirs labels only, omitting the conflict content").option("--json", "emit the results as JSON instead of text").action(guard(cmdConflicts));
|
|
15514
16207
|
program2.command("history").description("show recent session history: bash commands and web fetches (current-session or recent cache)").option("--limit <n>", "max entries to show (default: 30)").option("-j, --json", "output as JSON").action((opts) => guard(() => cmdHistory(opts))());
|
|
15515
16208
|
program2.command("changed [ref]").description("list files or symbols changed since a git ref").option("--since <ref>", "git ref to compare against (default: HEAD~5)").option("--symbol", "list symbols instead of files").option("-j, --json", "output as JSON").option("--grep <pattern>", "filter to changed files whose path matches this regex (falls back to a literal substring match when the pattern does not compile); applies to file paths even in --symbol mode").option("--exclude-tests", "hide changed files that live in a test file (opt-in; default output is unchanged); applies to file paths even in --symbol mode").action(
|
|
15516
16209
|
(ref, opts) => runExit(
|
|
@@ -15642,14 +16335,31 @@ function renderCapabilities(caps) {
|
|
|
15642
16335
|
|
|
15643
16336
|
// src/cli_session.ts
|
|
15644
16337
|
init_define_import_meta_env();
|
|
15645
|
-
import * as
|
|
16338
|
+
import * as fs40 from "node:fs";
|
|
15646
16339
|
|
|
15647
16340
|
// src/session_audit.ts
|
|
15648
16341
|
init_define_import_meta_env();
|
|
15649
16342
|
import * as fs34 from "node:fs";
|
|
15650
|
-
import * as
|
|
16343
|
+
import * as os16 from "node:os";
|
|
15651
16344
|
import * as path31 from "node:path";
|
|
15652
16345
|
import * as readline from "node:readline";
|
|
16346
|
+
var MODEL_VISIBLE_CATEGORIES = ["toolResults", "assistantText", "assistantThinking", "toolUseInputs", "attachments", "userTurns", "harnessMeta", "system"];
|
|
16347
|
+
function computeCalibration(estimated, measured) {
|
|
16348
|
+
let modelVisibleBytes = 0;
|
|
16349
|
+
let estimatedTokens = 0;
|
|
16350
|
+
for (const key of MODEL_VISIBLE_CATEGORIES) {
|
|
16351
|
+
modelVisibleBytes += estimated[key].bytes;
|
|
16352
|
+
estimatedTokens += estimated[key].estTokens;
|
|
16353
|
+
}
|
|
16354
|
+
const measuredFirstWriteTokens = measured.inputTokens + measured.cacheCreationTokens;
|
|
16355
|
+
return {
|
|
16356
|
+
modelVisibleBytes,
|
|
16357
|
+
estimatedTokens,
|
|
16358
|
+
measuredFirstWriteTokens,
|
|
16359
|
+
relativeError: measuredFirstWriteTokens === 0 ? 0 : (estimatedTokens - measuredFirstWriteTokens) / measuredFirstWriteTokens,
|
|
16360
|
+
bytesPerMeasuredToken: measuredFirstWriteTokens === 0 ? 0 : modelVisibleBytes / measuredFirstWriteTokens
|
|
16361
|
+
};
|
|
16362
|
+
}
|
|
15653
16363
|
function emptyMeasured() {
|
|
15654
16364
|
return { apiCalls: 0, inputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, outputTokens: 0 };
|
|
15655
16365
|
}
|
|
@@ -15724,8 +16434,12 @@ var DENY_TEMPLATES = [
|
|
|
15724
16434
|
{ kind: "doc_unchanged_deny", re: /is unchanged since last read\. Use `token-goat (?:section|read)/, tool: "Read" },
|
|
15725
16435
|
{ kind: "doc_diff_deny", re: /Content changed since last read of [\s\S]*?Use `token-goat (?:section|read)/, tool: "Read" },
|
|
15726
16436
|
{ kind: "read_served_deny", re: /was already served in this session, byte for byte/, tool: "Read" },
|
|
16437
|
+
// Its own kind rather than folded into read_served_deny above, whose stat name this branch shares: the wording is disjoint (a span, not a byte-for-byte whole serve) and so is the follow-up it invites, since the model still needs part of that span and has to choose a narrower call rather than recall the whole result.
|
|
16438
|
+
{ kind: "range_reread_deny", re: /Lines \d+\.\.\d+ of [\s\S]*?was already read this session/, tool: "Read" },
|
|
15727
16439
|
{ kind: "markdown_already_read_deny", re: /Markdown file already read this session\. Use `token-goat section/, tool: "Read" },
|
|
15728
16440
|
{ kind: "read_count_deny", re: /(?:Read|Tried to read) this file \d+ times already/, tool: "Read" },
|
|
16441
|
+
// Shares read_count_deny's stat name and matches none of its wording: this branch fires on the SHAPE of the reads (consecutive line windows walking one file) rather than on their count, and says so.
|
|
16442
|
+
{ kind: "sequential_paging_deny", re: /Sequential line-range paging detected on /, tool: "Read" },
|
|
15729
16443
|
{ kind: "generic_reread_deny", re: /was already read this session \(\d+ read/, tool: "Read" },
|
|
15730
16444
|
{ kind: "large_file_deny", re: /is very large \(\d+(?:\.\d+)?KB\)\./, tool: "Read" },
|
|
15731
16445
|
{ kind: "file_type_handler_deny", re: /too large to preview \(exceeds the in-hook scan cap\)|cannot be read as text\.|Read cannot return spreadsheet content|Read cannot return slide content|Read cannot return document content|Use Read with offset and limit parameters to read specific line ranges/, tool: "Read" },
|
|
@@ -15797,7 +16511,7 @@ function deepStringBytes(value) {
|
|
|
15797
16511
|
}
|
|
15798
16512
|
return 0;
|
|
15799
16513
|
}
|
|
15800
|
-
var OMISSION_MARKER_RE = /(?:--- (\d+) lines omitted ---|\[token-goat: \+?(\d+) more [a-z ]*lines omitted\]|--- patch: (\d+) lines omitted by token-goat ---|\.\.\. (\d+) lines omitted by token-goat \.\.\.)/g;
|
|
16514
|
+
var OMISSION_MARKER_RE = /(?:--- (\d+) lines omitted ---|\[token-goat: \+?(\d+) (?:more )?[a-z ]*lines omitted\]|--- patch: (\d+) lines omitted by token-goat ---|\.\.\. (\d+) lines omitted by token-goat \.\.\.)/g;
|
|
15801
16515
|
function toolResultText(content) {
|
|
15802
16516
|
if (typeof content === "string") return content;
|
|
15803
16517
|
if (Array.isArray(content)) {
|
|
@@ -15845,11 +16559,11 @@ function listCorpusTranscripts(corpusDir) {
|
|
|
15845
16559
|
return found.sort();
|
|
15846
16560
|
}
|
|
15847
16561
|
function defaultCorpusDir() {
|
|
15848
|
-
const claudeDir = path31.join(
|
|
16562
|
+
const claudeDir = path31.join(claudeConfigDir(), "projects");
|
|
15849
16563
|
if (fs34.existsSync(claudeDir) && listCorpusTranscripts(claudeDir).length > 0) {
|
|
15850
16564
|
return claudeDir;
|
|
15851
16565
|
}
|
|
15852
|
-
const home =
|
|
16566
|
+
const home = os16.homedir();
|
|
15853
16567
|
const appData = process.env["APPDATA"] || path31.join(home, "AppData", "Roaming");
|
|
15854
16568
|
const candidates = [
|
|
15855
16569
|
claudeDir,
|
|
@@ -15995,7 +16709,7 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
|
|
|
15995
16709
|
s.editErrorBaseline.totalEdits += 1;
|
|
15996
16710
|
}
|
|
15997
16711
|
if (openDenies.length > 0) {
|
|
15998
|
-
const bashCommand = name === "Bash" && typeof input["command"] === "string" ? input["command"] : void 0;
|
|
16712
|
+
const bashCommand = name === "Bash" && typeof input["command"] === "string" ? input["command"].toLowerCase() : void 0;
|
|
15999
16713
|
for (const o of openDenies) {
|
|
16000
16714
|
if (o.toolCalls.length >= 10) continue;
|
|
16001
16715
|
const isReadSamePath = readNorm !== void 0 && o.path !== "" && readNorm === o.path;
|
|
@@ -16046,13 +16760,13 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
|
|
|
16046
16760
|
if (name === "Read" || name === "Skill") {
|
|
16047
16761
|
const denyTemplate = DENY_TEMPLATES.find((t) => t.tool === name && t.re.test(text));
|
|
16048
16762
|
if (denyTemplate !== void 0) {
|
|
16049
|
-
const
|
|
16050
|
-
const lastSlash =
|
|
16763
|
+
const path42 = readPathById.get(id) ?? "";
|
|
16764
|
+
const lastSlash = path42.lastIndexOf("/");
|
|
16051
16765
|
openDenies.push({
|
|
16052
16766
|
kind: denyTemplate.kind,
|
|
16053
16767
|
withheldBytes: parseWithheldBytes(denyTemplate.kind, text),
|
|
16054
|
-
path:
|
|
16055
|
-
basename: lastSlash === -1 ?
|
|
16768
|
+
path: path42,
|
|
16769
|
+
basename: lastSlash === -1 ? path42 : path42.slice(lastSlash + 1),
|
|
16056
16770
|
callIndexAtDeny: perCall.length,
|
|
16057
16771
|
compactEpochAtOpen: compactEpoch,
|
|
16058
16772
|
compactEpochAtWindow: null,
|
|
@@ -16353,6 +17067,7 @@ async function auditSessionCorpus(opts = {}) {
|
|
|
16353
17067
|
system: emptyCategory(),
|
|
16354
17068
|
otherLocal: emptyCategory()
|
|
16355
17069
|
},
|
|
17070
|
+
calibration: { modelVisibleBytes: 0, estimatedTokens: 0, measuredFirstWriteTokens: 0, relativeError: 0, bytesPerMeasuredToken: 0 },
|
|
16356
17071
|
tools: [],
|
|
16357
17072
|
attachmentKinds: [],
|
|
16358
17073
|
hookOutputs: [],
|
|
@@ -16413,6 +17128,7 @@ async function auditSessionCorpus(opts = {}) {
|
|
|
16413
17128
|
summary.hookOutputs = [...hookMap.values()].sort((a, b) => b.contextBytes - a.contextBytes || b.fires - a.fires || a.origin.localeCompare(b.origin) || a.event.localeCompare(b.event));
|
|
16414
17129
|
summary.denyOutcomes = aggregateDenyOutcomes(denyRows);
|
|
16415
17130
|
summary.editErrorBaseline.rate = summary.editErrorBaseline.totalEdits === 0 ? 0 : summary.editErrorBaseline.totalErrors / summary.editErrorBaseline.totalEdits;
|
|
17131
|
+
summary.calibration = computeCalibration(summary.estimated, summary.measured);
|
|
16416
17132
|
summary.runtimeMs = Date.now() - started;
|
|
16417
17133
|
return summary;
|
|
16418
17134
|
}
|
|
@@ -16453,11 +17169,20 @@ function formatSessionAudit(s) {
|
|
|
16453
17169
|
["system lines", e.system],
|
|
16454
17170
|
["local bookkeeping (never sent)", e.otherLocal]
|
|
16455
17171
|
];
|
|
16456
|
-
const
|
|
17172
|
+
const cal = s.calibration;
|
|
17173
|
+
const modelVisibleBytes = cal.modelVisibleBytes;
|
|
16457
17174
|
for (const [label, cat] of rows.sort((a, b) => b[1].bytes - a[1].bytes)) {
|
|
16458
17175
|
lines.push(`${label.padEnd(31)} count ${fmt(cat.count).padStart(11)} bytes ${fmt(cat.bytes).padStart(15)} est-tokens ${fmt(cat.estTokens).padStart(13)} ${label === "local bookkeeping (never sent)" ? "(excluded from share)" : pct(cat.bytes, modelVisibleBytes)}`);
|
|
16459
17176
|
}
|
|
16460
17177
|
lines.push("");
|
|
17178
|
+
lines.push("## Estimator calibration (the estimate above against the billed ledger above it)");
|
|
17179
|
+
lines.push(`Model-visible bytes: ${fmt(cal.modelVisibleBytes).padStart(18)}`);
|
|
17180
|
+
lines.push(`Estimated tokens (chars/3): ${fmt(cal.estimatedTokens).padStart(18)}`);
|
|
17181
|
+
lines.push(`Measured first-write tokens: ${fmt(cal.measuredFirstWriteTokens).padStart(18)} (input + cache-write; NEVER input alone -- a cached session bills nearly every first write as a cache write)`);
|
|
17182
|
+
lines.push(`Estimator error: ${(cal.relativeError >= 0 ? "+" : "") + (cal.relativeError * 100).toFixed(1) + "%"} ${cal.relativeError >= 0 ? "over" : "under"} measured`);
|
|
17183
|
+
lines.push(`Implied bytes per token: ${cal.bytesPerMeasuredToken.toFixed(2)} (against the 3.0 the estimate divides by)`);
|
|
17184
|
+
lines.push("Read the error as a bound, not a correction factor. Two effects inflate the measured side and neither is separable here: a prefix whose cache entry expires is written again and billed twice while the estimate counts it once, and the system prompt and tool schemas are sent on every call but appear in the transcript only in part. Both push the measured figure up, so an estimate below it is expected and only the magnitude carries information.");
|
|
17185
|
+
lines.push("");
|
|
16461
17186
|
lines.push("## Tool results by tool (estimated content size; calls = tool_use invocations)");
|
|
16462
17187
|
for (const t of s.tools.slice(0, 25)) {
|
|
16463
17188
|
lines.push(`${displaySafeText(t.name).padEnd(42)} calls ${fmt(t.calls).padStart(9)} bytes ${fmt(t.resultBytes).padStart(15)} est-tokens ${fmt(t.resultEstTokens).padStart(12)}`);
|
|
@@ -16524,267 +17249,17 @@ function formatSessionAudit(s) {
|
|
|
16524
17249
|
|
|
16525
17250
|
// src/session_read.ts
|
|
16526
17251
|
init_define_import_meta_env();
|
|
16527
|
-
import * as fs36 from "node:fs";
|
|
16528
|
-
import * as path33 from "node:path";
|
|
16529
|
-
import * as readline2 from "node:readline";
|
|
16530
|
-
|
|
16531
|
-
// src/waste.ts
|
|
16532
|
-
init_define_import_meta_env();
|
|
16533
17252
|
import * as fs35 from "node:fs";
|
|
16534
|
-
import * as os18 from "node:os";
|
|
16535
17253
|
import * as path32 from "node:path";
|
|
16536
|
-
|
|
16537
|
-
const rootStr = path32.resolve(projectRoot);
|
|
16538
|
-
const slug = rootStr.replace(/[^A-Za-z0-9]/g, "-");
|
|
16539
|
-
return path32.join(os18.homedir(), ".claude", "projects", slug);
|
|
16540
|
-
}
|
|
16541
|
-
function findLatestTranscript(projectRoot) {
|
|
16542
|
-
const dir = projectTranscriptsDir(projectRoot);
|
|
16543
|
-
let entries;
|
|
16544
|
-
try {
|
|
16545
|
-
entries = fs35.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
|
|
16546
|
-
} catch {
|
|
16547
|
-
return null;
|
|
16548
|
-
}
|
|
16549
|
-
let best = null;
|
|
16550
|
-
for (const name of entries) {
|
|
16551
|
-
const full = path32.join(dir, name);
|
|
16552
|
-
let stat;
|
|
16553
|
-
try {
|
|
16554
|
-
stat = fs35.statSync(full);
|
|
16555
|
-
} catch {
|
|
16556
|
-
continue;
|
|
16557
|
-
}
|
|
16558
|
-
if (best === null || stat.mtimeMs > best.mtimeMs) best = { file: full, mtimeMs: stat.mtimeMs };
|
|
16559
|
-
}
|
|
16560
|
-
return best?.file ?? null;
|
|
16561
|
-
}
|
|
16562
|
-
function safeStringify(value) {
|
|
16563
|
-
try {
|
|
16564
|
-
return JSON.stringify(value) ?? "";
|
|
16565
|
-
} catch {
|
|
16566
|
-
return "";
|
|
16567
|
-
}
|
|
16568
|
-
}
|
|
16569
|
-
var FILE_PATH_TOOLS = /* @__PURE__ */ new Set(["Read", "Edit", "Write", "NotebookEdit", "MultiEdit"]);
|
|
16570
|
-
function extractFilePath(name, input) {
|
|
16571
|
-
if (!FILE_PATH_TOOLS.has(name)) return null;
|
|
16572
|
-
if (input === null || typeof input !== "object") return null;
|
|
16573
|
-
const o = input;
|
|
16574
|
-
const fp = o["file_path"];
|
|
16575
|
-
if (typeof fp === "string") return fp;
|
|
16576
|
-
const notebookPath = o["notebook_path"];
|
|
16577
|
-
return typeof notebookPath === "string" ? notebookPath : null;
|
|
16578
|
-
}
|
|
16579
|
-
function extractCommand(name, input) {
|
|
16580
|
-
if (name !== "Bash") return null;
|
|
16581
|
-
if (input === null || typeof input !== "object") return null;
|
|
16582
|
-
const cmd = input["command"];
|
|
16583
|
-
return typeof cmd === "string" ? cmd : null;
|
|
16584
|
-
}
|
|
16585
|
-
function summarizeInput(name, input, filePath, command) {
|
|
16586
|
-
if (filePath !== null) return filePath;
|
|
16587
|
-
if (command !== null) return command.length > 100 ? `${command.slice(0, 100)}\u2026` : command;
|
|
16588
|
-
if (input === null || typeof input !== "object") return "";
|
|
16589
|
-
const o = input;
|
|
16590
|
-
for (const key of ["pattern", "query", "path", "description", "prompt"]) {
|
|
16591
|
-
const v = o[key];
|
|
16592
|
-
if (typeof v === "string") return v.length > 100 ? `${v.slice(0, 100)}\u2026` : v;
|
|
16593
|
-
}
|
|
16594
|
-
const s = safeStringify(input);
|
|
16595
|
-
return s.length > 100 ? `${s.slice(0, 100)}\u2026` : s;
|
|
16596
|
-
}
|
|
16597
|
-
function extractResultText(content) {
|
|
16598
|
-
if (typeof content === "string") return content;
|
|
16599
|
-
if (Array.isArray(content)) {
|
|
16600
|
-
return content.map((b) => {
|
|
16601
|
-
if (b !== null && typeof b === "object" && b["type"] === "text") {
|
|
16602
|
-
const t = b["text"];
|
|
16603
|
-
return typeof t === "string" ? t : "";
|
|
16604
|
-
}
|
|
16605
|
-
return "";
|
|
16606
|
-
}).join("\n");
|
|
16607
|
-
}
|
|
16608
|
-
return "";
|
|
16609
|
-
}
|
|
16610
|
-
function parseTranscript2(transcriptPath) {
|
|
16611
|
-
const raw = fs35.readFileSync(transcriptPath, "utf-8");
|
|
16612
|
-
const calls = [];
|
|
16613
|
-
const resultTextById = /* @__PURE__ */ new Map();
|
|
16614
|
-
const assistantTurns = [];
|
|
16615
|
-
const resident = createResidentContextStats();
|
|
16616
|
-
let seq = 0;
|
|
16617
|
-
for (const line of raw.split("\n")) {
|
|
16618
|
-
const trimmed = line.trim();
|
|
16619
|
-
if (trimmed === "") continue;
|
|
16620
|
-
let obj;
|
|
16621
|
-
try {
|
|
16622
|
-
obj = JSON.parse(trimmed);
|
|
16623
|
-
} catch {
|
|
16624
|
-
continue;
|
|
16625
|
-
}
|
|
16626
|
-
if (obj === null || typeof obj !== "object") continue;
|
|
16627
|
-
const o = obj;
|
|
16628
|
-
accumulateResidentLine(resident, o, trimmed.length);
|
|
16629
|
-
const message = o["message"];
|
|
16630
|
-
if (message === null || typeof message !== "object") continue;
|
|
16631
|
-
const messageObj = message;
|
|
16632
|
-
const content = messageObj["content"];
|
|
16633
|
-
if (!Array.isArray(content)) continue;
|
|
16634
|
-
const role = typeof messageObj["role"] === "string" ? messageObj["role"] : null;
|
|
16635
|
-
const cwd = typeof o["cwd"] === "string" ? o["cwd"] : null;
|
|
16636
|
-
let turnTextTokens = 0;
|
|
16637
|
-
let sawTextBlock = false;
|
|
16638
|
-
for (const block of content) {
|
|
16639
|
-
if (block === null || typeof block !== "object") continue;
|
|
16640
|
-
const b = block;
|
|
16641
|
-
if (b["type"] === "tool_use" && typeof b["id"] === "string" && typeof b["name"] === "string") {
|
|
16642
|
-
const name = b["name"];
|
|
16643
|
-
const input = b["input"];
|
|
16644
|
-
const filePath = extractFilePath(name, input);
|
|
16645
|
-
const command = extractCommand(name, input);
|
|
16646
|
-
calls.push({
|
|
16647
|
-
seq: seq++,
|
|
16648
|
-
id: b["id"],
|
|
16649
|
-
name,
|
|
16650
|
-
input,
|
|
16651
|
-
filePath,
|
|
16652
|
-
command,
|
|
16653
|
-
summary: summarizeInput(name, input, filePath, command),
|
|
16654
|
-
cwd
|
|
16655
|
-
});
|
|
16656
|
-
} else if (b["type"] === "tool_result" && typeof b["tool_use_id"] === "string") {
|
|
16657
|
-
const id = b["tool_use_id"];
|
|
16658
|
-
if (!resultTextById.has(id)) {
|
|
16659
|
-
resultTextById.set(id, extractResultText(b["content"]));
|
|
16660
|
-
}
|
|
16661
|
-
} else if (role === "assistant" && b["type"] === "text" && typeof b["text"] === "string") {
|
|
16662
|
-
sawTextBlock = true;
|
|
16663
|
-
turnTextTokens += estimateTokens(b["text"]);
|
|
16664
|
-
}
|
|
16665
|
-
}
|
|
16666
|
-
if (role === "assistant" && sawTextBlock) assistantTurns.push(turnTextTokens);
|
|
16667
|
-
}
|
|
16668
|
-
return { calls, resultTextById, assistantTurns, resident };
|
|
16669
|
-
}
|
|
16670
|
-
function costPerCall(parsed) {
|
|
16671
|
-
return parsed.calls.map((c) => ({
|
|
16672
|
-
...c,
|
|
16673
|
-
tokens: estimateTokens(parsed.resultTextById.get(c.id) ?? "")
|
|
16674
|
-
}));
|
|
16675
|
-
}
|
|
16676
|
-
function aggregateSortedDesc(pairs) {
|
|
16677
|
-
return [...pairs].map(([key, tokens]) => ({ key, tokens })).sort((a, b) => b.tokens - a.tokens);
|
|
16678
|
-
}
|
|
16679
|
-
function tokensByTool(costs) {
|
|
16680
|
-
const m = /* @__PURE__ */ new Map();
|
|
16681
|
-
for (const c of costs) m.set(c.name, (m.get(c.name) ?? 0) + c.tokens);
|
|
16682
|
-
return aggregateSortedDesc(m);
|
|
16683
|
-
}
|
|
16684
|
-
function tokensByFile(costs) {
|
|
16685
|
-
const m = /* @__PURE__ */ new Map();
|
|
16686
|
-
for (const c of costs) {
|
|
16687
|
-
if (c.filePath === null) continue;
|
|
16688
|
-
m.set(c.filePath, (m.get(c.filePath) ?? 0) + c.tokens);
|
|
16689
|
-
}
|
|
16690
|
-
return aggregateSortedDesc(m);
|
|
16691
|
-
}
|
|
16692
|
-
function topExpensiveCalls(costs, n) {
|
|
16693
|
-
return [...costs].sort((a, b) => b.tokens - a.tokens).slice(0, n);
|
|
16694
|
-
}
|
|
16695
|
-
function neverTouchedAgain(costs) {
|
|
16696
|
-
const readsByFile = /* @__PURE__ */ new Map();
|
|
16697
|
-
for (const c of costs) {
|
|
16698
|
-
if (c.name !== "Read" || c.filePath === null) continue;
|
|
16699
|
-
const arr = readsByFile.get(c.filePath) ?? [];
|
|
16700
|
-
arr.push(c);
|
|
16701
|
-
readsByFile.set(c.filePath, arr);
|
|
16702
|
-
}
|
|
16703
|
-
const out2 = [];
|
|
16704
|
-
for (const [filePath, reads] of readsByFile) {
|
|
16705
|
-
const firstSeq = Math.min(...reads.map((r) => r.seq));
|
|
16706
|
-
const touchedLater = costs.some((c) => {
|
|
16707
|
-
if (c.seq <= firstSeq) return false;
|
|
16708
|
-
if (c.filePath === filePath) return true;
|
|
16709
|
-
return safeStringify(c.input).includes(filePath);
|
|
16710
|
-
});
|
|
16711
|
-
if (!touchedLater) {
|
|
16712
|
-
out2.push({ filePath, tokens: reads.reduce((n, r) => n + r.tokens, 0) });
|
|
16713
|
-
}
|
|
16714
|
-
}
|
|
16715
|
-
return out2.sort((a, b) => b.tokens - a.tokens);
|
|
16716
|
-
}
|
|
16717
|
-
async function repeatedUncompressedBashCommands(costs) {
|
|
16718
|
-
const groups = /* @__PURE__ */ new Map();
|
|
16719
|
-
for (const c of costs) {
|
|
16720
|
-
if (c.name !== "Bash" || c.command === null) continue;
|
|
16721
|
-
const norm = normalizeCommandForCacheKey(c.command);
|
|
16722
|
-
const arr = groups.get(norm) ?? [];
|
|
16723
|
-
arr.push(c);
|
|
16724
|
-
groups.set(norm, arr);
|
|
16725
|
-
}
|
|
16726
|
-
const out2 = [];
|
|
16727
|
-
for (const [norm, calls] of groups) {
|
|
16728
|
-
if (calls.length < 2) continue;
|
|
16729
|
-
let cached = false;
|
|
16730
|
-
for (const c of calls) {
|
|
16731
|
-
try {
|
|
16732
|
-
const hash = await commandHash(c.command, c.cwd);
|
|
16733
|
-
if (getBashOutput(hash) !== null) {
|
|
16734
|
-
cached = true;
|
|
16735
|
-
break;
|
|
16736
|
-
}
|
|
16737
|
-
} catch {
|
|
16738
|
-
}
|
|
16739
|
-
}
|
|
16740
|
-
if (cached) continue;
|
|
16741
|
-
const totalTokens = calls.reduce((n, c) => n + c.tokens, 0);
|
|
16742
|
-
out2.push({
|
|
16743
|
-
normalized: norm,
|
|
16744
|
-
count: calls.length,
|
|
16745
|
-
totalTokens,
|
|
16746
|
-
avgTokens: Math.round(totalTokens / calls.length)
|
|
16747
|
-
});
|
|
16748
|
-
}
|
|
16749
|
-
return out2.sort((a, b) => b.totalTokens - a.totalTokens);
|
|
16750
|
-
}
|
|
16751
|
-
function assistantOutputCost(assistantTurns) {
|
|
16752
|
-
const turnCount = assistantTurns.length;
|
|
16753
|
-
let generatedTokens = 0;
|
|
16754
|
-
let resendCeilingTokens = 0;
|
|
16755
|
-
for (let i = 0; i < turnCount; i++) {
|
|
16756
|
-
const tokens = assistantTurns[i] ?? 0;
|
|
16757
|
-
generatedTokens += tokens;
|
|
16758
|
-
resendCeilingTokens += tokens * (turnCount - 1 - i);
|
|
16759
|
-
}
|
|
16760
|
-
return { turnCount, generatedTokens, resendCeilingTokens };
|
|
16761
|
-
}
|
|
16762
|
-
async function buildWasteReport(transcriptPath, opts = {}) {
|
|
16763
|
-
const parsed = parseTranscript2(transcriptPath);
|
|
16764
|
-
const costs = costPerCall(parsed);
|
|
16765
|
-
const topN = opts.topN ?? 10;
|
|
16766
|
-
return {
|
|
16767
|
-
transcriptPath,
|
|
16768
|
-
totalTokens: costs.reduce((n, c) => n + c.tokens, 0),
|
|
16769
|
-
tokensByTool: tokensByTool(costs),
|
|
16770
|
-
tokensByFile: tokensByFile(costs),
|
|
16771
|
-
topCalls: topExpensiveCalls(costs, topN).map((c) => ({ seq: c.seq, name: c.name, summary: c.summary, tokens: c.tokens })),
|
|
16772
|
-
neverTouchedAgain: neverTouchedAgain(costs),
|
|
16773
|
-
repeatedUncompressedBash: await repeatedUncompressedBashCommands(costs),
|
|
16774
|
-
assistantOutput: assistantOutputCost(parsed.assistantTurns),
|
|
16775
|
-
residentContext: summarizeResidentContext(parsed.resident)
|
|
16776
|
-
};
|
|
16777
|
-
}
|
|
16778
|
-
|
|
16779
|
-
// src/session_read.ts
|
|
17254
|
+
import * as readline2 from "node:readline";
|
|
16780
17255
|
function resolveSessionTranscript(arg, opts = {}) {
|
|
16781
17256
|
if (arg !== void 0 && arg !== "") {
|
|
16782
|
-
if (
|
|
17257
|
+
if (fs35.existsSync(arg) && fs35.statSync(arg).isFile()) return arg;
|
|
16783
17258
|
const projectRoot2 = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
|
|
16784
17259
|
const dir = projectTranscriptsDir(projectRoot2);
|
|
16785
17260
|
const byId = arg.endsWith(".jsonl") ? arg : `${arg}.jsonl`;
|
|
16786
|
-
const candidate =
|
|
16787
|
-
if (
|
|
17261
|
+
const candidate = path32.join(dir, byId);
|
|
17262
|
+
if (fs35.existsSync(candidate) && fs35.statSync(candidate).isFile()) return candidate;
|
|
16788
17263
|
return null;
|
|
16789
17264
|
}
|
|
16790
17265
|
const projectRoot = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
|
|
@@ -16842,7 +17317,7 @@ function toolCallsForBlocks(blocks) {
|
|
|
16842
17317
|
return blocks.filter((b) => b.type === "tool_use" && b.name !== void 0).map((b) => b.name);
|
|
16843
17318
|
}
|
|
16844
17319
|
async function* streamTurns(transcriptPath) {
|
|
16845
|
-
const input =
|
|
17320
|
+
const input = fs35.createReadStream(transcriptPath, { encoding: "utf8" });
|
|
16846
17321
|
const rl = readline2.createInterface({ input, crlfDelay: Infinity });
|
|
16847
17322
|
try {
|
|
16848
17323
|
let lineNumber = 0;
|
|
@@ -16941,9 +17416,9 @@ function formatSessionSlice(turns) {
|
|
|
16941
17416
|
|
|
16942
17417
|
// src/cli_bootstrap_audit.ts
|
|
16943
17418
|
init_define_import_meta_env();
|
|
16944
|
-
import * as
|
|
16945
|
-
import * as
|
|
16946
|
-
import * as
|
|
17419
|
+
import * as fs36 from "node:fs";
|
|
17420
|
+
import * as os17 from "node:os";
|
|
17421
|
+
import * as path33 from "node:path";
|
|
16947
17422
|
import * as readline3 from "node:readline";
|
|
16948
17423
|
var FRONTMATTER_KEYS = /* @__PURE__ */ new Set(["description", "tools", "allowed-tools", "allowed_tools"]);
|
|
16949
17424
|
function parseBudget(name, value) {
|
|
@@ -16952,7 +17427,7 @@ function parseBudget(name, value) {
|
|
|
16952
17427
|
return value;
|
|
16953
17428
|
}
|
|
16954
17429
|
async function readFrontmatter(filePath) {
|
|
16955
|
-
const stream =
|
|
17430
|
+
const stream = fs36.createReadStream(filePath, { encoding: "utf8" });
|
|
16956
17431
|
const lines = readline3.createInterface({ input: stream, crlfDelay: Infinity });
|
|
16957
17432
|
let first = true;
|
|
16958
17433
|
let inFrontmatter = false;
|
|
@@ -16998,44 +17473,44 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
|
|
|
16998
17473
|
let logicalRoot;
|
|
16999
17474
|
let rootIsLink;
|
|
17000
17475
|
try {
|
|
17001
|
-
rootIsLink = (await
|
|
17002
|
-
logicalRoot = await
|
|
17476
|
+
rootIsLink = (await fs36.promises.lstat(root)).isSymbolicLink();
|
|
17477
|
+
logicalRoot = await fs36.promises.realpath(root);
|
|
17003
17478
|
} catch (error) {
|
|
17004
17479
|
if (error.code === "ENOENT") return entries;
|
|
17005
|
-
diagnostics.push({ path:
|
|
17480
|
+
diagnostics.push({ path: path33.resolve(root), reason: "unreadable root" });
|
|
17006
17481
|
return entries;
|
|
17007
17482
|
}
|
|
17008
17483
|
if (rootIsLink && !followLinks) {
|
|
17009
|
-
diagnostics.push({ path:
|
|
17484
|
+
diagnostics.push({ path: path33.resolve(root), reason: "external root link skipped (use --follow-links)" });
|
|
17010
17485
|
return entries;
|
|
17011
17486
|
}
|
|
17012
17487
|
const canonicalKey = (candidate) => process.platform === "win32" ? candidate.toLocaleLowerCase() : candidate;
|
|
17013
17488
|
const isContained = (candidate, allowedRoot) => {
|
|
17014
|
-
const relative3 =
|
|
17015
|
-
return relative3 === "" || relative3 !== ".." && !relative3.startsWith(`..${
|
|
17489
|
+
const relative3 = path33.relative(canonicalKey(allowedRoot), canonicalKey(candidate));
|
|
17490
|
+
return relative3 === "" || relative3 !== ".." && !relative3.startsWith(`..${path33.sep}`) && !path33.isAbsolute(relative3);
|
|
17016
17491
|
};
|
|
17017
17492
|
async function visit(candidate, allowedRoot, topLevel = false) {
|
|
17018
17493
|
let real;
|
|
17019
17494
|
let candidateIsLink;
|
|
17020
17495
|
try {
|
|
17021
|
-
candidateIsLink = (await
|
|
17022
|
-
real = await
|
|
17496
|
+
candidateIsLink = (await fs36.promises.lstat(candidate)).isSymbolicLink();
|
|
17497
|
+
real = await fs36.promises.realpath(candidate);
|
|
17023
17498
|
} catch {
|
|
17024
|
-
diagnostics.push({ path:
|
|
17499
|
+
diagnostics.push({ path: path33.resolve(candidate), reason: "broken or unreadable link" });
|
|
17025
17500
|
return;
|
|
17026
17501
|
}
|
|
17027
17502
|
const withinAllowedRoot = allowedRoot === void 0 ? isContained(real, logicalRoot) : isContained(real, allowedRoot);
|
|
17028
17503
|
const canFollowExternal = followLinks && topLevel;
|
|
17029
17504
|
if (!withinAllowedRoot && !canFollowExternal) {
|
|
17030
|
-
diagnostics.push({ path:
|
|
17505
|
+
diagnostics.push({ path: path33.resolve(candidate), reason: candidateIsLink ? "external link skipped (use --follow-links)" : "canonical target outside scan root" });
|
|
17031
17506
|
return;
|
|
17032
17507
|
}
|
|
17033
17508
|
const trustedRoot = !withinAllowedRoot && canFollowExternal ? real : allowedRoot ?? real;
|
|
17034
17509
|
let stat;
|
|
17035
17510
|
try {
|
|
17036
|
-
stat = await
|
|
17511
|
+
stat = await fs36.promises.stat(real);
|
|
17037
17512
|
} catch {
|
|
17038
|
-
diagnostics.push({ path:
|
|
17513
|
+
diagnostics.push({ path: path33.resolve(candidate), reason: "unreadable" });
|
|
17039
17514
|
return;
|
|
17040
17515
|
}
|
|
17041
17516
|
const canonical = process.platform === "win32" ? real.toLocaleLowerCase() : real;
|
|
@@ -17044,16 +17519,16 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
|
|
|
17044
17519
|
visitedDirs.add(canonical);
|
|
17045
17520
|
let children;
|
|
17046
17521
|
try {
|
|
17047
|
-
children = await
|
|
17522
|
+
children = await fs36.promises.readdir(real, { withFileTypes: true });
|
|
17048
17523
|
} catch {
|
|
17049
|
-
diagnostics.push({ path:
|
|
17524
|
+
diagnostics.push({ path: path33.resolve(candidate), reason: "unreadable directory" });
|
|
17050
17525
|
return;
|
|
17051
17526
|
}
|
|
17052
17527
|
children.sort((a, b) => a.name.localeCompare(b.name));
|
|
17053
|
-
for (const child of children) await visit(
|
|
17528
|
+
for (const child of children) await visit(path33.join(real, child.name), trustedRoot);
|
|
17054
17529
|
return;
|
|
17055
17530
|
}
|
|
17056
|
-
if (!stat.isFile() ||
|
|
17531
|
+
if (!stat.isFile() || path33.extname(real).toLowerCase() !== ".md") return;
|
|
17057
17532
|
if (seenFiles.has(canonical)) return;
|
|
17058
17533
|
seenFiles.add(canonical);
|
|
17059
17534
|
try {
|
|
@@ -17063,35 +17538,35 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
|
|
|
17063
17538
|
const toolsBytes = Buffer.byteLength(metadata.tools, "utf8");
|
|
17064
17539
|
entries.push({
|
|
17065
17540
|
kind,
|
|
17066
|
-
path:
|
|
17541
|
+
path: path33.resolve(real),
|
|
17067
17542
|
description_bytes: descriptionBytes,
|
|
17068
17543
|
tools_bytes: toolsBytes,
|
|
17069
17544
|
metadata_bytes: descriptionBytes + toolsBytes,
|
|
17070
17545
|
estimated_tokens: Math.floor((descriptionBytes + toolsBytes) / 4)
|
|
17071
17546
|
});
|
|
17072
17547
|
} catch {
|
|
17073
|
-
diagnostics.push({ path:
|
|
17548
|
+
diagnostics.push({ path: path33.resolve(candidate), reason: "unreadable file" });
|
|
17074
17549
|
}
|
|
17075
17550
|
}
|
|
17076
17551
|
try {
|
|
17077
|
-
const children = await
|
|
17552
|
+
const children = await fs36.promises.readdir(logicalRoot, { withFileTypes: true });
|
|
17078
17553
|
children.sort((a, b) => a.name.localeCompare(b.name));
|
|
17079
|
-
for (const child of children) await visit(
|
|
17554
|
+
for (const child of children) await visit(path33.join(logicalRoot, child.name), void 0, true);
|
|
17080
17555
|
} catch {
|
|
17081
|
-
diagnostics.push({ path:
|
|
17556
|
+
diagnostics.push({ path: path33.resolve(root), reason: "unreadable root" });
|
|
17082
17557
|
}
|
|
17083
17558
|
return entries;
|
|
17084
17559
|
}
|
|
17085
17560
|
async function buildBootstrapAudit(opts = {}) {
|
|
17086
17561
|
const project = resolveProjectRoot(opts.project === void 0 ? {} : { project: opts.project });
|
|
17087
|
-
const home =
|
|
17562
|
+
const home = path33.resolve(opts.home ?? os17.homedir());
|
|
17088
17563
|
const context = buildStats(project, home, opts.project === void 0 ? [] : [opts.project]);
|
|
17089
17564
|
const diagnostics = [];
|
|
17090
17565
|
const top = opts.top === void 0 ? 10 : parseBudget("--top", opts.top) ?? 10;
|
|
17091
17566
|
const visitedDirs = /* @__PURE__ */ new Set();
|
|
17092
17567
|
const seenFiles = /* @__PURE__ */ new Set();
|
|
17093
|
-
const agents = await scanMetadataRoot(
|
|
17094
|
-
const skills = await scanMetadataRoot(
|
|
17568
|
+
const agents = await scanMetadataRoot(path33.join(claudeConfigDir(home), "agents"), "agent", diagnostics, visitedDirs, seenFiles, opts.followLinks === true);
|
|
17569
|
+
const skills = await scanMetadataRoot(path33.join(claudeConfigDir(home), "skills"), "skill", diagnostics, visitedDirs, seenFiles, opts.followLinks === true);
|
|
17095
17570
|
const rankedEntries = [...agents, ...skills].sort((a, b) => b.metadata_bytes - a.metadata_bytes || a.path.localeCompare(b.path));
|
|
17096
17571
|
const largestTotal = rankedEntries.length;
|
|
17097
17572
|
const largest = rankedEntries.slice(0, top);
|
|
@@ -17158,8 +17633,8 @@ async function runBootstrapAudit(opts = {}) {
|
|
|
17158
17633
|
|
|
17159
17634
|
// src/cli_memory.ts
|
|
17160
17635
|
init_define_import_meta_env();
|
|
17161
|
-
import * as
|
|
17162
|
-
import * as
|
|
17636
|
+
import * as fs37 from "node:fs";
|
|
17637
|
+
import * as path34 from "node:path";
|
|
17163
17638
|
function removeExactDupLines(text, report) {
|
|
17164
17639
|
if (report.exactDupLines.length === 0) return text;
|
|
17165
17640
|
const toRemove = new Set(report.exactDupLines.map(([, dupIdx]) => dupIdx));
|
|
@@ -17217,7 +17692,7 @@ function printReport(reports, clusters) {
|
|
|
17217
17692
|
} else {
|
|
17218
17693
|
for (const cluster of clusters) {
|
|
17219
17694
|
w(
|
|
17220
|
-
` [${cluster.method}, similarity ${cluster.similarity}, ~${cluster.tokens} tok] ${cluster.members.map((m) => displaySafeText(
|
|
17695
|
+
` [${cluster.method}, similarity ${cluster.similarity}, ~${cluster.tokens} tok] ${cluster.members.map((m) => displaySafeText(path34.basename(m))).join(", ")}
|
|
17221
17696
|
`
|
|
17222
17697
|
);
|
|
17223
17698
|
}
|
|
@@ -17232,7 +17707,7 @@ async function runMemoryCommand(opts = {}) {
|
|
|
17232
17707
|
const claudeMds = findClaudeMdFiles(projectRoot);
|
|
17233
17708
|
const reports = auditClaudeMd(claudeMds);
|
|
17234
17709
|
const memoryMdPath = findMemoryMd(projectRoot);
|
|
17235
|
-
const clusters = memoryMdPath !== null ? await findContentDuplicates(
|
|
17710
|
+
const clusters = memoryMdPath !== null ? await findContentDuplicates(path34.dirname(memoryMdPath)) : [];
|
|
17236
17711
|
printReport(reports, clusters);
|
|
17237
17712
|
if (opts.fix !== true) return;
|
|
17238
17713
|
const changes = [];
|
|
@@ -17240,7 +17715,7 @@ async function runMemoryCommand(opts = {}) {
|
|
|
17240
17715
|
if (report.exactDupLines.length === 0) continue;
|
|
17241
17716
|
let before;
|
|
17242
17717
|
try {
|
|
17243
|
-
before =
|
|
17718
|
+
before = fs37.readFileSync(report.path, "utf-8");
|
|
17244
17719
|
} catch {
|
|
17245
17720
|
continue;
|
|
17246
17721
|
}
|
|
@@ -17278,142 +17753,8 @@ async function runMemoryCommand(opts = {}) {
|
|
|
17278
17753
|
|
|
17279
17754
|
// src/cli_waste.ts
|
|
17280
17755
|
init_define_import_meta_env();
|
|
17281
|
-
import * as
|
|
17282
|
-
import * as
|
|
17283
|
-
|
|
17284
|
-
// src/copilot_waste.ts
|
|
17285
|
-
init_define_import_meta_env();
|
|
17286
|
-
import * as fs39 from "node:fs";
|
|
17287
|
-
import * as path36 from "node:path";
|
|
17288
|
-
var HOOK_RECORD_TYPES = /* @__PURE__ */ new Set(["hook.start", "hook.end"]);
|
|
17289
|
-
function splitInjectedBlocks(transformed) {
|
|
17290
|
-
const out2 = [];
|
|
17291
|
-
const blockRe = /<([a-z_][a-z0-9_]*)>([\s\S]*?)<\/\1>/gi;
|
|
17292
|
-
let m;
|
|
17293
|
-
while ((m = blockRe.exec(transformed)) !== null) {
|
|
17294
|
-
const tag = (m[1] ?? "").toLowerCase();
|
|
17295
|
-
const body = m[2] ?? "";
|
|
17296
|
-
let kind = tag;
|
|
17297
|
-
if (tag === "system_reminder") {
|
|
17298
|
-
const inner = /^\s*<([a-z_][a-z0-9_]*)>/i.exec(body);
|
|
17299
|
-
kind = inner !== null ? `reminder:${(inner[1] ?? "").toLowerCase()}` : "reminder:text";
|
|
17300
|
-
}
|
|
17301
|
-
out2.push({ kind, body: m[0] });
|
|
17302
|
-
}
|
|
17303
|
-
return out2;
|
|
17304
|
-
}
|
|
17305
|
-
function findLatestCopilotSession() {
|
|
17306
|
-
const root = path36.join(copilotCliUserRoot(), "session-state");
|
|
17307
|
-
let entries;
|
|
17308
|
-
try {
|
|
17309
|
-
entries = fs39.readdirSync(root);
|
|
17310
|
-
} catch {
|
|
17311
|
-
return null;
|
|
17312
|
-
}
|
|
17313
|
-
let best = null;
|
|
17314
|
-
let bestMtime = -Infinity;
|
|
17315
|
-
for (const entry of entries) {
|
|
17316
|
-
const candidate = path36.join(root, entry, "events.jsonl");
|
|
17317
|
-
try {
|
|
17318
|
-
const st = fs39.statSync(candidate);
|
|
17319
|
-
if (!st.isFile()) continue;
|
|
17320
|
-
if (st.mtimeMs > bestMtime) {
|
|
17321
|
-
bestMtime = st.mtimeMs;
|
|
17322
|
-
best = candidate;
|
|
17323
|
-
}
|
|
17324
|
-
} catch {
|
|
17325
|
-
}
|
|
17326
|
-
}
|
|
17327
|
-
return best;
|
|
17328
|
-
}
|
|
17329
|
-
function readNumber(source, key) {
|
|
17330
|
-
const value = source[key];
|
|
17331
|
-
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
17332
|
-
}
|
|
17333
|
-
function buildCopilotWasteReport(eventsPath) {
|
|
17334
|
-
const raw = fs39.readFileSync(eventsPath, "utf-8");
|
|
17335
|
-
const report = {
|
|
17336
|
-
sessionPath: eventsPath,
|
|
17337
|
-
sessionId: path36.basename(path36.dirname(eventsPath)),
|
|
17338
|
-
turns: 0,
|
|
17339
|
-
tokens: null,
|
|
17340
|
-
blocks: [],
|
|
17341
|
-
compactions: [],
|
|
17342
|
-
hookRecordBytes: 0,
|
|
17343
|
-
totalEventBytes: Buffer.byteLength(raw, "utf-8"),
|
|
17344
|
-
// Read through the real resolution chain rather than passed in. Copilot's
|
|
17345
|
-
// own COPILOT_CACHE_HOME override is what tests point at a fixture, so the
|
|
17346
|
-
// shipping path is the tested path and there is no seam here that only a
|
|
17347
|
-
// test ever supplies.
|
|
17348
|
-
mcpTools: readCopilotMcpTools()
|
|
17349
|
-
};
|
|
17350
|
-
const classes = /* @__PURE__ */ new Map();
|
|
17351
|
-
const seen = /* @__PURE__ */ new Map();
|
|
17352
|
-
for (const line of raw.split("\n")) {
|
|
17353
|
-
const trimmed = line.trim();
|
|
17354
|
-
if (trimmed === "") continue;
|
|
17355
|
-
let event;
|
|
17356
|
-
try {
|
|
17357
|
-
event = JSON.parse(trimmed);
|
|
17358
|
-
} catch {
|
|
17359
|
-
continue;
|
|
17360
|
-
}
|
|
17361
|
-
const type = typeof event["type"] === "string" ? event["type"] : "";
|
|
17362
|
-
const data = event["data"] ?? {};
|
|
17363
|
-
if (HOOK_RECORD_TYPES.has(type)) {
|
|
17364
|
-
report.hookRecordBytes += Buffer.byteLength(trimmed, "utf-8");
|
|
17365
|
-
continue;
|
|
17366
|
-
}
|
|
17367
|
-
if (type === "session.shutdown") {
|
|
17368
|
-
report.tokens = {
|
|
17369
|
-
systemTokens: readNumber(data, "systemTokens"),
|
|
17370
|
-
toolDefinitionsTokens: readNumber(data, "toolDefinitionsTokens"),
|
|
17371
|
-
conversationTokens: readNumber(data, "conversationTokens"),
|
|
17372
|
-
currentTokens: readNumber(data, "currentTokens")
|
|
17373
|
-
};
|
|
17374
|
-
continue;
|
|
17375
|
-
}
|
|
17376
|
-
if (type === "session.compaction_complete") {
|
|
17377
|
-
const summary = typeof data["summaryContent"] === "string" ? data["summaryContent"] : "";
|
|
17378
|
-
report.compactions.push({
|
|
17379
|
-
trigger: typeof data["trigger"] === "string" ? data["trigger"] : "unknown",
|
|
17380
|
-
summaryBytes: Buffer.byteLength(summary, "utf-8"),
|
|
17381
|
-
preTokens: readNumber(data, "preCompactionTokens"),
|
|
17382
|
-
postTokens: readNumber(data, "postCompactionTokens")
|
|
17383
|
-
});
|
|
17384
|
-
continue;
|
|
17385
|
-
}
|
|
17386
|
-
if (type !== "user.message") continue;
|
|
17387
|
-
const transformed = typeof data["transformedContent"] === "string" ? data["transformedContent"] : "";
|
|
17388
|
-
if (transformed === "") continue;
|
|
17389
|
-
report.turns += 1;
|
|
17390
|
-
for (const block of splitInjectedBlocks(transformed)) {
|
|
17391
|
-
const bytes = Buffer.byteLength(block.body, "utf-8");
|
|
17392
|
-
let cls = classes.get(block.kind);
|
|
17393
|
-
if (cls === void 0) {
|
|
17394
|
-
cls = { kind: block.kind, count: 0, bytes: 0, repeatBytes: 0, repeatCount: 0 };
|
|
17395
|
-
classes.set(block.kind, cls);
|
|
17396
|
-
}
|
|
17397
|
-
cls.count += 1;
|
|
17398
|
-
cls.bytes += bytes;
|
|
17399
|
-
let bucket = seen.get(block.kind);
|
|
17400
|
-
if (bucket === void 0) {
|
|
17401
|
-
bucket = /* @__PURE__ */ new Set();
|
|
17402
|
-
seen.set(block.kind, bucket);
|
|
17403
|
-
}
|
|
17404
|
-
if (bucket.has(block.body)) {
|
|
17405
|
-
cls.repeatBytes += bytes;
|
|
17406
|
-
cls.repeatCount += 1;
|
|
17407
|
-
} else {
|
|
17408
|
-
bucket.add(block.body);
|
|
17409
|
-
}
|
|
17410
|
-
}
|
|
17411
|
-
}
|
|
17412
|
-
report.blocks = [...classes.values()].sort((a, b) => b.bytes - a.bytes);
|
|
17413
|
-
return report;
|
|
17414
|
-
}
|
|
17415
|
-
|
|
17416
|
-
// src/cli_waste.ts
|
|
17756
|
+
import * as fs38 from "node:fs";
|
|
17757
|
+
import * as path35 from "node:path";
|
|
17417
17758
|
function printCopilotReport(report) {
|
|
17418
17759
|
const w = (text) => {
|
|
17419
17760
|
process.stdout.write(text);
|
|
@@ -17612,9 +17953,50 @@ function printResidentContext(resident, w) {
|
|
|
17612
17953
|
w(resident.compactionCount === 0 ? "\n" : ", each re-injecting the fixed preamble (CLAUDE.md, memory, skill and agent listings)\n");
|
|
17613
17954
|
}
|
|
17614
17955
|
async function runWasteCommand(opts = {}) {
|
|
17956
|
+
const projectRoot = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
|
|
17957
|
+
if (opts.transcript !== void 0) {
|
|
17958
|
+
const resolvedPath = path35.resolve(opts.transcript);
|
|
17959
|
+
if (!fs38.existsSync(resolvedPath)) {
|
|
17960
|
+
if (opts.copilot === true) {
|
|
17961
|
+
const detail = `Copilot session event log not found: ${resolvedPath}`;
|
|
17962
|
+
if (opts.json === true) {
|
|
17963
|
+
process.stdout.write(`${displaySafeJson({ error: detail }, 0)}
|
|
17964
|
+
`);
|
|
17965
|
+
} else {
|
|
17966
|
+
process.stdout.write("\n# token-goat waste (Copilot CLI)\n");
|
|
17967
|
+
process.stdout.write(`${detail}
|
|
17968
|
+
`);
|
|
17969
|
+
}
|
|
17970
|
+
process.exitCode = 1;
|
|
17971
|
+
return;
|
|
17972
|
+
}
|
|
17973
|
+
process.stderr.write(`token-goat: transcript not found: ${resolvedPath}
|
|
17974
|
+
`);
|
|
17975
|
+
process.exitCode = 1;
|
|
17976
|
+
return;
|
|
17977
|
+
}
|
|
17978
|
+
if (opts.copilot === true || isCopilotTranscript(resolvedPath)) {
|
|
17979
|
+
const copilotReport = buildCopilotWasteReport(resolvedPath);
|
|
17980
|
+
if (opts.json === true) {
|
|
17981
|
+
process.stdout.write(`${displaySafeJson(copilotReport, 0)}
|
|
17982
|
+
`);
|
|
17983
|
+
return;
|
|
17984
|
+
}
|
|
17985
|
+
printCopilotReport(copilotReport);
|
|
17986
|
+
return;
|
|
17987
|
+
}
|
|
17988
|
+
const report2 = await buildWasteReport(resolvedPath, opts.top !== void 0 ? { topN: opts.top } : {});
|
|
17989
|
+
if (opts.json === true) {
|
|
17990
|
+
process.stdout.write(`${displaySafeJson(report2, 0)}
|
|
17991
|
+
`);
|
|
17992
|
+
return;
|
|
17993
|
+
}
|
|
17994
|
+
printReport2(report2);
|
|
17995
|
+
return;
|
|
17996
|
+
}
|
|
17615
17997
|
if (opts.copilot === true) {
|
|
17616
|
-
const eventsPath = opts.
|
|
17617
|
-
if (eventsPath === null || !
|
|
17998
|
+
const eventsPath = opts.project !== void 0 ? findLatestCopilotSession({ projectRoot }) : findLatestCopilotSession({ projectRoot }) ?? findLatestCopilotSession();
|
|
17999
|
+
if (eventsPath === null || !fs38.existsSync(eventsPath)) {
|
|
17618
18000
|
const detail = eventsPath === null ? "no Copilot CLI session found under <copilot-home>/session-state" : `Copilot session event log not found: ${eventsPath}`;
|
|
17619
18001
|
if (opts.json === true) {
|
|
17620
18002
|
process.stdout.write(`${displaySafeJson({ error: detail }, 0)}
|
|
@@ -17636,9 +18018,8 @@ async function runWasteCommand(opts = {}) {
|
|
|
17636
18018
|
printCopilotReport(copilotReport);
|
|
17637
18019
|
return;
|
|
17638
18020
|
}
|
|
17639
|
-
const
|
|
17640
|
-
|
|
17641
|
-
if (transcriptPath === null) {
|
|
18021
|
+
const detected = findProjectSession(projectRoot);
|
|
18022
|
+
if (detected === null) {
|
|
17642
18023
|
if (opts.json === true) {
|
|
17643
18024
|
process.stdout.write(`${displaySafeJson({ error: "no session transcript found", project: projectRoot }, 0)}
|
|
17644
18025
|
`);
|
|
@@ -17651,13 +18032,17 @@ async function runWasteCommand(opts = {}) {
|
|
|
17651
18032
|
process.exitCode = 1;
|
|
17652
18033
|
return;
|
|
17653
18034
|
}
|
|
17654
|
-
if (
|
|
17655
|
-
|
|
18035
|
+
if (detected.kind === "copilot") {
|
|
18036
|
+
const copilotReport = buildCopilotWasteReport(detected.path);
|
|
18037
|
+
if (opts.json === true) {
|
|
18038
|
+
process.stdout.write(`${displaySafeJson(copilotReport, 0)}
|
|
17656
18039
|
`);
|
|
17657
|
-
|
|
18040
|
+
return;
|
|
18041
|
+
}
|
|
18042
|
+
printCopilotReport(copilotReport);
|
|
17658
18043
|
return;
|
|
17659
18044
|
}
|
|
17660
|
-
const report = await buildWasteReport(
|
|
18045
|
+
const report = await buildWasteReport(detected.path, opts.top !== void 0 ? { topN: opts.top } : {});
|
|
17661
18046
|
if (opts.json === true) {
|
|
17662
18047
|
process.stdout.write(`${displaySafeJson(report, 0)}
|
|
17663
18048
|
`);
|
|
@@ -17668,13 +18053,13 @@ async function runWasteCommand(opts = {}) {
|
|
|
17668
18053
|
|
|
17669
18054
|
// src/cli_mcp_audit.ts
|
|
17670
18055
|
init_define_import_meta_env();
|
|
17671
|
-
import * as
|
|
17672
|
-
import * as
|
|
17673
|
-
import * as
|
|
18056
|
+
import * as fs39 from "node:fs";
|
|
18057
|
+
import * as os18 from "node:os";
|
|
18058
|
+
import * as path36 from "node:path";
|
|
17674
18059
|
function readMcpJsonFile(configPath2) {
|
|
17675
18060
|
try {
|
|
17676
|
-
if (!
|
|
17677
|
-
const content =
|
|
18061
|
+
if (!fs39.existsSync(configPath2)) return null;
|
|
18062
|
+
const content = fs39.readFileSync(configPath2, "utf-8");
|
|
17678
18063
|
const parsed = JSON.parse(content);
|
|
17679
18064
|
const servers = parsed && typeof parsed === "object" ? parsed.mcpServers ?? ("servers" in parsed ? null : parsed) : null;
|
|
17680
18065
|
return servers && typeof servers === "object" ? servers : null;
|
|
@@ -17690,8 +18075,8 @@ function driveLetterCaseVariants(p) {
|
|
|
17690
18075
|
}
|
|
17691
18076
|
function readClaudeJsonConfig(claudeJsonPath, projectRoot) {
|
|
17692
18077
|
try {
|
|
17693
|
-
if (!
|
|
17694
|
-
const parsed = JSON.parse(
|
|
18078
|
+
if (!fs39.existsSync(claudeJsonPath)) return null;
|
|
18079
|
+
const parsed = JSON.parse(fs39.readFileSync(claudeJsonPath, "utf-8"));
|
|
17695
18080
|
const projects = parsed && typeof parsed === "object" ? parsed.projects : null;
|
|
17696
18081
|
if (!projects || typeof projects !== "object") return null;
|
|
17697
18082
|
const slashForms = [projectRoot, projectRoot.replace(/\\/g, "/"), projectRoot.replace(/\//g, "\\")];
|
|
@@ -17709,9 +18094,9 @@ function readClaudeJsonConfig(claudeJsonPath, projectRoot) {
|
|
|
17709
18094
|
}
|
|
17710
18095
|
}
|
|
17711
18096
|
function discoverMcpConfig(projectRoot, home) {
|
|
17712
|
-
const mcpJsonPath =
|
|
17713
|
-
const claudeJsonPath =
|
|
17714
|
-
const copilotJsonPath =
|
|
18097
|
+
const mcpJsonPath = path36.join(projectRoot, ".mcp.json");
|
|
18098
|
+
const claudeJsonPath = path36.join(home, ".claude.json");
|
|
18099
|
+
const copilotJsonPath = path36.join(home, ".copilot", "mcp-config.json");
|
|
17715
18100
|
const sourcesChecked = [mcpJsonPath, claudeJsonPath, copilotJsonPath];
|
|
17716
18101
|
const fromMcpJson = readMcpJsonFile(mcpJsonPath);
|
|
17717
18102
|
if (fromMcpJson !== null) return { servers: fromMcpJson, sourcePath: mcpJsonPath, sourcesChecked };
|
|
@@ -17742,7 +18127,7 @@ function analyzeMcpCache() {
|
|
|
17742
18127
|
}
|
|
17743
18128
|
return serverMetrics;
|
|
17744
18129
|
}
|
|
17745
|
-
function buildMcpAuditReport(projectRoot, home =
|
|
18130
|
+
function buildMcpAuditReport(projectRoot, home = os18.homedir()) {
|
|
17746
18131
|
const discovery = discoverMcpConfig(projectRoot, home);
|
|
17747
18132
|
const config = discovery.servers;
|
|
17748
18133
|
const cacheMetrics = analyzeMcpCache();
|
|
@@ -17828,69 +18213,6 @@ async function runMcpAuditCommand(opts = {}) {
|
|
|
17828
18213
|
printReport3(report);
|
|
17829
18214
|
}
|
|
17830
18215
|
|
|
17831
|
-
// src/cli_recall.ts
|
|
17832
|
-
init_define_import_meta_env();
|
|
17833
|
-
var RECALL_COMMAND = {
|
|
17834
|
-
bash: "bash-output",
|
|
17835
|
-
web: "web-output",
|
|
17836
|
-
mcp: "mcp-output"
|
|
17837
|
-
};
|
|
17838
|
-
function fenceTagForCacheType(cacheType) {
|
|
17839
|
-
return cacheType === "web" ? UNTRUSTED_WEB_TAG : UNTRUSTED_TOOL_TAG;
|
|
17840
|
-
}
|
|
17841
|
-
function fenceRecallListing(text, hits) {
|
|
17842
|
-
const tags = new Set(hits.map((hit) => fenceTagForCacheType(hit.cacheType)));
|
|
17843
|
-
const tag = tags.size === 1 ? [...tags][0] ?? UNTRUSTED_TOOL_TAG : UNTRUSTED_TOOL_TAG;
|
|
17844
|
-
return fenceUntrusted(text, tag);
|
|
17845
|
-
}
|
|
17846
|
-
function fenceSnippetIfMatched(hit) {
|
|
17847
|
-
const matches2 = scanAndRecord(hit.snippet);
|
|
17848
|
-
if (matches2.length === 0) return hit.snippet;
|
|
17849
|
-
return fenceUntrustedContent(hit.snippet, matches2, fenceTagForCacheType(hit.cacheType));
|
|
17850
|
-
}
|
|
17851
|
-
function printHits(query, hits) {
|
|
17852
|
-
const w = (text) => {
|
|
17853
|
-
process.stdout.write(text);
|
|
17854
|
-
};
|
|
17855
|
-
if (hits.length === 0) {
|
|
17856
|
-
w(query === void 0 ? "No cache entries yet.\n" : `No cache entries match: ${query}
|
|
17857
|
-
`);
|
|
17858
|
-
return;
|
|
17859
|
-
}
|
|
17860
|
-
const body = hits.map((hit) => {
|
|
17861
|
-
const label = hit.label.length > 80 ? hit.label.slice(0, 77) + "..." : hit.label;
|
|
17862
|
-
const pointer = hit.blobPresent ? ` (token-goat ${RECALL_COMMAND[hit.cacheType]} ${hit.id})` : " (blob expired -- content below is all that remains)";
|
|
17863
|
-
return `[${pad(hit.cacheType, 4)}] ${hit.id}
|
|
17864
|
-
${pointer}
|
|
17865
|
-
${label}
|
|
17866
|
-
${hit.snippet}
|
|
17867
|
-
`;
|
|
17868
|
-
}).join("\n");
|
|
17869
|
-
w(`${fenceRecallListing(body, hits)}
|
|
17870
|
-
`);
|
|
17871
|
-
}
|
|
17872
|
-
function runRecallCommand(query, opts = {}) {
|
|
17873
|
-
const browse = query === void 0 || query.trim() === "";
|
|
17874
|
-
const scope = {
|
|
17875
|
-
...opts.type !== void 0 ? { type: opts.type } : {},
|
|
17876
|
-
...opts.limit !== void 0 ? { limit: opts.limit } : {}
|
|
17877
|
-
};
|
|
17878
|
-
const effectiveLimit = opts.limit ?? RECALL_DEFAULT_LIMIT;
|
|
17879
|
-
const fetched = browse ? listRecentRecall({ ...scope, limit: effectiveLimit + 1 }) : searchRecall(query, { ...scope, limit: effectiveLimit + 1 });
|
|
17880
|
-
const hits = fetched.slice(0, effectiveLimit);
|
|
17881
|
-
if (fetched.length > hits.length) {
|
|
17882
|
-
process.stderr.write(`Showing ${hits.length} entries; more are available (raise --limit to see them).
|
|
17883
|
-
`);
|
|
17884
|
-
}
|
|
17885
|
-
if (opts.json === true) {
|
|
17886
|
-
const fenced = hits.map((hit) => ({ ...hit, snippet: fenceSnippetIfMatched(hit) }));
|
|
17887
|
-
process.stdout.write(`${displaySafeJson(fenced, 0)}
|
|
17888
|
-
`);
|
|
17889
|
-
return;
|
|
17890
|
-
}
|
|
17891
|
-
printHits(browse ? void 0 : query, hits);
|
|
17892
|
-
}
|
|
17893
|
-
|
|
17894
18216
|
// src/cli_statusline.ts
|
|
17895
18217
|
init_define_import_meta_env();
|
|
17896
18218
|
var STDIN_TIMEOUT_MS = 1500;
|
|
@@ -17967,7 +18289,7 @@ async function runStatuslineCommand(opts = {}) {
|
|
|
17967
18289
|
return;
|
|
17968
18290
|
}
|
|
17969
18291
|
const line = renderStatusline(data);
|
|
17970
|
-
process.stdout.write(`${colorStdout() ? line :
|
|
18292
|
+
process.stdout.write(`${colorStdout() ? line : stripAnsiEscapes(line)}
|
|
17971
18293
|
`);
|
|
17972
18294
|
}
|
|
17973
18295
|
|
|
@@ -17986,14 +18308,20 @@ function efficacyCell(row) {
|
|
|
17986
18308
|
const pct3 = row.efficacyPct === null ? "n/a" : `${row.efficacyPct}%`;
|
|
17987
18309
|
return isSuppressionCategory(row.category) ? `${pct3} *` : pct3;
|
|
17988
18310
|
}
|
|
18311
|
+
function emittedCell(row) {
|
|
18312
|
+
return row.unobservable > 0 ? `${row.emitted} ~${row.unobservable}` : String(row.emitted);
|
|
18313
|
+
}
|
|
18314
|
+
function detectedCell(row) {
|
|
18315
|
+
return row.detected === 0 ? "-" : String(row.detected);
|
|
18316
|
+
}
|
|
17989
18317
|
function printSummary(rows) {
|
|
17990
18318
|
const w = (text) => {
|
|
17991
18319
|
process.stdout.write(text);
|
|
17992
18320
|
};
|
|
17993
|
-
w(pad("category", 22) + pad("emitted", 9) + pad("acted-on", 10) + pad("efficacy", 12) + pad("suppressed", 17) + pad("manual+", 9) + pad("manual-", 9) + "spent\n");
|
|
18321
|
+
w(pad("category", 22) + pad("emitted", 9) + pad("undisplayed", 13) + pad("acted-on", 10) + pad("efficacy", 12) + pad("suppressed", 17) + pad("manual+", 9) + pad("manual-", 9) + "spent-bytes\n");
|
|
17994
18322
|
for (const row of rows) {
|
|
17995
18323
|
w(
|
|
17996
|
-
pad(row.category, 22) + pad(
|
|
18324
|
+
pad(row.category, 22) + pad(emittedCell(row), 9) + pad(detectedCell(row), 13) + pad(String(row.actedOn), 10) + pad(efficacyCell(row), 12) + pad(suppressedCell(row), 17) + pad(String(row.manualEffective), 9) + pad(String(row.manualIneffective), 9) + formatSpentCell(row) + "\n"
|
|
17997
18325
|
);
|
|
17998
18326
|
}
|
|
17999
18327
|
}
|
|
@@ -18002,7 +18330,7 @@ function printTotals(totals) {
|
|
|
18002
18330
|
const legacyNote = totals.legacyEmissions > 0 ? ` (excludes ${totals.legacyEmissions} legacy emission(s) recorded before spend tracking)` : "";
|
|
18003
18331
|
process.stdout.write(
|
|
18004
18332
|
`
|
|
18005
|
-
TOTAL saved=${totals.savedBytes} (all-time, every hint kind) spent=${spent} (hint_emissions ledger only)${legacyNote}
|
|
18333
|
+
TOTAL saved-bytes=${totals.savedBytes} (all-time, every hint kind) spent-bytes=${spent} (hint_emissions ledger only)${legacyNote}
|
|
18006
18334
|
`
|
|
18007
18335
|
);
|
|
18008
18336
|
}
|
|
@@ -18030,7 +18358,7 @@ function runHintStatsCommand(opts = {}) {
|
|
|
18030
18358
|
`);
|
|
18031
18359
|
return;
|
|
18032
18360
|
}
|
|
18033
|
-
if (rows.every((r) => r.emitted === 0 && r.actedOn === 0)) {
|
|
18361
|
+
if (rows.every((r) => r.emitted === 0 && r.actedOn === 0 && r.unobservable === 0 && r.detected === 0)) {
|
|
18034
18362
|
process.stdout.write("No hint emissions recorded yet \u2014 the zeros below are absence of data, not measured ineffectiveness.\n");
|
|
18035
18363
|
}
|
|
18036
18364
|
printSummary(rows);
|
|
@@ -18039,6 +18367,16 @@ function runHintStatsCommand(opts = {}) {
|
|
|
18039
18367
|
'\n* Scored on an absence: these hints ask the agent NOT to do something, so a window that expires with no re-read counts as compliance. Their percentage is not comparable with the unmarked rows, which only count when the agent runs the command the hint named. A high starred figure means "the warned-against read was not seen", not "this hint persuaded anyone".\n'
|
|
18040
18368
|
);
|
|
18041
18369
|
}
|
|
18370
|
+
if (rows.some((r) => r.detected > 0)) {
|
|
18371
|
+
process.stdout.write(
|
|
18372
|
+
"\nundisplayed: detections that never reached the agent -- auto-suppressed, or declined by the hint's own net-benefit gate because the substitute it would have proposed was not smaller than the read it was redirecting. They cost nothing and are scored nowhere. A large figure against a small emitted count means the trigger is alive and the mute is doing the work; a `-` means it has gone quiet on its own.\n"
|
|
18373
|
+
);
|
|
18374
|
+
}
|
|
18375
|
+
if (rows.some((r) => r.unobservable > 0)) {
|
|
18376
|
+
process.stdout.write(
|
|
18377
|
+
"\n~N: N further emissions in that category carried no correlator -- no pointer a later command could have matched -- so no verdict about them was ever observed. They are not in the emitted or acted-on counts and not in the efficacy figure, which is scored only on the rows that could have gone either way. They ARE in the spend column: an unobservable hint costs the agent its bytes all the same.\n"
|
|
18378
|
+
);
|
|
18379
|
+
}
|
|
18042
18380
|
const permanent = rows.filter((r) => r.suppressionPermanent);
|
|
18043
18381
|
if (permanent.length > 0) {
|
|
18044
18382
|
const names = permanent.map((r) => r.category).join(", ");
|
|
@@ -18074,10 +18412,18 @@ function cmdWaste(opts = {}) {
|
|
|
18074
18412
|
...opts.project !== void 0 ? { project: opts.project } : {},
|
|
18075
18413
|
...opts.transcript !== void 0 ? { transcript: opts.transcript } : {},
|
|
18076
18414
|
...opts.json === true ? { json: true } : {},
|
|
18077
|
-
...opts.top !== void 0 ? { top:
|
|
18415
|
+
...opts.top !== void 0 ? { top: requireNonNegativeInt("--top", opts.top) } : {},
|
|
18078
18416
|
...opts.copilot === true ? { copilot: true } : {}
|
|
18079
18417
|
});
|
|
18080
18418
|
}
|
|
18419
|
+
async function cmdAudit(opts = {}) {
|
|
18420
|
+
const { runAuditCommand } = await import("./token-goat-chunk-DYUBZZNP.mjs");
|
|
18421
|
+
return runAuditCommand({
|
|
18422
|
+
...opts.project !== void 0 ? { project: opts.project } : {},
|
|
18423
|
+
...opts.transcript !== void 0 ? { transcript: opts.transcript } : {},
|
|
18424
|
+
...opts.json === true ? { json: true } : {}
|
|
18425
|
+
});
|
|
18426
|
+
}
|
|
18081
18427
|
async function cmdSessionAudit(opts = {}) {
|
|
18082
18428
|
let summary;
|
|
18083
18429
|
try {
|
|
@@ -18104,7 +18450,7 @@ ${formatSessionOutline(turns)}`;
|
|
|
18104
18450
|
}
|
|
18105
18451
|
function sessionTranscriptSize(transcriptPath) {
|
|
18106
18452
|
try {
|
|
18107
|
-
return
|
|
18453
|
+
return fs40.statSync(transcriptPath).size;
|
|
18108
18454
|
} catch {
|
|
18109
18455
|
return 0;
|
|
18110
18456
|
}
|
|
@@ -18140,7 +18486,7 @@ function cmdRecall(query, opts = {}) {
|
|
|
18140
18486
|
}
|
|
18141
18487
|
runRecallCommand(query, {
|
|
18142
18488
|
...type !== void 0 ? { type } : {},
|
|
18143
|
-
...opts.limit !== void 0 ? { limit:
|
|
18489
|
+
...opts.limit !== void 0 ? { limit: requireNonNegativeInt("--limit", opts.limit) } : {},
|
|
18144
18490
|
...opts.json === true ? { json: true } : {}
|
|
18145
18491
|
});
|
|
18146
18492
|
}
|
|
@@ -18164,14 +18510,14 @@ function cmdHintStats(opts = {}) {
|
|
|
18164
18510
|
|
|
18165
18511
|
// src/cli_skills.ts
|
|
18166
18512
|
init_define_import_meta_env();
|
|
18167
|
-
import * as
|
|
18168
|
-
import * as
|
|
18513
|
+
import * as fs41 from "node:fs";
|
|
18514
|
+
import * as path37 from "node:path";
|
|
18169
18515
|
async function cmdSkillBody(name, opts) {
|
|
18170
18516
|
const filePath = await getSkillFilePath(name);
|
|
18171
18517
|
if (filePath === null) {
|
|
18172
18518
|
throw new CliError(`skill '${name}' not found`);
|
|
18173
18519
|
}
|
|
18174
|
-
const body = decodeSource(
|
|
18520
|
+
const body = decodeSource(fs41.readFileSync(filePath));
|
|
18175
18521
|
if (opts.compact === true) {
|
|
18176
18522
|
const emitted = extractCompactFromMarker(body) ?? body;
|
|
18177
18523
|
out(emitted);
|
|
@@ -18198,7 +18544,7 @@ async function cmdSkillCompact(name, opts) {
|
|
|
18198
18544
|
}
|
|
18199
18545
|
let body2;
|
|
18200
18546
|
try {
|
|
18201
|
-
body2 = decodeSource(
|
|
18547
|
+
body2 = decodeSource(fs41.readFileSync(filePath));
|
|
18202
18548
|
} catch {
|
|
18203
18549
|
unresolvable++;
|
|
18204
18550
|
continue;
|
|
@@ -18232,19 +18578,19 @@ async function cmdSkillCompact(name, opts) {
|
|
|
18232
18578
|
if (!opts.path.trim()) {
|
|
18233
18579
|
throw new CliError("--path cannot be empty");
|
|
18234
18580
|
}
|
|
18235
|
-
if (!
|
|
18581
|
+
if (!fs41.existsSync(opts.path)) {
|
|
18236
18582
|
throw new CliError(`skill file not found: ${opts.path}`);
|
|
18237
18583
|
}
|
|
18238
18584
|
try {
|
|
18239
|
-
body =
|
|
18585
|
+
body = fs41.readFileSync(opts.path, "utf-8");
|
|
18240
18586
|
} catch (e) {
|
|
18241
18587
|
if (e.code === "ENOENT") {
|
|
18242
18588
|
throw new CliError(`skill file not found: ${opts.path}`);
|
|
18243
18589
|
}
|
|
18244
18590
|
throw new CliError(`failed to read skill file '${opts.path}': ${extractErrorMessage(e)}`);
|
|
18245
18591
|
}
|
|
18246
|
-
cacheName = name ??
|
|
18247
|
-
sourcePath =
|
|
18592
|
+
cacheName = name ?? path37.basename(path37.dirname(path37.resolve(opts.path)));
|
|
18593
|
+
sourcePath = path37.resolve(opts.path);
|
|
18248
18594
|
} else {
|
|
18249
18595
|
if (name === void 0 || !name.trim()) {
|
|
18250
18596
|
throw new CliError("skill-compact requires a <name> or --path <file>");
|
|
@@ -18253,7 +18599,7 @@ async function cmdSkillCompact(name, opts) {
|
|
|
18253
18599
|
if (filePath === null) {
|
|
18254
18600
|
throw new CliError(`skill '${name}' not found`);
|
|
18255
18601
|
}
|
|
18256
|
-
body =
|
|
18602
|
+
body = fs41.readFileSync(filePath, "utf-8");
|
|
18257
18603
|
cacheName = name;
|
|
18258
18604
|
sourcePath = filePath;
|
|
18259
18605
|
}
|
|
@@ -18385,8 +18731,8 @@ async function cmdSkillDiff(name) {
|
|
|
18385
18731
|
}
|
|
18386
18732
|
const newer = versions[0];
|
|
18387
18733
|
const older = versions[1];
|
|
18388
|
-
const newerBody = await
|
|
18389
|
-
const olderBody = await
|
|
18734
|
+
const newerBody = await fs41.promises.readFile(path37.resolve(dir, `${newer.outputId}.txt`), "utf-8").catch(() => null);
|
|
18735
|
+
const olderBody = await fs41.promises.readFile(path37.resolve(dir, `${older.outputId}.txt`), "utf-8").catch(() => null);
|
|
18390
18736
|
if (newerBody === null || olderBody === null) {
|
|
18391
18737
|
out(`a cached version of '${name}' was evicted while diffing -- try again`);
|
|
18392
18738
|
return;
|
|
@@ -18415,7 +18761,7 @@ async function cmdSkillSection(nameHeading, headingArg) {
|
|
|
18415
18761
|
if (!filePath) {
|
|
18416
18762
|
throw new CliError(`skill '${skillName}' not found`);
|
|
18417
18763
|
}
|
|
18418
|
-
const body = decodeSource(
|
|
18764
|
+
const body = decodeSource(fs41.readFileSync(filePath));
|
|
18419
18765
|
const extracted = extractNamedSection(body, heading);
|
|
18420
18766
|
if (!extracted) {
|
|
18421
18767
|
const allHeadings = listSections(filePath);
|
|
@@ -18436,94 +18782,12 @@ async function cmdSkillSection(nameHeading, headingArg) {
|
|
|
18436
18782
|
// src/cache_session_commands.ts
|
|
18437
18783
|
init_define_import_meta_env();
|
|
18438
18784
|
|
|
18439
|
-
// src/resume.ts
|
|
18440
|
-
init_define_import_meta_env();
|
|
18441
|
-
import { readFile } from "node:fs/promises";
|
|
18442
|
-
var MAX_RESUME_TOKENS = 2e3;
|
|
18443
|
-
var MAX_RESUME_CHARS = MAX_RESUME_TOKENS * 4;
|
|
18444
|
-
var SKILL_MAX_COUNT = 3;
|
|
18445
|
-
var SKILL_MAX_CHARS_EACH = 400;
|
|
18446
|
-
async function buildSkillsSection(sessionId) {
|
|
18447
|
-
const skills = await listSkills(sessionId);
|
|
18448
|
-
if (skills.length === 0) return [];
|
|
18449
|
-
const skillLines = ["## Skills"];
|
|
18450
|
-
for (const skill of skills.slice(0, SKILL_MAX_COUNT)) {
|
|
18451
|
-
let checklist = null;
|
|
18452
|
-
try {
|
|
18453
|
-
const filePath = await getSkillFilePath(skill.name);
|
|
18454
|
-
if (filePath !== null) {
|
|
18455
|
-
const body = await readFile(filePath, "utf-8");
|
|
18456
|
-
checklist = extractChecklistSection(body);
|
|
18457
|
-
}
|
|
18458
|
-
} catch {
|
|
18459
|
-
checklist = null;
|
|
18460
|
-
}
|
|
18461
|
-
if (checklist !== null) {
|
|
18462
|
-
const trimmed = checklist.length > SKILL_MAX_CHARS_EACH ? checklist.slice(0, SKILL_MAX_CHARS_EACH).trimEnd() + "\u2026" : checklist;
|
|
18463
|
-
skillLines.push(`**${skill.name}**:`);
|
|
18464
|
-
skillLines.push(trimmed);
|
|
18465
|
-
} else {
|
|
18466
|
-
skillLines.push(`**${skill.name}** \u2014 \`token-goat skill-body ${skill.name} --section DoD\``);
|
|
18467
|
-
}
|
|
18468
|
-
}
|
|
18469
|
-
skillLines.push("");
|
|
18470
|
-
return skillLines;
|
|
18471
|
-
}
|
|
18472
|
-
async function buildResumePacket(sessionId) {
|
|
18473
|
-
const blob = loadBlob(SESSIONS_SUBDIR, sessionId);
|
|
18474
|
-
if (blob === null || typeof blob !== "object") return null;
|
|
18475
|
-
const raw = blob;
|
|
18476
|
-
const filesArr = Array.isArray(raw["files"]) ? raw["files"] : [];
|
|
18477
|
-
const lines = [`# Resume packet \u2014 session ${sessionId}`, ""];
|
|
18478
|
-
try {
|
|
18479
|
-
lines.push(...await buildSkillsSection(sessionId));
|
|
18480
|
-
} catch {
|
|
18481
|
-
}
|
|
18482
|
-
const editedPaths = filesArr.filter((f) => f["wasEdited"] === true).map((f) => typeof f["path"] === "string" ? f["path"] : "").filter(Boolean).slice(0, 10);
|
|
18483
|
-
if (editedPaths.length > 0) {
|
|
18484
|
-
lines.push("## Edited files");
|
|
18485
|
-
for (const p of editedPaths) lines.push(`- ${p}`);
|
|
18486
|
-
lines.push("");
|
|
18487
|
-
}
|
|
18488
|
-
const topRead = [...filesArr].filter((f) => f["wasEdited"] !== true).sort((a, b) => (typeof b["readCount"] === "number" ? b["readCount"] : 0) - (typeof a["readCount"] === "number" ? a["readCount"] : 0)).slice(0, 8).map((f) => typeof f["path"] === "string" ? f["path"] : "").filter(Boolean);
|
|
18489
|
-
if (topRead.length > 0) {
|
|
18490
|
-
lines.push("## Top files read");
|
|
18491
|
-
for (const p of topRead) lines.push(`- ${p}`);
|
|
18492
|
-
lines.push("");
|
|
18493
|
-
}
|
|
18494
|
-
const bashOutputs = Array.isArray(raw["bashOutputs"]) ? raw["bashOutputs"] : [];
|
|
18495
|
-
const recentBash = bashOutputs.slice(-2);
|
|
18496
|
-
if (recentBash.length > 0) {
|
|
18497
|
-
lines.push("## Recent bash commands");
|
|
18498
|
-
for (const entry of recentBash) {
|
|
18499
|
-
if (Array.isArray(entry) && typeof entry[1] === "string") {
|
|
18500
|
-
const bashEntry = getBashOutput(entry[1]);
|
|
18501
|
-
if (bashEntry !== null) lines.push(`- ${displaySafeText(bashEntry.command)}`);
|
|
18502
|
-
}
|
|
18503
|
-
}
|
|
18504
|
-
lines.push("");
|
|
18505
|
-
}
|
|
18506
|
-
try {
|
|
18507
|
-
const projectRoot = resolveProjectRoot();
|
|
18508
|
-
const result = runGit(["diff", "--stat"], { cwd: projectRoot });
|
|
18509
|
-
if (result.exitCode === 0 && result.stdout.trim().length > 0) {
|
|
18510
|
-
lines.push("## Uncommitted changes (git diff --stat)");
|
|
18511
|
-
lines.push(result.stdout.trim());
|
|
18512
|
-
lines.push("");
|
|
18513
|
-
}
|
|
18514
|
-
} catch {
|
|
18515
|
-
}
|
|
18516
|
-
const text = lines.join("\n");
|
|
18517
|
-
if (text.length <= MAX_RESUME_CHARS) return text;
|
|
18518
|
-
return safeSlice(text, MAX_RESUME_CHARS) + "\n... (truncated to cap)\n";
|
|
18519
|
-
}
|
|
18520
|
-
|
|
18521
18785
|
// src/cli_stats.ts
|
|
18522
18786
|
init_define_import_meta_env();
|
|
18523
|
-
import * as
|
|
18524
|
-
import * as
|
|
18787
|
+
import * as fs42 from "node:fs";
|
|
18788
|
+
import * as path38 from "node:path";
|
|
18525
18789
|
function writeRaw(text) {
|
|
18526
|
-
const payload = colorStdout() ? text :
|
|
18790
|
+
const payload = colorStdout() ? text : stripAnsiEscapes(text);
|
|
18527
18791
|
process.stdout.write(ensureNewline(payload));
|
|
18528
18792
|
}
|
|
18529
18793
|
function rankByReadCount(entries, topN) {
|
|
@@ -18533,7 +18797,7 @@ function formatTopFiles(ranked) {
|
|
|
18533
18797
|
if (ranked.length === 0) return "";
|
|
18534
18798
|
const lines = ["Top files this session:"];
|
|
18535
18799
|
for (const { path: filePath, count } of ranked) {
|
|
18536
|
-
const basename10 =
|
|
18800
|
+
const basename10 = path38.basename(filePath);
|
|
18537
18801
|
lines.push(` ${count.toString().padStart(3)}x ${basename10} (${filePath})`);
|
|
18538
18802
|
}
|
|
18539
18803
|
return lines.join("\n");
|
|
@@ -18550,12 +18814,12 @@ function renderTopSessionFiles(topN = 5) {
|
|
|
18550
18814
|
}
|
|
18551
18815
|
function renderTopSessionFilesFromDisk(topN = 5, overrideSessionsDir) {
|
|
18552
18816
|
try {
|
|
18553
|
-
const
|
|
18554
|
-
if (!
|
|
18555
|
-
const files =
|
|
18817
|
+
const dir = overrideSessionsDir ?? sessionsDir();
|
|
18818
|
+
if (!fs42.existsSync(dir)) return "";
|
|
18819
|
+
const files = fs42.readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => ({ name: f, mtime: fs42.statSync(path38.join(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).slice(0, 3);
|
|
18556
18820
|
for (const { name } of files) {
|
|
18557
18821
|
try {
|
|
18558
|
-
const raw =
|
|
18822
|
+
const raw = fs42.readFileSync(path38.join(dir, name), "utf-8");
|
|
18559
18823
|
const data = JSON.parse(raw);
|
|
18560
18824
|
const filesList = data["files"];
|
|
18561
18825
|
if (!Array.isArray(filesList)) continue;
|
|
@@ -18619,6 +18883,10 @@ function runStats(opts = {}) {
|
|
|
18619
18883
|
renderMethodology(opts.json === true);
|
|
18620
18884
|
return;
|
|
18621
18885
|
}
|
|
18886
|
+
if (opts.hooks === true) {
|
|
18887
|
+
renderHookLatencyStats(void 0, opts.homeDir);
|
|
18888
|
+
return;
|
|
18889
|
+
}
|
|
18622
18890
|
const window = opts.windowDays ?? 30;
|
|
18623
18891
|
const summary = summarize(window, void 0, opts.homeDir);
|
|
18624
18892
|
if (opts.json === true) {
|
|
@@ -18643,13 +18911,10 @@ function runStats(opts = {}) {
|
|
|
18643
18911
|
}
|
|
18644
18912
|
|
|
18645
18913
|
// src/cache_session_commands.ts
|
|
18646
|
-
function emitErr3(text) {
|
|
18647
|
-
process.stderr.write(ensureNewline(text));
|
|
18648
|
-
}
|
|
18649
18914
|
function capAndNote(rows, limit) {
|
|
18650
18915
|
const shown = rows.slice(0, limit);
|
|
18651
18916
|
if (shown.length < rows.length) {
|
|
18652
|
-
|
|
18917
|
+
emitErr(`Showing ${shown.length} of ${rows.length} entries (raise --limit to see the rest).`);
|
|
18653
18918
|
}
|
|
18654
18919
|
return shown;
|
|
18655
18920
|
}
|
|
@@ -18659,11 +18924,11 @@ function parseLimitOpt(cmdName, limitStr, dflt = 30) {
|
|
|
18659
18924
|
try {
|
|
18660
18925
|
n = requireNonNegativeStrictInt("--limit", limitStr);
|
|
18661
18926
|
} catch (e) {
|
|
18662
|
-
|
|
18927
|
+
emitErr(`${cmdName}: --limit must be a positive number, got: "${limitStr}"`);
|
|
18663
18928
|
throw new Error(`invalid --limit: ${limitStr}`, { cause: e });
|
|
18664
18929
|
}
|
|
18665
18930
|
if (n === 0) {
|
|
18666
|
-
|
|
18931
|
+
emitErr(`${cmdName}: --limit must be a positive number, got: "${limitStr}"`);
|
|
18667
18932
|
throw new Error(`invalid --limit: ${limitStr}`);
|
|
18668
18933
|
}
|
|
18669
18934
|
return n;
|
|
@@ -18695,9 +18960,10 @@ function cmdBashHistory(opts) {
|
|
|
18695
18960
|
const allItems = blobs.map(({ id, mtime, value }) => {
|
|
18696
18961
|
if (typeof value !== "object" || value === null) return null;
|
|
18697
18962
|
const v = value;
|
|
18963
|
+
if (typeof v["command"] !== "string") return null;
|
|
18698
18964
|
return {
|
|
18699
18965
|
id,
|
|
18700
|
-
command:
|
|
18966
|
+
command: v["command"],
|
|
18701
18967
|
storedAt: typeof v["storedAt"] === "number" ? v["storedAt"] : mtime,
|
|
18702
18968
|
exitCode: typeof v["exitCode"] === "number" ? v["exitCode"] : -1,
|
|
18703
18969
|
sizeBytes: typeof v["sizeBytes"] === "number" ? v["sizeBytes"] : 0
|
|
@@ -18900,11 +19166,17 @@ function cmdCompactHint(opts) {
|
|
|
18900
19166
|
const cache = sessionId !== null ? loadSessionCache(sessionId) : null;
|
|
18901
19167
|
const transcriptPath = sessionId !== null ? resolveSessionTranscript(sessionId) : null;
|
|
18902
19168
|
const pressure = getContextPressure(cache ?? void 0, transcriptPath ?? void 0);
|
|
18903
|
-
|
|
19169
|
+
let manifest = "";
|
|
19170
|
+
let events = 0;
|
|
19171
|
+
if (sessionId !== null && cache !== null) {
|
|
19172
|
+
loadSessionState(sessionId);
|
|
19173
|
+
manifest = buildManifest(sessionId, process.cwd(), transcriptPath ?? void 0);
|
|
19174
|
+
events = eventCount(cache);
|
|
19175
|
+
}
|
|
18904
19176
|
const manifestTokens = estimateTokens(manifest);
|
|
18905
19177
|
const pct3 = (pressure.fillFraction * 100).toFixed(1);
|
|
18906
19178
|
if (opts.json === true) {
|
|
18907
|
-
const out2 = { tier: pressure.tier, fillFraction: pressure.fillFraction, pct: Number(pct3), manifestTokens, eventCount };
|
|
19179
|
+
const out2 = { tier: pressure.tier, fillFraction: pressure.fillFraction, pct: Number(pct3), manifestTokens, eventCount: events };
|
|
18908
19180
|
if (sessionId !== null) out2["sessionId"] = sessionId;
|
|
18909
19181
|
if (opts.trigger !== void 0) out2["trigger"] = opts.trigger;
|
|
18910
19182
|
process.stdout.write(displaySafeJson(out2) + "\n");
|
|
@@ -18914,7 +19186,7 @@ function cmdCompactHint(opts) {
|
|
|
18914
19186
|
`);
|
|
18915
19187
|
if (sessionId !== null) process.stdout.write(`Session: ${sessionId}
|
|
18916
19188
|
`);
|
|
18917
|
-
process.stdout.write(`Manifest: ${manifestTokens} tokens, ${countNoun(
|
|
19189
|
+
process.stdout.write(`Manifest: ${manifestTokens} tokens, ${countNoun(events, "event")}
|
|
18918
19190
|
`);
|
|
18919
19191
|
if (opts.trigger === "auto") {
|
|
18920
19192
|
const remaining = Math.max(0, Math.round((1 - pressure.fillFraction) * CONTEXT_AUTOCOMPACT_TOKENS));
|
|
@@ -19017,6 +19289,7 @@ function registerSessionCommands(program2, guard) {
|
|
|
19017
19289
|
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));
|
|
19018
19290
|
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));
|
|
19019
19291
|
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));
|
|
19292
|
+
program2.command("audit").description("session retrospective: analyze the current session transcript (Copilot CLI or Claude Code) and emit a Maintainer Feedback Card").option("--project <path>", "project root to analyze").option("--transcript <path>", "explicit transcript path (default: active or newest session for this project)").option("-j, --json", "output as JSON").action(guard(cmdAudit));
|
|
19020
19293
|
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));
|
|
19021
19294
|
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));
|
|
19022
19295
|
program2.command("session-audit").alias("audit-session").description("corpus-wide token attribution across every local Claude Code session transcript: measured billed usage, estimated content size by source and by tool, and billed cost by session position (aggregate counts only, never transcript content)").option("--dir <path>", "transcript corpus root to scan (default: ~/.claude/projects)").option("--json", "output JSON").action(guard(cmdSessionAudit));
|
|
@@ -19131,16 +19404,15 @@ var BRIDGE_CAPABILITY_MATRIX = [
|
|
|
19131
19404
|
harness: "codex",
|
|
19132
19405
|
label: "Codex CLI",
|
|
19133
19406
|
sourceFile: "src/bridges/codex_install.ts (CODEX_HOOK_EVENTS, CODEX_GLOBAL_HOOK_EVENTS)",
|
|
19134
|
-
verification: "
|
|
19135
|
-
verificationNote:
|
|
19407
|
+
verification: "dogfooded",
|
|
19408
|
+
verificationNote: `codex-cli 0.155.0, 2026-09-22: observed running the installed config.toml hooks and delivering real payloads to the shim -- PreToolUse/PostToolUse with tool_name "Bash" and tool_input.command, and PreCompact with trigger "auto" from a forced auto-compaction. A pre-tool deny was honored (the model dropped Get-Content and ran token-goat outline instead) and post-tool additionalContext reached the model; the pre-compact response did not, which is why codex is in PRE_COMPACT_CONTEXT_DROPPED. Two conditions gate the tool events and both fail silently. config.toml must carry the matching [hooks.state] trusted_hash the installer writes, and the matcher must name the tool as it appears on the hook wire -- "Bash" for shell execution, since Codex normalizes its own exec tool into Claude Code's vocabulary before matching, while apply_patch stays native. A matcher naming the internal spelling produced a run with eight pre_compact rows and no pre_tool_use or post_tool_use row at all, and that also stranded every queued compaction manifest. Verified end to end after the fix: a forced auto-compaction run recorded eight compactions and twelve manifest deliveries in its own rollout transcript.`,
|
|
19136
19409
|
implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact", "user_prompt_submit", "subagent_stop"]),
|
|
19137
19410
|
reasons: [
|
|
19138
19411
|
{ events: ["post_tool_use_failure"], reason: NO_SEPARATE_FAILURE_EVENT_REASON },
|
|
19139
|
-
{ events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
|
|
19140
19412
|
{ events: ["notification", "stop"], reason: NO_SERVER_HANDLER_REASON },
|
|
19141
19413
|
{
|
|
19142
|
-
events: ["session_start"],
|
|
19143
|
-
reason: "Codex CLI
|
|
19414
|
+
events: ["post_compact", "session_start"],
|
|
19415
|
+
reason: "Codex CLI 0.155.0 does declare PostCompact and SessionStart -- both appear in the hook-event enum next to PreToolUse and PreCompact, so the earlier claim that it had no session-start equivalent and no post-compact event was wrong. They stay unwired for the reason the rest of this table applies: neither channel has been measured. The post-compact input has no summary field anywhere in the binary's payload key list (session_id, turn_id, agent_id, agent_type, transcript_path, hook_event_name, model, permission_mode, trigger, tool_name, tool_input, tool_use_id), so postCompactHandler would have nothing to count, and whether SessionStart forwards additionalContext has not been run"
|
|
19144
19416
|
}
|
|
19145
19417
|
]
|
|
19146
19418
|
},
|
|
@@ -19281,7 +19553,7 @@ var BRIDGE_CAPABILITY_MATRIX = [
|
|
|
19281
19553
|
reasons: [
|
|
19282
19554
|
{
|
|
19283
19555
|
events: ["pre_compact", "post_tool_use_failure"],
|
|
19284
|
-
reason: "VS Code reads the shared Copilot hooks file but maps only sessionStart, sessionEnd, userPromptSubmitted, preToolUse, postToolUse, agentStop, subagentStop and errorOccurred out of it, so the file's preCompact and postToolUseFailure entries never fire there"
|
|
19556
|
+
reason: "VS Code reads the shared Copilot hooks file but maps only sessionStart, sessionEnd, userPromptSubmitted, preToolUse, postToolUse, agentStop, subagentStop and errorOccurred out of it, so the file's preCompact and postToolUseFailure entries never fire there. Re-read in the 1.138.0 bundle, where the github-copilot branch of that table still lists exactly those eight: the compaction manifest is unavailable on this harness rather than merely unwired, and unlike Copilot CLI and Codex there is no queueing fallback, because the trigger itself never arrives"
|
|
19285
19557
|
},
|
|
19286
19558
|
{ events: ["post_compact"], reason: NO_POST_COMPACT_EVENT_REASON },
|
|
19287
19559
|
{ events: ["notification", "stop"], reason: NO_SERVER_HANDLER_REASON }
|
|
@@ -19434,25 +19706,25 @@ function installVerificationNotice(harness) {
|
|
|
19434
19706
|
|
|
19435
19707
|
// src/purge.ts
|
|
19436
19708
|
init_define_import_meta_env();
|
|
19437
|
-
import * as
|
|
19438
|
-
import * as
|
|
19709
|
+
import * as fs43 from "node:fs";
|
|
19710
|
+
import * as path39 from "node:path";
|
|
19439
19711
|
function directorySize(root) {
|
|
19440
19712
|
let total = 0;
|
|
19441
19713
|
let entries;
|
|
19442
19714
|
try {
|
|
19443
|
-
entries =
|
|
19715
|
+
entries = fs43.readdirSync(root, { withFileTypes: true });
|
|
19444
19716
|
} catch {
|
|
19445
19717
|
return 0;
|
|
19446
19718
|
}
|
|
19447
19719
|
for (const entry of entries) {
|
|
19448
|
-
const full =
|
|
19720
|
+
const full = path39.join(root, entry.name);
|
|
19449
19721
|
if (entry.isDirectory()) {
|
|
19450
19722
|
total += directorySize(full);
|
|
19451
19723
|
continue;
|
|
19452
19724
|
}
|
|
19453
19725
|
if (!entry.isFile()) continue;
|
|
19454
19726
|
try {
|
|
19455
|
-
total +=
|
|
19727
|
+
total += fs43.statSync(full).size;
|
|
19456
19728
|
} catch {
|
|
19457
19729
|
}
|
|
19458
19730
|
}
|
|
@@ -19471,7 +19743,7 @@ function purgeDataDirectories() {
|
|
|
19471
19743
|
}
|
|
19472
19744
|
const bytes = directorySize(root);
|
|
19473
19745
|
try {
|
|
19474
|
-
|
|
19746
|
+
fs43.rmSync(root, { recursive: true, force: true });
|
|
19475
19747
|
result.removed.push({ path: root, bytes });
|
|
19476
19748
|
} catch (e) {
|
|
19477
19749
|
result.failed.push({ path: root, reason: e instanceof Error ? e.message : String(e) });
|
|
@@ -19618,8 +19890,8 @@ async function getSectionContent(fileId, heading, opts = {}) {
|
|
|
19618
19890
|
|
|
19619
19891
|
// src/cli_bench.ts
|
|
19620
19892
|
init_define_import_meta_env();
|
|
19621
|
-
import * as
|
|
19622
|
-
import * as
|
|
19893
|
+
import * as fs44 from "node:fs";
|
|
19894
|
+
import * as path40 from "node:path";
|
|
19623
19895
|
var ControlFilter = class extends ToolFilter {
|
|
19624
19896
|
constructor(name, transform) {
|
|
19625
19897
|
super();
|
|
@@ -19640,13 +19912,13 @@ var CONTROL_FILTERS = {
|
|
|
19640
19912
|
};
|
|
19641
19913
|
var MAX_BENCH_FILE_BYTES = 5 * 1024 * 1024;
|
|
19642
19914
|
function readCaseFile(id, filePath, what) {
|
|
19643
|
-
const bytes =
|
|
19915
|
+
const bytes = fs44.statSync(filePath).size;
|
|
19644
19916
|
if (bytes > MAX_BENCH_FILE_BYTES) {
|
|
19645
19917
|
throw new Error(
|
|
19646
19918
|
`bench case ${id}: ${what} is ${bytes} bytes, over the ${MAX_BENCH_FILE_BYTES}-byte limit for a corpus file (${filePath})`
|
|
19647
19919
|
);
|
|
19648
19920
|
}
|
|
19649
|
-
return
|
|
19921
|
+
return fs44.readFileSync(filePath, "utf8");
|
|
19650
19922
|
}
|
|
19651
19923
|
function parseCase(id, metaPath, outputPath) {
|
|
19652
19924
|
const metaText = readCaseFile(id, metaPath, "metadata");
|
|
@@ -19681,7 +19953,7 @@ function parseCase(id, metaPath, outputPath) {
|
|
|
19681
19953
|
function loadCorpus(dir) {
|
|
19682
19954
|
let entries;
|
|
19683
19955
|
try {
|
|
19684
|
-
entries =
|
|
19956
|
+
entries = fs44.readdirSync(dir);
|
|
19685
19957
|
} catch {
|
|
19686
19958
|
throw new Error(`bench corpus not found: ${dir}`);
|
|
19687
19959
|
}
|
|
@@ -19689,9 +19961,9 @@ function loadCorpus(dir) {
|
|
|
19689
19961
|
for (const entry of entries.sort()) {
|
|
19690
19962
|
if (!entry.endsWith(".json")) continue;
|
|
19691
19963
|
const id = entry.slice(0, -".json".length);
|
|
19692
|
-
const outputPath =
|
|
19693
|
-
if (!
|
|
19694
|
-
cases.push(parseCase(id,
|
|
19964
|
+
const outputPath = path40.join(dir, `${id}.txt`);
|
|
19965
|
+
if (!fs44.existsSync(outputPath)) throw new Error(`bench case ${id}: missing output file ${outputPath}`);
|
|
19966
|
+
cases.push(parseCase(id, path40.join(dir, entry), outputPath));
|
|
19695
19967
|
}
|
|
19696
19968
|
if (cases.length === 0) throw new Error(`bench corpus is empty: ${dir}`);
|
|
19697
19969
|
return cases;
|
|
@@ -19811,10 +20083,10 @@ function appendTsv(file, report, commit) {
|
|
|
19811
20083
|
report.mustKeepTotal,
|
|
19812
20084
|
report.coveredFilters
|
|
19813
20085
|
].join(" ");
|
|
19814
|
-
const dir =
|
|
19815
|
-
|
|
19816
|
-
const header =
|
|
19817
|
-
|
|
20086
|
+
const dir = path40.dirname(path40.resolve(file));
|
|
20087
|
+
fs44.mkdirSync(dir, { recursive: true });
|
|
20088
|
+
const header = fs44.existsSync(file) ? "" : TSV_HEADER;
|
|
20089
|
+
fs44.appendFileSync(file, header + row + "\n", "utf8");
|
|
19818
20090
|
}
|
|
19819
20091
|
function currentCommit(cwd) {
|
|
19820
20092
|
const opts = cwd === void 0 ? {} : { cwd };
|
|
@@ -19843,7 +20115,7 @@ function runBenchCommand(opts) {
|
|
|
19843
20115
|
var CliError = class extends Error {
|
|
19844
20116
|
};
|
|
19845
20117
|
function out(text) {
|
|
19846
|
-
const payload = colorStdout() ? text :
|
|
20118
|
+
const payload = colorStdout() ? text : stripAnsiEscapes(text);
|
|
19847
20119
|
process.stdout.write(ensureNewline(payload));
|
|
19848
20120
|
}
|
|
19849
20121
|
function err(text) {
|
|
@@ -19854,10 +20126,10 @@ function readBoundedText(text, file) {
|
|
|
19854
20126
|
if (text === void 0 && file === void 0) throw new CliError("provide text or --file");
|
|
19855
20127
|
let value;
|
|
19856
20128
|
if (file !== void 0) {
|
|
19857
|
-
if (
|
|
20129
|
+
if (fs45.statSync(file).size > CONTENT_MAX_INPUT_CHARS) {
|
|
19858
20130
|
throw new CliError(`file exceeds the ${CONTENT_MAX_INPUT_CHARS}-byte safety limit`);
|
|
19859
20131
|
}
|
|
19860
|
-
value =
|
|
20132
|
+
value = fs45.readFileSync(file, "utf8");
|
|
19861
20133
|
} else {
|
|
19862
20134
|
if (text === void 0) throw new CliError("provide text or --file");
|
|
19863
20135
|
value = text;
|
|
@@ -19902,35 +20174,11 @@ function cmdHandoffResolve(name, opts) {
|
|
|
19902
20174
|
if (result === null) throw new CliError(`no local handoff named "${name}" in this project`);
|
|
19903
20175
|
out(typeof result === "string" ? result : formatCompression(result, false, "payload withheld: inlining it would cost more tokens than the original text; pass --full to get the original text back outright"));
|
|
19904
20176
|
}
|
|
19905
|
-
function requireInt2(flag, raw) {
|
|
19906
|
-
if (!/^-?\d+$/.test(raw)) {
|
|
19907
|
-
throw new CliError(`${flag} must be a number, got: "${raw}"`);
|
|
19908
|
-
}
|
|
19909
|
-
const n = Number.parseInt(raw, 10);
|
|
19910
|
-
if (!Number.isFinite(n)) {
|
|
19911
|
-
throw new CliError(`${flag} must be a number, got: "${raw}"`);
|
|
19912
|
-
}
|
|
19913
|
-
return n;
|
|
19914
|
-
}
|
|
19915
|
-
function requireNonNegativeInt2(flag, raw) {
|
|
19916
|
-
const n = requireInt2(flag, raw);
|
|
19917
|
-
if (n < 0) {
|
|
19918
|
-
throw new CliError(`${flag} must be a non-negative number, got: "${raw}"`);
|
|
19919
|
-
}
|
|
19920
|
-
return n;
|
|
19921
|
-
}
|
|
19922
|
-
function requirePositiveInt2(flag, raw) {
|
|
19923
|
-
const n = requireInt2(flag, raw);
|
|
19924
|
-
if (n <= 0) {
|
|
19925
|
-
throw new CliError(`${flag} must be a positive number, got: "${raw}"`);
|
|
19926
|
-
}
|
|
19927
|
-
return n;
|
|
19928
|
-
}
|
|
19929
20177
|
async function cmdSemantic(query, opts) {
|
|
19930
20178
|
if (!query && !opts.preflight && !opts.warm) {
|
|
19931
20179
|
throw new CliError("missing required argument: query");
|
|
19932
20180
|
}
|
|
19933
|
-
const limit = opts.limit !== void 0 ?
|
|
20181
|
+
const limit = opts.limit !== void 0 ? requireNonNegativeInt("--limit", opts.limit) : 20;
|
|
19934
20182
|
const { text, code } = await runSemantic(query ?? "", {
|
|
19935
20183
|
limit,
|
|
19936
20184
|
...opts.json === true ? { json: true } : {},
|
|
@@ -19967,6 +20215,7 @@ async function cmdIndex(pathArg, opts = {}) {
|
|
|
19967
20215
|
const ixCfg = loadConfig().indexing;
|
|
19968
20216
|
if ((loadConfig().indexing?.embeddings_enabled ?? true) && embeddingsDepsAvailable(getDb(dbPath))) {
|
|
19969
20217
|
ensureEmbeddingProvenance(getDb(dbPath));
|
|
20218
|
+
pruneUnembeddableChunks(getDb(dbPath), ixCfg.max_chunks_per_file, deleteFileEmbeddings, { asset: assetEmbedSha, maxChunks: maxChunksEmbedSha });
|
|
19970
20219
|
}
|
|
19971
20220
|
let indexed = 0;
|
|
19972
20221
|
let failed = 0;
|
|
@@ -20003,20 +20252,24 @@ async function cmdIndex(pathArg, opts = {}) {
|
|
|
20003
20252
|
continue;
|
|
20004
20253
|
}
|
|
20005
20254
|
const sha = fingerprintFile(key);
|
|
20006
|
-
if (sha === null && !
|
|
20255
|
+
if (sha === null && !fs45.existsSync(key)) continue;
|
|
20256
|
+
recordKnownRootThrottled(key, dataDir(), dbPath);
|
|
20007
20257
|
const entry = sha !== null ? getFileEntry(key, dbPath) : null;
|
|
20008
20258
|
const spellingStale = entry !== null && indexedPathSpellingIsStale(entry.filePath, key);
|
|
20009
|
-
const parseUnchanged = !force && !spellingStale && sha !== null && entry?.sha === sha && entry.parserSha ===
|
|
20259
|
+
const parseUnchanged = !force && !spellingStale && sha !== null && entry?.sha === sha && entry.parserSha === parserFingerprintForLanguage(entry.language);
|
|
20010
20260
|
const embeddingsEnabled = loadConfig().indexing?.embeddings_enabled ?? true;
|
|
20011
20261
|
const depsAvailable = embeddingsEnabled && embeddingsDepsAvailable(getDb(dbPath));
|
|
20012
|
-
const
|
|
20013
|
-
|
|
20262
|
+
const embedFreshFor = (embedSha) => !force && !spellingStale && sha !== null && isEmbedFresh(
|
|
20263
|
+
embedSha,
|
|
20014
20264
|
sha,
|
|
20015
20265
|
embeddingsEnabled,
|
|
20016
20266
|
depsAvailable,
|
|
20017
20267
|
// See isEmbedFresh: an `oversize:` marker stays fresh only while indexing.large_file_symbol_only_kb is still what it was stamped under, so raising the threshold re-embeds the files it just admitted instead of leaving them permanently skipped. 0 matches no marker (config floors this key at 1), the safe direction for a partially-mocked config.
|
|
20018
|
-
loadConfig().indexing?.large_file_symbol_only_kb ?? 0
|
|
20268
|
+
loadConfig().indexing?.large_file_symbol_only_kb ?? 0,
|
|
20269
|
+
// Same reasoning and same 0 fallback for the chunk-count marker.
|
|
20270
|
+
loadConfig().indexing?.max_chunks_per_file ?? 0
|
|
20019
20271
|
);
|
|
20272
|
+
const embedUnchanged = embedFreshFor(entry?.embedSha);
|
|
20020
20273
|
if (parseUnchanged && embedUnchanged) {
|
|
20021
20274
|
skipped += 1;
|
|
20022
20275
|
continue;
|
|
@@ -20036,9 +20289,10 @@ async function cmdIndex(pathArg, opts = {}) {
|
|
|
20036
20289
|
}
|
|
20037
20290
|
continue;
|
|
20038
20291
|
}
|
|
20039
|
-
if (!
|
|
20292
|
+
if (!fs45.existsSync(key)) continue;
|
|
20040
20293
|
}
|
|
20041
|
-
|
|
20294
|
+
const embedFresh = parseUnchanged ? embedUnchanged : embedFreshFor(getFileEntry(key, dbPath)?.embedSha);
|
|
20295
|
+
if (!embedFresh) {
|
|
20042
20296
|
paintProgress("embedding");
|
|
20043
20297
|
await indexFileEmbeddings(key, dbPath, sha ?? void 0);
|
|
20044
20298
|
}
|
|
@@ -20096,7 +20350,7 @@ async function cmdMcpServe() {
|
|
|
20096
20350
|
let StdioServerTransport;
|
|
20097
20351
|
try {
|
|
20098
20352
|
;
|
|
20099
|
-
({ createMcpServer } = await import("./token-goat-chunk-
|
|
20353
|
+
({ createMcpServer } = await import("./token-goat-chunk-3OGZOFLS.mjs"));
|
|
20100
20354
|
({ StdioServerTransport } = await import("./token-goat-chunk-EVC4TOLE.mjs"));
|
|
20101
20355
|
} catch (err2) {
|
|
20102
20356
|
process.stderr.write(
|
|
@@ -20121,11 +20375,11 @@ async function cmdHook(event, opts) {
|
|
|
20121
20375
|
if (typeof opts.harness === "string" && opts.harness.length > 0) {
|
|
20122
20376
|
process.env[ENV_KEYS.HARNESS_OVERRIDE] = opts.harness;
|
|
20123
20377
|
}
|
|
20124
|
-
const { relay } = await import("./token-goat-chunk-
|
|
20378
|
+
const { relay } = await import("./token-goat-chunk-G6SYQQO7.mjs");
|
|
20125
20379
|
await relay(event);
|
|
20126
20380
|
}
|
|
20127
20381
|
function projectHooksCommitNote(pathFiles, hooksConfigPath) {
|
|
20128
|
-
const shim = hooksConfigPath === void 0 ? "" : ` (${displaySafePath(
|
|
20382
|
+
const shim = hooksConfigPath === void 0 ? "" : ` (${displaySafePath(path41.join(path41.dirname(hooksConfigPath), HOOKS_SCRIPT_FILE))} is generated with them)`;
|
|
20129
20383
|
return `Note: ${pathFiles.map((p) => displaySafePath(p)).join(", ")} ${pathFiles.length === 1 ? "holds" : "hold"} absolute paths to node and token-goat on this machine${shim}, so do not commit them: list them in .git/info/exclude or .gitignore.`;
|
|
20130
20384
|
}
|
|
20131
20385
|
function visualStudioManualSteps(scope) {
|
|
@@ -20158,7 +20412,7 @@ async function cmdInstall(opts) {
|
|
|
20158
20412
|
if (opts.project === true && opts.user === true) {
|
|
20159
20413
|
throw new Error("install takes either -p/--project or --user, not both.");
|
|
20160
20414
|
}
|
|
20161
|
-
await import("./token-goat-chunk-
|
|
20415
|
+
await import("./token-goat-chunk-G6SYQQO7.mjs");
|
|
20162
20416
|
const scope = opts.project === true ? "project" : "user";
|
|
20163
20417
|
if (wantsClaudeCodeBase(opts)) {
|
|
20164
20418
|
const result = installHooks(scope);
|
|
@@ -20304,16 +20558,16 @@ async function cmdInstall(opts) {
|
|
|
20304
20558
|
);
|
|
20305
20559
|
}
|
|
20306
20560
|
try {
|
|
20307
|
-
const skillDir2 =
|
|
20308
|
-
if (
|
|
20309
|
-
const entries =
|
|
20561
|
+
const skillDir2 = path41.join(claudeConfigDir(), "skills");
|
|
20562
|
+
if (fs45.existsSync(skillDir2)) {
|
|
20563
|
+
const entries = fs45.readdirSync(skillDir2, { withFileTypes: true });
|
|
20310
20564
|
const skillNames = [];
|
|
20311
20565
|
const sessionId = getSessionId();
|
|
20312
20566
|
for (const entry of entries) {
|
|
20313
20567
|
if (!entry.isDirectory()) continue;
|
|
20314
|
-
const skillFile =
|
|
20315
|
-
if (
|
|
20316
|
-
const body =
|
|
20568
|
+
const skillFile = path41.join(skillDir2, entry.name, "SKILL.md");
|
|
20569
|
+
if (fs45.existsSync(skillFile)) {
|
|
20570
|
+
const body = fs45.readFileSync(skillFile, "utf-8");
|
|
20317
20571
|
const compact = extractCompactFromMarker(body);
|
|
20318
20572
|
if (compact === null) continue;
|
|
20319
20573
|
const sourceSha = contentHash(body);
|
|
@@ -20324,9 +20578,9 @@ async function cmdInstall(opts) {
|
|
|
20324
20578
|
if (skillNames.length > 0) {
|
|
20325
20579
|
const dir = skillOutputsDir();
|
|
20326
20580
|
ensureDirSync(dir);
|
|
20327
|
-
const pregenPath =
|
|
20581
|
+
const pregenPath = path41.join(dir, "pregen.json");
|
|
20328
20582
|
const pregenData = { ts: Date.now(), names: skillNames };
|
|
20329
|
-
await
|
|
20583
|
+
await fs45.promises.writeFile(pregenPath, JSON.stringify(pregenData, null, 2));
|
|
20330
20584
|
out(`Pre-generated ${skillNames.length} skill compacts.`);
|
|
20331
20585
|
}
|
|
20332
20586
|
}
|
|
@@ -20466,13 +20720,14 @@ function cmdWorkerStatus() {
|
|
|
20466
20720
|
out(isWorkerRunning() ? "Worker is running." : "Worker is not running.");
|
|
20467
20721
|
}
|
|
20468
20722
|
function cmdStats(opts = {}) {
|
|
20469
|
-
const windowDays = opts.windowDays !== void 0 ?
|
|
20723
|
+
const windowDays = opts.windowDays !== void 0 ? requireNonNegativeInt("--window-days", opts.windowDays) : 30;
|
|
20470
20724
|
const statsOpts = {
|
|
20471
20725
|
json: opts.json === true,
|
|
20472
20726
|
windowDays,
|
|
20473
20727
|
full: opts.full === true,
|
|
20474
20728
|
short: opts.short === true,
|
|
20475
|
-
methodology: opts.methodology === true
|
|
20729
|
+
methodology: opts.methodology === true,
|
|
20730
|
+
hooks: opts.hooks === true
|
|
20476
20731
|
};
|
|
20477
20732
|
if (opts.homeDir !== void 0) {
|
|
20478
20733
|
statsOpts.homeDir = opts.homeDir;
|
|
@@ -20505,7 +20760,7 @@ async function cmdDoctor(opts) {
|
|
|
20505
20760
|
}
|
|
20506
20761
|
}
|
|
20507
20762
|
function _applyFiltersAndPrint(content, opts, fenceByProvenance = false, fenceTag = UNTRUSTED_WEB_TAG) {
|
|
20508
|
-
const
|
|
20763
|
+
const emit2 = (text) => {
|
|
20509
20764
|
if (!fenceByProvenance || text === "") {
|
|
20510
20765
|
out(text);
|
|
20511
20766
|
return text;
|
|
@@ -20536,7 +20791,7 @@ function _applyFiltersAndPrint(content, opts, fenceByProvenance = false, fenceTa
|
|
|
20536
20791
|
}
|
|
20537
20792
|
}
|
|
20538
20793
|
if (opts.grep !== void 0 && opts.maxMatches !== void 0) {
|
|
20539
|
-
const cap =
|
|
20794
|
+
const cap = requireNonNegativeInt("--max-matches", opts.maxMatches);
|
|
20540
20795
|
const matched = content === "" ? [] : content.split(/\r?\n/);
|
|
20541
20796
|
if (matched.length > cap) {
|
|
20542
20797
|
content = [...matched.slice(0, cap), "[token-goat: showing first " + cap + " of " + matched.length + " matching lines; raise --max-matches for more]"].join("\n");
|
|
@@ -20544,11 +20799,11 @@ function _applyFiltersAndPrint(content, opts, fenceByProvenance = false, fenceTa
|
|
|
20544
20799
|
}
|
|
20545
20800
|
const rawLines = content.split(/\r?\n/);
|
|
20546
20801
|
if (opts.full === true) {
|
|
20547
|
-
return
|
|
20802
|
+
return emit2(rawLines.join("\n"));
|
|
20548
20803
|
}
|
|
20549
20804
|
const lines = rawLines.length > 1 && rawLines[rawLines.length - 1] === "" ? rawLines.slice(0, -1) : rawLines;
|
|
20550
|
-
const headN = opts.head !== void 0 ?
|
|
20551
|
-
const tailN = opts.tail !== void 0 ?
|
|
20805
|
+
const headN = opts.head !== void 0 ? requireNonNegativeInt("--head", opts.head) : 30;
|
|
20806
|
+
const tailN = opts.tail !== void 0 ? requireNonNegativeInt("--tail", opts.tail) : 80;
|
|
20552
20807
|
const applyElision = (lines2, headN2, tailN2) => lines2.length > headN2 + tailN2 + 1 ? [...lines2.slice(0, headN2), "...(elided)...", ...lines2.slice(lines2.length - tailN2)] : lines2;
|
|
20553
20808
|
const noteLineCap = (which, flag, shown, total) => {
|
|
20554
20809
|
if (shown >= total) return;
|
|
@@ -20567,7 +20822,7 @@ function _applyFiltersAndPrint(content, opts, fenceByProvenance = false, fenceTa
|
|
|
20567
20822
|
result = lines.slice(Math.max(0, lines.length - tailN));
|
|
20568
20823
|
noteLineCap("last", "tail", result.length, lines.length);
|
|
20569
20824
|
}
|
|
20570
|
-
return
|
|
20825
|
+
return emit2(result.join("\n"));
|
|
20571
20826
|
}
|
|
20572
20827
|
function cmdBashOutput(id, opts) {
|
|
20573
20828
|
const parseVerifyThreshold = (optVal) => {
|
|
@@ -20588,7 +20843,7 @@ function cmdBashOutput(id, opts) {
|
|
|
20588
20843
|
}
|
|
20589
20844
|
let content;
|
|
20590
20845
|
try {
|
|
20591
|
-
const st =
|
|
20846
|
+
const st = fs45.statSync(opts.file);
|
|
20592
20847
|
if (st.isFIFO() || st.isSocket()) {
|
|
20593
20848
|
throw new CliError(`--file '${opts.file}' is a special file (FIFO or socket) \u2014 only regular files are supported`);
|
|
20594
20849
|
}
|
|
@@ -20603,7 +20858,7 @@ function cmdBashOutput(id, opts) {
|
|
|
20603
20858
|
`);
|
|
20604
20859
|
}
|
|
20605
20860
|
}
|
|
20606
|
-
content = redactIfDotenv(opts.file, decodeSource(
|
|
20861
|
+
content = redactIfDotenv(opts.file, decodeSource(fs45.readFileSync(opts.file)));
|
|
20607
20862
|
} catch (e) {
|
|
20608
20863
|
if (e instanceof CliError) throw e;
|
|
20609
20864
|
throw new CliError(`cannot read file: ${opts.file}`);
|
|
@@ -20667,15 +20922,15 @@ function cmdMcpOutput(id, opts) {
|
|
|
20667
20922
|
if (opts.file.includes("\0")) {
|
|
20668
20923
|
throw new CliError("--file path contains a null byte");
|
|
20669
20924
|
}
|
|
20670
|
-
if (!
|
|
20925
|
+
if (!fs45.existsSync(opts.file)) {
|
|
20671
20926
|
throw new CliError(`file not found: ${opts.file}`);
|
|
20672
20927
|
}
|
|
20673
20928
|
try {
|
|
20674
|
-
const st =
|
|
20929
|
+
const st = fs45.statSync(opts.file);
|
|
20675
20930
|
if (st.isFIFO() || st.isSocket()) {
|
|
20676
20931
|
throw new CliError(`--file '${opts.file}' is a special file (FIFO or socket) \u2014 only regular files are supported`);
|
|
20677
20932
|
}
|
|
20678
|
-
content = decodeSource(
|
|
20933
|
+
content = decodeSource(fs45.readFileSync(opts.file));
|
|
20679
20934
|
} catch (e) {
|
|
20680
20935
|
if (e instanceof CliError) throw e;
|
|
20681
20936
|
throw new CliError(`cannot read file: ${opts.file}`);
|
|
@@ -20696,7 +20951,7 @@ function cmdMcpOutput(id, opts) {
|
|
|
20696
20951
|
const data = extractJsonFromMcpOutput(content);
|
|
20697
20952
|
let head;
|
|
20698
20953
|
if (opts.head !== void 0) {
|
|
20699
|
-
head =
|
|
20954
|
+
head = requireNonNegativeInt("--head", opts.head);
|
|
20700
20955
|
}
|
|
20701
20956
|
const queryResult = queryJson(data, opts.jsonQuery);
|
|
20702
20957
|
const { head: _omittedHead, ...restOpts } = opts;
|
|
@@ -20748,13 +21003,13 @@ async function cmdCompress(commandArgs, opts = {}) {
|
|
|
20748
21003
|
process.exitCode = 1;
|
|
20749
21004
|
return;
|
|
20750
21005
|
}
|
|
20751
|
-
const bashRunner = await import("./token-goat-chunk-
|
|
21006
|
+
const bashRunner = await import("./token-goat-chunk-CBXLE54Q.mjs");
|
|
20752
21007
|
if (opts.compress === false) {
|
|
20753
21008
|
process.exitCode = bashRunner.runRaw(command, parseTimeout(opts.timeout, bashRunner.DEFAULT_TIMEOUT_SECONDS));
|
|
20754
21009
|
return;
|
|
20755
21010
|
}
|
|
20756
|
-
const maxTokens = opts.maxTokens !== void 0 ?
|
|
20757
|
-
process.exitCode = bashRunner.run(command, {
|
|
21011
|
+
const maxTokens = opts.maxTokens !== void 0 ? requireNonNegativeInt("--max-tokens", opts.maxTokens) : 0;
|
|
21012
|
+
process.exitCode = await bashRunner.run(command, {
|
|
20758
21013
|
filterName: opts.filter,
|
|
20759
21014
|
timeout: parseTimeout(opts.timeout, bashRunner.DEFAULT_TIMEOUT_SECONDS),
|
|
20760
21015
|
maxTokens,
|
|
@@ -20798,9 +21053,10 @@ ${content}`;
|
|
|
20798
21053
|
var originalHelpInformation = null;
|
|
20799
21054
|
function buildProgram() {
|
|
20800
21055
|
const program2 = new Command();
|
|
20801
|
-
program2.name("token-goat").description("Surgical token-reduction companion for AI coding agents").version(VERSION, "-v, --version", "print the token-goat version").option("--cwd <path>", "run as if invoked from this directory (overrides the real working directory)");
|
|
21056
|
+
program2.name("token-goat").description("Surgical token-reduction companion for AI coding agents").version(VERSION, "-v, --version", "print the token-goat version").option("--cwd <path>", "run as if invoked from this directory (overrides the real working directory)").option("--notice <text>", "print this line to stdout before the command's own output");
|
|
20802
21057
|
program2.hook("preAction", (thisCommand) => {
|
|
20803
|
-
const
|
|
21058
|
+
const opts = thisCommand.opts();
|
|
21059
|
+
const cwdOverride = opts.cwd;
|
|
20804
21060
|
if (cwdOverride !== void 0) {
|
|
20805
21061
|
try {
|
|
20806
21062
|
process.chdir(cwdOverride);
|
|
@@ -20808,6 +21064,7 @@ function buildProgram() {
|
|
|
20808
21064
|
throw new Error(`--cwd ${cwdOverride}: ${extractErrorMessage(e)}`, { cause: e });
|
|
20809
21065
|
}
|
|
20810
21066
|
}
|
|
21067
|
+
if (opts.notice !== void 0) out(opts.notice);
|
|
20811
21068
|
});
|
|
20812
21069
|
const guard = (fn) => async (...args) => {
|
|
20813
21070
|
process.exitCode = void 0;
|
|
@@ -20837,7 +21094,7 @@ function buildProgram() {
|
|
|
20837
21094
|
process.exitCode = 1;
|
|
20838
21095
|
}
|
|
20839
21096
|
};
|
|
20840
|
-
program2.command("symbol [name] [more...]").description("search for a symbol by name, or project-wide by --grep name pattern").option("-l, --limit <n>", "max results").option("-f, --file <path>", "restrict to one file").option("-k, --kind <kind>", "restrict to one kind (function, class, ...)").option("-p, --project [path]", "scope search to one project root instead of the global index (defaults to cwd)").option("-j, --json", "output as JSON").option("--grep <pattern>", "only show symbols whose name matches this regex (literal substring if it is not valid regex); cannot be combined with <name>").option("--exclude-tests", "hide symbols defined in a test file (opt-in; default output is unchanged)").option("--stats", "add per-result reference count and doc-coverage flag (project-wide count per NAME, not per definition site -- same-named symbols across files share a count)").action((name, more, opts) => {
|
|
21097
|
+
program2.command("symbol [name] [more...]").description("search for a symbol by name, or project-wide by --grep name pattern").option("-l, --limit <n>", "max results").option("-f, --file <path>", "restrict to one file").option("-k, --kind <kind>", "restrict to one kind (function, class, ...)").option("-p, --project [path]", "scope search to one project root instead of the global index (defaults to cwd)").option("-j, --json", "output as JSON").option("--grep <pattern>", "only show symbols whose name matches this regex (literal substring if it is not valid regex); cannot be combined with <name>").option("--exclude-tests", "hide symbols defined in a test file (opt-in; default output is unchanged)").option("--exclude-vendored", "hide symbols defined under a dependency or build directory the indexer skips (node_modules, dist, site-packages, ...) (opt-in; default output is unchanged)").option("--stats", "add per-result reference count and doc-coverage flag (project-wide count per NAME, not per definition site -- same-named symbols across files share a count)").action((name, more, opts) => {
|
|
20841
21098
|
let projectRoot;
|
|
20842
21099
|
if (opts.project === true) {
|
|
20843
21100
|
projectRoot = resolveProjectRoot({ project: process.cwd() });
|
|
@@ -20851,13 +21108,14 @@ function buildProgram() {
|
|
|
20851
21108
|
more,
|
|
20852
21109
|
() => runSymbol({
|
|
20853
21110
|
...name !== void 0 ? { name } : {},
|
|
20854
|
-
limit: opts.limit !== void 0 ?
|
|
21111
|
+
limit: opts.limit !== void 0 ? requireNonNegativeInt("--limit", opts.limit) : 20,
|
|
20855
21112
|
...opts.file !== void 0 ? { file: opts.file } : {},
|
|
20856
21113
|
...opts.kind !== void 0 ? { kind: opts.kind } : {},
|
|
20857
21114
|
...projectRoot !== void 0 ? { projectRoot } : {},
|
|
20858
21115
|
...opts.json === true ? { json: true } : {},
|
|
20859
21116
|
...opts.grep !== void 0 ? { grep: opts.grep } : {},
|
|
20860
21117
|
...opts.excludeTests === true ? { excludeTests: true } : {},
|
|
21118
|
+
...opts.excludeVendored === true ? { excludeVendored: true } : {},
|
|
20861
21119
|
...opts.stats === true ? { stats: true } : {}
|
|
20862
21120
|
}),
|
|
20863
21121
|
// `symbol` searches one name; there is no comma form that would search several.
|
|
@@ -20866,7 +21124,7 @@ function buildProgram() {
|
|
|
20866
21124
|
);
|
|
20867
21125
|
});
|
|
20868
21126
|
program2.command("read <spec> [more...]").description(
|
|
20869
|
-
"read one symbol's full body (spec: file::symbol; disambiguate a name shared by several classes with file::Parent.symbol; a trailing @LINE anchor -- file::symbol@LINE, or combined as file::Parent.symbol@LINE -- picks out a specific candidate by its exact starting line, for the case a Parent qualifier can't reach (e.g. a top-level definition); comma-separated file::a,b for a merged multi-symbol view, or a::x,b::y to merge symbols across several files)"
|
|
21127
|
+
"read one symbol's full body (spec: file::symbol; disambiguate a name shared by several classes with file::Parent.symbol; a trailing @LINE anchor -- file::symbol@LINE, or combined as file::Parent.symbol@LINE -- picks out a specific candidate by its exact starting line, for the case a Parent qualifier can't reach (e.g. a top-level definition); comma-separated file::a,b for a merged multi-symbol view, or a::x,b::y to merge symbols across several files; file:LINE or file:START-END resolves a line number to the region enclosing it -- the symbol, the file preamble, or the gap between two symbols -- while file@START-END serves those raw lines)"
|
|
20870
21128
|
).option("-j, --json", "output as JSON").option("--force-refresh", "reparse file from disk before querying (ignore stale index)").option("--stats", "add per-symbol reference count and doc-coverage flag").action(
|
|
20871
21129
|
(spec, more, opts) => runExitText(
|
|
20872
21130
|
() => noteExtraFileArgs(
|
|
@@ -20879,7 +21137,8 @@ function buildProgram() {
|
|
|
20879
21137
|
...opts.forceRefresh === true ? { forceRefresh: true } : {},
|
|
20880
21138
|
...opts.stats === true ? { stats: true } : {}
|
|
20881
21139
|
}),
|
|
20882
|
-
|
|
21140
|
+
// Whether the comma form the note names would actually run depends on the specs themselves, not on the command: `read` merges `::` specs and nothing else.
|
|
21141
|
+
{ noun: "spec", mergeable: readSpecsMergeable([spec, ...more]) }
|
|
20883
21142
|
)
|
|
20884
21143
|
)
|
|
20885
21144
|
);
|
|
@@ -20887,12 +21146,12 @@ function buildProgram() {
|
|
|
20887
21146
|
"symbol body + callers + containing doc section in one call (spec: file::symbol; also accepts the file::symbol@LINE anchor form documented under `read`; comma-separated file::a,b for a merged multi-symbol view; cross-file a.ts::x,b.ts::y is also supported)"
|
|
20888
21147
|
).option("-j, --json", "output as JSON").option("--limit <n>", "max callers to show (default: 20)").option("-C, --context <n>", "lines of call-site source to show before and after each caller (default 0)").option("--exclude-tests", "hide callers whose call site lives in a test file (opt-in; default output is unchanged)").option("--grep <pattern>", "only show callers whose enclosing symbol name matches this regex (literal substring if it is not valid regex)").action(
|
|
20889
21148
|
(spec, more, opts) => runExit(() => {
|
|
20890
|
-
emitExtraFileArgsNote("brief", spec, more, { noun: "spec" });
|
|
21149
|
+
emitExtraFileArgsNote("brief", spec, more, { noun: "spec", mergeable: namedSpecsMergeable([spec, ...more]) });
|
|
20891
21150
|
return runBrief({
|
|
20892
21151
|
spec,
|
|
20893
21152
|
...opts.json === true ? { json: true } : {},
|
|
20894
|
-
...opts.limit !== void 0 ? { limit:
|
|
20895
|
-
...opts.context !== void 0 ? { context:
|
|
21153
|
+
...opts.limit !== void 0 ? { limit: requireNonNegativeInt("--limit", opts.limit) } : {},
|
|
21154
|
+
...opts.context !== void 0 ? { context: requireNonNegativeInt("--context", opts.context) } : {},
|
|
20896
21155
|
...opts.excludeTests === true ? { excludeTests: true } : {},
|
|
20897
21156
|
...opts.grep !== void 0 ? { grep: opts.grep } : {}
|
|
20898
21157
|
});
|
|
@@ -20900,14 +21159,26 @@ function buildProgram() {
|
|
|
20900
21159
|
);
|
|
20901
21160
|
program2.command("section <spec> [more...]").description(
|
|
20902
21161
|
'read one section from a file (spec: file::heading, or file::<unambiguous heading prefix> \u2014 e.g. "Lesson 16" resolves a longer unique heading; comma-separated file::A,B for a merged multi-heading view), or list all sections with --list'
|
|
20903
|
-
).option("-j, --json", "output as JSON").option("--list", "list all section headings in the file instead of reading one").option("--grep <pattern>", "with --list, filter headings to this regex (literal substring if it is not valid regex)").action(
|
|
20904
|
-
|
|
21162
|
+
).option("-j, --json", "output as JSON").option("--list", "list all section headings in the file instead of reading one").option("--grep <pattern>", "with --list, filter headings to this regex (literal substring if it is not valid regex)").option("--max-lines <n>", "limit returned section content to at most N lines from the top").option("--head <n>", "alias for --max-lines").action((spec, more, opts) => {
|
|
21163
|
+
const maxLinesRaw = opts.maxLines ?? opts.head;
|
|
21164
|
+
const maxLines = maxLinesRaw !== void 0 ? requireNonNegativeInt("--max-lines", maxLinesRaw) : void 0;
|
|
21165
|
+
return opts.list === true ? runExit(() => {
|
|
20905
21166
|
emitExtraFileArgsNote("section --list", spec, more, { mergeable: false });
|
|
20906
21167
|
return runListSections({ file: spec, ...opts.json === true ? { json: true } : {}, ...opts.grep !== void 0 ? { grep: opts.grep } : {} });
|
|
20907
21168
|
}) : runExitText(
|
|
20908
|
-
() => noteExtraFileArgs(
|
|
20909
|
-
|
|
20910
|
-
|
|
21169
|
+
() => noteExtraFileArgs(
|
|
21170
|
+
"section",
|
|
21171
|
+
spec,
|
|
21172
|
+
more,
|
|
21173
|
+
() => runSection({
|
|
21174
|
+
spec,
|
|
21175
|
+
...opts.json === true ? { json: true } : {},
|
|
21176
|
+
...maxLines !== void 0 ? { maxLines } : {}
|
|
21177
|
+
}),
|
|
21178
|
+
{ noun: "spec", mergeable: namedSpecsMergeable([spec, ...more]) }
|
|
21179
|
+
)
|
|
21180
|
+
);
|
|
21181
|
+
});
|
|
20911
21182
|
program2.command("semantic [query]").description("semantic search (falls back to full-text search)").option("-l, --limit <n>", "max results").option("-j, --json", "output as JSON").option("--grep <pattern>", "filter to hits whose file path matches this regex (literal substring if it is not valid regex); matched against the path as rendered").option("--exclude-tests", "hide hits whose file is a test file (opt-in; default output is unchanged)").option("--preflight", "run semantic embedding preflight check and exit").option("--warm", "warm up the embedding model session in memory").action(guard(cmdSemantic));
|
|
20912
21183
|
const registerSymbolListing = (name, description, run2) => {
|
|
20913
21184
|
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(
|
|
@@ -20919,7 +21190,7 @@ function buildProgram() {
|
|
|
20919
21190
|
() => run2({
|
|
20920
21191
|
file,
|
|
20921
21192
|
...opts.json === true ? { json: true } : {},
|
|
20922
|
-
...opts.minLines !== void 0 ? { minLines:
|
|
21193
|
+
...opts.minLines !== void 0 ? { minLines: requireNonNegativeInt("--min-lines", opts.minLines) } : {},
|
|
20923
21194
|
...opts.grep !== void 0 ? { grep: opts.grep } : {},
|
|
20924
21195
|
...opts.forceRefresh === true ? { forceRefresh: true } : {},
|
|
20925
21196
|
...opts.stats === true ? { stats: true } : {}
|
|
@@ -20943,14 +21214,14 @@ function buildProgram() {
|
|
|
20943
21214
|
"for a high-fanout symbol, group references by file (count only) and show only the top N files by reference count instead of a per-line dump"
|
|
20944
21215
|
).option("-C, --context <n>", "lines of call-site source to show before and after each reference (default 0)").option("-j, --json", "output as JSON").option("--exclude-tests", "hide references whose call site lives in a test file (opt-in; default output is unchanged)").option("--grep <pattern>", "filter to references whose call-site file path matches this regex (falls back to a literal substring match when the pattern does not compile); matched against the path as rendered, so ^src/ matches what you see in every form -- drops test/vendored hits from a wide-fanout symbol").action(
|
|
20945
21216
|
(spec, more, opts) => runExit(() => {
|
|
20946
|
-
emitExtraFileArgsNote("refs", spec, more, { noun: "spec" });
|
|
21217
|
+
emitExtraFileArgsNote("refs", spec, more, { noun: "spec", mergeable: namedSpecsMergeable([spec, ...more]) });
|
|
20947
21218
|
return runRefs({
|
|
20948
21219
|
spec,
|
|
20949
21220
|
...opts.callers === true ? { callers: true } : {},
|
|
20950
21221
|
...opts.json === true ? { json: true } : {},
|
|
20951
|
-
...opts.limit !== void 0 ? { limit:
|
|
20952
|
-
...opts.top !== void 0 ? { top:
|
|
20953
|
-
...opts.context !== void 0 ? { context:
|
|
21222
|
+
...opts.limit !== void 0 ? { limit: requireNonNegativeInt("--limit", opts.limit) } : {},
|
|
21223
|
+
...opts.top !== void 0 ? { top: requireNonNegativeInt("--top", opts.top) } : {},
|
|
21224
|
+
...opts.context !== void 0 ? { context: requireNonNegativeInt("--context", opts.context) } : {},
|
|
20954
21225
|
...opts.excludeTests === true ? { excludeTests: true } : {},
|
|
20955
21226
|
...opts.grep !== void 0 ? { grep: opts.grep } : {}
|
|
20956
21227
|
});
|
|
@@ -20964,7 +21235,7 @@ function buildProgram() {
|
|
|
20964
21235
|
program2.command("bench").description("replay a corpus of captured command output through the compressors; prints the byte savings ratio and a fidelity check, and exits 1 when a must-keep line was dropped").option("--corpus <dir>", "corpus directory (default: tests/fixtures/bench, relative to the working directory)").option("--tsv <path>", "append one result row, keyed by the current commit, creating the file and its header if absent").option("--validate", "score the corpus with control filters instead: proves the ratio has a floor and the fidelity guard can actually fail").option("-j, --json", "output as JSON").action(
|
|
20965
21236
|
(opts) => runExitText(
|
|
20966
21237
|
() => runBenchCommand({
|
|
20967
|
-
corpus: opts.corpus ??
|
|
21238
|
+
corpus: opts.corpus ?? path41.join("tests", "fixtures", "bench"),
|
|
20968
21239
|
...opts.tsv !== void 0 ? { tsv: opts.tsv } : {},
|
|
20969
21240
|
...opts.validate === true ? { validate: true } : {},
|
|
20970
21241
|
...opts.json === true ? { json: true } : {}
|
|
@@ -20983,7 +21254,7 @@ function buildProgram() {
|
|
|
20983
21254
|
worker.command("start").description("start the background indexer").action(guard(cmdWorkerStart));
|
|
20984
21255
|
worker.command("stop").description("stop the background indexer").action(guard(cmdWorkerStop));
|
|
20985
21256
|
worker.command("status").description("check if the indexer is running").action(guard(cmdWorkerStatus));
|
|
20986
|
-
program2.command("stats").description("show session statistics (bare = totals only; --full for the breakdown)").option("-j, --json", "output as JSON").option("--full", "show the full breakdown (by source, by command, by day)").option("--short", "force the rich short KPI view even when stdout is not a TTY (e.g. piped)").option("--methodology", "explain local savings estimates and their billing limits").option("--window-days <days>", "days to include (0 = all time)", "30").option("--home-dir <path>", "home directory (for testing)").action(guard(cmdStats));
|
|
21257
|
+
program2.command("stats").description("show session statistics (bare = totals only; --full for the breakdown)").option("-j, --json", "output as JSON").option("--full", "show the full breakdown (by source, by command, by day)").option("--short", "force the rich short KPI view even when stdout is not a TTY (e.g. piped)").option("--methodology", "explain local savings estimates and their billing limits").option("--hooks", "show the per-event/per-harness hook latency breakdown (median/p95/slowest/last-seen)").option("--window-days <days>", "days to include (0 = all time)", "30").option("--home-dir <path>", "home directory (for testing)").action(guard(cmdStats));
|
|
20987
21258
|
program2.command("doctor").description("diagnose token-goat health").option("--context", "include context footprint analysis").option("--json", "emit check results as JSON instead of text").option("--repair", "automatically repair fixable issues (permissive settings, missing semantics models)").option("--fix", "alias for --repair").action(guard(cmdDoctor));
|
|
20988
21259
|
program2.command("bash-output [id]").description("retrieve cached bash output by ID or file").option("--head <n>", "show first N lines").option("--tail <n>", "show last N lines").option("--grep <pattern>", "filter lines matching regex").option("--max-matches <n>", "cap --grep output to the first N matching lines").option("--section <heading>", "extract a specific section from the output").option("--full", "print the entire cached entry with no head/tail elision").option("--file <path>", "read from raw output file instead of cache").option("--transcript", "parse the --file as a JSONL agent transcript: keep assistant text blocks in order before filtering").option("--verify-last-write [seconds]", "verify the file or cache entry was written within [seconds] (default: 60s) to catch stale/no-op terminal output").option("--strict", "exit non-zero when --verify-last-write detects stale output").action(guard(cmdBashOutput));
|
|
20989
21260
|
program2.command("web-output [id]").description("retrieve a cached WebFetch response body by ID").option("--head <n>", "show first N lines").option("--tail <n>", "show last N lines").option("--grep <pattern>", "filter lines matching regex").option("--max-matches <n>", "cap --grep output to the first N matching lines").option("--section <heading>", "extract a specific section from the response").option("--full", "print the entire cached entry with no head/tail elision").option("--raw", "return the body as actually fetched, before extractCleanText cleaning, instead of the default cleaned text").action(guard(cmdWebOutput));
|
|
@@ -20998,7 +21269,7 @@ function buildProgram() {
|
|
|
20998
21269
|
program2.command("compress [command...]").alias("bash").alias("run").description("run a shell command (under a POSIX shell / bash) and emit a compressed view of its output").allowUnknownOption(true).option("-c, --cmd <command>", "the shell command to run, as one string (use / for paths across platforms)").option("--cmd-b64 <payload>", "the shell command as a base64-encoded string (preserves quotes, backslashes, and symbols across platforms)").option("-f, --filter <name>", "filter name (auto-detected from the command when omitted)").option("--timeout <seconds>", "wall-clock timeout in seconds (0 = built-in default)").option("--no-compress", "stream output raw without compression (debug the wrapper)").option("--profile <name>", "compression profile: aggressive | balanced | minimal").option("--max-tokens <n>", "post-compress token cap (0 = no cap)").option("-q, --quiet-success", "on exit code 0, emit only [tg: ok] summary and store full output for recall via bash-output").option("--native", "use native platform shell (e.g. cmd.exe on Windows) instead of bash, preserving Windows path backslashes").action(cmdCompress);
|
|
20999
21270
|
program2.command("upgrade").alias("update").description("check for updates and upgrade token-goat to the latest version").option("--check", "check whether an update is available without installing").option("-j, --json", "emit version check status as JSON").action(
|
|
21000
21271
|
(opts) => guard(async () => {
|
|
21001
|
-
const { cmdUpgrade } = await import("./token-goat-chunk-
|
|
21272
|
+
const { cmdUpgrade } = await import("./token-goat-chunk-AU2K2QHR.mjs");
|
|
21002
21273
|
await cmdUpgrade(opts, () => cmdInstall({}));
|
|
21003
21274
|
})()
|
|
21004
21275
|
);
|
|
@@ -21068,6 +21339,9 @@ async function run(argv = process.argv) {
|
|
|
21068
21339
|
}
|
|
21069
21340
|
|
|
21070
21341
|
export {
|
|
21342
|
+
requireInt,
|
|
21343
|
+
requireNonNegativeInt,
|
|
21344
|
+
requirePositiveInt,
|
|
21071
21345
|
expandGlobs,
|
|
21072
21346
|
fenceFileText,
|
|
21073
21347
|
fenceFileFieldIfMatched,
|
|
@@ -21127,9 +21401,6 @@ export {
|
|
|
21127
21401
|
CliError,
|
|
21128
21402
|
out,
|
|
21129
21403
|
err,
|
|
21130
|
-
requireInt2 as requireInt,
|
|
21131
|
-
requireNonNegativeInt2 as requireNonNegativeInt,
|
|
21132
|
-
requirePositiveInt2 as requirePositiveInt,
|
|
21133
21404
|
cmdIndex,
|
|
21134
21405
|
visualStudioManualSteps,
|
|
21135
21406
|
leftoverIntegrations,
|