token-goat 2.8.6 → 2.9.2
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 +27 -717
- package/SECURITY.md +1 -1
- package/dist/token-goat-chunk-CGWACYYZ.mjs +79 -0
- package/dist/{token-goat-chunk-XAWIELXH.mjs → token-goat-chunk-FXAPKVRG.mjs} +545 -217
- package/dist/{token-goat-chunk-SHN4UTL4.mjs → token-goat-chunk-JMUBXBG7.mjs} +5 -5
- package/dist/{token-goat-chunk-U7M2LTGP.mjs → token-goat-chunk-KKIB7O3Z.mjs} +854 -283
- package/dist/{token-goat-chunk-ZIPBLIUZ.mjs → token-goat-chunk-LEBYARO3.mjs} +28 -4
- package/dist/{token-goat-chunk-DK4VLLYB.mjs → token-goat-chunk-LJ3CHCTT.mjs} +2061 -3612
- package/dist/{token-goat-chunk-L2XHDICZ.mjs → token-goat-chunk-NY4HYRSK.mjs} +28 -27
- package/dist/{token-goat-chunk-MA5237JN.mjs → token-goat-chunk-Q4LOQY44.mjs} +205 -60
- package/dist/{token-goat-chunk-GUNYAGOZ.mjs → token-goat-chunk-UZ2NFOOZ.mjs} +10103 -117
- package/dist/{token-goat-chunk-TELKICYU.mjs → token-goat-chunk-VCNW7BGU.mjs} +155 -519
- package/dist/{token-goat-chunk-LJEETTER.mjs → token-goat-chunk-ZVN5WHTO.mjs} +5 -5
- package/dist/token-goat-hook.mjs +5 -5
- package/dist/token-goat.core.mjs +5 -5
- package/docs/C4_RUNTIME_ARCHITECTURE.md +303 -0
- package/docs/cli.md +339 -0
- package/docs/install.md +331 -0
- package/docs/security.md +102 -0
- package/package.json +7 -2
- package/dist/token-goat-chunk-2JZ66BBE.mjs +0 -7157
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
AMBIGUOUS_HEADING_LIMIT,
|
|
6
6
|
CONTENT_MAX_INPUT_CHARS,
|
|
7
7
|
WHOLE_FILE_NOTE_SYMBOL,
|
|
8
|
+
buildImportGraph,
|
|
8
9
|
compressText,
|
|
9
10
|
computeFileFingerprint,
|
|
10
11
|
createHandoff,
|
|
@@ -15,7 +16,6 @@ import {
|
|
|
15
16
|
extractTranscriptText,
|
|
16
17
|
filterSimilarHeadings,
|
|
17
18
|
findSpecSeparator,
|
|
18
|
-
getTrackedFiles,
|
|
19
19
|
healStaleIndex,
|
|
20
20
|
isAvailable as isAvailable2,
|
|
21
21
|
listSections,
|
|
@@ -86,43 +86,38 @@ import {
|
|
|
86
86
|
runZipRead,
|
|
87
87
|
symbolNamesInFile,
|
|
88
88
|
upsertNote
|
|
89
|
-
} from "./token-goat-chunk-
|
|
89
|
+
} from "./token-goat-chunk-FXAPKVRG.mjs";
|
|
90
90
|
import {
|
|
91
|
-
|
|
91
|
+
DEFAULT_RECONCILE_BUDGET_MS,
|
|
92
92
|
GEMINI_TOOL_NAME_MAP,
|
|
93
93
|
HOOK_EVENTS,
|
|
94
|
-
RECALL_DEFAULT_LIMIT,
|
|
95
94
|
WEB_OUTPUT_SUBDIR,
|
|
96
95
|
accumulateResidentLine,
|
|
97
96
|
buildCommandManifest,
|
|
98
97
|
checkSymbolBodySize,
|
|
99
|
-
commandHash,
|
|
100
98
|
createResidentContextStats,
|
|
101
99
|
filterCommandManifest,
|
|
102
100
|
formatBytes,
|
|
103
101
|
formatCommandManifest,
|
|
104
102
|
formatTokenEstimate,
|
|
105
|
-
getBashOutput,
|
|
106
103
|
getWebOutput,
|
|
107
104
|
getWebOutputByUrlFromDisk,
|
|
108
105
|
getWebOutputRaw,
|
|
109
|
-
isRecallCacheType,
|
|
110
|
-
listRecentRecall,
|
|
111
|
-
normalizeCommandForCacheKey,
|
|
112
106
|
readCopilotMcpTools,
|
|
113
107
|
readStdinJson,
|
|
114
|
-
|
|
108
|
+
runReconcile,
|
|
115
109
|
storeWebOutput,
|
|
116
110
|
summarizeResidentContext
|
|
117
|
-
} from "./token-goat-chunk-
|
|
111
|
+
} from "./token-goat-chunk-VCNW7BGU.mjs";
|
|
118
112
|
import {
|
|
119
113
|
AGENT_SALT_MARKER,
|
|
114
|
+
BASH_OUTPUT_SUBDIR,
|
|
120
115
|
CONTEXT_AUTOCOMPACT_TOKENS,
|
|
121
116
|
DEFAULT_MAX_AGE_MS,
|
|
122
117
|
DEFAULT_MAX_COUNT,
|
|
123
|
-
MATERIALIZE_SHRUNK_IMAGE_JS,
|
|
124
118
|
MAX_FILES_SCANNED,
|
|
125
119
|
PARSER_FINGERPRINT,
|
|
120
|
+
RECALL_DEFAULT_LIMIT,
|
|
126
121
|
SESSIONS_SUBDIR,
|
|
127
122
|
SKILLS_OUTPUT_SUBDIR,
|
|
128
123
|
SKIP_DIRS,
|
|
@@ -131,7 +126,6 @@ import {
|
|
|
131
126
|
UNTRUSTED_WEB_TAG,
|
|
132
127
|
WORKER_HEARTBEAT_STALE_MS,
|
|
133
128
|
WorkerAlreadyRunningError,
|
|
134
|
-
anchoredMarkerPattern,
|
|
135
129
|
applyIndexingPriority,
|
|
136
130
|
auditClaudeMd,
|
|
137
131
|
buildExtractiveCompact,
|
|
@@ -140,14 +134,11 @@ import {
|
|
|
140
134
|
buildProjectMap,
|
|
141
135
|
buildStats,
|
|
142
136
|
capJsonRows,
|
|
137
|
+
commandHash,
|
|
143
138
|
compactDoc,
|
|
144
139
|
compactPathFor,
|
|
145
140
|
confirmAndApply,
|
|
146
141
|
contentHash,
|
|
147
|
-
copilotCliConfigPath,
|
|
148
|
-
copilotCliMcpToolsDir,
|
|
149
|
-
copilotCliScriptPath,
|
|
150
|
-
copilotCliUserRoot,
|
|
151
142
|
detectWalkMode,
|
|
152
143
|
dirtyQueuePathFor,
|
|
153
144
|
docxOutline,
|
|
@@ -171,13 +162,13 @@ import {
|
|
|
171
162
|
findMemSuggestionCandidates,
|
|
172
163
|
findMemoryMd,
|
|
173
164
|
findOrphanedChunkPaths,
|
|
174
|
-
findStrayClaudeMdBlocks,
|
|
175
165
|
findSystemTempFiles,
|
|
176
166
|
formatAge,
|
|
177
167
|
formatCsvTable,
|
|
178
168
|
formatMemSuggestions,
|
|
179
169
|
formatProjectMap,
|
|
180
170
|
formatXlsxRange,
|
|
171
|
+
getBashOutput,
|
|
181
172
|
getContextPressure,
|
|
182
173
|
getDirtyPathsFor,
|
|
183
174
|
getEmbeddingCoverage,
|
|
@@ -188,35 +179,24 @@ import {
|
|
|
188
179
|
getSessionFiles,
|
|
189
180
|
getSessionId,
|
|
190
181
|
getSkillFilePath,
|
|
191
|
-
|
|
182
|
+
getTrackedFiles,
|
|
192
183
|
headSheet,
|
|
193
184
|
incrementSkillHit,
|
|
194
185
|
indexFileEmbeddings,
|
|
195
186
|
indexFileSync,
|
|
196
187
|
indexedPathSpellingIsStale,
|
|
197
188
|
injectionFencingEnabled,
|
|
198
|
-
installClaudeMd,
|
|
199
|
-
installCodex,
|
|
200
|
-
installCopilotCli,
|
|
201
|
-
installGrok,
|
|
202
|
-
installHooks,
|
|
203
|
-
installKimi,
|
|
204
|
-
installSkill,
|
|
205
|
-
installVscode,
|
|
206
189
|
isAvailable,
|
|
207
|
-
isCodexInstalled,
|
|
208
190
|
isCompactFresh,
|
|
209
|
-
isCopilotCliInstalled,
|
|
210
191
|
isEmbedFresh,
|
|
211
192
|
isEmbeddableDocument,
|
|
212
|
-
isGrokInstalled,
|
|
213
193
|
isHintCategory,
|
|
214
|
-
isInstalled,
|
|
215
|
-
isKimiInstalled,
|
|
216
194
|
isParseSkipEligible,
|
|
195
|
+
isRecallCacheType,
|
|
217
196
|
isWorkerRunning,
|
|
218
197
|
listBlobs,
|
|
219
198
|
listOutputs,
|
|
199
|
+
listRecentRecall,
|
|
220
200
|
listSheets,
|
|
221
201
|
listSkills,
|
|
222
202
|
loadBlob,
|
|
@@ -224,7 +204,7 @@ import {
|
|
|
224
204
|
mapLookupBytesSaved,
|
|
225
205
|
markCategoryEffective,
|
|
226
206
|
markCategoryIneffective,
|
|
227
|
-
|
|
207
|
+
normalizeCommandForCacheKey,
|
|
228
208
|
parseWhereSpecs,
|
|
229
209
|
pptxNotesText,
|
|
230
210
|
pptxOutline,
|
|
@@ -245,6 +225,7 @@ import {
|
|
|
245
225
|
runContextStats,
|
|
246
226
|
runDetachedWorkerDaemon,
|
|
247
227
|
scanAndRecord,
|
|
228
|
+
searchRecall,
|
|
248
229
|
shrinkImage,
|
|
249
230
|
skillOutputsDir,
|
|
250
231
|
startDetachedWorker,
|
|
@@ -253,33 +234,35 @@ import {
|
|
|
253
234
|
storeOutput,
|
|
254
235
|
tokenGoatHome,
|
|
255
236
|
trimToBudget,
|
|
256
|
-
uninstallClaudeMd,
|
|
257
|
-
uninstallCodex,
|
|
258
|
-
uninstallCopilotCli,
|
|
259
|
-
uninstallGrok,
|
|
260
|
-
uninstallHooks,
|
|
261
|
-
uninstallKimi,
|
|
262
|
-
uninstallSkill,
|
|
263
|
-
uninstallVscode,
|
|
264
237
|
urlPolicyDenialReason,
|
|
265
|
-
|
|
238
|
+
visionTokensSavedByText,
|
|
266
239
|
walkProject,
|
|
267
240
|
writeCompact
|
|
268
|
-
} from "./token-goat-chunk-
|
|
241
|
+
} from "./token-goat-chunk-LJ3CHCTT.mjs";
|
|
269
242
|
import {
|
|
270
243
|
C,
|
|
271
244
|
CONFIG_KEY_ENV_OVERRIDES,
|
|
272
245
|
ENV_KEYS,
|
|
273
246
|
FILTERS,
|
|
274
247
|
LOCK_WAIT_MS_HARDENED,
|
|
248
|
+
MATERIALIZE_SHRUNK_IMAGE_JS,
|
|
249
|
+
PROJECT_LOCKED_KEYS,
|
|
250
|
+
PROJECT_LOCKED_SECTIONS,
|
|
275
251
|
RESET,
|
|
276
252
|
VERSION,
|
|
277
253
|
_useRichStats,
|
|
254
|
+
anchoredMarkerPattern,
|
|
278
255
|
atomicWriteBytes,
|
|
279
256
|
atomicWriteText,
|
|
280
257
|
canonicalize,
|
|
258
|
+
cappedSourceBytesSaved,
|
|
281
259
|
colorStdout,
|
|
260
|
+
compileCustomPatterns,
|
|
282
261
|
configPath,
|
|
262
|
+
copilotCliConfigPath,
|
|
263
|
+
copilotCliMcpToolsDir,
|
|
264
|
+
copilotCliScriptPath,
|
|
265
|
+
copilotCliUserRoot,
|
|
283
266
|
countNoun,
|
|
284
267
|
dataDir,
|
|
285
268
|
decodeSource,
|
|
@@ -289,11 +272,13 @@ import {
|
|
|
289
272
|
encodeSource,
|
|
290
273
|
ensureDirSync,
|
|
291
274
|
ensureNewline,
|
|
275
|
+
envBool,
|
|
292
276
|
escapeRegExp,
|
|
293
277
|
extractErrorMessage,
|
|
294
278
|
fg,
|
|
295
279
|
fileIsAbsent,
|
|
296
280
|
findProject,
|
|
281
|
+
findStrayClaudeMdBlocks,
|
|
297
282
|
fingerprintContent,
|
|
298
283
|
fingerprintFile,
|
|
299
284
|
foldPath,
|
|
@@ -304,9 +289,24 @@ import {
|
|
|
304
289
|
getLastProjectConfigParseError,
|
|
305
290
|
getProjectConfigInfo,
|
|
306
291
|
globalDbPath,
|
|
292
|
+
groupHasTokenGoat,
|
|
293
|
+
installClaudeMd,
|
|
294
|
+
installCodex,
|
|
295
|
+
installCopilotCli,
|
|
296
|
+
installGrok,
|
|
297
|
+
installHooks,
|
|
298
|
+
installKimi,
|
|
307
299
|
installSingleFilePlugin,
|
|
300
|
+
installSkill,
|
|
301
|
+
installVscode,
|
|
308
302
|
invalidateConfigCache,
|
|
303
|
+
isCodexInstalled,
|
|
304
|
+
isCopilotCliInstalled,
|
|
309
305
|
isDotenvPath,
|
|
306
|
+
isGrokInstalled,
|
|
307
|
+
isInstalled,
|
|
308
|
+
isKimiInstalled,
|
|
309
|
+
isTestFile,
|
|
310
310
|
isUnderBlockedRoot,
|
|
311
311
|
isWindows,
|
|
312
312
|
lastProjectConfigLockedKeys,
|
|
@@ -314,6 +314,7 @@ import {
|
|
|
314
314
|
loadPersistedConfig,
|
|
315
315
|
normalizeDarwinSystemAlias,
|
|
316
316
|
normalizePath,
|
|
317
|
+
otherScopeHasManagedServer,
|
|
317
318
|
pad,
|
|
318
319
|
parse,
|
|
319
320
|
pushAll,
|
|
@@ -341,14 +342,23 @@ import {
|
|
|
341
342
|
summarize,
|
|
342
343
|
toDisplayPath,
|
|
343
344
|
toKB,
|
|
345
|
+
uninstallClaudeMd,
|
|
346
|
+
uninstallCodex,
|
|
347
|
+
uninstallCopilotCli,
|
|
348
|
+
uninstallGrok,
|
|
349
|
+
uninstallHooks,
|
|
350
|
+
uninstallKimi,
|
|
344
351
|
uninstallSingleFilePlugin,
|
|
352
|
+
uninstallSkill,
|
|
353
|
+
uninstallVscode,
|
|
345
354
|
validateEnumField,
|
|
346
355
|
validateNumericField,
|
|
356
|
+
vscodeDecoderConfigured,
|
|
347
357
|
withExtension,
|
|
348
358
|
withFileLock,
|
|
349
359
|
withRetryOnLock,
|
|
350
360
|
writeJsonSettings
|
|
351
|
-
} from "./token-goat-chunk-
|
|
361
|
+
} from "./token-goat-chunk-UZ2NFOOZ.mjs";
|
|
352
362
|
import {
|
|
353
363
|
__export
|
|
354
364
|
} from "./token-goat-chunk-AEX54RUZ.mjs";
|
|
@@ -3467,9 +3477,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3467
3477
|
* @param {string} [path]
|
|
3468
3478
|
* @return {(string|null|Command)}
|
|
3469
3479
|
*/
|
|
3470
|
-
executableDir(
|
|
3471
|
-
if (
|
|
3472
|
-
this._executableDir =
|
|
3480
|
+
executableDir(path30) {
|
|
3481
|
+
if (path30 === void 0) return this._executableDir;
|
|
3482
|
+
this._executableDir = path30;
|
|
3473
3483
|
return this;
|
|
3474
3484
|
}
|
|
3475
3485
|
/**
|
|
@@ -3769,7 +3779,7 @@ function attemptedCommandName(argv) {
|
|
|
3769
3779
|
|
|
3770
3780
|
// src/cli.ts
|
|
3771
3781
|
import * as fs28 from "fs";
|
|
3772
|
-
import * as
|
|
3782
|
+
import * as path29 from "path";
|
|
3773
3783
|
import { homedir as homedir13 } from "os";
|
|
3774
3784
|
|
|
3775
3785
|
// src/walk_index.ts
|
|
@@ -4311,7 +4321,7 @@ function materializeShrunkImage(context: string | undefined): string | undefined
|
|
|
4311
4321
|
try {
|
|
4312
4322
|
pruneMaterializedShrinks();
|
|
4313
4323
|
const buf = Buffer.from(match[2], "base64");
|
|
4314
|
-
const name = \`token-goat-shrink-\${process.pid}-\${Date.now()}-\${
|
|
4324
|
+
const name = \`token-goat-shrink-\${process.pid}-\${Date.now()}-\${globalThis.crypto.randomUUID()}.\${match[1]}\`;
|
|
4315
4325
|
const file = path.join(os.tmpdir(), name);
|
|
4316
4326
|
fs.writeFileSync(file, buf);
|
|
4317
4327
|
return file;
|
|
@@ -5657,9 +5667,110 @@ function formatCues(cues) {
|
|
|
5657
5667
|
return cues.map((cue) => `[${formatTimestamp(cue.startSeconds)}]${cue.speaker !== null ? ` ${cue.speaker}:` : ""} ${cue.text}`).join("\n");
|
|
5658
5668
|
}
|
|
5659
5669
|
|
|
5670
|
+
// src/affected.ts
|
|
5671
|
+
import * as path9 from "node:path";
|
|
5672
|
+
var DEFAULT_AFFECTED_DEPTH = 5;
|
|
5673
|
+
function emit(text) {
|
|
5674
|
+
const payload = colorStdout() ? text : stripAnsi(text);
|
|
5675
|
+
process.stdout.write(ensureNewline(payload));
|
|
5676
|
+
}
|
|
5677
|
+
function emitErr(text) {
|
|
5678
|
+
process.stderr.write(ensureNewline(text));
|
|
5679
|
+
}
|
|
5680
|
+
function computeAffected(opts) {
|
|
5681
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
5682
|
+
const depth = opts.depth ?? DEFAULT_AFFECTED_DEPTH;
|
|
5683
|
+
const { files, importedBy } = buildImportGraph(cwd);
|
|
5684
|
+
const trackedByFolded = /* @__PURE__ */ new Map();
|
|
5685
|
+
for (const f of files) trackedByFolded.set(foldPath(normalizePath(f)), f);
|
|
5686
|
+
const unknownSeeds = [];
|
|
5687
|
+
let frontier = [];
|
|
5688
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5689
|
+
for (const raw of opts.files) {
|
|
5690
|
+
const abs = normalizePath(path9.resolve(cwd, raw));
|
|
5691
|
+
const tracked = trackedByFolded.get(foldPath(abs));
|
|
5692
|
+
if (tracked === void 0) {
|
|
5693
|
+
unknownSeeds.push(raw);
|
|
5694
|
+
continue;
|
|
5695
|
+
}
|
|
5696
|
+
if (seen.has(tracked)) continue;
|
|
5697
|
+
seen.add(tracked);
|
|
5698
|
+
frontier.push(tracked);
|
|
5699
|
+
}
|
|
5700
|
+
let hops = 0;
|
|
5701
|
+
while (frontier.length > 0 && hops < depth) {
|
|
5702
|
+
const next = [];
|
|
5703
|
+
for (const file of frontier) {
|
|
5704
|
+
for (const importer of importedBy.get(file) ?? []) {
|
|
5705
|
+
if (seen.has(importer)) continue;
|
|
5706
|
+
seen.add(importer);
|
|
5707
|
+
next.push(importer);
|
|
5708
|
+
}
|
|
5709
|
+
}
|
|
5710
|
+
frontier = next;
|
|
5711
|
+
hops++;
|
|
5712
|
+
}
|
|
5713
|
+
const depthLimited = frontier.length > 0;
|
|
5714
|
+
const matcher = opts.filter === void 0 ? null : new RegExp(opts.filter);
|
|
5715
|
+
const isAffectedTest = (f) => matcher === null ? isTestFile(f) : matcher.test(f);
|
|
5716
|
+
const root = getDisplayRoot(cwd);
|
|
5717
|
+
const testFiles = [...seen].filter(isAffectedTest).map((f) => toDisplayPath(root, f)).sort();
|
|
5718
|
+
return {
|
|
5719
|
+
testFiles,
|
|
5720
|
+
reachedCount: seen.size,
|
|
5721
|
+
unknownSeeds,
|
|
5722
|
+
depthLimited,
|
|
5723
|
+
unexploredAtFrontier: frontier.length,
|
|
5724
|
+
depth
|
|
5725
|
+
};
|
|
5726
|
+
}
|
|
5727
|
+
function runAffected(opts) {
|
|
5728
|
+
if (opts.depth !== void 0 && opts.depth <= 0) {
|
|
5729
|
+
emitErr(`--depth must be a positive number, got: ${opts.depth}`);
|
|
5730
|
+
return 1;
|
|
5731
|
+
}
|
|
5732
|
+
if (opts.files.length === 0) {
|
|
5733
|
+
emitErr("No changed files given. Pass paths as arguments, or pipe them in with --stdin.");
|
|
5734
|
+
return 1;
|
|
5735
|
+
}
|
|
5736
|
+
if (opts.filter !== void 0) {
|
|
5737
|
+
try {
|
|
5738
|
+
new RegExp(opts.filter);
|
|
5739
|
+
} catch (err2) {
|
|
5740
|
+
emitErr(`--filter is not a valid regular expression: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
5741
|
+
return 1;
|
|
5742
|
+
}
|
|
5743
|
+
}
|
|
5744
|
+
const result = computeAffected(opts);
|
|
5745
|
+
if (opts.json === true) {
|
|
5746
|
+
emit(JSON.stringify(result, null, 2));
|
|
5747
|
+
return 0;
|
|
5748
|
+
}
|
|
5749
|
+
if (opts.quiet === true) {
|
|
5750
|
+
for (const f of result.testFiles) emit(f);
|
|
5751
|
+
} else if (result.testFiles.length === 0) {
|
|
5752
|
+
const traced = opts.files.length - result.unknownSeeds.length;
|
|
5753
|
+
if (traced === 0) {
|
|
5754
|
+
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.`);
|
|
5755
|
+
} else {
|
|
5756
|
+
emit(`No test files import ${countNoun(traced, "changed file")} (within ${countNoun(result.depth, "hop")}).`);
|
|
5757
|
+
}
|
|
5758
|
+
} else {
|
|
5759
|
+
emit(`${countNoun(result.testFiles.length, "affected test file")} (${countNoun(result.reachedCount, "file")} reached within ${countNoun(result.depth, "hop")}):`);
|
|
5760
|
+
for (const f of result.testFiles) emit(` ${f}`);
|
|
5761
|
+
}
|
|
5762
|
+
if (result.unknownSeeds.length > 0) {
|
|
5763
|
+
emitErr(`Not tracked in this project, so nothing was traced from ${result.unknownSeeds.length === 1 ? "it" : "them"}: ${result.unknownSeeds.join(", ")}`);
|
|
5764
|
+
}
|
|
5765
|
+
if (result.depthLimited) {
|
|
5766
|
+
emitErr(`Stopped at --depth ${result.depth} with ${countNoun(result.unexploredAtFrontier, "file")} still unexplored; more tests may be affected (raise --depth).`);
|
|
5767
|
+
}
|
|
5768
|
+
return 0;
|
|
5769
|
+
}
|
|
5770
|
+
|
|
5660
5771
|
// src/purge.ts
|
|
5661
5772
|
import * as fs10 from "node:fs";
|
|
5662
|
-
import * as
|
|
5773
|
+
import * as path10 from "node:path";
|
|
5663
5774
|
function directorySize(root) {
|
|
5664
5775
|
let total = 0;
|
|
5665
5776
|
let entries;
|
|
@@ -5669,7 +5780,7 @@ function directorySize(root) {
|
|
|
5669
5780
|
return 0;
|
|
5670
5781
|
}
|
|
5671
5782
|
for (const entry of entries) {
|
|
5672
|
-
const full =
|
|
5783
|
+
const full = path10.join(root, entry.name);
|
|
5673
5784
|
if (entry.isDirectory()) {
|
|
5674
5785
|
total += directorySize(full);
|
|
5675
5786
|
continue;
|
|
@@ -5712,7 +5823,7 @@ function formatBytes2(bytes) {
|
|
|
5712
5823
|
|
|
5713
5824
|
// src/cli_stats.ts
|
|
5714
5825
|
import * as fs11 from "node:fs";
|
|
5715
|
-
import * as
|
|
5826
|
+
import * as path11 from "node:path";
|
|
5716
5827
|
function writeRaw(text) {
|
|
5717
5828
|
const payload = colorStdout() ? text : stripAnsi(text);
|
|
5718
5829
|
process.stdout.write(ensureNewline(payload));
|
|
@@ -5724,7 +5835,7 @@ function formatTopFiles(ranked) {
|
|
|
5724
5835
|
if (ranked.length === 0) return "";
|
|
5725
5836
|
const lines = ["Top files this session:"];
|
|
5726
5837
|
for (const { path: filePath, count } of ranked) {
|
|
5727
|
-
const basename8 =
|
|
5838
|
+
const basename8 = path11.basename(filePath);
|
|
5728
5839
|
lines.push(` ${count.toString().padStart(3)}x ${basename8} (${filePath})`);
|
|
5729
5840
|
}
|
|
5730
5841
|
return lines.join("\n");
|
|
@@ -5741,12 +5852,12 @@ function renderTopSessionFiles(topN = 5) {
|
|
|
5741
5852
|
}
|
|
5742
5853
|
function renderTopSessionFilesFromDisk(topN = 5, overrideSessionsDir) {
|
|
5743
5854
|
try {
|
|
5744
|
-
const sessionsDir = overrideSessionsDir ??
|
|
5855
|
+
const sessionsDir = overrideSessionsDir ?? path11.join(dataDir(), "sessions");
|
|
5745
5856
|
if (!fs11.existsSync(sessionsDir)) return "";
|
|
5746
|
-
const files = fs11.readdirSync(sessionsDir).filter((f) => f.endsWith(".json")).map((f) => ({ name: f, mtime: fs11.statSync(
|
|
5857
|
+
const files = fs11.readdirSync(sessionsDir).filter((f) => f.endsWith(".json")).map((f) => ({ name: f, mtime: fs11.statSync(path11.join(sessionsDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).slice(0, 3);
|
|
5747
5858
|
for (const { name } of files) {
|
|
5748
5859
|
try {
|
|
5749
|
-
const raw = fs11.readFileSync(
|
|
5860
|
+
const raw = fs11.readFileSync(path11.join(sessionsDir, name), "utf-8");
|
|
5750
5861
|
const data = JSON.parse(raw);
|
|
5751
5862
|
const filesList = data["files"];
|
|
5752
5863
|
if (!Array.isArray(filesList)) continue;
|
|
@@ -5766,8 +5877,8 @@ function renderTopSessionFilesFromDisk(topN = 5, overrideSessionsDir) {
|
|
|
5766
5877
|
var METHODOLOGY = {
|
|
5767
5878
|
estimate_scope: "Local estimate of content avoided or reduced by token-goat.",
|
|
5768
5879
|
billing: "They are not GitHub Copilot usage, provider-reported token consumption, or billing data.",
|
|
5769
|
-
byte_derived_formula: "Most read, hook, and command entries go through savedTokensFromBytes in src/stats.ts, which is Math.round(bytes_saved / 4).",
|
|
5770
|
-
filter_estimates: "Output compressors record their filter-calculated delta
|
|
5880
|
+
byte_derived_formula: "Most read, hook, and command entries go through savedTokensFromBytes in src/stats.ts, which is Math.round(bytes_saved / 4). Image entries are the exception: they are priced in visual tokens, not bytes.",
|
|
5881
|
+
filter_estimates: "Output compressors record their filter-calculated delta, measured against what the harness would actually have delivered rather than the full command output. Image entries are priced in visual tokens (28x28-pixel patches, capped at the configured vision tier), because an image is not billed by its byte size; where the pixel dimensions are unavailable the tier ceiling is used.",
|
|
5771
5882
|
advisory_events: "Zero-byte, zero-token advisory events show that guidance fired, not that an agent followed it.",
|
|
5772
5883
|
audit: "Use stats --full or stats --json for source and command breakdowns; reconcile billing with provider-exported usage data."
|
|
5773
5884
|
};
|
|
@@ -5835,12 +5946,12 @@ function runStats(opts = {}) {
|
|
|
5835
5946
|
// src/cli_doctor.ts
|
|
5836
5947
|
import * as fs12 from "fs";
|
|
5837
5948
|
import * as os8 from "os";
|
|
5838
|
-
import * as
|
|
5949
|
+
import * as path12 from "path";
|
|
5839
5950
|
import { execSync, spawnSync as spawnSync2 } from "child_process";
|
|
5840
5951
|
function globalMcpConfigPath() {
|
|
5841
5952
|
const copilotHome = process.env["COPILOT_HOME"];
|
|
5842
|
-
const root = copilotHome !== void 0 && copilotHome.trim() !== "" ?
|
|
5843
|
-
return
|
|
5953
|
+
const root = copilotHome !== void 0 && copilotHome.trim() !== "" ? path12.resolve(copilotHome) : path12.join(os8.homedir(), ".copilot");
|
|
5954
|
+
return path12.join(root, "mcp-config.json");
|
|
5844
5955
|
}
|
|
5845
5956
|
function checkGlobalMcpConfig(configPath2 = globalMcpConfigPath()) {
|
|
5846
5957
|
if (!fs12.existsSync(configPath2)) {
|
|
@@ -5957,7 +6068,7 @@ function checkWorkerRunning(dataDir2) {
|
|
|
5957
6068
|
}
|
|
5958
6069
|
var DB_SIZE_WARN_BYTES = 1024 * 1024 * 1024;
|
|
5959
6070
|
function checkDbExists(dataDir2) {
|
|
5960
|
-
const dbPath =
|
|
6071
|
+
const dbPath = path12.join(dataDir2, "global.db");
|
|
5961
6072
|
if (!fs12.existsSync(dbPath)) {
|
|
5962
6073
|
return {
|
|
5963
6074
|
name: "Database",
|
|
@@ -6295,6 +6406,40 @@ function checkStrayClaudeMdBlocks(searchRoot) {
|
|
|
6295
6406
|
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(", ")}`
|
|
6296
6407
|
};
|
|
6297
6408
|
}
|
|
6409
|
+
var LOCKED_BOOLEAN_SAFE_VALUE = {
|
|
6410
|
+
"gdrive.enabled": false,
|
|
6411
|
+
"injection.enabled": true,
|
|
6412
|
+
"redaction.strict": true,
|
|
6413
|
+
"mcp.confine_reads_to_project_root": true,
|
|
6414
|
+
"screenshot.block_private_targets": true,
|
|
6415
|
+
"network.offline": true,
|
|
6416
|
+
"indexing.cross_project_symbols": false,
|
|
6417
|
+
"webfetch.compress_bodies": true
|
|
6418
|
+
};
|
|
6419
|
+
function lockedEnvOverridableKeys() {
|
|
6420
|
+
const out2 = [];
|
|
6421
|
+
for (const key of Object.keys(CONFIG_KEY_ENV_OVERRIDES)) {
|
|
6422
|
+
const section = key.split(".")[0] ?? "";
|
|
6423
|
+
const locked = PROJECT_LOCKED_SECTIONS.includes(section) || PROJECT_LOCKED_KEYS.includes(key);
|
|
6424
|
+
if (locked && (CONFIG_KEY_ENV_OVERRIDES[key] ?? []).length > 0) out2.push(key);
|
|
6425
|
+
}
|
|
6426
|
+
return out2.sort();
|
|
6427
|
+
}
|
|
6428
|
+
function envOverriddenSecuritySettings() {
|
|
6429
|
+
const out2 = [];
|
|
6430
|
+
for (const setting of lockedEnvOverridableKeys()) {
|
|
6431
|
+
const safe = LOCKED_BOOLEAN_SAFE_VALUE[setting];
|
|
6432
|
+
for (const envVar of CONFIG_KEY_ENV_OVERRIDES[setting] ?? []) {
|
|
6433
|
+
if (safe === void 0) {
|
|
6434
|
+
const raw = process.env[envVar];
|
|
6435
|
+
if (raw !== void 0 && raw.trim() !== "") out2.push({ setting, envVar, kind: "replaced" });
|
|
6436
|
+
} else if (envBool(envVar, safe) !== safe) {
|
|
6437
|
+
out2.push({ setting, envVar, kind: "weakened" });
|
|
6438
|
+
}
|
|
6439
|
+
}
|
|
6440
|
+
}
|
|
6441
|
+
return out2;
|
|
6442
|
+
}
|
|
6298
6443
|
function checkSecurityPosture(cfg, dataDirPath) {
|
|
6299
6444
|
const results = [];
|
|
6300
6445
|
results.push({
|
|
@@ -6317,6 +6462,19 @@ function checkSecurityPosture(cfg, dataDirPath) {
|
|
|
6317
6462
|
status: "ok",
|
|
6318
6463
|
message: allow > 0 ? `${allow} allowed host pattern${allow === 1 ? "" : "s"}, ${deny} denied: nothing outside the allow list is fetched` : `no allow list, ${deny} denied pattern${deny === 1 ? "" : "s"}: any host not denied can be fetched`
|
|
6319
6464
|
});
|
|
6465
|
+
const custom = compileCustomPatterns(cfg.redaction.custom_patterns);
|
|
6466
|
+
const strictNote = cfg.redaction.strict ? "strict mode on" : "strict mode off (redaction.strict)";
|
|
6467
|
+
const patternNote = custom.patterns.length === 0 ? "built-in patterns only" : `${custom.patterns.length} custom pattern${custom.patterns.length === 1 ? "" : "s"} plus the built-in ones`;
|
|
6468
|
+
results.push(
|
|
6469
|
+
custom.problems.length > 0 ? {
|
|
6470
|
+
name: "Security redaction",
|
|
6471
|
+
// A pattern the operator believes is redacting, but which never compiled, is the one
|
|
6472
|
+
// failure here that is invisible from the output itself -- so it warns rather than
|
|
6473
|
+
// being reported as a healthy count that happens to be short.
|
|
6474
|
+
status: "warn",
|
|
6475
|
+
message: `${custom.problems.length} custom redaction pattern${custom.problems.length === 1 ? "" : "s"} could not be used and ${custom.problems.length === 1 ? "is" : "are"} not redacting anything: ` + custom.problems.map((p) => `${p.pattern} (${p.reason})`).join("; ")
|
|
6476
|
+
} : { name: "Security redaction", status: "ok", message: `${patternNote}, ${strictNote}` }
|
|
6477
|
+
);
|
|
6320
6478
|
const extraRoots = cfg.mcp.allowed_roots.length;
|
|
6321
6479
|
results.push(
|
|
6322
6480
|
cfg.mcp.confine_reads_to_project_root ? {
|
|
@@ -6325,6 +6483,27 @@ function checkSecurityPosture(cfg, dataDirPath) {
|
|
|
6325
6483
|
message: extraRoots === 0 ? "reads are confined to the project root" : `reads are confined to the project root plus ${extraRoots} configured root${extraRoots === 1 ? "" : "s"}`
|
|
6326
6484
|
} : { name: "Security mcp roots", status: "warn", message: "confinement is off (mcp.confine_reads_to_project_root): a read can leave the project" }
|
|
6327
6485
|
);
|
|
6486
|
+
const overridden = envOverriddenSecuritySettings();
|
|
6487
|
+
const weakened = overridden.filter((o) => o.kind === "weakened");
|
|
6488
|
+
const replaced = overridden.filter((o) => o.kind === "replaced");
|
|
6489
|
+
const describe = (o) => `${o.setting} (${o.envVar})`;
|
|
6490
|
+
const parts = [];
|
|
6491
|
+
if (weakened.length > 0) parts.push(`held open: ${weakened.map(describe).join(", ")}`);
|
|
6492
|
+
if (replaced.length > 0) parts.push(`set from the environment: ${replaced.map(describe).join(", ")}`);
|
|
6493
|
+
results.push(
|
|
6494
|
+
overridden.length === 0 ? {
|
|
6495
|
+
name: "Security config overrides",
|
|
6496
|
+
status: "ok",
|
|
6497
|
+
// Naming the count keeps this from reading as a guarantee about settings it never looked
|
|
6498
|
+
// at, which is what the first version of this line did while nine locked keys went
|
|
6499
|
+
// unchecked behind it.
|
|
6500
|
+
message: `no environment variable is overriding any of the ${lockedEnvOverridableKeys().length} project-locked settings`
|
|
6501
|
+
} : {
|
|
6502
|
+
name: "Security config overrides",
|
|
6503
|
+
status: "warn",
|
|
6504
|
+
message: `the environment, not the config file, is deciding ${overridden.length === 1 ? "a" : "these"} project-locked setting${overridden.length === 1 ? "" : "s"}. ${parts.join("; ")}. A project config cannot change these; an environment variable can, and one can be set by a file in a cloned repository.`
|
|
6505
|
+
}
|
|
6506
|
+
);
|
|
6328
6507
|
results.push(dataDirPermissionResult(dataDirPath));
|
|
6329
6508
|
return results;
|
|
6330
6509
|
}
|
|
@@ -6428,15 +6607,15 @@ function runDoctor(dataDir2, configPath2, rootDir, processes) {
|
|
|
6428
6607
|
results.push(checkStrayClaudeMdBlocks());
|
|
6429
6608
|
results.push(checkWorkerRunning(actualDataDir) ? { name: "Worker", status: "ok", message: "running" } : { name: "Worker", status: "warn", message: "not running" });
|
|
6430
6609
|
results.push(checkDbExists(actualDataDir));
|
|
6431
|
-
results.push(checkSymbolBodySize(
|
|
6432
|
-
results.push(checkSymbolCount(
|
|
6610
|
+
results.push(checkSymbolBodySize(path12.join(actualDataDir, "global.db")));
|
|
6611
|
+
results.push(checkSymbolCount(path12.join(actualDataDir, "global.db"), rootDir));
|
|
6433
6612
|
results.push(checkDirtyQueueHealth(actualDataDir));
|
|
6434
|
-
results.push(checkCompactionChannel(
|
|
6435
|
-
results.push(checkUnmappedTools(
|
|
6613
|
+
results.push(checkCompactionChannel(path12.join(actualDataDir, "global.db")));
|
|
6614
|
+
results.push(checkUnmappedTools(path12.join(actualDataDir, "global.db")));
|
|
6436
6615
|
const actualConfigPath = configPath2 || configPath();
|
|
6437
6616
|
results.push(checkConfigValid(actualConfigPath));
|
|
6438
6617
|
results.push(checkEmbeddings(loadConfig(rootDir)));
|
|
6439
|
-
results.push(checkEmbeddingCoverage(
|
|
6618
|
+
results.push(checkEmbeddingCoverage(path12.join(actualDataDir, "global.db"), rootDir));
|
|
6440
6619
|
for (const result of checkSecurityPosture(loadConfig(rootDir), actualDataDir)) results.push(result);
|
|
6441
6620
|
results.push(checkDiskSpace(actualDataDir));
|
|
6442
6621
|
const copilotResult = checkCopilotCli(copilotCliConfigPath(), copilotCliScriptPath());
|
|
@@ -6477,7 +6656,7 @@ async function runDoctorAndExit(opts) {
|
|
|
6477
6656
|
console.log();
|
|
6478
6657
|
try {
|
|
6479
6658
|
const dir = skillOutputsDir();
|
|
6480
|
-
const pregenPath =
|
|
6659
|
+
const pregenPath = path12.join(dir, "pregen.json");
|
|
6481
6660
|
if (fs12.existsSync(pregenPath)) {
|
|
6482
6661
|
const content = JSON.parse(fs12.readFileSync(pregenPath, "utf-8"));
|
|
6483
6662
|
const pregenNames = new Set(content.names || []);
|
|
@@ -6487,7 +6666,7 @@ async function runDoctorAndExit(opts) {
|
|
|
6487
6666
|
for (const entry of entries) {
|
|
6488
6667
|
if (!entry.isFile() || !entry.name.endsWith(".meta")) continue;
|
|
6489
6668
|
try {
|
|
6490
|
-
const meta = JSON.parse(fs12.readFileSync(
|
|
6669
|
+
const meta = JSON.parse(fs12.readFileSync(path12.join(dir, entry.name), "utf-8"));
|
|
6491
6670
|
if (meta.skillName && !pregenNames.has(meta.skillName)) {
|
|
6492
6671
|
skillsSeen.add(meta.skillName);
|
|
6493
6672
|
}
|
|
@@ -6509,12 +6688,96 @@ async function runDoctorAndExit(opts) {
|
|
|
6509
6688
|
return results.some((r) => r.status === "fail") ? 1 : 0;
|
|
6510
6689
|
}
|
|
6511
6690
|
|
|
6691
|
+
// src/capabilities.ts
|
|
6692
|
+
function collectCapabilities(config = loadConfig()) {
|
|
6693
|
+
const online = !config.network.offline;
|
|
6694
|
+
return [
|
|
6695
|
+
{
|
|
6696
|
+
id: "network.http_fetch",
|
|
6697
|
+
kind: "egress",
|
|
6698
|
+
what: "Fetches a URL when a command asks for one, including page text and remote images.",
|
|
6699
|
+
enabled: online,
|
|
6700
|
+
controlledBy: "network.offline",
|
|
6701
|
+
enforcedAt: "src/webfetch.ts::performHttpFetch"
|
|
6702
|
+
},
|
|
6703
|
+
{
|
|
6704
|
+
id: "network.google_drive",
|
|
6705
|
+
kind: "egress",
|
|
6706
|
+
what: "Exports a Google Doc so a section of it can be read.",
|
|
6707
|
+
// Two independent gates, so this reports the conjunction: turning either one off is enough.
|
|
6708
|
+
enabled: online && config.gdrive.enabled,
|
|
6709
|
+
controlledBy: "gdrive.enabled, network.offline",
|
|
6710
|
+
enforcedAt: "src/gdrive.ts::fetchDoc (network gate in src/webfetch.ts::performHttpFetch)"
|
|
6711
|
+
},
|
|
6712
|
+
{
|
|
6713
|
+
id: "network.embedding_model_download",
|
|
6714
|
+
kind: "egress",
|
|
6715
|
+
what: "Downloads the local embedding model once, so semantic search can run without a server.",
|
|
6716
|
+
enabled: online,
|
|
6717
|
+
controlledBy: "network.offline",
|
|
6718
|
+
enforcedAt: "src/embed_model.ts"
|
|
6719
|
+
},
|
|
6720
|
+
{
|
|
6721
|
+
id: "network.ocr_data_download",
|
|
6722
|
+
kind: "egress",
|
|
6723
|
+
what: "Downloads OCR language data once, so text can be read out of an image.",
|
|
6724
|
+
enabled: online,
|
|
6725
|
+
controlledBy: "network.offline",
|
|
6726
|
+
enforcedAt: "src/image_ocr.ts"
|
|
6727
|
+
},
|
|
6728
|
+
{
|
|
6729
|
+
id: "network.screenshot",
|
|
6730
|
+
kind: "egress",
|
|
6731
|
+
what: "Loads a page in a browser to capture it.",
|
|
6732
|
+
enabled: online,
|
|
6733
|
+
controlledBy: "network.offline",
|
|
6734
|
+
enforcedAt: "src/screenshot.ts"
|
|
6735
|
+
},
|
|
6736
|
+
{
|
|
6737
|
+
id: "at_rest.symbol_index",
|
|
6738
|
+
kind: "at-rest",
|
|
6739
|
+
what: "Stores the text of indexed source files in a local database, so a symbol can be returned without re-reading the file.",
|
|
6740
|
+
// Always on: it is the product. Reported anyway, because a reviewer deciding about data at
|
|
6741
|
+
// rest needs the always-on items more than the optional ones.
|
|
6742
|
+
enabled: true,
|
|
6743
|
+
controlledBy: "always on (this is what the tool does)",
|
|
6744
|
+
enforcedAt: "src/db.ts"
|
|
6745
|
+
},
|
|
6746
|
+
{
|
|
6747
|
+
id: "at_rest.command_output_cache",
|
|
6748
|
+
kind: "at-rest",
|
|
6749
|
+
what: "Keeps recent command and web output on disk so it can be recalled instead of re-run. Expires after 24 hours.",
|
|
6750
|
+
enabled: true,
|
|
6751
|
+
controlledBy: "always on, 24-hour expiry",
|
|
6752
|
+
enforcedAt: "src/disk_cache.ts"
|
|
6753
|
+
}
|
|
6754
|
+
];
|
|
6755
|
+
}
|
|
6756
|
+
function renderCapabilities(caps) {
|
|
6757
|
+
const lines = [];
|
|
6758
|
+
for (const kind of ["egress", "at-rest"]) {
|
|
6759
|
+
const group = caps.filter((c) => c.kind === kind);
|
|
6760
|
+
if (group.length === 0) continue;
|
|
6761
|
+
lines.push(kind === "egress" ? "Can send data off this machine:" : "Leaves data on this machine:");
|
|
6762
|
+
for (const c of group) {
|
|
6763
|
+
lines.push(` [${c.enabled ? "ON " : "OFF"}] ${c.id}`);
|
|
6764
|
+
lines.push(` ${c.what}`);
|
|
6765
|
+
lines.push(` controlled by: ${c.controlledBy}`);
|
|
6766
|
+
lines.push(` enforced at: ${c.enforcedAt}`);
|
|
6767
|
+
}
|
|
6768
|
+
lines.push("");
|
|
6769
|
+
}
|
|
6770
|
+
lines.push("Verify rather than trust: `token-goat capabilities --json` reports the state of the");
|
|
6771
|
+
lines.push('binary you have installed, and each "enforced at" names the code that decides it.');
|
|
6772
|
+
return lines.join("\n");
|
|
6773
|
+
}
|
|
6774
|
+
|
|
6512
6775
|
// src/webfetch.ts
|
|
6513
6776
|
import { existsSync as existsSync7, readdirSync as readdirSync5, statSync as statSync4, unlinkSync as unlinkSync2 } from "fs";
|
|
6514
6777
|
import * as http from "http";
|
|
6515
6778
|
import * as https from "https";
|
|
6516
6779
|
import { isIPv4, isIPv6 } from "net";
|
|
6517
|
-
import { resolve as
|
|
6780
|
+
import { resolve as resolve5, join as join10 } from "path";
|
|
6518
6781
|
import { URL as URL2 } from "url";
|
|
6519
6782
|
import { promisify } from "util";
|
|
6520
6783
|
import { lookup as dnsLookup } from "dns";
|
|
@@ -6703,7 +6966,7 @@ function cleanupStaleDownloads() {
|
|
|
6703
6966
|
const files = readdirSync5(cacheDir);
|
|
6704
6967
|
for (const file of files) {
|
|
6705
6968
|
if (file.endsWith(".tmp")) {
|
|
6706
|
-
const filePath =
|
|
6969
|
+
const filePath = resolve5(cacheDir, file);
|
|
6707
6970
|
try {
|
|
6708
6971
|
const stat = statSync4(filePath);
|
|
6709
6972
|
if (Date.now() - stat.mtimeMs < STALE_DOWNLOAD_AGE_MS) continue;
|
|
@@ -6969,7 +7232,7 @@ async function getSectionContent(fileId, heading, opts = {}) {
|
|
|
6969
7232
|
|
|
6970
7233
|
// src/pack.ts
|
|
6971
7234
|
import * as fs13 from "node:fs";
|
|
6972
|
-
import * as
|
|
7235
|
+
import * as path14 from "node:path";
|
|
6973
7236
|
|
|
6974
7237
|
// node_modules/minimatch/dist/esm/index.js
|
|
6975
7238
|
var esm_exports = {};
|
|
@@ -8093,11 +8356,11 @@ var qmarksTestNoExtDot = ([$0]) => {
|
|
|
8093
8356
|
return (f) => f.length === len && f !== "." && f !== "..";
|
|
8094
8357
|
};
|
|
8095
8358
|
var defaultPlatform = typeof process === "object" && process ? typeof process.env === "object" && process.env && process.env.__MINIMATCH_TESTING_PLATFORM__ || process.platform : "posix";
|
|
8096
|
-
var
|
|
8359
|
+
var path13 = {
|
|
8097
8360
|
win32: { sep: "\\" },
|
|
8098
8361
|
posix: { sep: "/" }
|
|
8099
8362
|
};
|
|
8100
|
-
var sep2 = defaultPlatform === "win32" ?
|
|
8363
|
+
var sep2 = defaultPlatform === "win32" ? path13.win32.sep : path13.posix.sep;
|
|
8101
8364
|
minimatch.sep = sep2;
|
|
8102
8365
|
var GLOBSTAR = /* @__PURE__ */ Symbol("globstar **");
|
|
8103
8366
|
minimatch.GLOBSTAR = GLOBSTAR;
|
|
@@ -8882,7 +9145,7 @@ var LANG_MAP = {
|
|
|
8882
9145
|
".exs": "elixir"
|
|
8883
9146
|
};
|
|
8884
9147
|
function getLang(filePath) {
|
|
8885
|
-
const ext2 =
|
|
9148
|
+
const ext2 = path14.extname(filePath).toLowerCase();
|
|
8886
9149
|
return LANG_MAP[ext2] ?? "";
|
|
8887
9150
|
}
|
|
8888
9151
|
function matches(rel, patterns) {
|
|
@@ -8892,8 +9155,8 @@ function matches(rel, patterns) {
|
|
|
8892
9155
|
return patterns.some((pat) => mm.minimatch(norm, pat) || mm.minimatch(base, pat));
|
|
8893
9156
|
}
|
|
8894
9157
|
function isPathWithinRoot(rootReal, resolvedPath) {
|
|
8895
|
-
const rel =
|
|
8896
|
-
return rel !== "" && rel !== ".." && !rel.startsWith(".." +
|
|
9158
|
+
const rel = path14.relative(rootReal, resolvedPath);
|
|
9159
|
+
return rel !== "" && rel !== ".." && !rel.startsWith(".." + path14.sep) && !path14.isAbsolute(rel);
|
|
8897
9160
|
}
|
|
8898
9161
|
function openWithinRoot(rootReal, p) {
|
|
8899
9162
|
let fd;
|
|
@@ -8933,11 +9196,11 @@ function openWithinRoot(rootReal, p) {
|
|
|
8933
9196
|
}
|
|
8934
9197
|
function* resolveOpenCandidates(projectRoot, patterns, rootReal, ignorePatterns, seen, skipped) {
|
|
8935
9198
|
for (const pattern of patterns) {
|
|
8936
|
-
const p =
|
|
9199
|
+
const p = path14.isAbsolute(pattern) ? pattern : path14.join(projectRoot, pattern);
|
|
8937
9200
|
if (seen.has(p)) continue;
|
|
8938
9201
|
let rel;
|
|
8939
9202
|
try {
|
|
8940
|
-
rel =
|
|
9203
|
+
rel = path14.relative(projectRoot, p).replace(/\\/g, "/");
|
|
8941
9204
|
} catch {
|
|
8942
9205
|
skipped.push(`${p} (outside project root)`);
|
|
8943
9206
|
continue;
|
|
@@ -9011,7 +9274,7 @@ function stripBlockComments(content, pattern, lineStates) {
|
|
|
9011
9274
|
);
|
|
9012
9275
|
}
|
|
9013
9276
|
function stripComments(content, filePath) {
|
|
9014
|
-
const ext2 =
|
|
9277
|
+
const ext2 = path14.extname(filePath).toLowerCase();
|
|
9015
9278
|
const lineStates = computeLineStartQuoteStates(content);
|
|
9016
9279
|
if (ext2 === ".py") {
|
|
9017
9280
|
return stripLineComments(content, PY_LINE_COMMENT_RE, lineStates);
|
|
@@ -9049,7 +9312,7 @@ var SAFE_EXTS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp
|
|
|
9049
9312
|
function scanSecrets(files) {
|
|
9050
9313
|
const hits = [];
|
|
9051
9314
|
for (const pf of files) {
|
|
9052
|
-
if (SAFE_EXTS.has(
|
|
9315
|
+
if (SAFE_EXTS.has(path14.extname(pf.path).toLowerCase())) {
|
|
9053
9316
|
continue;
|
|
9054
9317
|
}
|
|
9055
9318
|
for (const [lineno, line] of pf.content.split("\n").entries()) {
|
|
@@ -9072,7 +9335,7 @@ function scanSecrets(files) {
|
|
|
9072
9335
|
function collectFiles(projectRoot, patterns, opts = {}) {
|
|
9073
9336
|
const result = { files: [], skipped: [], total_lines: 0, total_tokens: 0 };
|
|
9074
9337
|
const seen = /* @__PURE__ */ new Set();
|
|
9075
|
-
const rootResolved =
|
|
9338
|
+
const rootResolved = path14.resolve(projectRoot);
|
|
9076
9339
|
let rootReal;
|
|
9077
9340
|
try {
|
|
9078
9341
|
rootReal = fs13.realpathSync(rootResolved);
|
|
@@ -9247,7 +9510,7 @@ function formatPack(result, style, opts = {}) {
|
|
|
9247
9510
|
function estimateBudget(projectRoot, patterns, opts = {}) {
|
|
9248
9511
|
const result = { entries: [], skipped: [], total_lines: 0, total_tokens: 0 };
|
|
9249
9512
|
const seen = /* @__PURE__ */ new Set();
|
|
9250
|
-
const rootResolved =
|
|
9513
|
+
const rootResolved = path14.resolve(projectRoot);
|
|
9251
9514
|
let rootReal;
|
|
9252
9515
|
try {
|
|
9253
9516
|
rootReal = fs13.realpathSync(rootResolved);
|
|
@@ -9744,7 +10007,7 @@ function formatFailureDeltaJson(delta, runner) {
|
|
|
9744
10007
|
|
|
9745
10008
|
// src/failures_state.ts
|
|
9746
10009
|
import * as fs14 from "node:fs";
|
|
9747
|
-
import * as
|
|
10010
|
+
import * as path15 from "node:path";
|
|
9748
10011
|
var KEY_RE = /^[A-Za-z0-9_-]{1,80}$/;
|
|
9749
10012
|
var DEFAULT_FAILURES_STATE_KEY = "default";
|
|
9750
10013
|
function validateKey(key) {
|
|
@@ -9756,7 +10019,7 @@ function validateKey(key) {
|
|
|
9756
10019
|
}
|
|
9757
10020
|
function failuresStatePath(projectHash, key) {
|
|
9758
10021
|
validateKey(key);
|
|
9759
|
-
return
|
|
10022
|
+
return path15.join(dataDir(), "projects", `${projectHash}_failures_${key}.json`);
|
|
9760
10023
|
}
|
|
9761
10024
|
function loadFailureSnapshot(projectHash, key) {
|
|
9762
10025
|
const p = failuresStatePath(projectHash, key);
|
|
@@ -9777,7 +10040,7 @@ function loadFailureSnapshot(projectHash, key) {
|
|
|
9777
10040
|
}
|
|
9778
10041
|
function saveFailureSnapshot(projectHash, key, snapshot) {
|
|
9779
10042
|
const p = failuresStatePath(projectHash, key);
|
|
9780
|
-
const dir =
|
|
10043
|
+
const dir = path15.dirname(p);
|
|
9781
10044
|
ensureDirSync(dir);
|
|
9782
10045
|
const content = JSON.stringify(snapshot);
|
|
9783
10046
|
const doSave = () => {
|
|
@@ -9789,18 +10052,18 @@ function saveFailureSnapshot(projectHash, key, snapshot) {
|
|
|
9789
10052
|
|
|
9790
10053
|
// src/text_commands.ts
|
|
9791
10054
|
import * as fs16 from "fs";
|
|
9792
|
-
import * as
|
|
10055
|
+
import * as path17 from "path";
|
|
9793
10056
|
|
|
9794
10057
|
// src/project_memory.ts
|
|
9795
10058
|
import * as fs15 from "node:fs";
|
|
9796
|
-
import * as
|
|
10059
|
+
import * as path16 from "node:path";
|
|
9797
10060
|
var MAX_ENTRIES = 30;
|
|
9798
10061
|
var KEY_RE2 = /^[A-Za-z0-9_-]{1,80}$/;
|
|
9799
10062
|
function ordinal(a, b) {
|
|
9800
10063
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
9801
10064
|
}
|
|
9802
10065
|
function memoryPath(projectHash) {
|
|
9803
|
-
return
|
|
10066
|
+
return path16.join(dataDir(), "projects", `${projectHash}_memory.toml`);
|
|
9804
10067
|
}
|
|
9805
10068
|
function validateKey2(key) {
|
|
9806
10069
|
if (!KEY_RE2.test(key)) {
|
|
@@ -9859,7 +10122,7 @@ function save(filePath, entries) {
|
|
|
9859
10122
|
lines.push(`${k} = "${escaped}"`);
|
|
9860
10123
|
}
|
|
9861
10124
|
const content = lines.length > 0 ? lines.join("\n") + "\n" : "";
|
|
9862
|
-
const dir =
|
|
10125
|
+
const dir = path16.dirname(filePath);
|
|
9863
10126
|
ensureDirSync(dir);
|
|
9864
10127
|
atomicWriteText(filePath, content);
|
|
9865
10128
|
}
|
|
@@ -9869,7 +10132,7 @@ function loadEntries(projectHash) {
|
|
|
9869
10132
|
function setEntry(projectHash, key, value) {
|
|
9870
10133
|
validateKey2(key);
|
|
9871
10134
|
const p = memoryPath(projectHash);
|
|
9872
|
-
const dir =
|
|
10135
|
+
const dir = path16.dirname(p);
|
|
9873
10136
|
ensureDirSync(dir);
|
|
9874
10137
|
const doSet = () => {
|
|
9875
10138
|
const entries = loadRaw(p);
|
|
@@ -9967,7 +10230,7 @@ function collectTodoFiles(patterns) {
|
|
|
9967
10230
|
if (patterns.length > 0) {
|
|
9968
10231
|
const results = [];
|
|
9969
10232
|
for (const p of patterns) {
|
|
9970
|
-
const abs =
|
|
10233
|
+
const abs = path17.resolve(p);
|
|
9971
10234
|
try {
|
|
9972
10235
|
const stat = fs16.statSync(abs);
|
|
9973
10236
|
if (stat.isDirectory()) {
|
|
@@ -10238,7 +10501,7 @@ function isProjectFrame(framePath, cwd) {
|
|
|
10238
10501
|
if (framePath.includes("/rustc/")) return false;
|
|
10239
10502
|
if (framePath.includes(".cargo/registry") || framePath.includes(".cargo\\registry")) return false;
|
|
10240
10503
|
if (/^<.+>$/.test(framePath)) return false;
|
|
10241
|
-
if (!
|
|
10504
|
+
if (!path17.isAbsolute(framePath) && !framePath.startsWith("..")) return true;
|
|
10242
10505
|
return false;
|
|
10243
10506
|
}
|
|
10244
10507
|
function resolveFrameSymbol(frame, projectRoot) {
|
|
@@ -10451,17 +10714,17 @@ function findLockfile(startPath) {
|
|
|
10451
10714
|
if (stat !== void 0 && stat.isFile()) {
|
|
10452
10715
|
return { file: startPath, others: [] };
|
|
10453
10716
|
}
|
|
10454
|
-
const dir = stat !== void 0 && stat.isDirectory() ? startPath :
|
|
10717
|
+
const dir = stat !== void 0 && stat.isDirectory() ? startPath : path17.dirname(startPath);
|
|
10455
10718
|
const found = [];
|
|
10456
10719
|
for (const name of LOCK_PRIORITY) {
|
|
10457
|
-
const candidate =
|
|
10720
|
+
const candidate = path17.join(dir, name);
|
|
10458
10721
|
if (fs16.existsSync(candidate)) found.push(candidate);
|
|
10459
10722
|
}
|
|
10460
10723
|
try {
|
|
10461
10724
|
const entries = fs16.readdirSync(dir);
|
|
10462
10725
|
for (const e of entries) {
|
|
10463
10726
|
if (/^requirements.*\.txt$/.test(e)) {
|
|
10464
|
-
const p =
|
|
10727
|
+
const p = path17.join(dir, e);
|
|
10465
10728
|
if (!found.includes(p)) found.push(p);
|
|
10466
10729
|
}
|
|
10467
10730
|
}
|
|
@@ -10658,7 +10921,7 @@ function parsePnpmLock(content) {
|
|
|
10658
10921
|
return deps;
|
|
10659
10922
|
}
|
|
10660
10923
|
function parseLockFile(filePath) {
|
|
10661
|
-
const base =
|
|
10924
|
+
const base = path17.basename(filePath);
|
|
10662
10925
|
const content = fs16.readFileSync(filePath, "utf8");
|
|
10663
10926
|
if (base === "package-lock.json") return { format: "npm", deps: parsePackageLockJson(content) };
|
|
10664
10927
|
if (base === "yarn.lock") return { format: "yarn", deps: parseYarnLock(content) };
|
|
@@ -10774,7 +11037,7 @@ Depended on by direct/top-level deps (${dependedOnBy.length}):
|
|
|
10774
11037
|
`);
|
|
10775
11038
|
}
|
|
10776
11039
|
function cmdLockdeps(filePath, opts) {
|
|
10777
|
-
const target = filePath !== void 0 ?
|
|
11040
|
+
const target = filePath !== void 0 ? path17.resolve(filePath) : process.cwd();
|
|
10778
11041
|
const found = findLockfile(target);
|
|
10779
11042
|
if (found === null) {
|
|
10780
11043
|
throw new Error("No lockfile found. Expected one of: " + LOCK_PRIORITY.join(", ") + ", requirements*.txt");
|
|
@@ -10862,7 +11125,7 @@ function cmdNote(action, key, value, opts) {
|
|
|
10862
11125
|
throw new Error(`Unknown note action: ${action}. Use: set, get, unset, list, clear`);
|
|
10863
11126
|
}
|
|
10864
11127
|
function loadAllSessionReadCounts() {
|
|
10865
|
-
const sessionsDir =
|
|
11128
|
+
const sessionsDir = path17.join(tokenGoatHome(), "sessions");
|
|
10866
11129
|
const totals = /* @__PURE__ */ new Map();
|
|
10867
11130
|
let entries;
|
|
10868
11131
|
try {
|
|
@@ -10872,7 +11135,7 @@ function loadAllSessionReadCounts() {
|
|
|
10872
11135
|
}
|
|
10873
11136
|
for (const entry of entries) {
|
|
10874
11137
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
10875
|
-
const filePath =
|
|
11138
|
+
const filePath = path17.join(sessionsDir, entry.name);
|
|
10876
11139
|
let raw;
|
|
10877
11140
|
try {
|
|
10878
11141
|
raw = JSON.parse(fs16.readFileSync(filePath, "utf8"));
|
|
@@ -11008,7 +11271,7 @@ function cmdIgnores(opts) {
|
|
|
11008
11271
|
}
|
|
11009
11272
|
function countPackIgnorePatterns(root) {
|
|
11010
11273
|
try {
|
|
11011
|
-
return fs16.readFileSync(
|
|
11274
|
+
return fs16.readFileSync(path17.join(root, ".tokengoatignore"), "utf8").split("\n").map((ln) => ln.trim()).filter((ln) => ln.length > 0 && !ln.startsWith("#")).length;
|
|
11012
11275
|
} catch {
|
|
11013
11276
|
return 0;
|
|
11014
11277
|
}
|
|
@@ -11017,7 +11280,7 @@ function countPackIgnorePatterns(root) {
|
|
|
11017
11280
|
// src/dep_docs.ts
|
|
11018
11281
|
import { createRequire } from "node:module";
|
|
11019
11282
|
import * as fs17 from "node:fs";
|
|
11020
|
-
import * as
|
|
11283
|
+
import * as path18 from "node:path";
|
|
11021
11284
|
var _require = createRequire(import.meta.url);
|
|
11022
11285
|
var _ts = null;
|
|
11023
11286
|
var _tsLoadAttempted = false;
|
|
@@ -11068,7 +11331,7 @@ function listInstalledPackageNames(nodeModulesDir) {
|
|
|
11068
11331
|
if (entry.name.startsWith("@")) {
|
|
11069
11332
|
let scoped;
|
|
11070
11333
|
try {
|
|
11071
|
-
scoped = fs17.readdirSync(
|
|
11334
|
+
scoped = fs17.readdirSync(path18.join(nodeModulesDir, entry.name), { withFileTypes: true });
|
|
11072
11335
|
} catch {
|
|
11073
11336
|
continue;
|
|
11074
11337
|
}
|
|
@@ -11085,7 +11348,7 @@ var README_NAME_RE = /^readme(\.(md|markdown|txt))?$/i;
|
|
|
11085
11348
|
function findReadmeFile(pkgDir) {
|
|
11086
11349
|
const direct = findReadmeIn(pkgDir);
|
|
11087
11350
|
if (direct !== null) return direct;
|
|
11088
|
-
return findReadmeIn(
|
|
11351
|
+
return findReadmeIn(path18.join(pkgDir, "docs"));
|
|
11089
11352
|
}
|
|
11090
11353
|
function findReadmeIn(dir) {
|
|
11091
11354
|
let entries;
|
|
@@ -11102,7 +11365,7 @@ function findReadmeIn(dir) {
|
|
|
11102
11365
|
return 1;
|
|
11103
11366
|
};
|
|
11104
11367
|
matches2.sort((a, b) => rank(a.name) - rank(b.name));
|
|
11105
|
-
return
|
|
11368
|
+
return path18.join(dir, matches2[0].name);
|
|
11106
11369
|
}
|
|
11107
11370
|
function typesPackageDirName(pkgName) {
|
|
11108
11371
|
if (pkgName.startsWith("@")) {
|
|
@@ -11129,30 +11392,30 @@ function resolveTypesLocation(pkgDir, pkgJson, nodeModulesDir, pkgName) {
|
|
|
11129
11392
|
if (declared !== void 0) {
|
|
11130
11393
|
const candidates = [declared, declared.endsWith(".d.ts") ? declared : `${declared}.d.ts`, declared.replace(/\.[cm]?[jt]s$/, ".d.ts")];
|
|
11131
11394
|
for (const c of candidates) {
|
|
11132
|
-
const p =
|
|
11395
|
+
const p = path18.join(pkgDir, c);
|
|
11133
11396
|
if (fileExists(p)) return { path: p, source: "bundled" };
|
|
11134
11397
|
}
|
|
11135
11398
|
}
|
|
11136
11399
|
const main = typeof pkgJson["main"] === "string" ? pkgJson["main"] : "index.js";
|
|
11137
|
-
const mainDts =
|
|
11400
|
+
const mainDts = path18.join(pkgDir, main.replace(/\.[cm]?js$/, ".d.ts"));
|
|
11138
11401
|
if (fileExists(mainDts)) return { path: mainDts, source: "bundled" };
|
|
11139
|
-
const indexDts =
|
|
11402
|
+
const indexDts = path18.join(pkgDir, "index.d.ts");
|
|
11140
11403
|
if (fileExists(indexDts)) return { path: indexDts, source: "bundled" };
|
|
11141
11404
|
const typesDirName = typesPackageDirName(pkgName);
|
|
11142
|
-
const typesPkgDir =
|
|
11143
|
-
const typesPkgJsonRaw = readFileTextOrNull(
|
|
11405
|
+
const typesPkgDir = path18.join(nodeModulesDir, "@types", typesDirName);
|
|
11406
|
+
const typesPkgJsonRaw = readFileTextOrNull(path18.join(typesPkgDir, "package.json"));
|
|
11144
11407
|
if (typesPkgJsonRaw !== null) {
|
|
11145
11408
|
try {
|
|
11146
11409
|
const typesPkgJson = JSON.parse(typesPkgJsonRaw);
|
|
11147
11410
|
const entry = (typeof typesPkgJson["types"] === "string" ? typesPkgJson["types"] : void 0) ?? (typeof typesPkgJson["main"] === "string" ? typesPkgJson["main"] : void 0) ?? "index.d.ts";
|
|
11148
11411
|
const entryCandidates = [entry, entry.endsWith(".d.ts") ? entry : `${entry}.d.ts`, entry.replace(/\.[cm]?[jt]s$/, ".d.ts")];
|
|
11149
11412
|
for (const c of entryCandidates) {
|
|
11150
|
-
const entryPath =
|
|
11413
|
+
const entryPath = path18.join(typesPkgDir, c);
|
|
11151
11414
|
if (fileExists(entryPath)) return { path: entryPath, source: "@types" };
|
|
11152
11415
|
}
|
|
11153
11416
|
} catch {
|
|
11154
11417
|
}
|
|
11155
|
-
const fallback =
|
|
11418
|
+
const fallback = path18.join(typesPkgDir, "index.d.ts");
|
|
11156
11419
|
if (fileExists(fallback)) return { path: fallback, source: "@types" };
|
|
11157
11420
|
}
|
|
11158
11421
|
return null;
|
|
@@ -11219,9 +11482,9 @@ function recordDepDocsStat(fullSourceBytes, emittedText, packageName) {
|
|
|
11219
11482
|
}
|
|
11220
11483
|
function runDepDocs(opts) {
|
|
11221
11484
|
const root = resolveProjectRoot({ project: opts.projectRoot ?? process.cwd() });
|
|
11222
|
-
const nodeModulesDir =
|
|
11223
|
-
const pkgDir =
|
|
11224
|
-
const pkgJsonPath =
|
|
11485
|
+
const nodeModulesDir = path18.join(root, "node_modules");
|
|
11486
|
+
const pkgDir = path18.join(nodeModulesDir, opts.packageName);
|
|
11487
|
+
const pkgJsonPath = path18.join(pkgDir, "package.json");
|
|
11225
11488
|
if (!dirExists(pkgDir) || !fileExists(pkgJsonPath)) {
|
|
11226
11489
|
const known = listInstalledPackageNames(nodeModulesDir);
|
|
11227
11490
|
const suggestions = suggestPackageNames(opts.packageName, known);
|
|
@@ -11384,13 +11647,13 @@ async function buildResumePacket(sessionId) {
|
|
|
11384
11647
|
}
|
|
11385
11648
|
|
|
11386
11649
|
// src/cache_session_commands.ts
|
|
11387
|
-
function
|
|
11650
|
+
function emitErr2(text) {
|
|
11388
11651
|
process.stderr.write(ensureNewline(text));
|
|
11389
11652
|
}
|
|
11390
11653
|
function capAndNote(rows, limit) {
|
|
11391
11654
|
const shown = rows.slice(0, limit);
|
|
11392
11655
|
if (shown.length < rows.length) {
|
|
11393
|
-
|
|
11656
|
+
emitErr2(`Showing ${shown.length} of ${rows.length} entries (raise --limit to see the rest).`);
|
|
11394
11657
|
}
|
|
11395
11658
|
return shown;
|
|
11396
11659
|
}
|
|
@@ -11400,11 +11663,11 @@ function parseLimitOpt(cmdName, limitStr, dflt = 30) {
|
|
|
11400
11663
|
try {
|
|
11401
11664
|
n = requireNonNegativeStrictInt("--limit", limitStr);
|
|
11402
11665
|
} catch (e) {
|
|
11403
|
-
|
|
11666
|
+
emitErr2(`${cmdName}: --limit must be a positive number, got: "${limitStr}"`);
|
|
11404
11667
|
throw new Error(`invalid --limit: ${limitStr}`, { cause: e });
|
|
11405
11668
|
}
|
|
11406
11669
|
if (n === 0) {
|
|
11407
|
-
|
|
11670
|
+
emitErr2(`${cmdName}: --limit must be a positive number, got: "${limitStr}"`);
|
|
11408
11671
|
throw new Error(`invalid --limit: ${limitStr}`);
|
|
11409
11672
|
}
|
|
11410
11673
|
return n;
|
|
@@ -11738,7 +12001,7 @@ function cmdBaseline(opts) {
|
|
|
11738
12001
|
|
|
11739
12002
|
// src/index_reclaim.ts
|
|
11740
12003
|
import * as fs18 from "node:fs";
|
|
11741
|
-
import * as
|
|
12004
|
+
import * as path19 from "node:path";
|
|
11742
12005
|
var DERIVED_TABLES = ["chunk_vectors", "chunks", "refs", "symbols", "files"];
|
|
11743
12006
|
function indexSizeBytes(dbPath) {
|
|
11744
12007
|
let total = 0;
|
|
@@ -11822,7 +12085,7 @@ function mb(bytes) {
|
|
|
11822
12085
|
}
|
|
11823
12086
|
function cmdReclaimIndex(opts) {
|
|
11824
12087
|
const dbPath = opts.dbPath ?? globalDbPath();
|
|
11825
|
-
if (opts.force !== true && isWorkerRunning(
|
|
12088
|
+
if (opts.force !== true && isWorkerRunning(path19.dirname(dbPath))) {
|
|
11826
12089
|
throw new Error(
|
|
11827
12090
|
"reclaim-index: the worker daemon is running and writing to this index. Stop it first with 'token-goat worker stop', then re-run. Pass --force to proceed anyway (results may be inaccurate if the worker is really live)."
|
|
11828
12091
|
);
|
|
@@ -11856,7 +12119,7 @@ function cmdReclaimIndex(opts) {
|
|
|
11856
12119
|
}
|
|
11857
12120
|
if (result.checkpointBusy) {
|
|
11858
12121
|
process.stdout.write(
|
|
11859
|
-
` note: a concurrent reader blocked WAL truncation, so some space is still held in ${
|
|
12122
|
+
` note: a concurrent reader blocked WAL truncation, so some space is still held in ${path19.basename(dbPath)}-wal and the figures above count the main file only. The next checkpoint releases it
|
|
11860
12123
|
`
|
|
11861
12124
|
);
|
|
11862
12125
|
}
|
|
@@ -11865,16 +12128,16 @@ function cmdReclaimIndex(opts) {
|
|
|
11865
12128
|
// src/config_commands.ts
|
|
11866
12129
|
import * as fs19 from "node:fs";
|
|
11867
12130
|
import * as os9 from "node:os";
|
|
11868
|
-
import * as
|
|
11869
|
-
function
|
|
12131
|
+
import * as path20 from "node:path";
|
|
12132
|
+
function emit2(text) {
|
|
11870
12133
|
const payload = colorStdout() ? text : stripAnsi(text);
|
|
11871
12134
|
process.stdout.write(ensureNewline(payload));
|
|
11872
12135
|
}
|
|
11873
|
-
function
|
|
12136
|
+
function emitErr3(text) {
|
|
11874
12137
|
process.stderr.write(ensureNewline(text));
|
|
11875
12138
|
}
|
|
11876
12139
|
function saveConfigSafe(cfg) {
|
|
11877
|
-
ensureDirSync(
|
|
12140
|
+
ensureDirSync(path20.dirname(configPath()));
|
|
11878
12141
|
saveConfig(cfg);
|
|
11879
12142
|
}
|
|
11880
12143
|
function levenshtein(a, b, cap = 3) {
|
|
@@ -12036,16 +12299,16 @@ function cmdConfig(opts) {
|
|
|
12036
12299
|
if (state.layer !== "global" && state.layer !== "project-unparsed") sources[k] = layerJson(state);
|
|
12037
12300
|
}
|
|
12038
12301
|
if (Object.keys(sources).length > 0) payload["_sources"] = sources;
|
|
12039
|
-
|
|
12302
|
+
emit2(JSON.stringify(payload, null, 2));
|
|
12040
12303
|
return;
|
|
12041
12304
|
}
|
|
12042
12305
|
for (const [k, v] of pairs) {
|
|
12043
12306
|
const state = resolveConfigKeyLayer(k, v, cfg, projectInfo);
|
|
12044
|
-
|
|
12307
|
+
emit2(`${k} = ${JSON.stringify(v)}${state.layer === "project-unparsed" ? "" : layerAnnotation(state)}`);
|
|
12045
12308
|
}
|
|
12046
12309
|
if (projectInfo !== null) {
|
|
12047
|
-
|
|
12048
|
-
|
|
12310
|
+
emit2("");
|
|
12311
|
+
emit2(
|
|
12049
12312
|
projectInfo.parseError !== null ? `# project override ${projectInfo.path} failed to parse (${projectInfo.parseError}); ignored` : `# project override: ${projectInfo.path}`
|
|
12050
12313
|
);
|
|
12051
12314
|
}
|
|
@@ -12063,11 +12326,11 @@ function cmdConfig(opts) {
|
|
|
12063
12326
|
}
|
|
12064
12327
|
const getState = resolveConfigKeyLayer(opts.key, result.value, cfg, getProjectConfigInfo());
|
|
12065
12328
|
if (opts.json === true) {
|
|
12066
|
-
|
|
12329
|
+
emit2(JSON.stringify({ key: opts.key, value: result.value, ...layerJson(getState) }, null, 2));
|
|
12067
12330
|
return;
|
|
12068
12331
|
}
|
|
12069
12332
|
const rendered = typeof result.value === "string" ? result.value : JSON.stringify(result.value);
|
|
12070
|
-
|
|
12333
|
+
emit2(`${rendered}${layerAnnotation(getState)}`);
|
|
12071
12334
|
return;
|
|
12072
12335
|
}
|
|
12073
12336
|
if (action === "set") {
|
|
@@ -12086,7 +12349,7 @@ function cmdConfig(opts) {
|
|
|
12086
12349
|
if (parseErrAtLoad !== null) {
|
|
12087
12350
|
try {
|
|
12088
12351
|
fs19.copyFileSync(configPath(), `${configPath()}.bak`);
|
|
12089
|
-
|
|
12352
|
+
emitErr3(`config set: warning: config.toml failed to parse (${parseErrAtLoad}); backed up the original to config.toml.bak and rewriting it from defaults`);
|
|
12090
12353
|
} catch {
|
|
12091
12354
|
}
|
|
12092
12355
|
}
|
|
@@ -12121,8 +12384,8 @@ function cmdConfig(opts) {
|
|
|
12121
12384
|
saveConfigSafe(cfg);
|
|
12122
12385
|
return coercedValue;
|
|
12123
12386
|
};
|
|
12124
|
-
ensureDirSync(
|
|
12125
|
-
const lockPath =
|
|
12387
|
+
ensureDirSync(path20.dirname(configPath()));
|
|
12388
|
+
const lockPath = path20.join(path20.dirname(configPath()), ".config.lock");
|
|
12126
12389
|
const lockResult = withFileLock(lockPath, applySet, { waitMs: LOCK_WAIT_MS_HARDENED });
|
|
12127
12390
|
const coerced = lockResult === void 0 ? applySet() : lockResult;
|
|
12128
12391
|
invalidateConfigCache();
|
|
@@ -12131,19 +12394,19 @@ function cmdConfig(opts) {
|
|
|
12131
12394
|
const setState = resolveConfigKeyLayer(key, setEffective.found ? setEffective.value : void 0, effectiveCfg, getProjectConfigInfo());
|
|
12132
12395
|
const shadowed = setEffective.found && setEffective.value !== coerced;
|
|
12133
12396
|
if (shadowed && setState.layer === "env") {
|
|
12134
|
-
|
|
12397
|
+
emitErr3(`config set: warning: ${key} was saved to config.toml, but ${setState.envVar} is currently set and overrides it at runtime \u2014 unset ${setState.envVar} for this change to take effect`);
|
|
12135
12398
|
} else if (shadowed && setState.layer === "env-invalid") {
|
|
12136
|
-
|
|
12399
|
+
emitErr3(`config set: warning: ${key} was saved to config.toml, but ${setState.envVar} is currently set to ${JSON.stringify(setState.rawValue)}${setState.reason !== null ? ` (${setState.reason})` : ""} and still overrides it at runtime, taking effect as ${JSON.stringify(setState.effectiveValue)} \u2014 unset ${setState.envVar} for this change to take effect`);
|
|
12137
12400
|
} else if (setState.layer === "project") {
|
|
12138
|
-
|
|
12401
|
+
emitErr3(`config set: warning: ${key} was saved to config.toml, but ${setState.path} also sets it and overrides it in this project \u2014 remove it there for this change to take effect here`);
|
|
12139
12402
|
} else if (setState.layer === "project-invalid") {
|
|
12140
|
-
|
|
12403
|
+
emitErr3(`config set: warning: ${key} was saved to config.toml, but ${setState.path} sets it to ${JSON.stringify(setState.rawValue)}${setState.reason !== null ? ` (${setState.reason})` : ""} and still overrides it in this project, taking effect as ${JSON.stringify(setState.effectiveValue)} \u2014 remove it there for this change to take effect here`);
|
|
12141
12404
|
}
|
|
12142
12405
|
if (opts.json === true) {
|
|
12143
|
-
|
|
12406
|
+
emit2(JSON.stringify({ key, value: coerced }, null, 2));
|
|
12144
12407
|
return;
|
|
12145
12408
|
}
|
|
12146
|
-
|
|
12409
|
+
emit2(`${key} = ${JSON.stringify(coerced)}`);
|
|
12147
12410
|
return;
|
|
12148
12411
|
}
|
|
12149
12412
|
if (action === "validate") {
|
|
@@ -12211,19 +12474,19 @@ function cmdConfig(opts) {
|
|
|
12211
12474
|
}
|
|
12212
12475
|
if (findings.length > 0) process.exitCode = 1;
|
|
12213
12476
|
if (opts.json === true) {
|
|
12214
|
-
|
|
12477
|
+
emit2(JSON.stringify({ findings, ok: findings.length === 0 }, null, 2));
|
|
12215
12478
|
return;
|
|
12216
12479
|
}
|
|
12217
12480
|
if (findings.length === 0) {
|
|
12218
|
-
|
|
12481
|
+
emit2("config validate: no issues found");
|
|
12219
12482
|
return;
|
|
12220
12483
|
}
|
|
12221
12484
|
for (const f of findings) {
|
|
12222
12485
|
const isNearMiss = f.kind === "unknown_section" || f.kind === "unknown_key";
|
|
12223
12486
|
const hint = f.suggestion === void 0 ? "" : isNearMiss ? ` (did you mean: ${f.suggestion}?)` : ` (${f.suggestion})`;
|
|
12224
|
-
|
|
12487
|
+
emit2(`[${f.kind}] ${f.key}${hint}`);
|
|
12225
12488
|
}
|
|
12226
|
-
|
|
12489
|
+
emit2(`config validate: ${findings.length} issue(s) found`);
|
|
12227
12490
|
return;
|
|
12228
12491
|
}
|
|
12229
12492
|
throw new Error(`config: unknown action '${action}'. Use list, get, set, or validate.`);
|
|
@@ -12235,33 +12498,33 @@ function cmdProject(opts) {
|
|
|
12235
12498
|
const active = findProject(process.cwd());
|
|
12236
12499
|
const blocked = cfg.worker.blocked_roots;
|
|
12237
12500
|
if (opts.json === true) {
|
|
12238
|
-
|
|
12501
|
+
emit2(JSON.stringify({ active: active ? { root: active.root, hash: active.hash, marker: active.marker } : null, blocked_roots: blocked }, null, 2));
|
|
12239
12502
|
return;
|
|
12240
12503
|
}
|
|
12241
12504
|
if (active) {
|
|
12242
|
-
|
|
12243
|
-
|
|
12505
|
+
emit2(`Active project: ${active.root}`);
|
|
12506
|
+
emit2(` marker: ${active.marker}`);
|
|
12244
12507
|
} else {
|
|
12245
|
-
|
|
12508
|
+
emit2("Active project: (none \u2014 not inside a recognized project root)");
|
|
12246
12509
|
}
|
|
12247
12510
|
if (blocked.length === 0) {
|
|
12248
|
-
|
|
12511
|
+
emit2("Blocked roots: (none)");
|
|
12249
12512
|
} else {
|
|
12250
|
-
|
|
12251
|
-
for (const r of blocked)
|
|
12513
|
+
emit2("Blocked roots:");
|
|
12514
|
+
for (const r of blocked) emit2(` ${r}`);
|
|
12252
12515
|
}
|
|
12253
12516
|
return;
|
|
12254
12517
|
}
|
|
12255
12518
|
if (action === "exclude") {
|
|
12256
12519
|
if (!opts.pathArg) {
|
|
12257
|
-
|
|
12520
|
+
emitErr3("project exclude requires a path argument");
|
|
12258
12521
|
throw new Error("missing path");
|
|
12259
12522
|
}
|
|
12260
|
-
const target =
|
|
12523
|
+
const target = path20.resolve(opts.pathArg);
|
|
12261
12524
|
const cfg = loadPersistedConfig();
|
|
12262
12525
|
const targetFolded = foldPath(normalizePath(target));
|
|
12263
12526
|
if (cfg.worker.blocked_roots.some((r) => foldPath(normalizePath(r)) === targetFolded)) {
|
|
12264
|
-
|
|
12527
|
+
emit2(`Already excluded: ${target}`);
|
|
12265
12528
|
return;
|
|
12266
12529
|
}
|
|
12267
12530
|
cfg.worker.blocked_roots = [...cfg.worker.blocked_roots, target];
|
|
@@ -12269,12 +12532,12 @@ function cmdProject(opts) {
|
|
|
12269
12532
|
invalidateConfigCache();
|
|
12270
12533
|
const purged = pruneBlockedRoot(target);
|
|
12271
12534
|
if (opts.json === true) {
|
|
12272
|
-
|
|
12535
|
+
emit2(JSON.stringify({ excluded: target, purgedFromIndex: purged.length, blocked_roots: cfg.worker.blocked_roots }, null, 2));
|
|
12273
12536
|
return;
|
|
12274
12537
|
}
|
|
12275
|
-
|
|
12538
|
+
emit2(`Excluded: ${target}`);
|
|
12276
12539
|
if (purged.length > 0) {
|
|
12277
|
-
|
|
12540
|
+
emit2(`Removed ${purged.length} already-indexed file${purged.length === 1 ? "" : "s"} under it from the index.`);
|
|
12278
12541
|
}
|
|
12279
12542
|
return;
|
|
12280
12543
|
}
|
|
@@ -12294,26 +12557,26 @@ function cmdProject(opts) {
|
|
|
12294
12557
|
const orphanChunkPaths = findOrphanedChunkPaths();
|
|
12295
12558
|
if (opts.dryRun === true) {
|
|
12296
12559
|
if (opts.json === true) {
|
|
12297
|
-
|
|
12560
|
+
emit2(JSON.stringify({ dryRun: true, wouldPrune: removed, stale, wouldPruneTempFiles: staleTempFiles.length, staleTempFiles, wouldPruneOrphanChunkFiles: orphanChunkPaths.length, orphanChunkPaths, blocked_roots: before }, null, 2));
|
|
12298
12561
|
return;
|
|
12299
12562
|
}
|
|
12300
12563
|
if (removed === 0) {
|
|
12301
|
-
|
|
12564
|
+
emit2("Would prune 0 stale root(s). Nothing to do.");
|
|
12302
12565
|
} else {
|
|
12303
|
-
|
|
12304
|
-
for (const r of stale)
|
|
12566
|
+
emit2(`Would prune ${removed} stale root(s):`);
|
|
12567
|
+
for (const r of stale) emit2(` ${r}`);
|
|
12305
12568
|
}
|
|
12306
12569
|
if (staleTempFiles.length === 0) {
|
|
12307
|
-
|
|
12570
|
+
emit2("Would prune 0 stale indexed temp-dir file(s). Nothing to do.");
|
|
12308
12571
|
} else {
|
|
12309
|
-
|
|
12310
|
-
for (const p of staleTempFiles)
|
|
12572
|
+
emit2(`Would prune ${staleTempFiles.length} stale indexed temp-dir file(s):`);
|
|
12573
|
+
for (const p of staleTempFiles) emit2(` ${p}`);
|
|
12311
12574
|
}
|
|
12312
12575
|
if (orphanChunkPaths.length === 0) {
|
|
12313
|
-
|
|
12576
|
+
emit2("Would prune 0 orphaned embedding chunk file(s). Nothing to do.");
|
|
12314
12577
|
} else {
|
|
12315
|
-
|
|
12316
|
-
for (const p of orphanChunkPaths)
|
|
12578
|
+
emit2(`Would prune orphaned embedding chunks for ${orphanChunkPaths.length} file(s):`);
|
|
12579
|
+
for (const p of orphanChunkPaths) emit2(` ${p}`);
|
|
12317
12580
|
}
|
|
12318
12581
|
return;
|
|
12319
12582
|
}
|
|
@@ -12323,37 +12586,37 @@ function cmdProject(opts) {
|
|
|
12323
12586
|
const prunedTempFiles = pruneSystemTempFiles();
|
|
12324
12587
|
const prunedOrphanChunks = pruneOrphanedChunks();
|
|
12325
12588
|
if (opts.json === true) {
|
|
12326
|
-
|
|
12589
|
+
emit2(JSON.stringify({ pruned: removed, blocked_roots: after, prunedTempFiles: prunedTempFiles.length, prunedOrphanChunkFiles: prunedOrphanChunks.length }, null, 2));
|
|
12327
12590
|
return;
|
|
12328
12591
|
}
|
|
12329
|
-
|
|
12330
|
-
|
|
12331
|
-
|
|
12592
|
+
emit2(`Pruned ${removed} stale root(s). Remaining: ${after.length}`);
|
|
12593
|
+
emit2(`Pruned ${prunedTempFiles.length} stale indexed temp-dir file(s).`);
|
|
12594
|
+
emit2(`Pruned orphaned embedding chunks for ${prunedOrphanChunks.length} file(s).`);
|
|
12332
12595
|
return;
|
|
12333
12596
|
}
|
|
12334
|
-
|
|
12597
|
+
emitErr3(`project: unknown action '${action}'. Use list, exclude, or prune.`);
|
|
12335
12598
|
throw new Error(`unknown project action: ${action}`);
|
|
12336
12599
|
}
|
|
12337
12600
|
function recordCompactDocStat(fullSourceBytes, emittedText, detail) {
|
|
12338
|
-
const bytesSaved =
|
|
12601
|
+
const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(emittedText, "utf8"));
|
|
12339
12602
|
recordStat("compact_doc", bytesSaved, savedTokensFromBytes(bytesSaved), void 0, detail);
|
|
12340
12603
|
}
|
|
12341
12604
|
function cmdCompactDoc(opts) {
|
|
12342
|
-
const resolved =
|
|
12605
|
+
const resolved = path20.resolve(opts.filePath);
|
|
12343
12606
|
if (opts.heading !== void 0) {
|
|
12344
12607
|
const result = compactDoc(resolved, opts.heading);
|
|
12345
12608
|
if (result === null) {
|
|
12346
|
-
|
|
12609
|
+
emitErr3(`compact-doc: could not read or compact '${resolved}'`);
|
|
12347
12610
|
throw new Error(`could not compact: ${resolved}`);
|
|
12348
12611
|
}
|
|
12349
12612
|
const legacyFullBytes = fs19.statSync(resolved).size;
|
|
12350
12613
|
if (opts.json === true) {
|
|
12351
12614
|
const jsonText = JSON.stringify({ path: resolved, compact: result }, null, 2);
|
|
12352
|
-
|
|
12615
|
+
emit2(jsonText);
|
|
12353
12616
|
recordCompactDocStat(legacyFullBytes, jsonText, resolved);
|
|
12354
12617
|
return;
|
|
12355
12618
|
}
|
|
12356
|
-
|
|
12619
|
+
emit2(result);
|
|
12357
12620
|
recordCompactDocStat(legacyFullBytes, result, resolved);
|
|
12358
12621
|
return;
|
|
12359
12622
|
}
|
|
@@ -12362,7 +12625,7 @@ function cmdCompactDoc(opts) {
|
|
|
12362
12625
|
try {
|
|
12363
12626
|
sentences = requirePositiveStrictInt("--sentences", opts.sentences);
|
|
12364
12627
|
} catch (e) {
|
|
12365
|
-
|
|
12628
|
+
emitErr3(`compact-doc: --sentences must be a positive number, got: "${opts.sentences}"`);
|
|
12366
12629
|
throw new Error(`invalid --sentences: ${opts.sentences}`, { cause: e });
|
|
12367
12630
|
}
|
|
12368
12631
|
}
|
|
@@ -12375,7 +12638,7 @@ function cmdCompactDoc(opts) {
|
|
|
12375
12638
|
try {
|
|
12376
12639
|
sourceText = fs19.readFileSync(resolved, "utf-8");
|
|
12377
12640
|
} catch {
|
|
12378
|
-
|
|
12641
|
+
emitErr3(`compact-doc: could not read or compact '${resolved}'`);
|
|
12379
12642
|
throw new Error(`could not compact: ${resolved}`);
|
|
12380
12643
|
}
|
|
12381
12644
|
body = buildExtractiveCompact(sourceText, sentences);
|
|
@@ -12384,7 +12647,7 @@ function cmdCompactDoc(opts) {
|
|
|
12384
12647
|
} else {
|
|
12385
12648
|
const existing = readCompactBody(compactPath);
|
|
12386
12649
|
if (existing === null) {
|
|
12387
|
-
|
|
12650
|
+
emitErr3(`compact-doc: could not read or compact '${resolved}'`);
|
|
12388
12651
|
throw new Error(`could not compact: ${resolved}`);
|
|
12389
12652
|
}
|
|
12390
12653
|
body = existing;
|
|
@@ -12392,16 +12655,16 @@ function cmdCompactDoc(opts) {
|
|
|
12392
12655
|
const extractiveFullBytes = fs19.statSync(resolved).size;
|
|
12393
12656
|
if (opts.json === true) {
|
|
12394
12657
|
const jsonText = JSON.stringify({ path: resolved, compactPath, rebuilt, compact: body }, null, 2);
|
|
12395
|
-
|
|
12658
|
+
emit2(jsonText);
|
|
12396
12659
|
recordCompactDocStat(extractiveFullBytes, jsonText, resolved);
|
|
12397
12660
|
return;
|
|
12398
12661
|
}
|
|
12399
12662
|
if (opts.show === true) {
|
|
12400
|
-
|
|
12663
|
+
emit2(body);
|
|
12401
12664
|
recordCompactDocStat(extractiveFullBytes, body, resolved);
|
|
12402
12665
|
return;
|
|
12403
12666
|
}
|
|
12404
|
-
|
|
12667
|
+
emit2(
|
|
12405
12668
|
`Compact sidecar ${rebuilt ? "built" : "already fresh"} at ${compactPath} (source: ${resolved}). Use --show to print it, --force to rebuild.`
|
|
12406
12669
|
);
|
|
12407
12670
|
}
|
|
@@ -12442,11 +12705,11 @@ async function cmdFetchImage(opts) {
|
|
|
12442
12705
|
try {
|
|
12443
12706
|
fetched = await fetchBuffer(opts.url);
|
|
12444
12707
|
} catch (e) {
|
|
12445
|
-
|
|
12708
|
+
emitErr3(`fetch-image: network error \u2014 ${extractErrorMessage(e)}`);
|
|
12446
12709
|
throw new Error(`fetch failed: ${opts.url}`, { cause: e });
|
|
12447
12710
|
}
|
|
12448
12711
|
const buf = fetched.body;
|
|
12449
|
-
const outPath = opts.out ??
|
|
12712
|
+
const outPath = opts.out ?? path20.join(os9.tmpdir(), `tg-fetch-${Date.now()}${extensionForContentType(fetched.contentType)}`);
|
|
12450
12713
|
const originalBytes = buf.length;
|
|
12451
12714
|
let shrunkBytes;
|
|
12452
12715
|
let outData;
|
|
@@ -12469,11 +12732,11 @@ async function cmdFetchImage(opts) {
|
|
|
12469
12732
|
}
|
|
12470
12733
|
atomicWriteBytes(finalPath, outData);
|
|
12471
12734
|
if (opts.json === true) {
|
|
12472
|
-
|
|
12735
|
+
emit2(JSON.stringify({ url: opts.url, out: finalPath, originalBytes, shrunkBytes, wasShrunk }, null, 2));
|
|
12473
12736
|
return;
|
|
12474
12737
|
}
|
|
12475
12738
|
const savings = wasShrunk ? ` (saved ${originalBytes - shrunkBytes} bytes)` : " (not shrunk \u2014 already small or unsupported format)";
|
|
12476
|
-
|
|
12739
|
+
emit2(`Fetched ${originalBytes} bytes \u2192 ${finalPath}${savings}`);
|
|
12477
12740
|
}
|
|
12478
12741
|
function cmdHistory(opts) {
|
|
12479
12742
|
let limit = 30;
|
|
@@ -12481,11 +12744,11 @@ function cmdHistory(opts) {
|
|
|
12481
12744
|
try {
|
|
12482
12745
|
limit = requireNonNegativeStrictInt("--limit", opts.limit);
|
|
12483
12746
|
} catch (e) {
|
|
12484
|
-
|
|
12747
|
+
emitErr3(`history: --limit must be a non-negative number, got: "${opts.limit}"`);
|
|
12485
12748
|
throw new Error(`invalid --limit: ${opts.limit}`, { cause: e });
|
|
12486
12749
|
}
|
|
12487
12750
|
if (limit === 0) {
|
|
12488
|
-
|
|
12751
|
+
emitErr3(`history: --limit must be a positive number, got: "${opts.limit}"`);
|
|
12489
12752
|
throw new Error(`invalid --limit: ${opts.limit}`);
|
|
12490
12753
|
}
|
|
12491
12754
|
}
|
|
@@ -12520,21 +12783,21 @@ function cmdHistory(opts) {
|
|
|
12520
12783
|
return;
|
|
12521
12784
|
}
|
|
12522
12785
|
if (items.length === 0) {
|
|
12523
|
-
|
|
12786
|
+
emit2("No history entries found (no cached bash outputs or web fetches).");
|
|
12524
12787
|
return;
|
|
12525
12788
|
}
|
|
12526
12789
|
const pad2 = (s, n) => s.length >= n ? s : s + " ".repeat(n - s.length);
|
|
12527
|
-
|
|
12790
|
+
emit2(`${pad2("type", 5)} ${pad2("id", 18)} summary`);
|
|
12528
12791
|
for (const item of items) {
|
|
12529
12792
|
const preview = item.summary.length > 80 ? item.summary.slice(0, 77) + "..." : item.summary;
|
|
12530
|
-
|
|
12793
|
+
emit2(`${pad2(item.type, 5)} ${pad2(item.id, 18)} ${preview}`);
|
|
12531
12794
|
}
|
|
12532
12795
|
}
|
|
12533
12796
|
|
|
12534
12797
|
// src/cli_bootstrap_audit.ts
|
|
12535
12798
|
import * as fs20 from "node:fs";
|
|
12536
12799
|
import * as os10 from "node:os";
|
|
12537
|
-
import * as
|
|
12800
|
+
import * as path21 from "node:path";
|
|
12538
12801
|
import * as readline from "node:readline";
|
|
12539
12802
|
var FRONTMATTER_KEYS = /* @__PURE__ */ new Set(["description", "tools", "allowed-tools", "allowed_tools"]);
|
|
12540
12803
|
function parseBudget(name, value) {
|
|
@@ -12593,17 +12856,17 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
|
|
|
12593
12856
|
logicalRoot = await fs20.promises.realpath(root);
|
|
12594
12857
|
} catch (error) {
|
|
12595
12858
|
if (error.code === "ENOENT") return entries;
|
|
12596
|
-
diagnostics.push({ path:
|
|
12859
|
+
diagnostics.push({ path: path21.resolve(root), reason: "unreadable root" });
|
|
12597
12860
|
return entries;
|
|
12598
12861
|
}
|
|
12599
12862
|
if (rootIsLink && !followLinks) {
|
|
12600
|
-
diagnostics.push({ path:
|
|
12863
|
+
diagnostics.push({ path: path21.resolve(root), reason: "external root link skipped (use --follow-links)" });
|
|
12601
12864
|
return entries;
|
|
12602
12865
|
}
|
|
12603
12866
|
const canonicalKey = (candidate) => process.platform === "win32" ? candidate.toLocaleLowerCase() : candidate;
|
|
12604
12867
|
const isContained = (candidate, allowedRoot) => {
|
|
12605
|
-
const relative3 =
|
|
12606
|
-
return relative3 === "" || relative3 !== ".." && !relative3.startsWith(`..${
|
|
12868
|
+
const relative3 = path21.relative(canonicalKey(allowedRoot), canonicalKey(candidate));
|
|
12869
|
+
return relative3 === "" || relative3 !== ".." && !relative3.startsWith(`..${path21.sep}`) && !path21.isAbsolute(relative3);
|
|
12607
12870
|
};
|
|
12608
12871
|
async function visit(candidate, allowedRoot, topLevel = false) {
|
|
12609
12872
|
let real;
|
|
@@ -12612,13 +12875,13 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
|
|
|
12612
12875
|
candidateIsLink = (await fs20.promises.lstat(candidate)).isSymbolicLink();
|
|
12613
12876
|
real = await fs20.promises.realpath(candidate);
|
|
12614
12877
|
} catch {
|
|
12615
|
-
diagnostics.push({ path:
|
|
12878
|
+
diagnostics.push({ path: path21.resolve(candidate), reason: "broken or unreadable link" });
|
|
12616
12879
|
return;
|
|
12617
12880
|
}
|
|
12618
12881
|
const withinAllowedRoot = allowedRoot === void 0 ? isContained(real, logicalRoot) : isContained(real, allowedRoot);
|
|
12619
12882
|
const canFollowExternal = followLinks && topLevel;
|
|
12620
12883
|
if (!withinAllowedRoot && !canFollowExternal) {
|
|
12621
|
-
diagnostics.push({ path:
|
|
12884
|
+
diagnostics.push({ path: path21.resolve(candidate), reason: candidateIsLink ? "external link skipped (use --follow-links)" : "canonical target outside scan root" });
|
|
12622
12885
|
return;
|
|
12623
12886
|
}
|
|
12624
12887
|
const trustedRoot = !withinAllowedRoot && canFollowExternal ? real : allowedRoot ?? real;
|
|
@@ -12626,7 +12889,7 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
|
|
|
12626
12889
|
try {
|
|
12627
12890
|
stat = await fs20.promises.stat(real);
|
|
12628
12891
|
} catch {
|
|
12629
|
-
diagnostics.push({ path:
|
|
12892
|
+
diagnostics.push({ path: path21.resolve(candidate), reason: "unreadable" });
|
|
12630
12893
|
return;
|
|
12631
12894
|
}
|
|
12632
12895
|
const canonical = process.platform === "win32" ? real.toLocaleLowerCase() : real;
|
|
@@ -12637,14 +12900,14 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
|
|
|
12637
12900
|
try {
|
|
12638
12901
|
children = await fs20.promises.readdir(real, { withFileTypes: true });
|
|
12639
12902
|
} catch {
|
|
12640
|
-
diagnostics.push({ path:
|
|
12903
|
+
diagnostics.push({ path: path21.resolve(candidate), reason: "unreadable directory" });
|
|
12641
12904
|
return;
|
|
12642
12905
|
}
|
|
12643
12906
|
children.sort((a, b) => a.name.localeCompare(b.name));
|
|
12644
|
-
for (const child of children) await visit(
|
|
12907
|
+
for (const child of children) await visit(path21.join(real, child.name), trustedRoot);
|
|
12645
12908
|
return;
|
|
12646
12909
|
}
|
|
12647
|
-
if (!stat.isFile() ||
|
|
12910
|
+
if (!stat.isFile() || path21.extname(real).toLowerCase() !== ".md") return;
|
|
12648
12911
|
if (seenFiles.has(canonical)) return;
|
|
12649
12912
|
seenFiles.add(canonical);
|
|
12650
12913
|
try {
|
|
@@ -12654,35 +12917,35 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
|
|
|
12654
12917
|
const toolsBytes = Buffer.byteLength(metadata.tools, "utf8");
|
|
12655
12918
|
entries.push({
|
|
12656
12919
|
kind,
|
|
12657
|
-
path:
|
|
12920
|
+
path: path21.resolve(real),
|
|
12658
12921
|
description_bytes: descriptionBytes,
|
|
12659
12922
|
tools_bytes: toolsBytes,
|
|
12660
12923
|
metadata_bytes: descriptionBytes + toolsBytes,
|
|
12661
12924
|
estimated_tokens: Math.floor((descriptionBytes + toolsBytes) / 4)
|
|
12662
12925
|
});
|
|
12663
12926
|
} catch {
|
|
12664
|
-
diagnostics.push({ path:
|
|
12927
|
+
diagnostics.push({ path: path21.resolve(candidate), reason: "unreadable file" });
|
|
12665
12928
|
}
|
|
12666
12929
|
}
|
|
12667
12930
|
try {
|
|
12668
12931
|
const children = await fs20.promises.readdir(logicalRoot, { withFileTypes: true });
|
|
12669
12932
|
children.sort((a, b) => a.name.localeCompare(b.name));
|
|
12670
|
-
for (const child of children) await visit(
|
|
12933
|
+
for (const child of children) await visit(path21.join(logicalRoot, child.name), void 0, true);
|
|
12671
12934
|
} catch {
|
|
12672
|
-
diagnostics.push({ path:
|
|
12935
|
+
diagnostics.push({ path: path21.resolve(root), reason: "unreadable root" });
|
|
12673
12936
|
}
|
|
12674
12937
|
return entries;
|
|
12675
12938
|
}
|
|
12676
12939
|
async function buildBootstrapAudit(opts = {}) {
|
|
12677
12940
|
const project = resolveProjectRoot(opts.project === void 0 ? {} : { project: opts.project });
|
|
12678
|
-
const home =
|
|
12941
|
+
const home = path21.resolve(opts.home ?? os10.homedir());
|
|
12679
12942
|
const context = buildStats(project, home, opts.project === void 0 ? [] : [opts.project]);
|
|
12680
12943
|
const diagnostics = [];
|
|
12681
12944
|
const top = opts.top === void 0 ? 10 : parseBudget("--top", opts.top) ?? 10;
|
|
12682
12945
|
const visitedDirs = /* @__PURE__ */ new Set();
|
|
12683
12946
|
const seenFiles = /* @__PURE__ */ new Set();
|
|
12684
|
-
const agents = await scanMetadataRoot(
|
|
12685
|
-
const skills = await scanMetadataRoot(
|
|
12947
|
+
const agents = await scanMetadataRoot(path21.join(home, ".claude", "agents"), "agent", diagnostics, visitedDirs, seenFiles, opts.followLinks === true);
|
|
12948
|
+
const skills = await scanMetadataRoot(path21.join(home, ".claude", "skills"), "skill", diagnostics, visitedDirs, seenFiles, opts.followLinks === true);
|
|
12686
12949
|
const rankedEntries = [...agents, ...skills].sort((a, b) => b.metadata_bytes - a.metadata_bytes || a.path.localeCompare(b.path));
|
|
12687
12950
|
const largestTotal = rankedEntries.length;
|
|
12688
12951
|
const largest = rankedEntries.slice(0, top);
|
|
@@ -12749,7 +13012,7 @@ async function runBootstrapAudit(opts = {}) {
|
|
|
12749
13012
|
|
|
12750
13013
|
// src/cli_memory.ts
|
|
12751
13014
|
import * as fs21 from "node:fs";
|
|
12752
|
-
import * as
|
|
13015
|
+
import * as path22 from "node:path";
|
|
12753
13016
|
function removeExactDupLines(text, report) {
|
|
12754
13017
|
if (report.exactDupLines.length === 0) return text;
|
|
12755
13018
|
const toRemove = new Set(report.exactDupLines.map(([, dupIdx]) => dupIdx));
|
|
@@ -12807,7 +13070,7 @@ function printReport(reports, clusters) {
|
|
|
12807
13070
|
} else {
|
|
12808
13071
|
for (const cluster of clusters) {
|
|
12809
13072
|
w(
|
|
12810
|
-
` [${cluster.method}, similarity ${cluster.similarity}, ~${cluster.tokens} tok] ${cluster.members.map((m) =>
|
|
13073
|
+
` [${cluster.method}, similarity ${cluster.similarity}, ~${cluster.tokens} tok] ${cluster.members.map((m) => path22.basename(m)).join(", ")}
|
|
12811
13074
|
`
|
|
12812
13075
|
);
|
|
12813
13076
|
}
|
|
@@ -12822,7 +13085,7 @@ async function runMemoryCommand(opts = {}) {
|
|
|
12822
13085
|
const claudeMds = findClaudeMdFiles(projectRoot);
|
|
12823
13086
|
const reports = auditClaudeMd(claudeMds);
|
|
12824
13087
|
const memoryMdPath = findMemoryMd(projectRoot);
|
|
12825
|
-
const clusters = memoryMdPath !== null ? await findContentDuplicates(
|
|
13088
|
+
const clusters = memoryMdPath !== null ? await findContentDuplicates(path22.dirname(memoryMdPath)) : [];
|
|
12826
13089
|
printReport(reports, clusters);
|
|
12827
13090
|
if (opts.fix !== true) return;
|
|
12828
13091
|
const changes = [];
|
|
@@ -12868,16 +13131,16 @@ async function runMemoryCommand(opts = {}) {
|
|
|
12868
13131
|
|
|
12869
13132
|
// src/cli_waste.ts
|
|
12870
13133
|
import * as fs24 from "node:fs";
|
|
12871
|
-
import * as
|
|
13134
|
+
import * as path25 from "node:path";
|
|
12872
13135
|
|
|
12873
13136
|
// src/waste.ts
|
|
12874
13137
|
import * as fs22 from "node:fs";
|
|
12875
13138
|
import * as os11 from "node:os";
|
|
12876
|
-
import * as
|
|
13139
|
+
import * as path23 from "node:path";
|
|
12877
13140
|
function projectTranscriptsDir(projectRoot) {
|
|
12878
|
-
const rootStr =
|
|
13141
|
+
const rootStr = path23.resolve(projectRoot);
|
|
12879
13142
|
const slug = rootStr.replace(/[^A-Za-z0-9]/g, "-");
|
|
12880
|
-
return
|
|
13143
|
+
return path23.join(os11.homedir(), ".claude", "projects", slug);
|
|
12881
13144
|
}
|
|
12882
13145
|
function findLatestTranscript(projectRoot) {
|
|
12883
13146
|
const dir = projectTranscriptsDir(projectRoot);
|
|
@@ -12889,7 +13152,7 @@ function findLatestTranscript(projectRoot) {
|
|
|
12889
13152
|
}
|
|
12890
13153
|
let best = null;
|
|
12891
13154
|
for (const name of entries) {
|
|
12892
|
-
const full =
|
|
13155
|
+
const full = path23.join(dir, name);
|
|
12893
13156
|
let stat;
|
|
12894
13157
|
try {
|
|
12895
13158
|
stat = fs22.statSync(full);
|
|
@@ -13119,7 +13382,7 @@ async function buildWasteReport(transcriptPath, opts = {}) {
|
|
|
13119
13382
|
|
|
13120
13383
|
// src/copilot_waste.ts
|
|
13121
13384
|
import * as fs23 from "node:fs";
|
|
13122
|
-
import * as
|
|
13385
|
+
import * as path24 from "node:path";
|
|
13123
13386
|
var HOOK_RECORD_TYPES = /* @__PURE__ */ new Set(["hook.start", "hook.end"]);
|
|
13124
13387
|
function splitInjectedBlocks(transformed) {
|
|
13125
13388
|
const out2 = [];
|
|
@@ -13138,7 +13401,7 @@ function splitInjectedBlocks(transformed) {
|
|
|
13138
13401
|
return out2;
|
|
13139
13402
|
}
|
|
13140
13403
|
function findLatestCopilotSession() {
|
|
13141
|
-
const root =
|
|
13404
|
+
const root = path24.join(copilotCliUserRoot(), "session-state");
|
|
13142
13405
|
let entries;
|
|
13143
13406
|
try {
|
|
13144
13407
|
entries = fs23.readdirSync(root);
|
|
@@ -13148,7 +13411,7 @@ function findLatestCopilotSession() {
|
|
|
13148
13411
|
let best = null;
|
|
13149
13412
|
let bestMtime = -Infinity;
|
|
13150
13413
|
for (const entry of entries) {
|
|
13151
|
-
const candidate =
|
|
13414
|
+
const candidate = path24.join(root, entry, "events.jsonl");
|
|
13152
13415
|
try {
|
|
13153
13416
|
const st = fs23.statSync(candidate);
|
|
13154
13417
|
if (!st.isFile()) continue;
|
|
@@ -13169,7 +13432,7 @@ function buildCopilotWasteReport(eventsPath) {
|
|
|
13169
13432
|
const raw = fs23.readFileSync(eventsPath, "utf-8");
|
|
13170
13433
|
const report = {
|
|
13171
13434
|
sessionPath: eventsPath,
|
|
13172
|
-
sessionId:
|
|
13435
|
+
sessionId: path24.basename(path24.dirname(eventsPath)),
|
|
13173
13436
|
turns: 0,
|
|
13174
13437
|
tokens: null,
|
|
13175
13438
|
blocks: [],
|
|
@@ -13448,7 +13711,7 @@ function printResidentContext(resident, w) {
|
|
|
13448
13711
|
}
|
|
13449
13712
|
async function runWasteCommand(opts = {}) {
|
|
13450
13713
|
if (opts.copilot === true) {
|
|
13451
|
-
const eventsPath = opts.transcript !== void 0 ?
|
|
13714
|
+
const eventsPath = opts.transcript !== void 0 ? path25.resolve(opts.transcript) : findLatestCopilotSession();
|
|
13452
13715
|
if (eventsPath === null || !fs24.existsSync(eventsPath)) {
|
|
13453
13716
|
const detail = eventsPath === null ? "no Copilot CLI session found under <copilot-home>/session-state" : `Copilot session event log not found: ${eventsPath}`;
|
|
13454
13717
|
if (opts.json === true) {
|
|
@@ -13472,7 +13735,7 @@ async function runWasteCommand(opts = {}) {
|
|
|
13472
13735
|
return;
|
|
13473
13736
|
}
|
|
13474
13737
|
const projectRoot = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
|
|
13475
|
-
const transcriptPath = opts.transcript !== void 0 ?
|
|
13738
|
+
const transcriptPath = opts.transcript !== void 0 ? path25.resolve(opts.transcript) : findLatestTranscript(projectRoot);
|
|
13476
13739
|
if (transcriptPath === null) {
|
|
13477
13740
|
if (opts.json === true) {
|
|
13478
13741
|
process.stdout.write(`${JSON.stringify({ error: "no session transcript found", project: projectRoot })}
|
|
@@ -13503,7 +13766,7 @@ async function runWasteCommand(opts = {}) {
|
|
|
13503
13766
|
|
|
13504
13767
|
// src/session_read.ts
|
|
13505
13768
|
import * as fs25 from "node:fs";
|
|
13506
|
-
import * as
|
|
13769
|
+
import * as path26 from "node:path";
|
|
13507
13770
|
import * as readline2 from "node:readline";
|
|
13508
13771
|
function resolveSessionTranscript(arg, opts = {}) {
|
|
13509
13772
|
if (arg !== void 0 && arg !== "") {
|
|
@@ -13511,7 +13774,7 @@ function resolveSessionTranscript(arg, opts = {}) {
|
|
|
13511
13774
|
const projectRoot2 = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
|
|
13512
13775
|
const dir = projectTranscriptsDir(projectRoot2);
|
|
13513
13776
|
const byId = arg.endsWith(".jsonl") ? arg : `${arg}.jsonl`;
|
|
13514
|
-
const candidate =
|
|
13777
|
+
const candidate = path26.join(dir, byId);
|
|
13515
13778
|
if (fs25.existsSync(candidate) && fs25.statSync(candidate).isFile()) return candidate;
|
|
13516
13779
|
return null;
|
|
13517
13780
|
}
|
|
@@ -13670,7 +13933,7 @@ function formatSessionSlice(turns) {
|
|
|
13670
13933
|
// src/session_audit.ts
|
|
13671
13934
|
import * as fs26 from "node:fs";
|
|
13672
13935
|
import * as os12 from "node:os";
|
|
13673
|
-
import * as
|
|
13936
|
+
import * as path27 from "node:path";
|
|
13674
13937
|
import * as readline3 from "node:readline";
|
|
13675
13938
|
function emptyMeasured() {
|
|
13676
13939
|
return { apiCalls: 0, inputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, outputTokens: 0 };
|
|
@@ -13717,6 +13980,64 @@ var CACHE_READ_MULTIPLIER = 0.1;
|
|
|
13717
13980
|
var READ_DIVERT_MARKER_RE = /(?:was already read this session|Already read |You've already read|Use `token-goat (?:section|read|bash-output|config-get|skeleton)|token-goat bash-output --file)/;
|
|
13718
13981
|
var READ_DIVERT_MAX_BYTES = 2500;
|
|
13719
13982
|
var READ_FULL_SERVE_MIN_BYTES = 10240;
|
|
13983
|
+
var DENY_TEMPLATES = [
|
|
13984
|
+
{ kind: "node_modules_deny", re: /node_modules is typically noise/ },
|
|
13985
|
+
{ kind: "lock_file_deny", re: /Lock files are rarely useful to read in full/ },
|
|
13986
|
+
{ kind: "tsbuildinfo_deny", re: /TypeScript incremental build cache file/ },
|
|
13987
|
+
{ kind: "generated_build_deny", re: /Generated\/build artifact — read the source file instead\./ },
|
|
13988
|
+
{ kind: "compact_sidecar_served", re: /Serving the extractive compact sidecar in place of the full file/ },
|
|
13989
|
+
{ kind: "notebook_sidecar_served", re: /Serving the output-stripped notebook in place of the full file/ },
|
|
13990
|
+
// Must stay ABOVE markdown_heading_tree_deny: this deny's message embeds the same "Large markdown file (N headings)" guidance block, so the generic alternative would swallow it and the new intervention would be uncountable. DENY_TEMPLATES is scanned with `.find`, so the more specific wording has to come first.
|
|
13991
|
+
{ kind: "subagent_markdown_first_read_deny", re: /Subagent first read of a large markdown file/ },
|
|
13992
|
+
{ kind: "markdown_heading_tree_deny", re: /Large markdown file \(\d+ headings\)/ },
|
|
13993
|
+
// The first alternative is a wording hooks_read.ts no longer emits (it claimed a compact-manifest
|
|
13994
|
+
// section that never existed, reworded in 3d044feb). It stays because this table classifies a
|
|
13995
|
+
// historical corpus, not just today's output: transcripts written before the rewording still
|
|
13996
|
+
// carry the old text, and dropping the alternative silently shrank this kind from 51 events to
|
|
13997
|
+
// 30 -- 41% of its history -- with a green suite. A superseded alternative is only removable
|
|
13998
|
+
// once no transcript contains it, which source code cannot tell you. See the superseded-wording
|
|
13999
|
+
// test in tests/deny_outcomes.test.ts.
|
|
14000
|
+
{ kind: "memory_md_reread_deny", re: /MEMORY\.md was read this session\. Its content is in the compact manifest|already read this session\. Memory files rarely change mid-session/ },
|
|
14001
|
+
{ kind: "improve_state_reread_deny", re: /Orchestrator state already read this session/ },
|
|
14002
|
+
{ kind: "env_reread_deny", re: /Environment files rarely change mid-session/ },
|
|
14003
|
+
{ kind: "session_artifact_truncated_deny", re: /File was truncated on last read\. Use `token-goat bash-output/ },
|
|
14004
|
+
{ kind: "session_artifact_unchanged_deny", re: /is unchanged since last read\. Use `token-goat bash-output/ },
|
|
14005
|
+
{ kind: "session_artifact_diff_deny", re: /Content changed since last read of [\s\S]*?bash-output --file/ },
|
|
14006
|
+
{ kind: "session_artifact_large_deny", re: /(?:Session transcript|Tool-result file) is large \(/ },
|
|
14007
|
+
{ kind: "session_artifact_generic_reread_deny", re: /already read this session\. Use `token-goat bash-output --file/ },
|
|
14008
|
+
{ kind: "truncated_read_deny", re: /File was truncated on last read \(>33K tokens\)/ },
|
|
14009
|
+
{ kind: "doc_unchanged_deny", re: /is unchanged since last read\. Use `token-goat (?:section|read)/ },
|
|
14010
|
+
{ kind: "doc_diff_deny", re: /Content changed since last read of [\s\S]*?Use `token-goat (?:section|read)/ },
|
|
14011
|
+
{ kind: "read_served_deny", re: /was already served in this session, byte for byte/ },
|
|
14012
|
+
{ kind: "markdown_already_read_deny", re: /Markdown file already read this session\. Use `token-goat section/ },
|
|
14013
|
+
{ kind: "read_count_deny", re: /Read this file \d+ times already/ },
|
|
14014
|
+
{ kind: "generic_reread_deny", re: /was already read this session \(\d+ read/ },
|
|
14015
|
+
{ kind: "large_file_deny", re: /is very large \(\d+(?:\.\d+)?KB\)\./ },
|
|
14016
|
+
{ 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/ }
|
|
14017
|
+
];
|
|
14018
|
+
var DENY_KINDS_WITH_SIZE = /* @__PURE__ */ new Set(["large_file_deny", "session_artifact_large_deny", "file_type_handler_deny"]);
|
|
14019
|
+
var DENY_SIZE_RE = /(\d+(?:\.\d+)?)\s*(B|KB|MB|GB|TB)\b/;
|
|
14020
|
+
function parseWithheldBytes(kind, text) {
|
|
14021
|
+
if (!DENY_KINDS_WITH_SIZE.has(kind)) return null;
|
|
14022
|
+
const m = DENY_SIZE_RE.exec(text);
|
|
14023
|
+
if (m === null) return null;
|
|
14024
|
+
const n = Number.parseFloat(m[1]);
|
|
14025
|
+
const unit = m[2].toUpperCase();
|
|
14026
|
+
const mult = unit === "B" ? 1 : unit === "KB" ? 1024 : unit === "MB" ? 1024 * 1024 : unit === "GB" ? 1024 ** 3 : 1024 ** 4;
|
|
14027
|
+
return Math.round(n * mult);
|
|
14028
|
+
}
|
|
14029
|
+
var SURGICAL_COMMAND_RE = /(?:^|[\s;&|(])(?:token-goat|tg|node\s+\S*token-goat\.mjs)\s+(read|section|symbol|skeleton|outline|bash-output|config-get|skill-section|skill-body)\b/;
|
|
14030
|
+
var SHELL_READER_TOKEN_RE = /\b(?:sed|grep|cat|head|tail|awk|rg|less|nl|python3?|node)\b/;
|
|
14031
|
+
function escapeRegExp2(s) {
|
|
14032
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
14033
|
+
}
|
|
14034
|
+
function shellReadMatch(command, basename8) {
|
|
14035
|
+
if (basename8 === "" || !SHELL_READER_TOKEN_RE.test(command)) return "none";
|
|
14036
|
+
if (!command.includes(basename8)) return "none";
|
|
14037
|
+
const escaped = escapeRegExp2(basename8);
|
|
14038
|
+
const strict = new RegExp(`(?:^|["'\`/\\\\\\s])${escaped}(?:$|["'\`/\\\\\\s,;:)])`);
|
|
14039
|
+
return strict.test(command) ? "match" : "ambiguous";
|
|
14040
|
+
}
|
|
13720
14041
|
var BASH_FILTER_MARKER_RE = /\[token-goat[:\]]/;
|
|
13721
14042
|
var BASH_SMALL_RESULT_MAX_BYTES = 1024;
|
|
13722
14043
|
function commandHead(raw) {
|
|
@@ -13771,7 +14092,7 @@ function listCorpusTranscripts(corpusDir) {
|
|
|
13771
14092
|
const found = [];
|
|
13772
14093
|
const walk = (dir, entries) => {
|
|
13773
14094
|
for (const entry of entries) {
|
|
13774
|
-
const full =
|
|
14095
|
+
const full = path27.join(dir, entry.name);
|
|
13775
14096
|
if (entry.name.endsWith(".jsonl")) {
|
|
13776
14097
|
found.push(full);
|
|
13777
14098
|
} else if (entry.isDirectory()) {
|
|
@@ -13789,9 +14110,9 @@ function listCorpusTranscripts(corpusDir) {
|
|
|
13789
14110
|
return found.sort();
|
|
13790
14111
|
}
|
|
13791
14112
|
function defaultCorpusDir() {
|
|
13792
|
-
return
|
|
14113
|
+
return path27.join(os12.homedir(), ".claude", "projects");
|
|
13793
14114
|
}
|
|
13794
|
-
async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneObservations, bashHeadMap) {
|
|
14115
|
+
async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneObservations, bashHeadMap, denyRows) {
|
|
13795
14116
|
const isLane = filePath.split(/[\\/]/).includes("subagents");
|
|
13796
14117
|
let laneFirstPrefix = null;
|
|
13797
14118
|
let laneBriefBytes = -1;
|
|
@@ -13799,8 +14120,12 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
|
|
|
13799
14120
|
const bashHeadById = /* @__PURE__ */ new Map();
|
|
13800
14121
|
let pendingBashReread = [];
|
|
13801
14122
|
const readCallById = /* @__PURE__ */ new Map();
|
|
14123
|
+
const readPathById = /* @__PURE__ */ new Map();
|
|
14124
|
+
const editPathById = /* @__PURE__ */ new Map();
|
|
14125
|
+
const editErrorById = /* @__PURE__ */ new Map();
|
|
13802
14126
|
const readPathEpoch = /* @__PURE__ */ new Map();
|
|
13803
14127
|
let compactEpoch = 0;
|
|
14128
|
+
const openDenies = [];
|
|
13804
14129
|
let sawTokenGoatHook = false;
|
|
13805
14130
|
let fileRepeats = 0;
|
|
13806
14131
|
let fileRepeatsFullNoRange = 0;
|
|
@@ -13895,13 +14220,38 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
|
|
|
13895
14220
|
toolNameById.set(block["id"], name);
|
|
13896
14221
|
toolRollup(toolMap, name).calls += 1;
|
|
13897
14222
|
const input = block["input"] !== null && typeof block["input"] === "object" ? block["input"] : {};
|
|
14223
|
+
let readNorm;
|
|
14224
|
+
let editNorm;
|
|
13898
14225
|
if (name === "Bash" && typeof input["command"] === "string") {
|
|
13899
14226
|
bashHeadById.set(block["id"], commandHead(input["command"]));
|
|
13900
14227
|
} else if (name === "Read" && typeof input["file_path"] === "string") {
|
|
13901
|
-
|
|
13902
|
-
const priorEpoch = readPathEpoch.get(
|
|
14228
|
+
readNorm = normalizeReadPath(input["file_path"]);
|
|
14229
|
+
const priorEpoch = readPathEpoch.get(readNorm);
|
|
13903
14230
|
readCallById.set(block["id"], { wasSeenBefore: priorEpoch !== void 0, hasRange: input["offset"] !== void 0 || input["limit"] !== void 0, afterCompaction: priorEpoch !== void 0 && compactEpoch > priorEpoch });
|
|
13904
|
-
|
|
14231
|
+
readPathById.set(block["id"], readNorm);
|
|
14232
|
+
readPathEpoch.set(readNorm, compactEpoch);
|
|
14233
|
+
} else if (name === "Edit" && typeof input["file_path"] === "string") {
|
|
14234
|
+
editNorm = normalizeReadPath(input["file_path"]);
|
|
14235
|
+
editPathById.set(block["id"], editNorm);
|
|
14236
|
+
s.editErrorBaseline.totalEdits += 1;
|
|
14237
|
+
}
|
|
14238
|
+
if (openDenies.length > 0) {
|
|
14239
|
+
const bashCommand = name === "Bash" && typeof input["command"] === "string" ? input["command"] : void 0;
|
|
14240
|
+
for (const o of openDenies) {
|
|
14241
|
+
if (o.toolCalls.length >= 10) continue;
|
|
14242
|
+
const isReadSamePath = readNorm !== void 0 && o.path !== "" && readNorm === o.path;
|
|
14243
|
+
const isSurgicalSamePath = bashCommand !== void 0 && o.basename !== "" && SURGICAL_COMMAND_RE.test(bashCommand) && bashCommand.includes(o.basename);
|
|
14244
|
+
const shellRead = bashCommand !== void 0 && o.basename !== "" ? shellReadMatch(bashCommand, o.basename) : "none";
|
|
14245
|
+
const isEditSamePath = editNorm !== void 0 && o.path !== "" && editNorm === o.path;
|
|
14246
|
+
o.toolCalls.push({
|
|
14247
|
+
isReadSamePath,
|
|
14248
|
+
isSurgicalSamePath,
|
|
14249
|
+
isShellReadSamePath: shellRead === "match",
|
|
14250
|
+
isShellReadAmbiguous: shellRead === "ambiguous",
|
|
14251
|
+
editCallId: isEditSamePath ? block["id"] : void 0
|
|
14252
|
+
});
|
|
14253
|
+
if (o.toolCalls.length === 3) o.compactEpochAtWindow = compactEpoch;
|
|
14254
|
+
}
|
|
13905
14255
|
}
|
|
13906
14256
|
}
|
|
13907
14257
|
let inputBytes;
|
|
@@ -13930,8 +14280,27 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
|
|
|
13930
14280
|
if (!toolNameById.has(id)) roll.calls += 1;
|
|
13931
14281
|
roll.resultBytes += bytes;
|
|
13932
14282
|
roll.resultEstTokens += estimateTokensFromLength(bytes);
|
|
14283
|
+
if (name === "Edit" && block["is_error"] === true) {
|
|
14284
|
+
s.editErrorBaseline.totalErrors += 1;
|
|
14285
|
+
editErrorById.set(id, true);
|
|
14286
|
+
}
|
|
13933
14287
|
if (name === "Read") {
|
|
13934
14288
|
s.readInterception.readResults += 1;
|
|
14289
|
+
const denyTemplate = DENY_TEMPLATES.find((t) => t.re.test(text));
|
|
14290
|
+
if (denyTemplate !== void 0) {
|
|
14291
|
+
const path30 = readPathById.get(id) ?? "";
|
|
14292
|
+
const lastSlash = path30.lastIndexOf("/");
|
|
14293
|
+
openDenies.push({
|
|
14294
|
+
kind: denyTemplate.kind,
|
|
14295
|
+
withheldBytes: parseWithheldBytes(denyTemplate.kind, text),
|
|
14296
|
+
path: path30,
|
|
14297
|
+
basename: lastSlash === -1 ? path30 : path30.slice(lastSlash + 1),
|
|
14298
|
+
callIndexAtDeny: perCall.length,
|
|
14299
|
+
compactEpochAtOpen: compactEpoch,
|
|
14300
|
+
compactEpochAtWindow: null,
|
|
14301
|
+
toolCalls: []
|
|
14302
|
+
});
|
|
14303
|
+
}
|
|
13935
14304
|
if (bytes < READ_DIVERT_MAX_BYTES && READ_DIVERT_MARKER_RE.test(text)) {
|
|
13936
14305
|
s.readInterception.divertedByMarker += 1;
|
|
13937
14306
|
s.readInterception.divertedBytes += bytes;
|
|
@@ -14068,6 +14437,42 @@ async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneOb
|
|
|
14068
14437
|
}
|
|
14069
14438
|
flushLane(0);
|
|
14070
14439
|
flushLane(1);
|
|
14440
|
+
for (const o of openDenies) {
|
|
14441
|
+
if (o.compactEpochAtWindow === null) o.compactEpochAtWindow = compactEpoch;
|
|
14442
|
+
const windowCalls = o.toolCalls.slice(0, 3);
|
|
14443
|
+
let outcome;
|
|
14444
|
+
if (o.compactEpochAtWindow > o.compactEpochAtOpen) {
|
|
14445
|
+
outcome = "compacted";
|
|
14446
|
+
} else if (windowCalls.some((c) => c.isReadSamePath)) {
|
|
14447
|
+
outcome = "retried";
|
|
14448
|
+
} else if (windowCalls.some((c) => c.isSurgicalSamePath)) {
|
|
14449
|
+
outcome = "substituted";
|
|
14450
|
+
} else if (windowCalls.some((c) => c.isShellReadSamePath)) {
|
|
14451
|
+
outcome = "shell_read";
|
|
14452
|
+
} else if (o.toolCalls.length < 3) {
|
|
14453
|
+
outcome = "unresolved";
|
|
14454
|
+
} else {
|
|
14455
|
+
outcome = "abandoned";
|
|
14456
|
+
}
|
|
14457
|
+
const retriedWithin10 = o.toolCalls.some((c) => c.isReadSamePath);
|
|
14458
|
+
const shellReadAmbiguous = windowCalls.some((c) => c.isShellReadAmbiguous);
|
|
14459
|
+
const editWithin10 = o.toolCalls.some((c) => c.editCallId !== void 0);
|
|
14460
|
+
const editErrorWithin10 = o.toolCalls.some((c) => c.editCallId !== void 0 && editErrorById.get(c.editCallId) === true);
|
|
14461
|
+
const R = perCall.length - o.callIndexAtDeny;
|
|
14462
|
+
const nextCall = perCall[o.callIndexAtDeny];
|
|
14463
|
+
denyRows.push({
|
|
14464
|
+
kind: o.kind,
|
|
14465
|
+
withheldBytes: o.withheldBytes,
|
|
14466
|
+
R,
|
|
14467
|
+
nextCallInputTotal: nextCall?.inputTotal ?? 0,
|
|
14468
|
+
nextCallCacheRead: nextCall?.cacheRead ?? 0,
|
|
14469
|
+
outcome,
|
|
14470
|
+
retriedWithin10,
|
|
14471
|
+
shellReadAmbiguous,
|
|
14472
|
+
editWithin10,
|
|
14473
|
+
editErrorWithin10
|
|
14474
|
+
});
|
|
14475
|
+
}
|
|
14071
14476
|
if (sawTokenGoatHook) {
|
|
14072
14477
|
s.readInterception.repeatInHookedSessions += fileRepeats;
|
|
14073
14478
|
s.readInterception.repeatFullNoRangeInHookedSessions += fileRepeatsFullNoRange;
|
|
@@ -14117,8 +14522,47 @@ function hookRollup(hookMap, origin, event) {
|
|
|
14117
14522
|
}
|
|
14118
14523
|
return roll;
|
|
14119
14524
|
}
|
|
14525
|
+
function median(arr) {
|
|
14526
|
+
if (arr.length === 0) return 0;
|
|
14527
|
+
const sorted = [...arr].sort((a, b) => a - b);
|
|
14528
|
+
return sorted[Math.floor(sorted.length / 2)];
|
|
14529
|
+
}
|
|
14530
|
+
function aggregateDenyOutcomes(rows) {
|
|
14531
|
+
const byKind = /* @__PURE__ */ new Map();
|
|
14532
|
+
for (const r of rows) {
|
|
14533
|
+
const bucket = byKind.get(r.kind);
|
|
14534
|
+
if (bucket === void 0) byKind.set(r.kind, [r]);
|
|
14535
|
+
else bucket.push(r);
|
|
14536
|
+
}
|
|
14537
|
+
const result = [];
|
|
14538
|
+
for (const [kind, kindRows] of byKind) {
|
|
14539
|
+
const count = kindRows.length;
|
|
14540
|
+
const rateOf = (outcome) => kindRows.filter((r) => r.outcome === outcome).length / count;
|
|
14541
|
+
const knownBytes = kindRows.map((r) => r.withheldBytes).filter((b) => b !== null);
|
|
14542
|
+
result.push({
|
|
14543
|
+
kind,
|
|
14544
|
+
count,
|
|
14545
|
+
compactedRate: rateOf("compacted"),
|
|
14546
|
+
retriedRate: rateOf("retried"),
|
|
14547
|
+
substitutedRate: rateOf("substituted"),
|
|
14548
|
+
shellReadRate: rateOf("shell_read"),
|
|
14549
|
+
unresolvedRate: rateOf("unresolved"),
|
|
14550
|
+
abandonedRate: rateOf("abandoned"),
|
|
14551
|
+
retriedWithin10Rate: kindRows.filter((r) => r.retriedWithin10).length / count,
|
|
14552
|
+
medianWithheldBytes: knownBytes.length === 0 ? null : median(knownBytes),
|
|
14553
|
+
withheldBytesUnknownFraction: (count - knownBytes.length) / count,
|
|
14554
|
+
medianR: median(kindRows.map((r) => r.R)),
|
|
14555
|
+
medianNextCallInputTotal: median(kindRows.map((r) => r.nextCallInputTotal)),
|
|
14556
|
+
medianNextCallCacheRead: median(kindRows.map((r) => r.nextCallCacheRead)),
|
|
14557
|
+
shellReadAmbiguousCount: kindRows.filter((r) => r.shellReadAmbiguous).length,
|
|
14558
|
+
editWithin10Count: kindRows.filter((r) => r.editWithin10).length,
|
|
14559
|
+
editErrorWithin10Count: kindRows.filter((r) => r.editErrorWithin10).length
|
|
14560
|
+
});
|
|
14561
|
+
}
|
|
14562
|
+
return result.sort((a, b) => b.count - a.count || a.kind.localeCompare(b.kind));
|
|
14563
|
+
}
|
|
14120
14564
|
async function auditSessionCorpus(opts = {}) {
|
|
14121
|
-
const corpusDir =
|
|
14565
|
+
const corpusDir = path27.resolve(opts.dir ?? defaultCorpusDir());
|
|
14122
14566
|
if (!fs26.existsSync(corpusDir)) {
|
|
14123
14567
|
throw new Error(`session corpus directory not found: ${corpusDir}`);
|
|
14124
14568
|
}
|
|
@@ -14156,6 +14600,8 @@ async function auditSessionCorpus(opts = {}) {
|
|
|
14156
14600
|
laneAgentTypes: [],
|
|
14157
14601
|
readInterception: { readResults: 0, divertedByMarker: 0, divertedBytes: 0, fullServesOver10k: 0, fullServeBytesOver10k: 0, fullServesFirstRead: 0, fullServesRepeat: 0, repeatBytes: 0, repeatWithRange: 0, repeatFullNoRange: 0, repeatInHookedSessions: 0, repeatFullNoRangeInHookedSessions: 0, repeatFullNoRangeHookedAfterCompaction: 0, fullServesPathUnknown: 0 },
|
|
14158
14602
|
bashInterception: { bashResults: 0, markedByFilter: 0, markedBytes: 0, smallUntouched: 0, smallUntouchedBytes: 0, untouched: 0, untouchedBytes: 0, untouchedEstTokens: 0, untouchedRereadTokens: 0, untouchedBilledEquivTokens: 0, untouchedHeads: [] },
|
|
14603
|
+
denyOutcomes: [],
|
|
14604
|
+
editErrorBaseline: { totalEdits: 0, totalErrors: 0, rate: 0 },
|
|
14159
14605
|
lineTypes: {},
|
|
14160
14606
|
omissionMarkers: { fires: 0, linesOmitted: 0 }
|
|
14161
14607
|
};
|
|
@@ -14164,9 +14610,10 @@ async function auditSessionCorpus(opts = {}) {
|
|
|
14164
14610
|
const hookMap = /* @__PURE__ */ new Map();
|
|
14165
14611
|
const laneObservations = [];
|
|
14166
14612
|
const bashHeadMap = /* @__PURE__ */ new Map();
|
|
14613
|
+
const denyRows = [];
|
|
14167
14614
|
for (const file of files) {
|
|
14168
14615
|
try {
|
|
14169
|
-
await auditOneFile(file, summary, toolMap, attachmentMap, hookMap, laneObservations, bashHeadMap);
|
|
14616
|
+
await auditOneFile(file, summary, toolMap, attachmentMap, hookMap, laneObservations, bashHeadMap, denyRows);
|
|
14170
14617
|
summary.filesScanned += 1;
|
|
14171
14618
|
} catch {
|
|
14172
14619
|
summary.filesFailed += 1;
|
|
@@ -14203,6 +14650,8 @@ async function auditSessionCorpus(opts = {}) {
|
|
|
14203
14650
|
}
|
|
14204
14651
|
summary.attachmentKinds = [...attachmentMap.values()].sort((a, b) => b.billedEquivTokens - a.billedEquivTokens || b.injections - a.injections || a.kind.localeCompare(b.kind));
|
|
14205
14652
|
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));
|
|
14653
|
+
summary.denyOutcomes = aggregateDenyOutcomes(denyRows);
|
|
14654
|
+
summary.editErrorBaseline.rate = summary.editErrorBaseline.totalEdits === 0 ? 0 : summary.editErrorBaseline.totalErrors / summary.editErrorBaseline.totalEdits;
|
|
14206
14655
|
summary.runtimeMs = Date.now() - started;
|
|
14207
14656
|
return summary;
|
|
14208
14657
|
}
|
|
@@ -14288,6 +14737,15 @@ function formatSessionAudit(s) {
|
|
|
14288
14737
|
lines.push(`Full-serve split (same transcript file; a session's lanes are separate files, so repeats UNDER-count): first read ${fmt(ri.fullServesFirstRead)}, repeat ${fmt(ri.fullServesRepeat)} (${fmt(ri.repeatBytes)} bytes), path unknown ${fmt(ri.fullServesPathUnknown)}`);
|
|
14289
14738
|
lines.push(`Repeats: with offset/limit ${fmt(ri.repeatWithRange)} (deliberate paging), whole-file ${fmt(ri.repeatFullNoRange)} (divert-miss candidates), in sessions with a token-goat hook fire ${fmt(ri.repeatInHookedSessions)} (whole-file among them: ${fmt(ri.repeatFullNoRangeInHookedSessions)}, of which post-compaction and so correct by design: ${fmt(ri.repeatFullNoRangeHookedAfterCompaction)})`);
|
|
14290
14739
|
lines.push("");
|
|
14740
|
+
lines.push("## Deny outcomes (what actually happened after a token-goat Read deny; raw tokens, not billed units)");
|
|
14741
|
+
const eb = s.editErrorBaseline;
|
|
14742
|
+
lines.push(`Edit-error baseline (all Edit tool_results, corpus-wide, independent of any deny): ${fmt(eb.totalErrors)}/${fmt(eb.totalEdits)} (${(eb.rate * 100).toFixed(1)}%) -- compare each row's edit-error<=10 ratio against this rate.`);
|
|
14743
|
+
for (const d of s.denyOutcomes) {
|
|
14744
|
+
const editErrorRatio = d.editWithin10Count > 0 ? `${fmt(d.editErrorWithin10Count)}/${fmt(d.editWithin10Count)}` : "n/a";
|
|
14745
|
+
lines.push(`${d.kind.padEnd(34)} n ${fmt(d.count).padStart(6)} compacted ${pct(d.compactedRate * d.count, d.count)} retried ${pct(d.retriedRate * d.count, d.count)} substituted ${pct(d.substitutedRate * d.count, d.count)} shell-read ${pct(d.shellReadRate * d.count, d.count)} unresolved ${pct(d.unresolvedRate * d.count, d.count)} abandoned ${pct(d.abandonedRate * d.count, d.count)} retried<=10 ${pct(d.retriedWithin10Rate * d.count, d.count)} median-withheld ${d.medianWithheldBytes === null ? "n/a" : fmt(d.medianWithheldBytes) + "B"} (unknown ${pct(d.withheldBytesUnknownFraction * d.count, d.count)}) median-R ${fmt(d.medianR)} shell-read-ambiguous ${fmt(d.shellReadAmbiguousCount)} edit-error<=10 ${editErrorRatio}`);
|
|
14746
|
+
}
|
|
14747
|
+
if (s.denyOutcomes.length === 0) lines.push("(no denies matched a known template)");
|
|
14748
|
+
lines.push("");
|
|
14291
14749
|
lines.push("## Bash filter fire-rate (token-goat in-band markers inside Bash tool results)");
|
|
14292
14750
|
const bi = s.bashInterception;
|
|
14293
14751
|
lines.push(`Bash results: ${fmt(bi.bashResults)}; marked by a filter: ${fmt(bi.markedByFilter)} (${fmt(bi.markedBytes)} bytes); small unmarked <${fmt(BASH_SMALL_RESULT_MAX_BYTES)} B: ${fmt(bi.smallUntouched)} (${fmt(bi.smallUntouchedBytes)} bytes)`);
|
|
@@ -14306,7 +14764,7 @@ function formatSessionAudit(s) {
|
|
|
14306
14764
|
// src/cli_mcp_audit.ts
|
|
14307
14765
|
import * as fs27 from "node:fs";
|
|
14308
14766
|
import * as os13 from "node:os";
|
|
14309
|
-
import * as
|
|
14767
|
+
import * as path28 from "node:path";
|
|
14310
14768
|
function readMcpJsonFile(configPath2) {
|
|
14311
14769
|
try {
|
|
14312
14770
|
if (!fs27.existsSync(configPath2)) return null;
|
|
@@ -14345,8 +14803,8 @@ function readClaudeJsonConfig(claudeJsonPath, projectRoot) {
|
|
|
14345
14803
|
}
|
|
14346
14804
|
}
|
|
14347
14805
|
function discoverMcpConfig(projectRoot, home) {
|
|
14348
|
-
const mcpJsonPath =
|
|
14349
|
-
const claudeJsonPath =
|
|
14806
|
+
const mcpJsonPath = path28.join(projectRoot, ".mcp.json");
|
|
14807
|
+
const claudeJsonPath = path28.join(home, ".claude.json");
|
|
14350
14808
|
const sourcesChecked = [mcpJsonPath, claudeJsonPath];
|
|
14351
14809
|
const fromMcpJson = readMcpJsonFile(mcpJsonPath);
|
|
14352
14810
|
if (fromMcpJson !== null) return { servers: fromMcpJson, sourcePath: mcpJsonPath, sourcesChecked };
|
|
@@ -14906,7 +15364,7 @@ async function cmdMcpServe() {
|
|
|
14906
15364
|
let StdioServerTransport;
|
|
14907
15365
|
try {
|
|
14908
15366
|
;
|
|
14909
|
-
({ createMcpServer } = await import("./token-goat-chunk-
|
|
15367
|
+
({ createMcpServer } = await import("./token-goat-chunk-LEBYARO3.mjs"));
|
|
14910
15368
|
({ StdioServerTransport } = await import("./token-goat-chunk-324QOJYZ.mjs"));
|
|
14911
15369
|
} catch (err2) {
|
|
14912
15370
|
process.stderr.write(
|
|
@@ -14919,8 +15377,8 @@ async function cmdMcpServe() {
|
|
|
14919
15377
|
const server = await createMcpServer();
|
|
14920
15378
|
const transport = new StdioServerTransport();
|
|
14921
15379
|
await server.connect(transport);
|
|
14922
|
-
await new Promise((
|
|
14923
|
-
server.onclose =
|
|
15380
|
+
await new Promise((resolve14) => {
|
|
15381
|
+
server.onclose = resolve14;
|
|
14924
15382
|
});
|
|
14925
15383
|
}
|
|
14926
15384
|
function printBridgeVerificationNotice(harness) {
|
|
@@ -14931,7 +15389,7 @@ async function cmdHook(event, opts) {
|
|
|
14931
15389
|
if (typeof opts.harness === "string" && opts.harness.length > 0) {
|
|
14932
15390
|
process.env[ENV_KEYS.HARNESS_OVERRIDE] = opts.harness;
|
|
14933
15391
|
}
|
|
14934
|
-
const { relay } = await import("./token-goat-chunk-
|
|
15392
|
+
const { relay } = await import("./token-goat-chunk-ZVN5WHTO.mjs");
|
|
14935
15393
|
await relay(event);
|
|
14936
15394
|
}
|
|
14937
15395
|
async function cmdInstall(opts) {
|
|
@@ -15044,14 +15502,14 @@ async function cmdInstall(opts) {
|
|
|
15044
15502
|
);
|
|
15045
15503
|
}
|
|
15046
15504
|
try {
|
|
15047
|
-
const skillDir =
|
|
15505
|
+
const skillDir = path29.join(homedir13(), ".claude", "skills");
|
|
15048
15506
|
if (fs28.existsSync(skillDir)) {
|
|
15049
15507
|
const entries = fs28.readdirSync(skillDir, { withFileTypes: true });
|
|
15050
15508
|
const skillNames = [];
|
|
15051
15509
|
const sessionId = getSessionId();
|
|
15052
15510
|
for (const entry of entries) {
|
|
15053
15511
|
if (!entry.isDirectory()) continue;
|
|
15054
|
-
const skillFile =
|
|
15512
|
+
const skillFile = path29.join(skillDir, entry.name, "SKILL.md");
|
|
15055
15513
|
if (fs28.existsSync(skillFile)) {
|
|
15056
15514
|
const body = fs28.readFileSync(skillFile, "utf-8");
|
|
15057
15515
|
const compact = extractCompactFromMarker(body);
|
|
@@ -15064,7 +15522,7 @@ async function cmdInstall(opts) {
|
|
|
15064
15522
|
if (skillNames.length > 0) {
|
|
15065
15523
|
const dir = skillOutputsDir();
|
|
15066
15524
|
await fs28.promises.mkdir(dir, { recursive: true });
|
|
15067
|
-
const pregenPath =
|
|
15525
|
+
const pregenPath = path29.join(dir, "pregen.json");
|
|
15068
15526
|
const pregenData = { ts: Date.now(), names: skillNames };
|
|
15069
15527
|
await fs28.promises.writeFile(pregenPath, JSON.stringify(pregenData, null, 2));
|
|
15070
15528
|
out(`Pre-generated ${skillNames.length} skill compacts.`);
|
|
@@ -15265,7 +15723,7 @@ async function cmdSessionOutline(sessionIdOrPath, opts = {}) {
|
|
|
15265
15723
|
${formatSessionOutline(turns)}`;
|
|
15266
15724
|
out(text);
|
|
15267
15725
|
const fullSourceBytes = sessionTranscriptSize(transcriptPath);
|
|
15268
|
-
const bytesSaved =
|
|
15726
|
+
const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
|
|
15269
15727
|
recordStat("session_outline", bytesSaved, savedTokensFromBytes(bytesSaved));
|
|
15270
15728
|
}
|
|
15271
15729
|
function sessionTranscriptSize(transcriptPath) {
|
|
@@ -15287,7 +15745,7 @@ async function cmdSessionSlice(sessionIdOrPath, opts) {
|
|
|
15287
15745
|
const text = opts.json === true ? JSON.stringify({ transcriptPath, turns }) : formatSessionSlice(turns);
|
|
15288
15746
|
out(text);
|
|
15289
15747
|
const fullSourceBytes = sessionTranscriptSize(transcriptPath);
|
|
15290
|
-
const bytesSaved =
|
|
15748
|
+
const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
|
|
15291
15749
|
recordStat("session_slice", bytesSaved, savedTokensFromBytes(bytesSaved));
|
|
15292
15750
|
}
|
|
15293
15751
|
function cmdMcpAudit(opts = {}) {
|
|
@@ -15328,7 +15786,7 @@ function cmdHintStats(opts = {}) {
|
|
|
15328
15786
|
});
|
|
15329
15787
|
}
|
|
15330
15788
|
function _applyFiltersAndPrint(content, opts, fenceByProvenance = false, fenceTag = UNTRUSTED_WEB_TAG) {
|
|
15331
|
-
const
|
|
15789
|
+
const emit3 = (text) => {
|
|
15332
15790
|
if (!fenceByProvenance || text === "") {
|
|
15333
15791
|
out(text);
|
|
15334
15792
|
return text;
|
|
@@ -15365,7 +15823,7 @@ function _applyFiltersAndPrint(content, opts, fenceByProvenance = false, fenceTa
|
|
|
15365
15823
|
}
|
|
15366
15824
|
const rawLines = content.split(/\r?\n/);
|
|
15367
15825
|
if (opts.full === true) {
|
|
15368
|
-
return
|
|
15826
|
+
return emit3(rawLines.join("\n"));
|
|
15369
15827
|
}
|
|
15370
15828
|
const lines = rawLines.length > 1 && rawLines[rawLines.length - 1] === "" ? rawLines.slice(0, -1) : rawLines;
|
|
15371
15829
|
const headN = opts.head !== void 0 ? requireNonNegativeInt("--head", opts.head) : 30;
|
|
@@ -15388,7 +15846,7 @@ function _applyFiltersAndPrint(content, opts, fenceByProvenance = false, fenceTa
|
|
|
15388
15846
|
result = lines.slice(Math.max(0, lines.length - tailN));
|
|
15389
15847
|
noteLineCap("last", "tail", result.length, lines.length);
|
|
15390
15848
|
}
|
|
15391
|
-
return
|
|
15849
|
+
return emit3(result.join("\n"));
|
|
15392
15850
|
}
|
|
15393
15851
|
function fenceFileText(text) {
|
|
15394
15852
|
return fenceUntrusted(text, UNTRUSTED_FILE_TAG);
|
|
@@ -15463,7 +15921,7 @@ async function cmdPdfExtract(file, opts) {
|
|
|
15463
15921
|
const text = await runPdfExtractText(file, opts.pages, opts.layout === true);
|
|
15464
15922
|
const printed = _applyFiltersAndPrint(text, opts, true, UNTRUSTED_FILE_TAG);
|
|
15465
15923
|
const fullSourceBytes = fileSizeOrZero(file);
|
|
15466
|
-
const bytesSaved =
|
|
15924
|
+
const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(printed, "utf8"));
|
|
15467
15925
|
recordStat("pdf_extract", bytesSaved, savedTokensFromBytes(bytesSaved));
|
|
15468
15926
|
}
|
|
15469
15927
|
async function cmdPdfLocate(file, pattern, opts) {
|
|
@@ -15493,7 +15951,7 @@ ${countNoun(matches2.length, "match", "matches")} across ${countNoun(pages.lengt
|
|
|
15493
15951
|
out(printed);
|
|
15494
15952
|
}
|
|
15495
15953
|
const fullSourceBytes = fileSizeOrZero(file);
|
|
15496
|
-
const bytesSaved =
|
|
15954
|
+
const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(printed, "utf8"));
|
|
15497
15955
|
recordStat("pdf_locate", bytesSaved, savedTokensFromBytes(bytesSaved));
|
|
15498
15956
|
}
|
|
15499
15957
|
async function cmdPdfOutline(file, opts) {
|
|
@@ -15509,7 +15967,7 @@ async function cmdPdfOutline(file, opts) {
|
|
|
15509
15967
|
const text = opts.json === true ? JSON.stringify(entries.map((e) => ({ ...e, title: fenceFileFieldIfMatched(e.title) })), null, 2) : fenceFileText(entries.map((e) => `${" ".repeat(e.level)}${e.title}${e.page !== null ? ` (p.${e.page})` : ""}`).join("\n"));
|
|
15510
15968
|
out(text);
|
|
15511
15969
|
const fullSourceBytes = fileSizeOrZero(file);
|
|
15512
|
-
const bytesSaved =
|
|
15970
|
+
const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
|
|
15513
15971
|
recordStat("pdf_outline", bytesSaved, savedTokensFromBytes(bytesSaved));
|
|
15514
15972
|
}
|
|
15515
15973
|
async function cmdPdfMeta(file, opts = {}) {
|
|
@@ -15523,7 +15981,7 @@ async function cmdPdfMeta(file, opts = {}) {
|
|
|
15523
15981
|
const text = opts.json === true ? JSON.stringify({ pageCount: meta.pageCount, title: meta.title, author: meta.author, hasTextLayer: meta.hasTextLayer }, null, 2) : lines.join("\n");
|
|
15524
15982
|
out(text);
|
|
15525
15983
|
const fullSourceBytes = fileSizeOrZero(file);
|
|
15526
|
-
const bytesSaved =
|
|
15984
|
+
const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
|
|
15527
15985
|
recordStat("pdf_meta", bytesSaved, savedTokensFromBytes(bytesSaved));
|
|
15528
15986
|
}
|
|
15529
15987
|
async function cmdImageMeta(file, opts = {}) {
|
|
@@ -15545,8 +16003,8 @@ ${msg}`;
|
|
|
15545
16003
|
const text = opts.json === true ? JSON.stringify(meta, null, 2) : lines.join("\n");
|
|
15546
16004
|
out(text);
|
|
15547
16005
|
const fullSourceBytes = fileSizeOrZero(file);
|
|
15548
|
-
const bytesSaved =
|
|
15549
|
-
recordStat("image_meta", bytesSaved,
|
|
16006
|
+
const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
|
|
16007
|
+
recordStat("image_meta", bytesSaved, visionTokensSavedByText(meta.width, meta.height, Buffer.byteLength(text, "utf8"), loadConfig().image_shrink.vision_tier));
|
|
15550
16008
|
}
|
|
15551
16009
|
function fenceOcrText(text) {
|
|
15552
16010
|
if (!injectionFencingEnabled()) return text;
|
|
@@ -15577,8 +16035,8 @@ ${fenceOcrText(result.text)}` : `${lines.join("\n")}
|
|
|
15577
16035
|
}
|
|
15578
16036
|
out(text);
|
|
15579
16037
|
const fullSourceBytes = fileSizeOrZero(file);
|
|
15580
|
-
const bytesSaved =
|
|
15581
|
-
recordStat("image_text", bytesSaved,
|
|
16038
|
+
const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
|
|
16039
|
+
recordStat("image_text", bytesSaved, visionTokensSavedByText(null, null, Buffer.byteLength(text, "utf8"), loadConfig().image_shrink.vision_tier));
|
|
15582
16040
|
}
|
|
15583
16041
|
function cmdVideoChapters(file) {
|
|
15584
16042
|
const { chapters, subtitleStreams } = extractVideoChapters(file);
|
|
@@ -15603,7 +16061,7 @@ function cmdVideoChapters(file) {
|
|
|
15603
16061
|
const text = lines.join("\n");
|
|
15604
16062
|
out(text);
|
|
15605
16063
|
const fullSourceBytes = fileSizeOrZero(file);
|
|
15606
|
-
const bytesSaved =
|
|
16064
|
+
const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(text, "utf8"));
|
|
15607
16065
|
recordStat("video_chapters", bytesSaved, savedTokensFromBytes(bytesSaved));
|
|
15608
16066
|
}
|
|
15609
16067
|
function formatVideoTimestamp(totalSeconds) {
|
|
@@ -15630,7 +16088,7 @@ function cmdSharepointResolve(url) {
|
|
|
15630
16088
|
}
|
|
15631
16089
|
function recordXlsxStat(kind, file, emitted) {
|
|
15632
16090
|
const fullSourceBytes = fileSizeOrZero(file);
|
|
15633
|
-
const bytesSaved =
|
|
16091
|
+
const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(emitted, "utf8"));
|
|
15634
16092
|
recordStat(kind, bytesSaved, savedTokensFromBytes(bytesSaved));
|
|
15635
16093
|
}
|
|
15636
16094
|
async function cmdXlsxSheets(file, opts = {}) {
|
|
@@ -15665,7 +16123,7 @@ async function cmdXlsxQuery(file, opts) {
|
|
|
15665
16123
|
}
|
|
15666
16124
|
function recordDocStat(kind, file, emitted) {
|
|
15667
16125
|
const fullSourceBytes = fileSizeOrZero(file);
|
|
15668
|
-
const bytesSaved =
|
|
16126
|
+
const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(emitted, "utf8"));
|
|
15669
16127
|
recordStat(kind, bytesSaved, savedTokensFromBytes(bytesSaved));
|
|
15670
16128
|
}
|
|
15671
16129
|
async function cmdPptxOutline(file, opts) {
|
|
@@ -15823,6 +16281,18 @@ function cmdConflicts(targetPath, opts) {
|
|
|
15823
16281
|
async function cmdScreenshot(url, destPath, opts) {
|
|
15824
16282
|
out(await runScreenshot(url, destPath, opts));
|
|
15825
16283
|
}
|
|
16284
|
+
function readStdinPaths() {
|
|
16285
|
+
if (process.stdin.isTTY) {
|
|
16286
|
+
throw new CliError("--stdin requires piped input, e.g. `git diff --name-only | token-goat affected --stdin`");
|
|
16287
|
+
}
|
|
16288
|
+
let raw;
|
|
16289
|
+
try {
|
|
16290
|
+
raw = fs28.readFileSync(0, "utf8");
|
|
16291
|
+
} catch (e) {
|
|
16292
|
+
throw new CliError(`could not read the file list from stdin: ${extractErrorMessage(e)}`);
|
|
16293
|
+
}
|
|
16294
|
+
return raw.split("\n").map((line) => line.trim()).filter((line) => line !== "");
|
|
16295
|
+
}
|
|
15826
16296
|
function runExit(fn) {
|
|
15827
16297
|
try {
|
|
15828
16298
|
process.exitCode = fn();
|
|
@@ -15853,7 +16323,7 @@ function emitExtraFileArgsNote(command, first, extras, opts = {}) {
|
|
|
15853
16323
|
}
|
|
15854
16324
|
async function cmdCompress(opts) {
|
|
15855
16325
|
try {
|
|
15856
|
-
const bashRunner = await import("./token-goat-chunk-
|
|
16326
|
+
const bashRunner = await import("./token-goat-chunk-NY4HYRSK.mjs");
|
|
15857
16327
|
if (opts.compress === false) {
|
|
15858
16328
|
process.exitCode = bashRunner.runRaw(opts.cmd, parseTimeout(opts.timeout, bashRunner.DEFAULT_TIMEOUT_SECONDS));
|
|
15859
16329
|
return;
|
|
@@ -15951,8 +16421,8 @@ async function cmdSkillCompact(name, opts) {
|
|
|
15951
16421
|
}
|
|
15952
16422
|
throw new CliError(`failed to read skill file '${opts.path}': ${extractErrorMessage(e)}`);
|
|
15953
16423
|
}
|
|
15954
|
-
cacheName = name ??
|
|
15955
|
-
sourcePath =
|
|
16424
|
+
cacheName = name ?? path29.basename(path29.dirname(path29.resolve(opts.path)));
|
|
16425
|
+
sourcePath = path29.resolve(opts.path);
|
|
15956
16426
|
} else {
|
|
15957
16427
|
if (name === void 0 || !name.trim()) {
|
|
15958
16428
|
throw new CliError("skill-compact requires a <name> or --path <file>");
|
|
@@ -16093,8 +16563,8 @@ async function cmdSkillDiff(name) {
|
|
|
16093
16563
|
}
|
|
16094
16564
|
const newer = versions[0];
|
|
16095
16565
|
const older = versions[1];
|
|
16096
|
-
const newerBody = await fs28.promises.readFile(
|
|
16097
|
-
const olderBody = await fs28.promises.readFile(
|
|
16566
|
+
const newerBody = await fs28.promises.readFile(path29.resolve(dir, `${newer.outputId}.txt`), "utf-8").catch(() => null);
|
|
16567
|
+
const olderBody = await fs28.promises.readFile(path29.resolve(dir, `${older.outputId}.txt`), "utf-8").catch(() => null);
|
|
16098
16568
|
if (newerBody === null || olderBody === null) {
|
|
16099
16569
|
out(`a cached version of '${name}' was evicted while diffing -- try again`);
|
|
16100
16570
|
return;
|
|
@@ -16146,7 +16616,7 @@ function atomicWriteBuffer(dest, data) {
|
|
|
16146
16616
|
if (e.code !== "ENOENT") throw e;
|
|
16147
16617
|
}
|
|
16148
16618
|
const rnd = randomBytes(4).toString("hex");
|
|
16149
|
-
const tmp =
|
|
16619
|
+
const tmp = path29.join(path29.dirname(path29.resolve(dest)), `.tmp.${process.pid}.${rnd}`);
|
|
16150
16620
|
try {
|
|
16151
16621
|
fs28.writeFileSync(tmp, data, { mode: 384 });
|
|
16152
16622
|
try {
|
|
@@ -16184,9 +16654,9 @@ function mapFsError(e, src, dest, srcLabel = "source") {
|
|
|
16184
16654
|
const fe = e;
|
|
16185
16655
|
if (fe.code === "ENOENT") {
|
|
16186
16656
|
const errPath = fe.path ?? "";
|
|
16187
|
-
const isSource = src !== void 0 &&
|
|
16657
|
+
const isSource = src !== void 0 && path29.resolve(errPath) === path29.resolve(src);
|
|
16188
16658
|
if (isSource) throw new CliError(`${/\bfile$/i.test(srcLabel) ? srcLabel : `${srcLabel} file`} not found: ${src}`);
|
|
16189
|
-
const destDir = dest ?
|
|
16659
|
+
const destDir = dest ? path29.dirname(path29.resolve(dest)) : path29.dirname(path29.resolve(errPath || "."));
|
|
16190
16660
|
throw new CliError(`destination directory does not exist: ${destDir}`);
|
|
16191
16661
|
}
|
|
16192
16662
|
if (fe.code === "ENOTDIR") {
|
|
@@ -16197,7 +16667,7 @@ function mapFsError(e, src, dest, srcLabel = "source") {
|
|
|
16197
16667
|
}
|
|
16198
16668
|
if (fe.code === "EISDIR") {
|
|
16199
16669
|
const errPath = fe.path ?? "";
|
|
16200
|
-
const isSource = src !== void 0 && (errPath === "" ||
|
|
16670
|
+
const isSource = src !== void 0 && (errPath === "" || path29.resolve(errPath) === path29.resolve(src));
|
|
16201
16671
|
if (isSource) throw new CliError(`source is a directory, not a file: ${src}`);
|
|
16202
16672
|
throw new CliError(`destination is a directory, not a file: ${dest ?? (errPath || "(unknown)")}`);
|
|
16203
16673
|
}
|
|
@@ -16266,7 +16736,7 @@ function validateWritablePath(dest, label) {
|
|
|
16266
16736
|
throw new CliError(`${label} path contains a null byte`);
|
|
16267
16737
|
}
|
|
16268
16738
|
if (isWindows()) {
|
|
16269
|
-
const base =
|
|
16739
|
+
const base = path29.basename(dest);
|
|
16270
16740
|
const stem = base.replace(/\.[^.]*$/, "").toUpperCase();
|
|
16271
16741
|
if (WIN_RESERVED.has(stem)) {
|
|
16272
16742
|
throw new CliError(`${label} '${base}' is a reserved Windows device name`);
|
|
@@ -16407,7 +16877,7 @@ function cmdWriteFile(dest, opts) {
|
|
|
16407
16877
|
throw new CliError(`TOKEN_GOAT_MAX_STDIN_MB must be a positive integer; got '${process.env["TOKEN_GOAT_MAX_STDIN_MB"] ?? ""}'`);
|
|
16408
16878
|
}
|
|
16409
16879
|
const maxBytes = maxMB * 1024 * 1024;
|
|
16410
|
-
return new Promise((
|
|
16880
|
+
return new Promise((resolve14, reject) => {
|
|
16411
16881
|
const chunks = [];
|
|
16412
16882
|
let totalBytes = 0;
|
|
16413
16883
|
let settled = false;
|
|
@@ -16431,7 +16901,7 @@ function cmdWriteFile(dest, opts) {
|
|
|
16431
16901
|
try {
|
|
16432
16902
|
atomicWriteBuffer(dest, Buffer.concat(chunks));
|
|
16433
16903
|
enqueueDirtyPathSafe(dest);
|
|
16434
|
-
|
|
16904
|
+
resolve14();
|
|
16435
16905
|
} catch (e) {
|
|
16436
16906
|
try {
|
|
16437
16907
|
mapFsError(e, void 0, dest);
|
|
@@ -16783,7 +17253,7 @@ ${content}`;
|
|
|
16783
17253
|
}
|
|
16784
17254
|
const toEmit = fenceUntrusted(emitted, UNTRUSTED_WEB_TAG);
|
|
16785
17255
|
out(toEmit);
|
|
16786
|
-
const bytesSaved =
|
|
17256
|
+
const bytesSaved = cappedSourceBytesSaved(fullSourceBytes, Buffer.byteLength(toEmit, "utf8"));
|
|
16787
17257
|
recordStat("gdrive_sections", bytesSaved, savedTokensFromBytes(bytesSaved));
|
|
16788
17258
|
}
|
|
16789
17259
|
function expandGlobs(root, patterns, globFnOverride) {
|
|
@@ -16793,17 +17263,17 @@ function expandGlobs(root, patterns, globFnOverride) {
|
|
|
16793
17263
|
if (globFn !== void 0 && (p.includes("*") || p.includes("?") || p.includes("{"))) {
|
|
16794
17264
|
try {
|
|
16795
17265
|
const hits = globFn(p, { cwd: root });
|
|
16796
|
-
for (const h of hits) out2.push(
|
|
17266
|
+
for (const h of hits) out2.push(path29.isAbsolute(h) ? h : path29.join(root, h));
|
|
16797
17267
|
continue;
|
|
16798
17268
|
} catch {
|
|
16799
17269
|
}
|
|
16800
17270
|
}
|
|
16801
|
-
out2.push(
|
|
17271
|
+
out2.push(path29.isAbsolute(p) ? p : path29.join(root, p));
|
|
16802
17272
|
}
|
|
16803
17273
|
return out2;
|
|
16804
17274
|
}
|
|
16805
17275
|
function readIgnoreFile(root) {
|
|
16806
|
-
const ignorePath =
|
|
17276
|
+
const ignorePath = path29.join(root, ".tokengoatignore");
|
|
16807
17277
|
let raw;
|
|
16808
17278
|
try {
|
|
16809
17279
|
raw = fs28.readFileSync(ignorePath, "utf8");
|
|
@@ -16874,7 +17344,7 @@ function cmdTokens(patterns, opts) {
|
|
|
16874
17344
|
if (opts.tree === true) {
|
|
16875
17345
|
const dirs = /* @__PURE__ */ new Map();
|
|
16876
17346
|
for (const e of entries) {
|
|
16877
|
-
const dir =
|
|
17347
|
+
const dir = path29.dirname(e.rel_path);
|
|
16878
17348
|
if (!dirs.has(dir)) dirs.set(dir, []);
|
|
16879
17349
|
dirs.get(dir).push(e);
|
|
16880
17350
|
}
|
|
@@ -16884,7 +17354,7 @@ function cmdTokens(patterns, opts) {
|
|
|
16884
17354
|
const pct2 = result.total_tokens > 0 ? Math.round(dirTokens / result.total_tokens * 100) : 0;
|
|
16885
17355
|
lines2.push(`${dir}/ (${dirTokens} tokens, ${pct2}%)`);
|
|
16886
17356
|
for (const e of dirEntries) {
|
|
16887
|
-
lines2.push(` ${
|
|
17357
|
+
lines2.push(` ${path29.basename(e.rel_path).padEnd(30)} ${String(e.tokens).padStart(8)} tokens`);
|
|
16888
17358
|
}
|
|
16889
17359
|
}
|
|
16890
17360
|
out(lines2.join("\n"));
|
|
@@ -16935,6 +17405,64 @@ function cmdFailures(src, opts) {
|
|
|
16935
17405
|
saveFailureSnapshot(project.hash, key, { signatures, runner: result.runner, storedAt: Date.now() });
|
|
16936
17406
|
out(opts.json === true ? formatFailureDeltaJson(delta, result.runner) : formatFailureDeltaText(delta, result.runner));
|
|
16937
17407
|
}
|
|
17408
|
+
var originalHelpInformation = null;
|
|
17409
|
+
function generateCompactHelp() {
|
|
17410
|
+
const lines = [
|
|
17411
|
+
"Usage: token-goat [options] [command]",
|
|
17412
|
+
"",
|
|
17413
|
+
"Surgical token-reduction companion for AI coding agents",
|
|
17414
|
+
"",
|
|
17415
|
+
"Options:",
|
|
17416
|
+
" -v, --version print the token-goat version",
|
|
17417
|
+
" --cwd <path> run as if invoked from this directory",
|
|
17418
|
+
" -h, --help display help for command",
|
|
17419
|
+
"",
|
|
17420
|
+
"Reads: symbol, read, brief, section, semantic, skeleton, outline, refs, scope,",
|
|
17421
|
+
" similar, context-for, call-chain, impact, callers, dead, test-for",
|
|
17422
|
+
"",
|
|
17423
|
+
"Analysis: arch, affected, blame, deps, types, coverage-gaps, changed, diff, log,",
|
|
17424
|
+
" exports, imports, find, grep",
|
|
17425
|
+
"",
|
|
17426
|
+
"File Formats: pdf-meta, pdf-outline, pdf-extract, pdf-locate, xlsx-sheets,",
|
|
17427
|
+
" xlsx-head, xlsx-query, xlsx-range, yaml-outline, yaml-query, json-outline,",
|
|
17428
|
+
" json-query, xml-outline, xml-query, docx-outline, docx-text, pptx-outline,",
|
|
17429
|
+
" pptx-slide, pptx-text, pptx-notes",
|
|
17430
|
+
"",
|
|
17431
|
+
"Index & Search: index, map, reconcile, doctor, commands, ask, pack, tokens,",
|
|
17432
|
+
" budget, failures, todo, trace, logfold, lockdeps, dep-docs, recent, hot,",
|
|
17433
|
+
" snapshot-snapshot, baseline, cost, coverage-report-gaps, clean-cache,",
|
|
17434
|
+
" cache-audit, reclaim-index, project",
|
|
17435
|
+
"",
|
|
17436
|
+
"Session: stats, waste, session-outline, session-audit, session-slice,",
|
|
17437
|
+
" session-summary, context-stats, bootstrap-audit, memory, hint-stats,",
|
|
17438
|
+
" mcp-audit",
|
|
17439
|
+
"",
|
|
17440
|
+
"Skills: skill-body, skill-compact, skill-list, skill-size, skill-history,",
|
|
17441
|
+
" skill-diff, skill-section",
|
|
17442
|
+
"",
|
|
17443
|
+
"Compression: compress-text, retrieve, handoff-create, handoff-resolve,",
|
|
17444
|
+
" compress, recall, bash-output, web-output, mcp-output, compress-text,",
|
|
17445
|
+
" compress",
|
|
17446
|
+
"",
|
|
17447
|
+
"Config & Integration: install, uninstall, mcp-serve, mcp-status, hook,",
|
|
17448
|
+
" bridges-status, worker, statusline, version, config, config-get,",
|
|
17449
|
+
" capabilities, help",
|
|
17450
|
+
"",
|
|
17451
|
+
"Data: note, note-add, note-get, note-list, sqlite-query, sqlite-schema,",
|
|
17452
|
+
" csv-profile, csv-query",
|
|
17453
|
+
"",
|
|
17454
|
+
"Utils: ignores, bash-history, web-history, openapi-op, openapi-outline,",
|
|
17455
|
+
" gdrive-sections, screenshot, opencode-*, fetch-image, image-meta,",
|
|
17456
|
+
" image-text, start, stop, resume, replace, insert-section, pr-slice,",
|
|
17457
|
+
" sharepoint-resolve, transcript, transcript-outline, video-chapters,",
|
|
17458
|
+
" write-file, zip-list, zip-read, mcp-history, compact-doc, compact-hint,",
|
|
17459
|
+
" conflicts, prune-cache",
|
|
17460
|
+
"",
|
|
17461
|
+
"Tip: look up one command instead of this whole list: 'token-goat help <command>', or 'token-goat commands --grep <pattern>' anchored ('^read$' ~700B; bare 'read' matches descriptions too, ~7KB).",
|
|
17462
|
+
""
|
|
17463
|
+
];
|
|
17464
|
+
return lines.join("\n");
|
|
17465
|
+
}
|
|
16938
17466
|
function buildProgram() {
|
|
16939
17467
|
const program2 = new Command();
|
|
16940
17468
|
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)");
|
|
@@ -17113,6 +17641,21 @@ function buildProgram() {
|
|
|
17113
17641
|
worker.command("stop").description("stop the background indexer").action(guard(cmdWorkerStop));
|
|
17114
17642
|
worker.command("status").description("check if the indexer is running").action(guard(cmdWorkerStatus));
|
|
17115
17643
|
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));
|
|
17644
|
+
program2.command("reconcile").description("sweep this project for files that changed while token-goat was not watching, and queue them for reindexing").option("--dry-run", "report the drift without queueing anything").option("--budget-ms <ms>", `wall-clock budget for the sweep (default ${DEFAULT_RECONCILE_BUDGET_MS})`).option("-j, --json", "output as JSON").action(
|
|
17645
|
+
(opts) => runExit(
|
|
17646
|
+
() => runReconcile({
|
|
17647
|
+
...opts.dryRun === true ? { dryRun: true } : {},
|
|
17648
|
+
...opts.budgetMs !== void 0 ? { budgetMs: requireNonNegativeInt("--budget-ms", opts.budgetMs) } : {},
|
|
17649
|
+
...opts.json === true ? { json: true } : {}
|
|
17650
|
+
})
|
|
17651
|
+
)
|
|
17652
|
+
);
|
|
17653
|
+
program2.command("capabilities").description("report every capability that can send data off this machine, and its current state").option("-j, --json", "emit as JSON, for asserting on in a pipeline").action(
|
|
17654
|
+
guard((opts) => {
|
|
17655
|
+
const caps = collectCapabilities();
|
|
17656
|
+
console.log(opts.json ? JSON.stringify({ version: VERSION, capabilities: caps }, null, 2) : renderCapabilities(caps));
|
|
17657
|
+
})
|
|
17658
|
+
);
|
|
17116
17659
|
program2.command("doctor").description("diagnose token-goat health").option("--context", "include context footprint analysis").option("--json", "emit check results as JSON instead of text").action(guard(cmdDoctor));
|
|
17117
17660
|
program2.command("context-stats").description("show context statistics").option("--project <path>", "project root to analyze").option("-j, --json", "output as JSON").option("--fix", "apply automatic fixes (confirm-gated; shows a diff before writing)").option("-y, --yes", "with --fix, apply without prompting (non-interactive / scripted use)").action(guard(cmdContextStats));
|
|
17118
17661
|
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));
|
|
@@ -17263,10 +17806,22 @@ function buildProgram() {
|
|
|
17263
17806
|
})
|
|
17264
17807
|
)
|
|
17265
17808
|
);
|
|
17266
|
-
program2.command("arch").description("internal import graph analysis: hubs, entry points, cycles").option("--top <n>", "limit hubs
|
|
17809
|
+
program2.command("arch").description("internal import graph analysis: hubs, entry points, cycles, and with --modules the groups of files that mostly import each other").option("--top <n>", "limit hubs, entry points, modules and cross-module pairs to top N (default 10)").option("--modules", "also group files into modules by who imports whom, and report which modules reach into which (--json carries the full member list of each)").option("-j, --json", "output as JSON").action(
|
|
17267
17810
|
(opts) => runExit(
|
|
17268
17811
|
() => runArch({
|
|
17269
17812
|
...opts.top !== void 0 ? { top: requireNonNegativeInt("--top", opts.top) } : {},
|
|
17813
|
+
...opts.json === true ? { json: true } : {},
|
|
17814
|
+
...opts.modules === true ? { modules: true } : {}
|
|
17815
|
+
})
|
|
17816
|
+
)
|
|
17817
|
+
);
|
|
17818
|
+
program2.command("affected [files...]").description("test files that transitively import the given changed files (for narrowing a CI run)").option("--stdin", "read the changed-file list from stdin, one path per line").option("--depth <n>", `max reverse-import hops (default ${DEFAULT_AFFECTED_DEPTH})`).option("--filter <regex>", "regex deciding which reached files count as tests (default: the built-in test-file heuristic)").option("-q, --quiet", "print bare paths only, for piping into a test runner").option("-j, --json", "output as JSON").action(
|
|
17819
|
+
(files, opts) => runExit(
|
|
17820
|
+
() => runAffected({
|
|
17821
|
+
files: opts.stdin === true ? [...files, ...readStdinPaths()] : files,
|
|
17822
|
+
...opts.depth !== void 0 ? { depth: requireNonNegativeInt("--depth", opts.depth) } : {},
|
|
17823
|
+
...opts.filter !== void 0 ? { filter: opts.filter } : {},
|
|
17824
|
+
...opts.quiet === true ? { quiet: true } : {},
|
|
17270
17825
|
...opts.json === true ? { json: true } : {}
|
|
17271
17826
|
})
|
|
17272
17827
|
)
|
|
@@ -17491,8 +18046,24 @@ function buildProgram() {
|
|
|
17491
18046
|
out(VERSION);
|
|
17492
18047
|
})
|
|
17493
18048
|
);
|
|
17494
|
-
|
|
17495
|
-
|
|
18049
|
+
program2.command("help [command]").description("show help for a command").option("--full", "show full original help instead of compact summary").action(
|
|
18050
|
+
guard((cmd, opts) => {
|
|
18051
|
+
const options = opts;
|
|
18052
|
+
if (options?.["full"] && !cmd) {
|
|
18053
|
+
out(originalHelpInformation ? originalHelpInformation() : generateCompactHelp());
|
|
18054
|
+
return;
|
|
18055
|
+
}
|
|
18056
|
+
if (cmd) {
|
|
18057
|
+
const argv = [process.argv[0] ?? "node", process.argv[1] ?? "token-goat", cmd, "--help"];
|
|
18058
|
+
program2.parse(argv);
|
|
18059
|
+
} else {
|
|
18060
|
+
out(generateCompactHelp());
|
|
18061
|
+
}
|
|
18062
|
+
})
|
|
18063
|
+
);
|
|
18064
|
+
program2.helpOption("-h, --help", "display help for command");
|
|
18065
|
+
originalHelpInformation = program2.helpInformation.bind(program2);
|
|
18066
|
+
program2.helpInformation = () => generateCompactHelp();
|
|
17496
18067
|
return program2;
|
|
17497
18068
|
}
|
|
17498
18069
|
function applyExitOverride(command) {
|