token-goat 2.6.30 → 2.6.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +54 -15
  2. package/SECURITY.md +72 -0
  3. package/dist/token-goat-chunk-4WGVGN6W.mjs +54 -0
  4. package/dist/token-goat-chunk-56C3XUQY.mjs +156 -0
  5. package/dist/token-goat-chunk-DCGBS2YZ.mjs +112 -0
  6. package/dist/token-goat-chunk-HPY7XE77.mjs +18311 -0
  7. package/dist/token-goat-chunk-J6JTYE2B.mjs +24 -0
  8. package/dist/token-goat-chunk-N2S7UAVU.mjs +13674 -0
  9. package/dist/token-goat-chunk-OIBTYBVR.mjs +901 -0
  10. package/dist/token-goat-chunk-P7I6LS5H.mjs +9643 -0
  11. package/dist/token-goat-chunk-PWD5XZ6C.mjs +9441 -0
  12. package/dist/token-goat-chunk-Q4N6SQWR.mjs +14860 -0
  13. package/dist/token-goat-chunk-QL2743EW.mjs +707 -0
  14. package/dist/token-goat-chunk-VYC34TBW.mjs +33 -0
  15. package/dist/token-goat-chunk-X7PIWZMB.mjs +5337 -0
  16. package/dist/token-goat-chunk-XEJBPPD7.mjs +15545 -0
  17. package/dist/token-goat-hook-chunk-2K7FV2ME.mjs +9438 -0
  18. package/dist/token-goat-hook-chunk-3OJELLK5.mjs +156 -0
  19. package/dist/{token-goat-hook-chunk-ZRJUZZQE.mjs → token-goat-hook-chunk-5AQYD2SZ.mjs} +1 -2
  20. package/dist/token-goat-hook-chunk-66W63A4Y.mjs +9643 -0
  21. package/dist/token-goat-hook-chunk-JT5RMRIY.mjs +901 -0
  22. package/dist/token-goat-hook-chunk-JVJUBWKT.mjs +13674 -0
  23. package/dist/{token-goat-hook-chunk-FMQ37OQD.mjs → token-goat-hook-chunk-K6HLYRRI.mjs} +25 -23
  24. package/dist/token-goat-hook-chunk-NIVRFAJ4.mjs +5323 -0
  25. package/dist/token-goat-hook-chunk-SJM4BUZQ.mjs +18309 -0
  26. package/dist/token-goat-hook-chunk-ULGSXGZK.mjs +23 -0
  27. package/dist/{token-goat-hook-chunk-XNZLMFQ2.mjs → token-goat-hook-chunk-YHRJMMEF.mjs} +2629 -801
  28. package/dist/token-goat-hook.mjs +5 -2
  29. package/dist/token-goat.core.mjs +14 -89574
  30. package/package.json +5 -4
  31. package/dist/token-goat-hook-chunk-4SM2PAR6.mjs +0 -33590
  32. package/dist/token-goat-hook-chunk-5B3OWRNC.mjs +0 -145
  33. package/dist/token-goat-hook-chunk-VKAQNN2R.mjs +0 -24416
@@ -1,17 +1,96 @@
1
1
  import { createRequire as __cjsRequire } from 'node:module';
2
2
  const require = __cjsRequire(import.meta.url);
3
+ import {
4
+ ALL_SYMBOLS_IN_FILE_LIMIT,
5
+ CONTENT_MAX_INPUT_CHARS,
6
+ WHOLE_FILE_NOTE_SYMBOL,
7
+ compressText,
8
+ computeFileFingerprint,
9
+ createHandoff,
10
+ didYouMean,
11
+ enclosingSymbol,
12
+ extraFileArgsNote,
13
+ extractSection,
14
+ extractTranscriptText,
15
+ filterSimilarHeadings,
16
+ findSpecSeparator,
17
+ getTrackedFiles,
18
+ healStaleIndex,
19
+ isAvailable,
20
+ listSections,
21
+ load,
22
+ loadError,
23
+ rankSimilarNames,
24
+ readSection,
25
+ resolveBody,
26
+ resolveHandoff,
27
+ resolveSymbolMatch,
28
+ retrieveText,
29
+ runArch,
30
+ runAsk,
31
+ runBlame,
32
+ runBrief,
33
+ runCallChain,
34
+ runCallers,
35
+ runChanged,
36
+ runConfigGet,
37
+ runConflicts,
38
+ runContextFor,
39
+ runCoverageGaps,
40
+ runCoverageReportGaps,
41
+ runCsvProfile,
42
+ runCsvQuery,
43
+ runDead,
44
+ runDeps,
45
+ runDiff,
46
+ runExports,
47
+ runFind,
48
+ runGrep,
49
+ runImageMeta,
50
+ runImageText,
51
+ runImpact,
52
+ runImports,
53
+ runJsonOutline,
54
+ runJsonQuery,
55
+ runListSections,
56
+ runLog,
57
+ runNoteGet,
58
+ runNoteList,
59
+ runOpenApiOp,
60
+ runOpenApiOutline,
61
+ runOutline,
62
+ runPdfExtractText,
63
+ runPdfMeta,
64
+ runPdfOutline,
65
+ runPrSlice,
66
+ runRead,
67
+ runRefs,
68
+ runScope,
69
+ runScreenshot,
70
+ runSection,
71
+ runSemantic,
72
+ runSimilar,
73
+ runSkeleton,
74
+ runSqliteQuery,
75
+ runSqliteSchema,
76
+ runSymbol,
77
+ runTestFor,
78
+ runTypes,
79
+ runYamlOutline,
80
+ runYamlQuery,
81
+ runZipList,
82
+ runZipRead,
83
+ symbolNamesInFile,
84
+ upsertNote
85
+ } from "./token-goat-hook-chunk-66W63A4Y.mjs";
3
86
  import {
4
87
  BASH_OUTPUT_SUBDIR,
5
88
  GEMINI_TOOL_NAME_MAP,
6
89
  HOOK_EVENTS,
7
- ToolFilter,
8
90
  WEB_OUTPUT_SUBDIR,
9
91
  buildCommandManifest,
10
- capTokens,
11
- combineStreams,
92
+ checkSymbolBodySize,
12
93
  commandHash,
13
- compressOutput,
14
- filterByName,
15
94
  filterCommandManifest,
16
95
  formatCommandManifest,
17
96
  getBashOutput,
@@ -22,43 +101,23 @@ import {
22
101
  listRecentRecall,
23
102
  normalizeCommandForCacheKey,
24
103
  readStdinJson,
25
- relay,
26
- resolveMinNetSavingsBytes,
27
- runDoctor,
28
- runDoctorAndExit,
29
104
  searchRecall,
30
- selectFilter,
31
- shlexSplit,
32
- storeWebOutput,
33
- wrappedShell
34
- } from "./token-goat-hook-chunk-VKAQNN2R.mjs";
35
- import {
36
- CONTENT_MAX_INPUT_CHARS,
37
- compressText,
38
- createHandoff,
39
- resolveHandoff,
40
- retrieveText
41
- } from "./token-goat-hook-chunk-5B3OWRNC.mjs";
105
+ storeWebOutput
106
+ } from "./token-goat-hook-chunk-JT5RMRIY.mjs";
42
107
  import {
43
108
  AGENT_SALT_MARKER,
44
- C,
45
- CONFIG_KEY_ENV_OVERRIDES,
46
109
  CONTEXT_AUTOCOMPACT_TOKENS,
47
110
  DEFAULT_MAX_AGE_MS,
48
111
  DEFAULT_MAX_COUNT,
49
- ENV_KEYS,
50
- LOCK_WAIT_MS_HARDENED,
51
112
  MAX_FILES_SCANNED,
52
- RESET,
53
113
  SESSIONS_SUBDIR,
54
114
  SKILLS_OUTPUT_SUBDIR,
55
- VERSION,
56
- WHOLE_FILE_NOTE_SYMBOL,
115
+ SKIP_DIRS,
116
+ UNTRUSTED_TOOL_TAG,
117
+ UNTRUSTED_WEB_TAG,
118
+ WORKER_HEARTBEAT_STALE_MS,
57
119
  WorkerAlreadyRunningError,
58
- _useRichStats,
59
120
  anchoredMarkerPattern,
60
- atomicWriteBytes,
61
- atomicWriteText,
62
121
  auditClaudeMd,
63
122
  buildExtractiveCompact,
64
123
  buildLineDiff,
@@ -66,77 +125,49 @@ import {
66
125
  buildProjectMap,
67
126
  buildStats,
68
127
  capJsonRows,
69
- cmdHot,
70
- cmdIgnores,
71
- cmdLockdeps,
72
- cmdLogfold,
73
- cmdNote,
74
- cmdRecent,
75
- cmdTodo,
76
- cmdTrace,
77
- colorStdout,
78
128
  compactDoc,
79
129
  compactPathFor,
80
- computeFileFingerprint,
81
- configPath,
82
130
  confirmAndApply,
83
131
  contentHash,
84
- countNoun,
85
- dataDir,
86
- defaultConfig,
132
+ copilotCliConfigPath,
133
+ copilotCliScriptPath,
87
134
  detectLanguage,
88
- didYouMean,
135
+ detectWalkMode,
136
+ dirtyQueuePathFor,
89
137
  docxOutline,
90
138
  docxText,
139
+ drainHeartbeatPathFor,
91
140
  embeddingsDepsAvailable,
141
+ emptyIndexMessage,
92
142
  enqueueDirtyPathSafe,
93
- ensureDirSync,
94
- ensureNewline,
95
143
  estimateTokens,
96
- extraFileArgsNote,
97
144
  extractChecklistSection,
98
145
  extractCompactFromMarker,
99
- extractErrorMessage,
100
146
  extractNamedSection,
101
- extractSection,
102
- extractTranscriptText,
103
- fg,
104
- filterSimilarHeadings,
147
+ fenceUntrustedContent,
105
148
  findClaudeMdFiles,
106
149
  findContentDuplicates,
107
150
  findLatestSessionId,
108
151
  findMemSuggestionCandidates,
109
152
  findMemoryMd,
110
- findProject,
111
- findSpecSeparator,
112
153
  findStrayClaudeMdBlocks,
113
154
  findSystemTempFiles,
114
- fingerprintContent,
115
- fingerprintFile,
116
- foldPath,
117
155
  formatAge,
118
156
  formatCsvTable,
119
- formatLocalTimestamp,
120
157
  formatMemSuggestions,
121
158
  formatProjectMap,
122
159
  formatXlsxRange,
123
160
  getContextPressure,
124
- getDb,
125
161
  getDirtyPathsFor,
126
162
  getFileEntry,
127
163
  getHintStatsSummary,
128
164
  getHintStatsTotals,
129
- getLastConfigParseError,
130
- getLastProjectConfigParseError,
131
- getProjectConfigInfo,
165
+ getProjectIndexCounts,
132
166
  getSessionFiles,
133
167
  getSessionId,
134
168
  getSkillFilePath,
135
- getTrackedFiles,
136
- globalDbPath,
137
169
  groupHasTokenGoat,
138
170
  headSheet,
139
- healStaleIndex,
140
171
  incrementSkillHit,
141
172
  indexFileEmbeddings,
142
173
  indexFileSync,
@@ -145,35 +176,28 @@ import {
145
176
  installCopilotCli,
146
177
  installGrok,
147
178
  installHooks,
148
- installSingleFilePlugin,
149
179
  installSkill,
150
180
  installVscode,
151
- invalidateConfigCache,
181
+ isCodexInstalled,
152
182
  isCompactFresh,
183
+ isCopilotCliInstalled,
153
184
  isEmbedFresh,
154
185
  isEmbeddableDocument,
186
+ isGrokInstalled,
155
187
  isHintCategory,
156
188
  isInstalled,
157
189
  isParseSkipEligible,
158
- isUnderBlockedRoot,
159
- isWindows,
160
190
  isWorkerRunning,
161
191
  listBlobs,
162
192
  listOutputs,
163
- listSections,
164
193
  listSheets,
165
194
  listSkills,
166
195
  loadBlob,
167
- loadConfig,
168
- loadPersistedConfig,
169
196
  loadSessionCache,
170
197
  mapLookupBytesSaved,
171
198
  markCategoryEffective,
172
199
  markCategoryIneffective,
173
- normalizePath,
174
200
  otherScopeHasManagedServer,
175
- pad,
176
- parse,
177
201
  parseWhereSpecs,
178
202
  pptxNotesText,
179
203
  pptxOutline,
@@ -184,161 +208,153 @@ import {
184
208
  pruneSkillOutputs,
185
209
  pruneSystemTempFiles,
186
210
  querySheet,
211
+ querySymbols,
187
212
  rangeSheet,
188
- rankSimilarNames,
189
213
  readCompactBody,
190
- readSection,
191
- recordStat,
192
- redactUrlQuery,
193
214
  removeFileFromIndex,
194
- renderShortStats,
195
- renderStats,
196
- requireNonNegativeStrictInt,
197
- requirePositiveStrictInt,
198
215
  resetHintStats,
199
- resolveConfigKeyLayer,
200
- resolveIndexPath,
201
- resolveProjectRoot,
202
- resolveSymbolMatch,
203
- runArch,
204
- runAsk,
205
- runBlame,
206
- runBrief,
207
- runCallChain,
208
- runCallers,
209
- runChanged,
210
- runConfigGet,
211
- runConflicts,
212
- runContextFor,
213
216
  runContextStats,
214
- runCoverageGaps,
215
- runCoverageReportGaps,
216
- runCsvProfile,
217
- runCsvQuery,
218
- runDead,
219
- runDeps,
220
217
  runDetachedWorkerDaemon,
221
- runDiff,
222
- runExports,
223
- runFind,
224
- runGit,
225
- runGrep,
226
- runImageMeta,
227
- runImageText,
228
- runImpact,
229
- runImports,
230
- runJsonOutline,
231
- runJsonQuery,
232
- runListSections,
233
- runLog,
234
- runNoteGet,
235
- runNoteList,
236
- runOpenApiOp,
237
- runOpenApiOutline,
238
- runOutline,
239
- runPdfExtractText,
240
- runPdfMeta,
241
- runPdfOutline,
242
- runPrSlice,
243
- runRead,
244
- runRefs,
245
- runScope,
246
- runScreenshot,
247
- runSection,
248
- runSemantic,
249
- runSimilar,
250
- runSkeleton,
251
- runSqliteQuery,
252
- runSqliteSchema,
253
- runSymbol,
254
- runTestFor,
255
- runTypes,
256
- runYamlOutline,
257
- runYamlQuery,
258
- runZipList,
259
- runZipRead,
260
- safeSlice,
261
- saveConfig,
218
+ scanForInjectionPatterns,
262
219
  shrinkImage,
263
220
  skillOutputsDir,
264
- sleepSync,
265
221
  startDetachedWorker,
266
222
  stopWorker,
267
223
  storeCompact,
268
224
  storeOutput,
269
- stripAnsi,
270
- stripOwnHooksFromMap,
271
- stripStaleGroupHooks,
272
- suggestPackageNames,
273
- summarize,
274
- symbolNamesInFile,
225
+ tokenGoatHome,
275
226
  trimToBudget,
276
227
  uninstallClaudeMd,
277
228
  uninstallCodex,
278
229
  uninstallCopilotCli,
279
230
  uninstallGrok,
280
231
  uninstallHooks,
281
- uninstallSingleFilePlugin,
282
232
  uninstallSkill,
283
233
  uninstallVscode,
284
- upsertNote,
285
- validateEnumField,
286
- validateNumericField,
234
+ urlPolicyDenialReason,
287
235
  vscodeDecoderConfigured,
288
236
  walkProject,
289
- withExtension,
290
- withFileLock,
291
- withRetryOnLock,
292
- writeCompact,
293
- writeJsonSettings
294
- } from "./token-goat-hook-chunk-4SM2PAR6.mjs";
295
- import "./token-goat-hook-chunk-KNVHTGEX.mjs";
237
+ writeCompact
238
+ } from "./token-goat-hook-chunk-SJM4BUZQ.mjs";
296
239
  import {
297
- __commonJS,
298
- __export,
299
- __require,
300
- __toESM,
301
- init_define_import_meta_env
302
- } from "./token-goat-hook-chunk-7WC2H3EW.mjs";
303
-
304
- // node_modules/commander/lib/error.js
305
- var require_error = __commonJS({
306
- "node_modules/commander/lib/error.js"(exports) {
307
- init_define_import_meta_env();
308
- var CommanderError2 = class extends Error {
309
- /**
310
- * Constructs the CommanderError class
311
- * @param {number} exitCode suggested exit code which could be used with process.exit
312
- * @param {string} code an id string representing the error
313
- * @param {string} message human-readable description of the error
314
- */
315
- constructor(exitCode, code, message) {
316
- super(message);
317
- Error.captureStackTrace(this, this.constructor);
318
- this.name = this.constructor.name;
319
- this.code = code;
320
- this.exitCode = exitCode;
321
- this.nestedError = void 0;
322
- }
323
- };
324
- var InvalidArgumentError2 = class extends CommanderError2 {
325
- /**
326
- * Constructs the InvalidArgumentError class
327
- * @param {string} [message] explanation of why argument is invalid
328
- */
329
- constructor(message) {
330
- super(1, "commander.invalidArgument", message);
331
- Error.captureStackTrace(this, this.constructor);
332
- this.name = this.constructor.name;
333
- }
334
- };
335
- exports.CommanderError = CommanderError2;
336
- exports.InvalidArgumentError = InvalidArgumentError2;
337
- }
338
- });
339
-
340
- // node_modules/commander/lib/argument.js
341
- var require_argument = __commonJS({
240
+ C,
241
+ CONFIG_KEY_ENV_OVERRIDES,
242
+ ENV_KEYS,
243
+ FILTERS,
244
+ LOCK_WAIT_MS_HARDENED,
245
+ RESET,
246
+ VERSION,
247
+ _useRichStats,
248
+ atomicWriteBytes,
249
+ atomicWriteText,
250
+ canonicalize,
251
+ colorStdout,
252
+ configPath,
253
+ countNoun,
254
+ dataDir,
255
+ defaultConfig,
256
+ ensureDirSync,
257
+ ensureNewline,
258
+ escapeRegExp,
259
+ extractErrorMessage,
260
+ fg,
261
+ findProject,
262
+ fingerprintContent,
263
+ fingerprintFile,
264
+ foldPath,
265
+ formatLocalTimestamp,
266
+ getDb,
267
+ getDisplayRoot,
268
+ getLastConfigParseError,
269
+ getLastProjectConfigParseError,
270
+ getProjectConfigInfo,
271
+ globalDbPath,
272
+ installSingleFilePlugin,
273
+ invalidateConfigCache,
274
+ isUnderBlockedRoot,
275
+ isWindows,
276
+ lastProjectConfigLockedKeys,
277
+ loadConfig,
278
+ loadPersistedConfig,
279
+ normalizeDarwinSystemAlias,
280
+ normalizePath,
281
+ pad,
282
+ parse,
283
+ recordStat,
284
+ redactUrlQuery,
285
+ renderShortStats,
286
+ renderStats,
287
+ requireNonNegativeStrictInt,
288
+ requirePositiveStrictInt,
289
+ resolveConfigKeyLayer,
290
+ resolveIndexPath,
291
+ resolveProjectRoot,
292
+ runGit,
293
+ safeSlice,
294
+ saveConfig,
295
+ sleepSync,
296
+ stripAnsi,
297
+ stripOwnHooksFromMap,
298
+ stripStaleGroupHooks,
299
+ suggestPackageNames,
300
+ summarize,
301
+ toDisplayPath,
302
+ toKB,
303
+ uninstallSingleFilePlugin,
304
+ validateEnumField,
305
+ validateNumericField,
306
+ withExtension,
307
+ withFileLock,
308
+ withRetryOnLock,
309
+ writeJsonSettings
310
+ } from "./token-goat-hook-chunk-NIVRFAJ4.mjs";
311
+ import "./token-goat-hook-chunk-KNVHTGEX.mjs";
312
+ import {
313
+ __commonJS,
314
+ __export,
315
+ __require,
316
+ __toESM,
317
+ init_define_import_meta_env
318
+ } from "./token-goat-hook-chunk-7WC2H3EW.mjs";
319
+
320
+ // node_modules/commander/lib/error.js
321
+ var require_error = __commonJS({
322
+ "node_modules/commander/lib/error.js"(exports) {
323
+ init_define_import_meta_env();
324
+ var CommanderError2 = class extends Error {
325
+ /**
326
+ * Constructs the CommanderError class
327
+ * @param {number} exitCode suggested exit code which could be used with process.exit
328
+ * @param {string} code an id string representing the error
329
+ * @param {string} message human-readable description of the error
330
+ */
331
+ constructor(exitCode, code, message) {
332
+ super(message);
333
+ Error.captureStackTrace(this, this.constructor);
334
+ this.name = this.constructor.name;
335
+ this.code = code;
336
+ this.exitCode = exitCode;
337
+ this.nestedError = void 0;
338
+ }
339
+ };
340
+ var InvalidArgumentError2 = class extends CommanderError2 {
341
+ /**
342
+ * Constructs the InvalidArgumentError class
343
+ * @param {string} [message] explanation of why argument is invalid
344
+ */
345
+ constructor(message) {
346
+ super(1, "commander.invalidArgument", message);
347
+ Error.captureStackTrace(this, this.constructor);
348
+ this.name = this.constructor.name;
349
+ }
350
+ };
351
+ exports.CommanderError = CommanderError2;
352
+ exports.InvalidArgumentError = InvalidArgumentError2;
353
+ }
354
+ });
355
+
356
+ // node_modules/commander/lib/argument.js
357
+ var require_argument = __commonJS({
342
358
  "node_modules/commander/lib/argument.js"(exports) {
343
359
  init_define_import_meta_env();
344
360
  var { InvalidArgumentError: InvalidArgumentError2 } = require_error();
@@ -1240,8 +1256,8 @@ var require_command = __commonJS({
1240
1256
  init_define_import_meta_env();
1241
1257
  var EventEmitter = __require("node:events").EventEmitter;
1242
1258
  var childProcess = __require("node:child_process");
1243
- var path22 = __require("node:path");
1244
- var fs20 = __require("node:fs");
1259
+ var path26 = __require("node:path");
1260
+ var fs26 = __require("node:fs");
1245
1261
  var process2 = __require("node:process");
1246
1262
  var { Argument: Argument2, humanReadableArgName } = require_argument();
1247
1263
  var { CommanderError: CommanderError2 } = require_error();
@@ -2173,11 +2189,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
2173
2189
  let launchWithNode = false;
2174
2190
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
2175
2191
  function findFile(baseDir, baseName) {
2176
- const localBin = path22.resolve(baseDir, baseName);
2177
- if (fs20.existsSync(localBin)) return localBin;
2178
- if (sourceExt.includes(path22.extname(baseName))) return void 0;
2192
+ const localBin = path26.resolve(baseDir, baseName);
2193
+ if (fs26.existsSync(localBin)) return localBin;
2194
+ if (sourceExt.includes(path26.extname(baseName))) return void 0;
2179
2195
  const foundExt = sourceExt.find(
2180
- (ext2) => fs20.existsSync(`${localBin}${ext2}`)
2196
+ (ext2) => fs26.existsSync(`${localBin}${ext2}`)
2181
2197
  );
2182
2198
  if (foundExt) return `${localBin}${foundExt}`;
2183
2199
  return void 0;
@@ -2189,21 +2205,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
2189
2205
  if (this._scriptPath) {
2190
2206
  let resolvedScriptPath;
2191
2207
  try {
2192
- resolvedScriptPath = fs20.realpathSync(this._scriptPath);
2208
+ resolvedScriptPath = fs26.realpathSync(this._scriptPath);
2193
2209
  } catch (err2) {
2194
2210
  resolvedScriptPath = this._scriptPath;
2195
2211
  }
2196
- executableDir = path22.resolve(
2197
- path22.dirname(resolvedScriptPath),
2212
+ executableDir = path26.resolve(
2213
+ path26.dirname(resolvedScriptPath),
2198
2214
  executableDir
2199
2215
  );
2200
2216
  }
2201
2217
  if (executableDir) {
2202
2218
  let localFile = findFile(executableDir, executableFile);
2203
2219
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
2204
- const legacyName = path22.basename(
2220
+ const legacyName = path26.basename(
2205
2221
  this._scriptPath,
2206
- path22.extname(this._scriptPath)
2222
+ path26.extname(this._scriptPath)
2207
2223
  );
2208
2224
  if (legacyName !== this._name) {
2209
2225
  localFile = findFile(
@@ -2214,7 +2230,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2214
2230
  }
2215
2231
  executableFile = localFile || executableFile;
2216
2232
  }
2217
- launchWithNode = sourceExt.includes(path22.extname(executableFile));
2233
+ launchWithNode = sourceExt.includes(path26.extname(executableFile));
2218
2234
  let proc;
2219
2235
  if (process2.platform !== "win32") {
2220
2236
  if (launchWithNode) {
@@ -3054,7 +3070,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3054
3070
  * @return {Command}
3055
3071
  */
3056
3072
  nameFromFilename(filename) {
3057
- this._name = path22.basename(filename, path22.extname(filename));
3073
+ this._name = path26.basename(filename, path26.extname(filename));
3058
3074
  return this;
3059
3075
  }
3060
3076
  /**
@@ -3068,9 +3084,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3068
3084
  * @param {string} [path]
3069
3085
  * @return {(string|null|Command)}
3070
3086
  */
3071
- executableDir(path23) {
3072
- if (path23 === void 0) return this._executableDir;
3073
- this._executableDir = path23;
3087
+ executableDir(path27) {
3088
+ if (path27 === void 0) return this._executableDir;
3089
+ this._executableDir = path27;
3074
3090
  return this;
3075
3091
  }
3076
3092
  /**
@@ -3367,9 +3383,9 @@ function attemptedCommandName(argv) {
3367
3383
  }
3368
3384
 
3369
3385
  // src/cli.ts
3370
- import * as fs19 from "fs";
3371
- import * as path21 from "path";
3372
- import { homedir as homedir10 } from "os";
3386
+ import * as fs25 from "fs";
3387
+ import * as path25 from "path";
3388
+ import { homedir as homedir11 } from "os";
3373
3389
 
3374
3390
  // src/walk_index.ts
3375
3391
  init_define_import_meta_env();
@@ -3551,6 +3567,18 @@ function uninstallGemini() {
3551
3567
  writeJsonSettings(p, settings);
3552
3568
  return true;
3553
3569
  }
3570
+ function isGeminiInstalled() {
3571
+ const settings = readGeminiSettings(geminiSettingsPath());
3572
+ const hooks = settings.hooks;
3573
+ if (hooks === void 0) return false;
3574
+ for (const event of GEMINI_HOOK_EVENTS) {
3575
+ const command = geminiHookCommand(GEMINI_EVENT_ARG[event]);
3576
+ for (const matcher of desiredMatchersFor(event)) {
3577
+ if (!groupHasTokenGoat(hooks[event], matcher, (c) => isCurrentGeminiTokenGoatCommand(c, command))) return false;
3578
+ }
3579
+ }
3580
+ return true;
3581
+ }
3554
3582
 
3555
3583
  // src/bridges/qwen_install.ts
3556
3584
  init_define_import_meta_env();
@@ -3666,9 +3694,20 @@ function uninstallQwen() {
3666
3694
  writeJsonSettings(p, settings);
3667
3695
  return true;
3668
3696
  }
3697
+ function isQwenInstalled() {
3698
+ const settings = readQwenSettings(qwenSettingsPath());
3699
+ const hooks = settings.hooks;
3700
+ if (hooks === void 0) return false;
3701
+ for (const event of QWEN_HOOK_EVENTS) {
3702
+ const command = qwenHookCommand(QWEN_EVENT_ARG[event]);
3703
+ if (!groupHasTokenGoat2(hooks[event], (c) => isCurrentQwenTokenGoatCommand(c, command))) return false;
3704
+ }
3705
+ return true;
3706
+ }
3669
3707
 
3670
3708
  // src/bridges/pi_install.ts
3671
3709
  init_define_import_meta_env();
3710
+ import * as fs4 from "node:fs";
3672
3711
  import * as os4 from "node:os";
3673
3712
  import * as path4 from "node:path";
3674
3713
 
@@ -4011,9 +4050,13 @@ function uninstallPi(opts = {}) {
4011
4050
  const localRemoved = uninstallPiScope({ local: true });
4012
4051
  return globalRemoved || localRemoved;
4013
4052
  }
4053
+ function isPiInstalled(opts = {}) {
4054
+ return fs4.existsSync(piExtensionPath(opts));
4055
+ }
4014
4056
 
4015
4057
  // src/bridges/opencode_install.ts
4016
4058
  init_define_import_meta_env();
4059
+ import * as fs5 from "node:fs";
4017
4060
  import * as os5 from "node:os";
4018
4061
  import * as path5 from "node:path";
4019
4062
 
@@ -4315,10 +4358,13 @@ function installOpencode() {
4315
4358
  function uninstallOpencode() {
4316
4359
  return uninstallSingleFilePlugin(opencodePluginPath(), opencodeEntrySidecarPath());
4317
4360
  }
4361
+ function isOpencodeInstalled() {
4362
+ return fs5.existsSync(opencodePluginPath());
4363
+ }
4318
4364
 
4319
4365
  // src/bridges/openclaw_install.ts
4320
4366
  init_define_import_meta_env();
4321
- import * as fs4 from "node:fs";
4367
+ import * as fs6 from "node:fs";
4322
4368
  import * as os6 from "node:os";
4323
4369
  import * as path6 from "node:path";
4324
4370
 
@@ -4470,7 +4516,7 @@ function openclawEntrySidecarPath() {
4470
4516
  function readOpenclawConfig(p, opts = {}) {
4471
4517
  let raw;
4472
4518
  try {
4473
- raw = fs4.readFileSync(p, "utf8");
4519
+ raw = fs6.readFileSync(p, "utf8");
4474
4520
  } catch {
4475
4521
  return {};
4476
4522
  }
@@ -4510,7 +4556,7 @@ function installOpenclaw() {
4510
4556
  const pluginPath = openclawPluginPath();
4511
4557
  let existingPlugin;
4512
4558
  try {
4513
- existingPlugin = fs4.readFileSync(pluginPath, "utf8");
4559
+ existingPlugin = fs6.readFileSync(pluginPath, "utf8");
4514
4560
  } catch {
4515
4561
  existingPlugin = void 0;
4516
4562
  }
@@ -4551,12 +4597,12 @@ function uninstallOpenclaw() {
4551
4597
  const pluginPath = openclawPluginPath();
4552
4598
  let removed = false;
4553
4599
  try {
4554
- fs4.unlinkSync(pluginPath);
4600
+ fs6.unlinkSync(pluginPath);
4555
4601
  removed = true;
4556
4602
  } catch {
4557
4603
  }
4558
4604
  try {
4559
- fs4.unlinkSync(openclawEntrySidecarPath());
4605
+ fs6.unlinkSync(openclawEntrySidecarPath());
4560
4606
  } catch {
4561
4607
  }
4562
4608
  const settings = readOpenclawConfig(configPath2);
@@ -4593,134 +4639,15 @@ function uninstallOpenclaw() {
4593
4639
  }
4594
4640
  return removed;
4595
4641
  }
4596
-
4597
- // src/bash_runner.ts
4598
- init_define_import_meta_env();
4599
- import { spawnSync } from "node:child_process";
4600
- import * as os7 from "node:os";
4601
- var IdentityFilter = class extends ToolFilter {
4602
- name = "passthrough";
4603
- };
4604
- var DEFAULT_TIMEOUT_SECONDS = 600;
4605
- var MAX_CAPTURE_BYTES = 32 * 1024 * 1024;
4606
- var MIN_RECORD_STAT_BYTES = 32;
4607
- function resolveFilter(command, filterName, cwd) {
4608
- if (filterName) {
4609
- const named = filterByName(filterName);
4610
- if (named !== null) return named;
4611
- }
4612
- let argv;
4613
- try {
4614
- argv = shlexSplit(command);
4615
- } catch {
4616
- return null;
4617
- }
4618
- return selectFilter(argv, cwd);
4619
- }
4620
- function baseSpawnOptions(timeout, cwd, env) {
4621
- return { shell: wrappedShell(), timeout: timeout * 1e3, cwd, env };
4622
- }
4623
- function signalExitCode(signal) {
4624
- const num = os7.constants.signals[signal];
4625
- return 128 + (num ?? 0);
4626
- }
4627
- function resolveProfile(explicit) {
4628
- if (explicit) return explicit;
4629
- try {
4630
- const p = loadConfig().compression.profile;
4631
- return p && p !== "auto" ? p : "balanced";
4632
- } catch {
4633
- return "balanced";
4634
- }
4635
- }
4636
- function resolveCompressLimits() {
4637
- try {
4638
- const bc = loadConfig().bash_compress;
4639
- return { maxLines: bc.max_lines, maxBytes: bc.max_bytes };
4640
- } catch {
4641
- return { maxLines: 1e3, maxBytes: 64 * 1024 };
4642
- }
4643
- }
4644
- function run(command, opts = {}) {
4645
- const timeout = opts.timeout ?? DEFAULT_TIMEOUT_SECONDS;
4646
- const filter2 = resolveFilter(command, opts.filterName, opts.cwd);
4647
- if (filter2 === null) {
4648
- if ((opts.maxTokens ?? 0) > 0) {
4649
- return wrapAndCompress(command, new IdentityFilter(), timeout, resolveProfile(opts.compressionProfile), opts);
4650
- }
4651
- return passthrough(command, timeout, opts.cwd, opts.env);
4652
- }
4653
- return wrapAndCompress(command, filter2, timeout, resolveProfile(opts.compressionProfile), opts);
4654
- }
4655
- function runRaw(command, timeout = DEFAULT_TIMEOUT_SECONDS) {
4656
- return passthrough(command, timeout, void 0, void 0);
4657
- }
4658
- function passthrough(command, timeout, cwd, env) {
4659
- const result = spawnSync(command, { ...baseSpawnOptions(timeout, cwd, env), stdio: "inherit" });
4660
- if (isTimeout(result.error)) return 124;
4661
- if (result.status !== null) return result.status;
4662
- if (result.signal) return signalExitCode(result.signal);
4663
- return 0;
4664
- }
4665
- function isTimeout(error) {
4666
- return !!error && error.code === "ETIMEDOUT";
4667
- }
4668
- function decode(buf) {
4669
- if (buf == null) return "";
4670
- return typeof buf === "string" ? buf : buf.toString("utf8");
4671
- }
4672
- function wrapAndCompress(command, filter2, timeout, profile, opts) {
4673
- const writeStdout = opts.writeStdout ?? ((s) => process.stdout.write(s));
4674
- const result = spawnSync(command, {
4675
- ...baseSpawnOptions(timeout, opts.cwd, opts.env),
4676
- stdio: ["ignore", "pipe", "pipe"],
4677
- maxBuffer: MAX_CAPTURE_BYTES
4678
- });
4679
- const timedOut = isTimeout(result.error);
4680
- const overflowed = !!result.error && result.error.code === "ENOBUFS";
4681
- let stdoutText = decode(result.stdout);
4682
- let stderrText = decode(result.stderr);
4683
- if (overflowed) {
4684
- stdoutText += `
4685
- [token-goat: capture capped at ${MAX_CAPTURE_BYTES / (1024 * 1024)} MiB]`;
4686
- }
4687
- let exitCode;
4688
- if (timedOut) {
4689
- exitCode = 124;
4690
- stderrText = (stderrText ? stderrText + "\n" : "") + `[token-goat: command exceeded ${timeout}s timeout and was killed]`;
4691
- } else if (result.status !== null) {
4692
- exitCode = result.status;
4693
- } else if (result.signal) {
4694
- exitCode = signalExitCode(result.signal);
4695
- } else {
4696
- exitCode = 0;
4697
- }
4698
- let argv;
4699
- try {
4700
- argv = shlexSplit(command);
4701
- } catch {
4702
- argv = [command];
4703
- }
4704
- const limits = resolveCompressLimits();
4705
- const compressed = compressOutput(filter2, stdoutText, stderrText, exitCode, argv, {
4706
- compressionProfile: profile,
4707
- maxLines: limits.maxLines,
4708
- maxBytes: limits.maxBytes
4709
- });
4710
- const minNetSavingsBytes = resolveMinNetSavingsBytes();
4711
- const applied = compressed.worthApplying(minNetSavingsBytes);
4712
- let text = applied ? compressed.text : combineStreams(stdoutText, stderrText);
4713
- const maxTokens = opts.maxTokens ?? 0;
4714
- if (maxTokens > 0) text = capTokens(text, maxTokens);
4715
- const marker = applied ? compressed.withMarker(minNetSavingsBytes).slice(compressed.text.length) : "";
4716
- const body = text + marker;
4717
- writeStdout(body.endsWith("\n") ? body : body + "\n");
4718
- if (applied) recordSavings(compressed);
4719
- return exitCode;
4720
- }
4721
- function recordSavings(result) {
4722
- if (result.bytesSaved < MIN_RECORD_STAT_BYTES) return;
4723
- recordStat(`bash_compress:${result.filterName}`, result.bytesSaved, result.tokensSaved);
4642
+ function isOpenclawInstalled() {
4643
+ if (!fs6.existsSync(openclawPluginPath())) return false;
4644
+ const settings = readOpenclawConfig(openclawConfigPath());
4645
+ const plugins = settings.plugins;
4646
+ if (plugins === void 0) return false;
4647
+ const targetFolded = foldPath(openclawPluginPath());
4648
+ const hasPath = (plugins.load?.paths ?? []).some((p) => foldPath(p) === targetFolded);
4649
+ const hasEntry = plugins.entries?.[OPENCLAW_PLUGIN_ID] !== void 0;
4650
+ return hasPath && hasEntry;
4724
4651
  }
4725
4652
 
4726
4653
  // src/bridges_status.ts
@@ -4894,8 +4821,8 @@ function formatBridgesStatus(matrix = BRIDGE_CAPABILITY_MATRIX) {
4894
4821
 
4895
4822
  // src/sharepoint_resolve.ts
4896
4823
  init_define_import_meta_env();
4897
- import * as fs5 from "node:fs";
4898
- import * as os8 from "node:os";
4824
+ import * as fs7 from "node:fs";
4825
+ import * as os7 from "node:os";
4899
4826
  import * as path7 from "node:path";
4900
4827
  var TENANT_HOST_RE = /^([a-z0-9-]+?)(-my)?\.sharepoint\.com$/i;
4901
4828
  function parseShareUrl(url) {
@@ -4944,7 +4871,7 @@ function candidateRoots(env, home) {
4944
4871
  if (commercial !== void 0) roots.push(commercial);
4945
4872
  if (personal !== void 0 && personal !== commercial) roots.push(personal);
4946
4873
  try {
4947
- for (const entry of fs5.readdirSync(home, { withFileTypes: true })) {
4874
+ for (const entry of fs7.readdirSync(home, { withFileTypes: true })) {
4948
4875
  if (entry.isDirectory() && /onedrive/i.test(entry.name)) {
4949
4876
  const full = path7.join(home, entry.name);
4950
4877
  if (!roots.includes(full)) roots.push(full);
@@ -4957,18 +4884,18 @@ function candidateRoots(env, home) {
4957
4884
  function tryLibraryPaths(root, libSegments, triedPaths) {
4958
4885
  const rawJoined = path7.join(root, ...libSegments);
4959
4886
  triedPaths.push(rawJoined);
4960
- if (fs5.existsSync(rawJoined)) return rawJoined;
4887
+ if (fs7.existsSync(rawJoined)) return rawJoined;
4961
4888
  if (libSegments.length > 0) {
4962
4889
  const aliasedFirst = normalizeLibrarySegment(libSegments[0]);
4963
4890
  if (aliasedFirst !== libSegments[0]) {
4964
4891
  const aliasedJoined = path7.join(root, aliasedFirst, ...libSegments.slice(1));
4965
4892
  triedPaths.push(aliasedJoined);
4966
- if (fs5.existsSync(aliasedJoined)) return aliasedJoined;
4893
+ if (fs7.existsSync(aliasedJoined)) return aliasedJoined;
4967
4894
  }
4968
4895
  }
4969
4896
  return null;
4970
4897
  }
4971
- function resolveLocalPath(parsed, env = process.env, home = os8.homedir()) {
4898
+ function resolveLocalPath(parsed, env = process.env, home = os7.homedir()) {
4972
4899
  const roots = candidateRoots(env, home);
4973
4900
  const libSegments = parsed.libraryPath.split(/[/\\]+/).filter((s) => s.length > 0 && s !== "." && s !== "..");
4974
4901
  const triedPaths = [];
@@ -4987,7 +4914,7 @@ function resolveLocalPath(parsed, env = process.env, home = os8.homedir()) {
4987
4914
  if (found !== null) return { resolvedPath: found, triedPaths };
4988
4915
  if (parsed.siteType === "site" && parsed.siteName.length > 0) {
4989
4916
  try {
4990
- for (const entry of fs5.readdirSync(root, { withFileTypes: true })) {
4917
+ for (const entry of fs7.readdirSync(root, { withFileTypes: true })) {
4991
4918
  if (!entry.isDirectory() || !entry.name.toLowerCase().includes(parsed.siteName.toLowerCase())) continue;
4992
4919
  const siteRoot = path7.join(root, entry.name);
4993
4920
  const siteFound = safeTryLibraryPaths(siteRoot, libSegments, triedPaths);
@@ -5002,12 +4929,12 @@ function resolveLocalPath(parsed, env = process.env, home = os8.homedir()) {
5002
4929
 
5003
4930
  // src/video_chapters.ts
5004
4931
  init_define_import_meta_env();
5005
- import { spawnSync as spawnSync2 } from "node:child_process";
4932
+ import { spawnSync } from "node:child_process";
5006
4933
  var _ffprobeAvailable;
5007
4934
  function isFfprobeAvailable() {
5008
4935
  if (_ffprobeAvailable !== void 0) return _ffprobeAvailable;
5009
4936
  try {
5010
- const res = spawnSync2("ffprobe", ["-version"], { stdio: "ignore" });
4937
+ const res = spawnSync("ffprobe", ["-version"], { stdio: "ignore" });
5011
4938
  _ffprobeAvailable = res.status === 0;
5012
4939
  } catch {
5013
4940
  _ffprobeAvailable = false;
@@ -5018,7 +4945,7 @@ function extractVideoChapters(file) {
5018
4945
  if (!isFfprobeAvailable()) {
5019
4946
  throw new Error("ffprobe not found on PATH; video-chapters requires ffmpeg (install ffmpeg and ensure ffprobe is on PATH)");
5020
4947
  }
5021
- const res = spawnSync2(
4948
+ const res = spawnSync(
5022
4949
  "ffprobe",
5023
4950
  ["-v", "error", "-print_format", "json", "-show_chapters", "-show_streams", file],
5024
4951
  { encoding: "utf8", maxBuffer: 64 * 1024 * 1024 }
@@ -5050,7 +4977,7 @@ function extractVideoChapters(file) {
5050
4977
 
5051
4978
  // src/transcript_extract.ts
5052
4979
  init_define_import_meta_env();
5053
- import * as fs6 from "node:fs";
4980
+ import * as fs8 from "node:fs";
5054
4981
  var TIMESTAMP_RE = /(\d{1,2}:)?(\d{2}):(\d{2})[.,](\d{1,3})/;
5055
4982
  var CUE_LINE_RE = new RegExp(`^\\s*${TIMESTAMP_RE.source}\\s*-->\\s*${TIMESTAMP_RE.source}`);
5056
4983
  var V_TAG_RE = /^<v(?:\.\w+)?\s+([^>]+)>\s*(.*)$/;
@@ -5102,7 +5029,7 @@ function parseTranscript(content) {
5102
5029
  return cues;
5103
5030
  }
5104
5031
  function readTranscript(filePath) {
5105
- const content = fs6.readFileSync(filePath, "utf8");
5032
+ const content = fs8.readFileSync(filePath, "utf8");
5106
5033
  return parseTranscript(content);
5107
5034
  }
5108
5035
  function formatTimestamp(seconds) {
@@ -5162,10 +5089,64 @@ function formatCues(cues) {
5162
5089
  return cues.map((cue) => `[${formatTimestamp(cue.startSeconds)}]${cue.speaker !== null ? ` ${cue.speaker}:` : ""} ${cue.text}`).join("\n");
5163
5090
  }
5164
5091
 
5165
- // src/cli_stats.ts
5092
+ // src/purge.ts
5166
5093
  init_define_import_meta_env();
5167
- import * as fs7 from "node:fs";
5094
+ import * as fs9 from "node:fs";
5168
5095
  import * as path8 from "node:path";
5096
+ function directorySize(root) {
5097
+ let total = 0;
5098
+ let entries;
5099
+ try {
5100
+ entries = fs9.readdirSync(root, { withFileTypes: true });
5101
+ } catch {
5102
+ return 0;
5103
+ }
5104
+ for (const entry of entries) {
5105
+ const full = path8.join(root, entry.name);
5106
+ if (entry.isDirectory()) {
5107
+ total += directorySize(full);
5108
+ continue;
5109
+ }
5110
+ if (!entry.isFile()) continue;
5111
+ try {
5112
+ total += fs9.statSync(full).size;
5113
+ } catch {
5114
+ }
5115
+ }
5116
+ return total;
5117
+ }
5118
+ function purgeRoots() {
5119
+ const roots = [dataDir(), tokenGoatHome()];
5120
+ return roots.filter((root, index) => roots.indexOf(root) === index);
5121
+ }
5122
+ function purgeDataDirectories() {
5123
+ const result = { removed: [], absent: [], failed: [] };
5124
+ for (const root of purgeRoots()) {
5125
+ if (!fs9.existsSync(root)) {
5126
+ result.absent.push(root);
5127
+ continue;
5128
+ }
5129
+ const bytes = directorySize(root);
5130
+ try {
5131
+ fs9.rmSync(root, { recursive: true, force: true });
5132
+ result.removed.push({ path: root, bytes });
5133
+ } catch (e) {
5134
+ result.failed.push({ path: root, reason: e instanceof Error ? e.message : String(e) });
5135
+ }
5136
+ }
5137
+ return result;
5138
+ }
5139
+ function formatBytes(bytes) {
5140
+ if (bytes < 1024) return `${bytes} B`;
5141
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
5142
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
5143
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
5144
+ }
5145
+
5146
+ // src/cli_stats.ts
5147
+ init_define_import_meta_env();
5148
+ import * as fs10 from "node:fs";
5149
+ import * as path9 from "node:path";
5169
5150
  function writeRaw(text) {
5170
5151
  const payload = colorStdout() ? text : stripAnsi(text);
5171
5152
  process.stdout.write(ensureNewline(payload));
@@ -5177,8 +5158,8 @@ function formatTopFiles(ranked) {
5177
5158
  if (ranked.length === 0) return "";
5178
5159
  const lines = ["Top files this session:"];
5179
5160
  for (const { path: filePath, count } of ranked) {
5180
- const basename6 = path8.basename(filePath);
5181
- lines.push(` ${count.toString().padStart(3)}x ${basename6} (${filePath})`);
5161
+ const basename7 = path9.basename(filePath);
5162
+ lines.push(` ${count.toString().padStart(3)}x ${basename7} (${filePath})`);
5182
5163
  }
5183
5164
  return lines.join("\n");
5184
5165
  }
@@ -5194,12 +5175,12 @@ function renderTopSessionFiles(topN = 5) {
5194
5175
  }
5195
5176
  function renderTopSessionFilesFromDisk(topN = 5, overrideSessionsDir) {
5196
5177
  try {
5197
- const sessionsDir = overrideSessionsDir ?? path8.join(dataDir(), "sessions");
5198
- if (!fs7.existsSync(sessionsDir)) return "";
5199
- const files = fs7.readdirSync(sessionsDir).filter((f) => f.endsWith(".json")).map((f) => ({ name: f, mtime: fs7.statSync(path8.join(sessionsDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).slice(0, 3);
5178
+ const sessionsDir = overrideSessionsDir ?? path9.join(dataDir(), "sessions");
5179
+ if (!fs10.existsSync(sessionsDir)) return "";
5180
+ const files = fs10.readdirSync(sessionsDir).filter((f) => f.endsWith(".json")).map((f) => ({ name: f, mtime: fs10.statSync(path9.join(sessionsDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).slice(0, 3);
5200
5181
  for (const { name } of files) {
5201
5182
  try {
5202
- const raw = fs7.readFileSync(path8.join(sessionsDir, name), "utf-8");
5183
+ const raw = fs10.readFileSync(path9.join(sessionsDir, name), "utf-8");
5203
5184
  const data = JSON.parse(raw);
5204
5185
  const filesList = data["files"];
5205
5186
  if (!Array.isArray(filesList)) continue;
@@ -5281,58 +5262,596 @@ function runStats(opts = {}) {
5281
5262
  }
5282
5263
  }
5283
5264
 
5284
- // src/gdrive.ts
5285
- init_define_import_meta_env();
5286
-
5287
- // src/webfetch.ts
5265
+ // src/cli_doctor.ts
5288
5266
  init_define_import_meta_env();
5289
- import { existsSync as existsSync4, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
5290
- import * as http from "http";
5291
- import * as https from "https";
5292
- import { isIPv4, isIPv6 } from "net";
5293
- import { resolve as resolve3, join as join8 } from "path";
5294
- import { URL as URL2 } from "url";
5295
- import { promisify } from "util";
5296
- import { lookup as dnsLookup } from "dns";
5297
- var dnsLookupAsync = promisify(dnsLookup);
5298
- var MAX_URL_IN_ERROR = 200;
5299
- var ALLOW_UNRESOLVED = (process.env["TOKEN_GOAT_WEBFETCH_ALLOW_UNRESOLVED"] ?? "").toLowerCase() === "true";
5300
- function webCacheDir() {
5301
- return join8(dataDir(), "web_cache");
5302
- }
5303
- var BLOCKED_HOSTNAMES = /* @__PURE__ */ new Set([
5304
- "localhost",
5305
- "ip6-localhost",
5306
- "ip6-loopback",
5307
- "metadata.google.internal",
5308
- "169.254.169.254"
5309
- ]);
5310
- function truncateUrl(url, maxLen = MAX_URL_IN_ERROR) {
5311
- const sanitized = url.replace(/[\r\n]/g, "");
5312
- return sanitized.length > maxLen ? sanitized.slice(0, maxLen) + "\u2026" : sanitized;
5313
- }
5314
- async function resolveSsrfSafeAddress(hostname) {
5315
- const hostnameLower = hostname.toLowerCase().replace(/\.$/, "");
5316
- if (BLOCKED_HOSTNAMES.has(hostnameLower)) return { kind: "blocked" };
5317
- let results;
5267
+ import * as fs11 from "fs";
5268
+ import * as os8 from "os";
5269
+ import * as path10 from "path";
5270
+ import { execSync, spawnSync as spawnSync2 } from "child_process";
5271
+ function globalMcpConfigPath() {
5272
+ const copilotHome = process.env["COPILOT_HOME"];
5273
+ const root = copilotHome !== void 0 && copilotHome.trim() !== "" ? path10.resolve(copilotHome) : path10.join(os8.homedir(), ".copilot");
5274
+ return path10.join(root, "mcp-config.json");
5275
+ }
5276
+ function checkGlobalMcpConfig(configPath2 = globalMcpConfigPath()) {
5277
+ if (!fs11.existsSync(configPath2)) {
5278
+ return { name: "Global MCP configuration", status: "ok", message: `no global Copilot MCP configuration found at ${configPath2}` };
5279
+ }
5280
+ let parsed;
5318
5281
  try {
5319
- const r = await dnsLookupAsync(hostnameLower, { all: true });
5320
- if (!Array.isArray(r) || r.length === 0) return { kind: "unresolved" };
5321
- results = r;
5282
+ parsed = JSON.parse(fs11.readFileSync(configPath2, "utf8"));
5322
5283
  } catch {
5323
- return { kind: "unresolved" };
5284
+ return {
5285
+ name: "Global MCP configuration",
5286
+ status: "warn",
5287
+ message: `could not read global Copilot MCP configuration at ${configPath2}; unable to audit heavy launchers.`
5288
+ };
5324
5289
  }
5325
- for (const addr of results) {
5326
- const ip = addr.address || "";
5327
- const isPrivate = isIPv4(ip) ? isPrivateIPv4(ip) : isIPv6(ip) ? isPrivateIPv6(ip) : true;
5328
- if (isPrivate) return { kind: "blocked" };
5290
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
5291
+ return {
5292
+ name: "Global MCP configuration",
5293
+ status: "warn",
5294
+ message: `global Copilot MCP configuration at ${configPath2} has an unsupported format; unable to audit heavy launchers.`
5295
+ };
5329
5296
  }
5330
- const first = results[0];
5331
- if (!first) return { kind: "unresolved" };
5332
- return { kind: "safe", address: first.address, family: first.family, addresses: results };
5297
+ const configuredServers = parsed["mcpServers"];
5298
+ if (typeof configuredServers !== "object" || configuredServers === null || Array.isArray(configuredServers)) {
5299
+ return { name: "Global MCP configuration", status: "ok", message: `no global stdio MCP servers configured at ${configPath2}` };
5300
+ }
5301
+ let chromeDevTools = 0;
5302
+ let playwright = 0;
5303
+ for (const server of Object.values(configuredServers)) {
5304
+ if (typeof server !== "object" || server === null || Array.isArray(server)) continue;
5305
+ const entry = server;
5306
+ const rawCommand = entry["command"];
5307
+ const command = typeof rawCommand === "string" ? rawCommand : "";
5308
+ const args = entry["args"];
5309
+ if (!/\bnpx(?:\.cmd)?\b/i.test(command) || !Array.isArray(args)) continue;
5310
+ const invocation = args.filter((arg) => typeof arg === "string").join(" ");
5311
+ if (/\bchrome-devtools-mcp\b/i.test(invocation)) chromeDevTools += 1;
5312
+ if (/@playwright[\\/]mcp\b/i.test(invocation)) playwright += 1;
5313
+ }
5314
+ if (chromeDevTools > 0 || playwright > 0) {
5315
+ const launchers = [];
5316
+ if (chromeDevTools > 0) launchers.push(`${chromeDevTools} Chrome DevTools MCP launcher${chromeDevTools === 1 ? "" : "s"}`);
5317
+ if (playwright > 0) launchers.push(`${playwright} Playwright MCP launcher${playwright === 1 ? "" : "s"}`);
5318
+ return {
5319
+ name: "Global MCP configuration",
5320
+ status: "warn",
5321
+ message: `${launchers.join(" and ")} configured at ${configPath2}. Move heavy launchers to project scope or remove them when not actively needed.`
5322
+ };
5323
+ }
5324
+ return { name: "Global MCP configuration", status: "ok", message: `no known heavy global MCP launchers configured at ${configPath2}` };
5333
5325
  }
5334
- function ssrfPinnedLookup(hostname, options, callback) {
5335
- resolveSsrfSafeAddress(hostname).then((result) => {
5326
+ function checkMcpProcessHealth(processes) {
5327
+ if (processes === null) {
5328
+ return {
5329
+ name: "MCP process health",
5330
+ status: "warn",
5331
+ message: "could not read the process list (PowerShell did not answer), so duplicate MCP launchers and orphaned Node processes were not checked"
5332
+ };
5333
+ }
5334
+ const byPid = new Set(processes.map((process2) => process2.processId));
5335
+ const nodeProcesses = processes.filter((process2) => process2.name.toLowerCase() === "node.exe");
5336
+ const chromeLaunchers = nodeProcesses.filter((process2) => /npx-cli\.js.*chrome-devtools-mcp/i.test(process2.commandLine));
5337
+ const playwrightLaunchers = nodeProcesses.filter((process2) => /npx-cli\.js.*@playwright[\\/]mcp/i.test(process2.commandLine));
5338
+ const orphanedNodeProcesses = nodeProcesses.filter((process2) => !byPid.has(process2.parentProcessId));
5339
+ const launchers = chromeLaunchers.length + playwrightLaunchers.length;
5340
+ if (launchers > 2 || orphanedNodeProcesses.length > 0) {
5341
+ const details = [];
5342
+ if (chromeLaunchers.length > 1) details.push(`${chromeLaunchers.length} Chrome DevTools MCP launchers`);
5343
+ if (playwrightLaunchers.length > 1) details.push(`${playwrightLaunchers.length} Playwright MCP launchers`);
5344
+ if (orphanedNodeProcesses.length > 0) details.push(`${orphanedNodeProcesses.length} orphaned Node process${orphanedNodeProcesses.length === 1 ? "" : "es"}`);
5345
+ return {
5346
+ name: "MCP process health",
5347
+ status: "warn",
5348
+ message: `${details.join("; ")} detected. These are host-managed processes; close stale Copilot sessions before terminating a specific confirmed orphan.`
5349
+ };
5350
+ }
5351
+ return { name: "MCP process health", status: "ok", message: "no duplicate MCP launchers or orphaned Node processes detected" };
5352
+ }
5353
+ function runProcessListCommand() {
5354
+ const command = "Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,Name,CommandLine | ConvertTo-Json -Compress";
5355
+ return execSync(`powershell.exe -NoProfile -NonInteractive -Command "${command}"`, {
5356
+ encoding: "utf8",
5357
+ timeout: 2e4,
5358
+ maxBuffer: 10 * 1024 * 1024,
5359
+ windowsHide: true
5360
+ });
5361
+ }
5362
+ function readWindowsProcesses(runCommand = runProcessListCommand) {
5363
+ if (process.platform !== "win32") return [];
5364
+ try {
5365
+ const output = runCommand().trim();
5366
+ if (output === "") return [];
5367
+ const parsed = JSON.parse(output);
5368
+ const rows = Array.isArray(parsed) ? parsed : [parsed];
5369
+ return rows.flatMap((row) => {
5370
+ if (typeof row !== "object" || row === null) return [];
5371
+ const value = row;
5372
+ if (typeof value["ProcessId"] !== "number" || typeof value["ParentProcessId"] !== "number" || typeof value["Name"] !== "string") return [];
5373
+ return [{
5374
+ processId: value["ProcessId"],
5375
+ parentProcessId: value["ParentProcessId"],
5376
+ name: value["Name"],
5377
+ commandLine: typeof value["CommandLine"] === "string" ? value["CommandLine"] : ""
5378
+ }];
5379
+ });
5380
+ } catch {
5381
+ return null;
5382
+ }
5383
+ }
5384
+ function checkWorkerRunning(dataDir2) {
5385
+ return dataDir2 !== void 0 ? isWorkerRunning(dataDir2) : isWorkerRunning();
5386
+ }
5387
+ var DB_SIZE_WARN_BYTES = 1024 * 1024 * 1024;
5388
+ function checkDbExists(dataDir2) {
5389
+ const dbPath = path10.join(dataDir2, "global.db");
5390
+ if (!fs11.existsSync(dbPath)) {
5391
+ return {
5392
+ name: "Database",
5393
+ status: "warn",
5394
+ message: `global.db not found at ${dbPath}`
5395
+ };
5396
+ }
5397
+ const sizeBytes = fs11.statSync(dbPath).size;
5398
+ const SQLITE_HEADER = "SQLite format 3\0";
5399
+ let header = "";
5400
+ try {
5401
+ const fd = fs11.openSync(dbPath, "r");
5402
+ try {
5403
+ const buf = Buffer.alloc(SQLITE_HEADER.length);
5404
+ const bytesRead = fs11.readSync(fd, buf, 0, buf.length, 0);
5405
+ header = buf.toString("latin1", 0, bytesRead);
5406
+ } finally {
5407
+ fs11.closeSync(fd);
5408
+ }
5409
+ } catch {
5410
+ }
5411
+ if (header !== SQLITE_HEADER) {
5412
+ return {
5413
+ name: "Database",
5414
+ status: "fail",
5415
+ message: `global.db at ${dbPath} is not a valid SQLite file (${sizeBytes} bytes) \u2014 likely truncated or corrupt`
5416
+ };
5417
+ }
5418
+ if (sizeBytes > DB_SIZE_WARN_BYTES) {
5419
+ return {
5420
+ name: "Database",
5421
+ status: "warn",
5422
+ message: `global.db is ${Math.round(sizeBytes / (1024 * 1024))} MB at ${dbPath} \u2014 far larger than a healthy index. Large writes against it can exceed the 15s busy_timeout and appear as "database is locked". Try 'token-goat reclaim-index' first (a plain VACUUM, cheap, can recover a useful amount on its own); only reach for 'token-goat reclaim-index --rebuild' if that isn't enough, since --rebuild reparses and re-embeds every indexed file across every project and can take a long time on a large multi-project index`
5423
+ };
5424
+ }
5425
+ return {
5426
+ name: "Database",
5427
+ status: "ok",
5428
+ message: `global.db exists (${toKB(sizeBytes)} KB) at ${dbPath}`
5429
+ };
5430
+ }
5431
+ function checkSymbolCount(dbPath, rootDir) {
5432
+ if (!fs11.existsSync(dbPath)) {
5433
+ return { name: "Symbols", status: "ok", message: "no database yet" };
5434
+ }
5435
+ try {
5436
+ const { fileCount, symbolCount } = getProjectIndexCounts(dbPath, rootDir);
5437
+ if (fileCount > 0 && symbolCount === 0) {
5438
+ return {
5439
+ name: "Symbols",
5440
+ status: "warn",
5441
+ message: `${fileCount} file(s) indexed but 0 symbols extracted \u2014 the parser may not be running (check the worker log); try 'token-goat index --force'`
5442
+ };
5443
+ }
5444
+ if (fileCount === 0 && symbolCount === 0) {
5445
+ return {
5446
+ name: "Symbols",
5447
+ status: "warn",
5448
+ message: emptyIndexMessage(rootDir ?? process.cwd())
5449
+ };
5450
+ }
5451
+ return {
5452
+ name: "Symbols",
5453
+ status: "ok",
5454
+ message: `${symbolCount} symbol(s) across ${fileCount} indexed file(s)`
5455
+ };
5456
+ } catch (err2) {
5457
+ return {
5458
+ name: "Symbols",
5459
+ status: "warn",
5460
+ message: `could not query symbol count: ${extractErrorMessage(err2)}`
5461
+ };
5462
+ }
5463
+ }
5464
+ var DIRTY_QUEUE_BACKLOG_WARN_THRESHOLD = 500;
5465
+ function checkDirtyQueueHealth(dataDir2) {
5466
+ let pendingCount = 0;
5467
+ try {
5468
+ const raw = fs11.readFileSync(dirtyQueuePathFor(dataDir2), "utf8");
5469
+ pendingCount = raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).length;
5470
+ } catch {
5471
+ }
5472
+ if (pendingCount > DIRTY_QUEUE_BACKLOG_WARN_THRESHOLD) {
5473
+ return {
5474
+ name: "Dirty queue",
5475
+ status: "warn",
5476
+ message: `${pendingCount} file(s) pending reindex -- the worker may be falling behind or stalled; check 'token-goat worker status'`
5477
+ };
5478
+ }
5479
+ if (!isWorkerRunning(dataDir2)) {
5480
+ return { name: "Dirty queue", status: "ok", message: `${pendingCount} file(s) pending (worker not running)` };
5481
+ }
5482
+ let heartbeatAgeMs = null;
5483
+ try {
5484
+ heartbeatAgeMs = Date.now() - fs11.statSync(drainHeartbeatPathFor(dataDir2)).mtimeMs;
5485
+ } catch {
5486
+ }
5487
+ if (heartbeatAgeMs !== null && heartbeatAgeMs > WORKER_HEARTBEAT_STALE_MS) {
5488
+ return {
5489
+ name: "Dirty queue",
5490
+ status: "warn",
5491
+ message: `worker process is running but hasn't completed a drain cycle in ${Math.round(heartbeatAgeMs / 1e3)}s -- possibly deadlocked or stuck; check the worker error log`
5492
+ };
5493
+ }
5494
+ return { name: "Dirty queue", status: "ok", message: `${pendingCount} file(s) pending, worker actively draining` };
5495
+ }
5496
+ function checkInstall() {
5497
+ try {
5498
+ const output = execSync("token-goat --version", { encoding: "utf-8" });
5499
+ return {
5500
+ name: "Installation",
5501
+ status: "ok",
5502
+ message: output.trim()
5503
+ };
5504
+ } catch {
5505
+ return {
5506
+ name: "Installation",
5507
+ status: "fail",
5508
+ message: "token-goat command not found; run: npm install -g token-goat-ts"
5509
+ };
5510
+ }
5511
+ }
5512
+ function checkTsCompiler() {
5513
+ if (isAvailable()) {
5514
+ return { name: "TypeScript compiler", status: "ok", message: "available" };
5515
+ }
5516
+ const err2 = loadError();
5517
+ return {
5518
+ name: "TypeScript compiler",
5519
+ status: "warn",
5520
+ message: err2 !== null ? `unavailable: ${extractErrorMessage(err2)}` : "unavailable (not attempted)"
5521
+ };
5522
+ }
5523
+ function checkConfigValid(configPath2) {
5524
+ if (!fs11.existsSync(configPath2)) {
5525
+ return {
5526
+ name: "Config",
5527
+ status: "warn",
5528
+ message: `config file not found at ${configPath2}`
5529
+ };
5530
+ }
5531
+ try {
5532
+ const content = fs11.readFileSync(configPath2, "utf-8");
5533
+ parse(content);
5534
+ return {
5535
+ name: "Config",
5536
+ status: "ok",
5537
+ message: `config file valid (${content.length} bytes)`
5538
+ };
5539
+ } catch (err2) {
5540
+ return {
5541
+ name: "Config",
5542
+ status: "fail",
5543
+ message: `config invalid: ${extractErrorMessage(err2, "unknown error")}`
5544
+ };
5545
+ }
5546
+ }
5547
+ function formatDiskSpace(bytes) {
5548
+ const units = ["B", "KB", "MB", "GB", "TB"];
5549
+ let value = bytes;
5550
+ let unitIndex = 0;
5551
+ while (value >= 1024 && unitIndex < units.length - 1) {
5552
+ value /= 1024;
5553
+ unitIndex++;
5554
+ }
5555
+ return `${value.toFixed(1)} ${units[unitIndex]}`;
5556
+ }
5557
+ var LOW_DISK_WARN_BYTES = 1024 * 1024 * 1024;
5558
+ function checkDiskSpace(dataDir2) {
5559
+ if (typeof fs11.statfsSync === "function") {
5560
+ try {
5561
+ const stats = fs11.statfsSync(dataDir2);
5562
+ const availableBytes = stats.bavail * stats.bsize;
5563
+ const status = availableBytes < LOW_DISK_WARN_BYTES ? "warn" : "ok";
5564
+ const suffix = status === "warn" ? " \u2014 running low, indexing/embeddings writes may start failing" : "";
5565
+ return { name: "Disk Space", status, message: `${formatDiskSpace(availableBytes)} available${suffix}` };
5566
+ } catch {
5567
+ }
5568
+ }
5569
+ if (process.platform !== "win32") {
5570
+ try {
5571
+ const result = spawnSync2("df", ["-Pk", dataDir2], { encoding: "utf-8" });
5572
+ const stdout = typeof result.stdout === "string" ? result.stdout : "";
5573
+ if (result.error === void 0 && result.status === 0 && stdout) {
5574
+ const lines = stdout.trim().split("\n");
5575
+ if (lines.length >= 2) {
5576
+ const parts = lines[1].trim().split(/\s+/);
5577
+ const availableKb = Number.parseInt(parts[3] ?? "", 10);
5578
+ if (Number.isFinite(availableKb)) {
5579
+ const availableBytes = availableKb * 1024;
5580
+ const status = availableBytes < LOW_DISK_WARN_BYTES ? "warn" : "ok";
5581
+ const suffix = status === "warn" ? " \u2014 running low, indexing/embeddings writes may start failing" : "";
5582
+ return { name: "Disk Space", status, message: `${formatDiskSpace(availableBytes)} available${suffix}` };
5583
+ }
5584
+ }
5585
+ }
5586
+ } catch {
5587
+ }
5588
+ }
5589
+ return { name: "Disk Space", status: "warn", message: "disk space check unavailable on this platform" };
5590
+ }
5591
+ function checkCopilotCli(configPath2, scriptPath) {
5592
+ if (!fs11.existsSync(configPath2) || !fs11.existsSync(scriptPath)) {
5593
+ return null;
5594
+ }
5595
+ let config;
5596
+ try {
5597
+ config = JSON.parse(fs11.readFileSync(configPath2, "utf-8"));
5598
+ } catch (err2) {
5599
+ return {
5600
+ name: "Copilot CLI",
5601
+ status: "fail",
5602
+ message: `hook config at ${configPath2} is not valid JSON: ${extractErrorMessage(err2, "unknown error")}`
5603
+ };
5604
+ }
5605
+ const preToolUseCommand = config.hooks?.["preToolUse"]?.[0]?.command;
5606
+ if (typeof preToolUseCommand !== "string" || preToolUseCommand === "") {
5607
+ return {
5608
+ name: "Copilot CLI",
5609
+ status: "fail",
5610
+ message: `hook config at ${configPath2} has no preToolUse entry; run: token-goat install --copilot`
5611
+ };
5612
+ }
5613
+ const bakedExecPath = /^"([^"]+)"/.exec(preToolUseCommand)?.[1];
5614
+ if (bakedExecPath !== void 0 && !fs11.existsSync(bakedExecPath)) {
5615
+ return {
5616
+ name: "Copilot CLI",
5617
+ status: "fail",
5618
+ message: `hook points at a node binary that no longer exists (${bakedExecPath}) -- likely stale after an nvm/fnm/volta node upgrade. Recovery: run "token-goat install --copilot", then fully restart Copilot CLI (renaming/reinstalling the hook has no effect on an already-running session -- Copilot caches hook configs at startup).`
5619
+ };
5620
+ }
5621
+ const synthetic = JSON.stringify({
5622
+ sessionId: "doctor-check",
5623
+ cwd: process.cwd(),
5624
+ toolName: "view",
5625
+ toolArgs: { path: "doctor-check.txt" }
5626
+ });
5627
+ const res = spawnSync2(preToolUseCommand, {
5628
+ input: synthetic,
5629
+ encoding: "utf-8",
5630
+ shell: true,
5631
+ windowsHide: true,
5632
+ timeout: 15e3
5633
+ });
5634
+ if (res.error) {
5635
+ return {
5636
+ name: "Copilot CLI",
5637
+ status: "fail",
5638
+ message: `hook failed to launch: ${extractErrorMessage(res.error, "unknown error")}. Recovery: run "token-goat install --copilot", then fully restart Copilot CLI.`
5639
+ };
5640
+ }
5641
+ if (res.status !== 0) {
5642
+ return {
5643
+ name: "Copilot CLI",
5644
+ status: "fail",
5645
+ message: `hook exited with status ${res.status} -- Copilot's preToolUse fails closed on a non-zero exit and denies every tool call for the rest of the session. Recovery: run "token-goat install --copilot", then fully restart Copilot CLI (a live session won't pick up the fix).`
5646
+ };
5647
+ }
5648
+ try {
5649
+ JSON.parse(res.stdout ?? "");
5650
+ } catch {
5651
+ return {
5652
+ name: "Copilot CLI",
5653
+ status: "fail",
5654
+ message: 'hook did not return valid JSON -- Copilot treats this as a hook error and denies every tool call. Recovery: run "token-goat install --copilot", then fully restart Copilot CLI.'
5655
+ };
5656
+ }
5657
+ return { name: "Copilot CLI", status: "ok", message: "preToolUse hook invokes cleanly and returns valid JSON" };
5658
+ }
5659
+ function checkStrayClaudeMdBlocks(searchRoot) {
5660
+ const strays = findStrayClaudeMdBlocks(searchRoot);
5661
+ if (strays.length === 0) {
5662
+ return { name: "CLAUDE.md block", status: "ok", message: "no stray copies outside CLAUDE.md" };
5663
+ }
5664
+ return {
5665
+ name: "CLAUDE.md block",
5666
+ status: "warn",
5667
+ 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(", ")}`
5668
+ };
5669
+ }
5670
+ function checkSecurityPosture(cfg, dataDirPath) {
5671
+ const results = [];
5672
+ results.push({
5673
+ name: "Security network",
5674
+ status: "ok",
5675
+ message: cfg.network.offline ? "offline mode is on: no fetch, model download, OCR data, screenshot, or Drive call" : "offline mode is off (network.offline)"
5676
+ });
5677
+ results.push(
5678
+ cfg.injection.enabled ? { name: "Security injection", status: "ok", message: "fetched and MCP content is scanned and fenced" } : { name: "Security injection", status: "warn", message: "scanning is off (injection.enabled): fetched and MCP content reaches the model unfenced" }
5679
+ );
5680
+ results.push({
5681
+ name: "Security gdrive",
5682
+ status: "ok",
5683
+ message: cfg.gdrive.enabled ? "enabled (gdrive.enabled = false turns it off)" : "disabled"
5684
+ });
5685
+ const allow = cfg.webfetch.allow.length;
5686
+ const deny = cfg.webfetch.deny.length;
5687
+ results.push({
5688
+ name: "Security fetch policy",
5689
+ status: "ok",
5690
+ 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`
5691
+ });
5692
+ const extraRoots = cfg.mcp.allowed_roots.length;
5693
+ results.push(
5694
+ cfg.mcp.confine_reads_to_project_root ? {
5695
+ name: "Security mcp roots",
5696
+ status: "ok",
5697
+ message: extraRoots === 0 ? "reads are confined to the project root" : `reads are confined to the project root plus ${extraRoots} configured root${extraRoots === 1 ? "" : "s"}`
5698
+ } : { name: "Security mcp roots", status: "warn", message: "confinement is off (mcp.confine_reads_to_project_root): a read can leave the project" }
5699
+ );
5700
+ results.push(dataDirPermissionResult(dataDirPath));
5701
+ return results;
5702
+ }
5703
+ function dataDirPermissionResult(dataDirPath) {
5704
+ if (process.platform === "win32") {
5705
+ return { name: "Security data dir", status: "ok", message: "inherits the parent ACL (POSIX modes do not apply on Windows)" };
5706
+ }
5707
+ try {
5708
+ const mode = fs11.statSync(dataDirPath).mode & 511;
5709
+ if ((mode & 63) !== 0) {
5710
+ return {
5711
+ name: "Security data dir",
5712
+ status: "warn",
5713
+ message: `mode ${mode.toString(8).padStart(3, "0")}: other local users can read the indexed source text`
5714
+ };
5715
+ }
5716
+ return { name: "Security data dir", status: "ok", message: `mode ${mode.toString(8).padStart(3, "0")}: owner only` };
5717
+ } catch {
5718
+ return { name: "Security data dir", status: "warn", message: "could not be read, so its permissions are unknown" };
5719
+ }
5720
+ }
5721
+ function runDoctor(dataDir2, configPath2, rootDir, processes) {
5722
+ const results = [];
5723
+ const actualDataDir = dataDir2 || dataDir();
5724
+ results.push(checkInstall());
5725
+ results.push(checkTsCompiler());
5726
+ results.push(checkStrayClaudeMdBlocks());
5727
+ results.push(checkWorkerRunning(actualDataDir) ? { name: "Worker", status: "ok", message: "running" } : { name: "Worker", status: "warn", message: "not running" });
5728
+ results.push(checkDbExists(actualDataDir));
5729
+ results.push(checkSymbolBodySize(path10.join(actualDataDir, "global.db")));
5730
+ results.push(checkSymbolCount(path10.join(actualDataDir, "global.db"), rootDir));
5731
+ results.push(checkDirtyQueueHealth(actualDataDir));
5732
+ const actualConfigPath = configPath2 || configPath();
5733
+ results.push(checkConfigValid(actualConfigPath));
5734
+ for (const result of checkSecurityPosture(loadConfig(rootDir), actualDataDir)) results.push(result);
5735
+ results.push(checkDiskSpace(actualDataDir));
5736
+ const copilotResult = checkCopilotCli(copilotCliConfigPath(), copilotCliScriptPath());
5737
+ if (copilotResult) results.push(copilotResult);
5738
+ results.push(checkGlobalMcpConfig());
5739
+ if (process.platform === "win32") results.push(checkMcpProcessHealth(processes ?? readWindowsProcesses()));
5740
+ return results;
5741
+ }
5742
+ function printDoctorResults(results) {
5743
+ console.log("\ntoken-goat doctor\n");
5744
+ const grouped = /* @__PURE__ */ new Map();
5745
+ for (const result of results) {
5746
+ const key = result.name.split(" ")[0];
5747
+ if (!grouped.has(key)) {
5748
+ grouped.set(key, []);
5749
+ }
5750
+ grouped.get(key).push(result);
5751
+ }
5752
+ for (const [, items] of grouped) {
5753
+ for (const item of items) {
5754
+ const prefix = item.status === "ok" ? " " : ` [${item.status.toUpperCase()}] `;
5755
+ console.log(`${prefix}${item.name}: ${item.message}`);
5756
+ }
5757
+ }
5758
+ const hasFailures = results.some((r) => r.status === "fail");
5759
+ console.log(hasFailures ? "\nFAILURES DETECTED" : "\nAll checks passed");
5760
+ console.log();
5761
+ }
5762
+ async function runDoctorAndExit(opts) {
5763
+ const results = runDoctor(opts?.dataDir, opts?.configPath, opts?.rootDir, opts?.processes);
5764
+ printDoctorResults(results);
5765
+ if (opts?.context === true) {
5766
+ console.log("\n## Context footprint\n");
5767
+ await runContextStats({});
5768
+ console.log();
5769
+ try {
5770
+ const dir = skillOutputsDir();
5771
+ const pregenPath = path10.join(dir, "pregen.json");
5772
+ if (fs11.existsSync(pregenPath)) {
5773
+ const content = JSON.parse(fs11.readFileSync(pregenPath, "utf-8"));
5774
+ const pregenNames = new Set(content.names || []);
5775
+ const skillsSeen = /* @__PURE__ */ new Set();
5776
+ try {
5777
+ const entries = fs11.readdirSync(dir, { withFileTypes: true });
5778
+ for (const entry of entries) {
5779
+ if (!entry.isFile() || !entry.name.endsWith(".meta")) continue;
5780
+ try {
5781
+ const meta = JSON.parse(fs11.readFileSync(path10.join(dir, entry.name), "utf-8"));
5782
+ if (meta.skillName && !pregenNames.has(meta.skillName)) {
5783
+ skillsSeen.add(meta.skillName);
5784
+ }
5785
+ } catch {
5786
+ }
5787
+ }
5788
+ } catch {
5789
+ }
5790
+ const skills = [...skillsSeen];
5791
+ if (skills.length > 0) {
5792
+ console.log(`Missing from pregen.json: ${skills.join(", ")}`);
5793
+ console.log(`Remediation: token-goat skill-compact --all
5794
+ `);
5795
+ }
5796
+ }
5797
+ } catch {
5798
+ }
5799
+ }
5800
+ return results.some((r) => r.status === "fail") ? 1 : 0;
5801
+ }
5802
+
5803
+ // src/gdrive.ts
5804
+ init_define_import_meta_env();
5805
+
5806
+ // src/webfetch.ts
5807
+ init_define_import_meta_env();
5808
+ import { existsSync as existsSync8, readdirSync as readdirSync5, statSync as statSync4, unlinkSync as unlinkSync2 } from "fs";
5809
+ import * as http from "http";
5810
+ import * as https from "https";
5811
+ import { isIPv4, isIPv6 } from "net";
5812
+ import { resolve as resolve4, join as join10 } from "path";
5813
+ import { URL as URL2 } from "url";
5814
+ import { promisify } from "util";
5815
+ import { lookup as dnsLookup } from "dns";
5816
+ var dnsLookupAsync = promisify(dnsLookup);
5817
+ var MAX_URL_IN_ERROR = 200;
5818
+ var ALLOW_UNRESOLVED = (process.env["TOKEN_GOAT_WEBFETCH_ALLOW_UNRESOLVED"] ?? "").toLowerCase() === "true";
5819
+ function webCacheDir() {
5820
+ return join10(dataDir(), "web_cache");
5821
+ }
5822
+ var BLOCKED_HOSTNAMES = /* @__PURE__ */ new Set([
5823
+ "localhost",
5824
+ "ip6-localhost",
5825
+ "ip6-loopback",
5826
+ "metadata.google.internal",
5827
+ "169.254.169.254"
5828
+ ]);
5829
+ function truncateUrl(url, maxLen = MAX_URL_IN_ERROR) {
5830
+ const sanitized = url.replace(/[\r\n]/g, "");
5831
+ return sanitized.length > maxLen ? sanitized.slice(0, maxLen) + "\u2026" : sanitized;
5832
+ }
5833
+ async function resolveSsrfSafeAddress(hostname) {
5834
+ const hostnameLower = hostname.toLowerCase().replace(/\.$/, "");
5835
+ if (BLOCKED_HOSTNAMES.has(hostnameLower)) return { kind: "blocked" };
5836
+ let results;
5837
+ try {
5838
+ const r = await dnsLookupAsync(hostnameLower, { all: true });
5839
+ if (!Array.isArray(r) || r.length === 0) return { kind: "unresolved" };
5840
+ results = r;
5841
+ } catch {
5842
+ return { kind: "unresolved" };
5843
+ }
5844
+ for (const addr of results) {
5845
+ const ip = addr.address || "";
5846
+ const isPrivate = isIPv4(ip) ? isPrivateIPv4(ip) : isIPv6(ip) ? isPrivateIPv6(ip) : true;
5847
+ if (isPrivate) return { kind: "blocked" };
5848
+ }
5849
+ const first = results[0];
5850
+ if (!first) return { kind: "unresolved" };
5851
+ return { kind: "safe", address: first.address, family: first.family, addresses: results };
5852
+ }
5853
+ function ssrfPinnedLookup(hostname, options, callback) {
5854
+ resolveSsrfSafeAddress(hostname).then((result) => {
5336
5855
  if (result.kind === "safe") {
5337
5856
  if (options.all) {
5338
5857
  callback(null, result.addresses);
@@ -5380,13 +5899,18 @@ function isPrivateIPv4(ip) {
5380
5899
  if (octets.length !== 4 || octets.some((o) => !Number.isFinite(o))) return false;
5381
5900
  const a = octets[0];
5382
5901
  const b = octets[1];
5902
+ const c = octets[2];
5383
5903
  return a === 0 || // 0.0.0.0/8 ("this network" / unspecified-source range)
5384
5904
  a === 127 || // 127.x.x.x
5385
5905
  a === 10 || // 10.x.x.x
5386
5906
  a === 100 && b >= 64 && b <= 127 || // 100.64.0.0/10 (carrier-grade NAT, RFC 6598)
5387
5907
  a === 172 && b >= 16 && b <= 31 || // 172.16-31.x.x
5388
5908
  a === 192 && b === 168 || // 192.168.x.x
5389
- a === 169 && b === 254;
5909
+ a === 169 && b === 254 || // 169.254.x.x
5910
+ a === 192 && b === 0 && c === 0 || // 192.0.0.0/24 (IETF protocol assignments, RFC 6890)
5911
+ a === 198 && (b === 18 || b === 19) || // 198.18.0.0/15 (benchmarking, RFC 2544)
5912
+ a >= 224 && a <= 239 || // 224.0.0.0/4 multicast, including the all-hosts group
5913
+ a >= 240;
5390
5914
  }
5391
5915
  function parseIPv6Groups(ip) {
5392
5916
  if (!isIPv6(ip)) return null;
@@ -5435,6 +5959,13 @@ function matchesIPv6Prefix(groups, prefixGroups, prefixBits) {
5435
5959
  }
5436
5960
  var FC00_PREFIX = [64512, 0, 0, 0, 0, 0, 0, 0];
5437
5961
  var FE80_PREFIX = [65152, 0, 0, 0, 0, 0, 0, 0];
5962
+ var FEC0_PREFIX = [65216, 0, 0, 0, 0, 0, 0, 0];
5963
+ var FF00_PREFIX = [65280, 0, 0, 0, 0, 0, 0, 0];
5964
+ var SIXTOFOUR_PREFIX = [8194, 0, 0, 0, 0, 0, 0, 0];
5965
+ var NAT64_PREFIX = [100, 65435, 0, 0, 0, 0, 0, 0];
5966
+ function embeddedIPv4(hi, lo) {
5967
+ return `${hi >> 8 & 255}.${hi & 255}.${lo >> 8 & 255}.${lo & 255}`;
5968
+ }
5438
5969
  function isPrivateIPv6(ip) {
5439
5970
  const lower = ip.toLowerCase();
5440
5971
  if (lower === "::1") return true;
@@ -5444,27 +5975,32 @@ function isPrivateIPv6(ip) {
5444
5975
  const isIPv4Translated = groups[0] === 0 && groups[1] === 0 && groups[2] === 0 && groups[3] === 0 && groups[4] === 65535 && groups[5] === 0;
5445
5976
  const isIPv4Compatible = groups[0] === 0 && groups[1] === 0 && groups[2] === 0 && groups[3] === 0 && groups[4] === 0 && groups[5] === 0;
5446
5977
  if (isIPv4Mapped || isIPv4Translated || isIPv4Compatible) {
5447
- const hi = groups[6] ?? 0;
5448
- const lo = groups[7] ?? 0;
5449
- const embedded = `${hi >> 8 & 255}.${hi & 255}.${lo >> 8 & 255}.${lo & 255}`;
5450
- return isPrivateIPv4(embedded);
5978
+ return isPrivateIPv4(embeddedIPv4(groups[6] ?? 0, groups[7] ?? 0));
5979
+ }
5980
+ if (matchesIPv6Prefix(groups, SIXTOFOUR_PREFIX, 16)) {
5981
+ return isPrivateIPv4(embeddedIPv4(groups[1] ?? 0, groups[2] ?? 0));
5982
+ }
5983
+ if (matchesIPv6Prefix(groups, NAT64_PREFIX, 96)) {
5984
+ return isPrivateIPv4(embeddedIPv4(groups[6] ?? 0, groups[7] ?? 0));
5451
5985
  }
5452
5986
  if (matchesIPv6Prefix(groups, FC00_PREFIX, 7)) return true;
5453
5987
  if (matchesIPv6Prefix(groups, FE80_PREFIX, 10)) return true;
5988
+ if (matchesIPv6Prefix(groups, FEC0_PREFIX, 10)) return true;
5989
+ if (matchesIPv6Prefix(groups, FF00_PREFIX, 8)) return true;
5454
5990
  return false;
5455
5991
  }
5456
5992
  var STALE_DOWNLOAD_AGE_MS = 10 * 60 * 1e3;
5457
5993
  function cleanupStaleDownloads() {
5458
5994
  const cacheDir = webCacheDir();
5459
- if (!existsSync4(cacheDir)) return 0;
5995
+ if (!existsSync8(cacheDir)) return 0;
5460
5996
  let removed = 0;
5461
5997
  try {
5462
- const files = readdirSync3(cacheDir);
5998
+ const files = readdirSync5(cacheDir);
5463
5999
  for (const file of files) {
5464
6000
  if (file.endsWith(".tmp")) {
5465
- const filePath = resolve3(cacheDir, file);
6001
+ const filePath = resolve4(cacheDir, file);
5466
6002
  try {
5467
- const stat = statSync2(filePath);
6003
+ const stat = statSync4(filePath);
5468
6004
  if (Date.now() - stat.mtimeMs < STALE_DOWNLOAD_AGE_MS) continue;
5469
6005
  unlinkSync2(filePath);
5470
6006
  removed++;
@@ -5489,6 +6025,15 @@ function performHttpFetch(targetUrl, opts) {
5489
6025
  rejectPromise(new Error(`URL blocked by SSRF safety check: ${truncateUrl(targetUrl)}`));
5490
6026
  return;
5491
6027
  }
6028
+ if (loadConfig().network.offline) {
6029
+ rejectPromise(new Error(`Offline mode is on (network.offline): refusing to fetch ${truncateUrl(targetUrl)}`));
6030
+ return;
6031
+ }
6032
+ const policyDenial = urlPolicyDenialReason(targetUrl, loadConfig().webfetch);
6033
+ if (policyDenial !== null) {
6034
+ rejectPromise(new Error(`${policyDenial}: ${truncateUrl(targetUrl)}`));
6035
+ return;
6036
+ }
5492
6037
  const literalIp = parsed.hostname.startsWith("[") && parsed.hostname.endsWith("]") ? parsed.hostname.slice(1, -1) : parsed.hostname;
5493
6038
  const isLiteralPrivate = isIPv4(literalIp) ? isPrivateIPv4(literalIp) : isIPv6(literalIp) ? isPrivateIPv6(literalIp) : false;
5494
6039
  if (isLiteralPrivate) {
@@ -5537,7 +6082,7 @@ function performHttpFetch(targetUrl, opts) {
5537
6082
  rejectPromise(new Error(`Invalid redirect location fetching ${truncateUrl(targetUrl)}`));
5538
6083
  return;
5539
6084
  }
5540
- const sameOrigin = nextParsed.host === parsed.host;
6085
+ const sameOrigin = nextParsed.origin === parsed.origin;
5541
6086
  const nextOpts = {
5542
6087
  ...opts,
5543
6088
  redirectsLeft: opts.redirectsLeft - 1,
@@ -5656,11 +6201,11 @@ function parseDocSections(text) {
5656
6201
  const newlineLen = newlineMatch ? newlineMatch[0].length : 0;
5657
6202
  const fenceMatch = /^\s*(`{3,}|~{3,})/.exec(line);
5658
6203
  if (fenceMatch !== null && fenceMatch[1] !== void 0) {
5659
- const run3 = fenceMatch[1];
5660
- const ch = run3[0] ?? "";
6204
+ const run2 = fenceMatch[1];
6205
+ const ch = run2[0] ?? "";
5661
6206
  if (fence === null) {
5662
- fence = { ch, len: run3.length };
5663
- } else if (ch === fence.ch && run3.length >= fence.len && /^[ \t]*$/.test(line.slice(fenceMatch[0].length))) {
6207
+ fence = { ch, len: run2.length };
6208
+ } else if (ch === fence.ch && run2.length >= fence.len && /^[ \t]*$/.test(line.slice(fenceMatch[0].length))) {
5664
6209
  fence = null;
5665
6210
  }
5666
6211
  if (currentSection !== null) {
@@ -5719,8 +6264,8 @@ async function getSectionContent(fileId, heading, opts = {}) {
5719
6264
 
5720
6265
  // src/pack.ts
5721
6266
  init_define_import_meta_env();
5722
- import * as fs8 from "node:fs";
5723
- import * as path10 from "node:path";
6267
+ import * as fs12 from "node:fs";
6268
+ import * as path12 from "node:path";
5724
6269
 
5725
6270
  // node_modules/minimatch/dist/esm/index.js
5726
6271
  var esm_exports = {};
@@ -6803,11 +7348,11 @@ var qmarksTestNoExtDot = ([$0]) => {
6803
7348
  return (f) => f.length === len && f !== "." && f !== "..";
6804
7349
  };
6805
7350
  var defaultPlatform = typeof process === "object" && process ? typeof process.env === "object" && process.env && process.env.__MINIMATCH_TESTING_PLATFORM__ || process.platform : "posix";
6806
- var path9 = {
7351
+ var path11 = {
6807
7352
  win32: { sep: "\\" },
6808
7353
  posix: { sep: "/" }
6809
7354
  };
6810
- var sep2 = defaultPlatform === "win32" ? path9.win32.sep : path9.posix.sep;
7355
+ var sep2 = defaultPlatform === "win32" ? path11.win32.sep : path11.posix.sep;
6811
7356
  minimatch.sep = sep2;
6812
7357
  var GLOBSTAR = Symbol("globstar **");
6813
7358
  minimatch.GLOBSTAR = GLOBSTAR;
@@ -7592,7 +8137,7 @@ var LANG_MAP = {
7592
8137
  ".exs": "elixir"
7593
8138
  };
7594
8139
  function getLang(filePath) {
7595
- const ext2 = path10.extname(filePath).toLowerCase();
8140
+ const ext2 = path12.extname(filePath).toLowerCase();
7596
8141
  return LANG_MAP[ext2] ?? "";
7597
8142
  }
7598
8143
  function matches(rel, patterns) {
@@ -7602,30 +8147,30 @@ function matches(rel, patterns) {
7602
8147
  return patterns.some((pat) => mm.minimatch(norm, pat) || mm.minimatch(base, pat));
7603
8148
  }
7604
8149
  function isPathWithinRoot(rootReal, resolvedPath) {
7605
- const rel = path10.relative(rootReal, resolvedPath);
7606
- return rel !== "" && rel !== ".." && !rel.startsWith(".." + path10.sep) && !path10.isAbsolute(rel);
8150
+ const rel = path12.relative(rootReal, resolvedPath);
8151
+ return rel !== "" && rel !== ".." && !rel.startsWith(".." + path12.sep) && !path12.isAbsolute(rel);
7607
8152
  }
7608
8153
  function openWithinRoot(rootReal, p) {
7609
8154
  let fd;
7610
8155
  try {
7611
- fd = fs8.openSync(p, "r");
8156
+ fd = fs12.openSync(p, "r");
7612
8157
  } catch {
7613
8158
  return null;
7614
8159
  }
7615
8160
  let ownershipTransferred = false;
7616
8161
  try {
7617
- const stat = fs8.fstatSync(fd);
8162
+ const stat = fs12.fstatSync(fd);
7618
8163
  if (!stat.isFile()) return null;
7619
8164
  let realPath;
7620
8165
  try {
7621
- realPath = fs8.realpathSync(p);
8166
+ realPath = fs12.realpathSync(p);
7622
8167
  } catch {
7623
8168
  return "outside-root";
7624
8169
  }
7625
8170
  if (!isPathWithinRoot(rootReal, realPath)) return "outside-root";
7626
8171
  let realStat;
7627
8172
  try {
7628
- realStat = fs8.statSync(realPath);
8173
+ realStat = fs12.statSync(realPath);
7629
8174
  } catch {
7630
8175
  return "outside-root";
7631
8176
  }
@@ -7635,7 +8180,7 @@ function openWithinRoot(rootReal, p) {
7635
8180
  } finally {
7636
8181
  if (!ownershipTransferred) {
7637
8182
  try {
7638
- fs8.closeSync(fd);
8183
+ fs12.closeSync(fd);
7639
8184
  } catch {
7640
8185
  }
7641
8186
  }
@@ -7643,11 +8188,11 @@ function openWithinRoot(rootReal, p) {
7643
8188
  }
7644
8189
  function* resolveOpenCandidates(projectRoot, patterns, rootReal, ignorePatterns, seen, skipped) {
7645
8190
  for (const pattern of patterns) {
7646
- const p = path10.isAbsolute(pattern) ? pattern : path10.join(projectRoot, pattern);
8191
+ const p = path12.isAbsolute(pattern) ? pattern : path12.join(projectRoot, pattern);
7647
8192
  if (seen.has(p)) continue;
7648
8193
  let rel;
7649
8194
  try {
7650
- rel = path10.relative(projectRoot, p).replace(/\\/g, "/");
8195
+ rel = path12.relative(projectRoot, p).replace(/\\/g, "/");
7651
8196
  } catch {
7652
8197
  skipped.push(`${p} (outside project root)`);
7653
8198
  continue;
@@ -7659,7 +8204,7 @@ function* resolveOpenCandidates(projectRoot, patterns, rootReal, ignorePatterns,
7659
8204
  continue;
7660
8205
  }
7661
8206
  if (ignorePatterns && matches(rel, ignorePatterns)) {
7662
- fs8.closeSync(opened.fd);
8207
+ fs12.closeSync(opened.fd);
7663
8208
  continue;
7664
8209
  }
7665
8210
  yield { p, rel, fd: opened.fd, stat: opened.stat };
@@ -7721,7 +8266,7 @@ function stripBlockComments(content, pattern, lineStates) {
7721
8266
  );
7722
8267
  }
7723
8268
  function stripComments(content, filePath) {
7724
- const ext2 = path10.extname(filePath).toLowerCase();
8269
+ const ext2 = path12.extname(filePath).toLowerCase();
7725
8270
  const lineStates = computeLineStartQuoteStates(content);
7726
8271
  if (ext2 === ".py") {
7727
8272
  return stripLineComments(content, PY_LINE_COMMENT_RE, lineStates);
@@ -7759,7 +8304,7 @@ var SAFE_EXTS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp
7759
8304
  function scanSecrets(files) {
7760
8305
  const hits = [];
7761
8306
  for (const pf of files) {
7762
- if (SAFE_EXTS.has(path10.extname(pf.path).toLowerCase())) {
8307
+ if (SAFE_EXTS.has(path12.extname(pf.path).toLowerCase())) {
7763
8308
  continue;
7764
8309
  }
7765
8310
  for (const [lineno, line] of pf.content.split("\n").entries()) {
@@ -7782,10 +8327,10 @@ function scanSecrets(files) {
7782
8327
  function collectFiles(projectRoot, patterns, opts = {}) {
7783
8328
  const result = { files: [], skipped: [], total_lines: 0, total_tokens: 0 };
7784
8329
  const seen = /* @__PURE__ */ new Set();
7785
- const rootResolved = path10.resolve(projectRoot);
8330
+ const rootResolved = path12.resolve(projectRoot);
7786
8331
  let rootReal;
7787
8332
  try {
7788
- rootReal = fs8.realpathSync(rootResolved);
8333
+ rootReal = fs12.realpathSync(rootResolved);
7789
8334
  } catch {
7790
8335
  rootReal = rootResolved;
7791
8336
  }
@@ -7799,7 +8344,7 @@ function collectFiles(projectRoot, patterns, opts = {}) {
7799
8344
  }
7800
8345
  let content;
7801
8346
  try {
7802
- content = fs8.readFileSync(fd, "utf8");
8347
+ content = fs12.readFileSync(fd, "utf8");
7803
8348
  } catch {
7804
8349
  result.skipped.push(`${rel} (unreadable)`);
7805
8350
  continue;
@@ -7821,13 +8366,13 @@ function collectFiles(projectRoot, patterns, opts = {}) {
7821
8366
  result.total_lines += lines;
7822
8367
  result.total_tokens += tokens;
7823
8368
  } finally {
7824
- fs8.closeSync(fd);
8369
+ fs12.closeSync(fd);
7825
8370
  }
7826
8371
  }
7827
8372
  return result;
7828
8373
  }
7829
8374
  function collectFromStdin(projectRoot, opts = {}) {
7830
- const input = fs8.readFileSync(0, "utf8");
8375
+ const input = fs12.readFileSync(0, "utf8");
7831
8376
  const paths = input.split("\n").map((ln) => ln.trim()).filter((ln) => ln.length > 0);
7832
8377
  return collectFiles(projectRoot, paths, opts);
7833
8378
  }
@@ -7957,10 +8502,10 @@ function formatPack(result, style, opts = {}) {
7957
8502
  function estimateBudget(projectRoot, patterns, opts = {}) {
7958
8503
  const result = { entries: [], skipped: [], total_lines: 0, total_tokens: 0 };
7959
8504
  const seen = /* @__PURE__ */ new Set();
7960
- const rootResolved = path10.resolve(projectRoot);
8505
+ const rootResolved = path12.resolve(projectRoot);
7961
8506
  let rootReal;
7962
8507
  try {
7963
- rootReal = fs8.realpathSync(rootResolved);
8508
+ rootReal = fs12.realpathSync(rootResolved);
7964
8509
  } catch {
7965
8510
  rootReal = rootResolved;
7966
8511
  }
@@ -7975,7 +8520,7 @@ function estimateBudget(projectRoot, patterns, opts = {}) {
7975
8520
  let lines;
7976
8521
  let tokens;
7977
8522
  try {
7978
- const data = fs8.readFileSync(fd);
8523
+ const data = fs12.readFileSync(fd);
7979
8524
  const sampleSize = Math.min(1e3, data.length);
7980
8525
  const sampleLines = (data.toString("utf8", 0, sampleSize).match(/\n/g) || []).length;
7981
8526
  lines = data.length > sampleSize ? Math.ceil(sampleLines * (data.length / sampleSize)) : sampleLines + 1;
@@ -7993,7 +8538,7 @@ function estimateBudget(projectRoot, patterns, opts = {}) {
7993
8538
  result.total_lines += lines;
7994
8539
  result.total_tokens += tokens;
7995
8540
  } finally {
7996
- fs8.closeSync(fd);
8541
+ fs12.closeSync(fd);
7997
8542
  }
7998
8543
  }
7999
8544
  result.entries.sort((a, b) => b.tokens - a.tokens);
@@ -8269,193 +8814,1407 @@ function extractCargo(lines) {
8269
8814
  const body = detail && detail.length > 0 ? detail.join("\n").trim() : summaryByName.get(name);
8270
8815
  result.blocks.push({ name, body });
8271
8816
  }
8272
- return result;
8817
+ return result;
8818
+ }
8819
+ function extractGeneric(lines) {
8820
+ const result = {
8821
+ runner: "unknown",
8822
+ blocks: [],
8823
+ summaryLines: [],
8824
+ statsLine: ""
8825
+ };
8826
+ for (const line of lines) {
8827
+ const s = line.trimEnd();
8828
+ if (/\b(FAILED|FAILURE|ERROR)\b/i.test(s)) {
8829
+ result.summaryLines.push(s);
8830
+ }
8831
+ }
8832
+ return result;
8833
+ }
8834
+ function extractFailures(text, options) {
8835
+ const cleaned = stripAnsi(text);
8836
+ const lines = cleaned.split("\n");
8837
+ const detected = options?.runner ?? detectRunner(cleaned);
8838
+ if (detected === "pytest") {
8839
+ return extractPytest(lines);
8840
+ }
8841
+ if (detected === "jest") {
8842
+ return extractJest(lines);
8843
+ }
8844
+ if (detected === "go") {
8845
+ return extractGo(lines);
8846
+ }
8847
+ if (detected === "cargo") {
8848
+ return extractCargo(lines);
8849
+ }
8850
+ return extractGeneric(lines);
8851
+ }
8852
+ function formatFailuresText(result) {
8853
+ if (result.blocks.length === 0 && result.summaryLines.length === 0) {
8854
+ return "No failures found.";
8855
+ }
8856
+ const parts = [];
8857
+ const sep5 = "\u2500".repeat(60);
8858
+ for (const block of result.blocks) {
8859
+ parts.push(sep5, `FAIL ${block.name}`, sep5, block.body, "");
8860
+ }
8861
+ if (result.summaryLines.length > 0) {
8862
+ if (parts.length > 0) {
8863
+ parts.push(sep5);
8864
+ }
8865
+ parts.push(...result.summaryLines);
8866
+ }
8867
+ if (result.statsLine) {
8868
+ parts.push(result.statsLine);
8869
+ }
8870
+ const n = getFailureCount(result);
8871
+ parts.push(`
8872
+ ${n} failure(s) [${result.runner}]`);
8873
+ return parts.join("\n");
8874
+ }
8875
+ function formatFailuresJson(result) {
8876
+ return JSON.stringify(
8877
+ {
8878
+ runner: result.runner,
8879
+ count: getFailureCount(result),
8880
+ failures: result.blocks.map((b) => ({ name: b.name, body: b.body })),
8881
+ summary: result.summaryLines,
8882
+ stats: result.statsLine
8883
+ },
8884
+ null,
8885
+ 2
8886
+ );
8887
+ }
8888
+ function failureSignatures(result) {
8889
+ const raw = result.blocks.length > 0 ? result.blocks.map((b) => b.name.trim()) : result.summaryLines.map((s) => s.trim()).filter((s) => s !== "");
8890
+ return Array.from(new Set(raw));
8891
+ }
8892
+ function computeFailureDelta(prevSignatures, currSignatures) {
8893
+ if (prevSignatures === null) {
8894
+ return { hasBaseline: false, newlyFailing: [...currSignatures], newlyFixed: [], stillFailing: [] };
8895
+ }
8896
+ const prevSet = new Set(prevSignatures);
8897
+ const currSet = new Set(currSignatures);
8898
+ return {
8899
+ hasBaseline: true,
8900
+ newlyFailing: currSignatures.filter((s) => !prevSet.has(s)),
8901
+ newlyFixed: prevSignatures.filter((s) => !currSet.has(s)),
8902
+ stillFailing: currSignatures.filter((s) => prevSet.has(s))
8903
+ };
8904
+ }
8905
+ function formatFailureDeltaText(delta, runner) {
8906
+ if (!delta.hasBaseline) {
8907
+ const lines2 = [
8908
+ `No baseline yet for this project/key -- showing all ${delta.newlyFailing.length} current failure(s) as new. [${runner}]`
8909
+ ];
8910
+ if (delta.newlyFailing.length === 0) {
8911
+ lines2.push("(no failures)");
8912
+ } else {
8913
+ for (const name of delta.newlyFailing) lines2.push(`+ ${name}`);
8914
+ }
8915
+ return lines2.join("\n");
8916
+ }
8917
+ const lines = [];
8918
+ lines.push(`Newly failing (${delta.newlyFailing.length}):`);
8919
+ if (delta.newlyFailing.length === 0) {
8920
+ lines.push(" (none)");
8921
+ } else {
8922
+ for (const name of delta.newlyFailing) lines.push(` + ${name}`);
8923
+ }
8924
+ lines.push("");
8925
+ lines.push(`Newly fixed (${delta.newlyFixed.length}):`);
8926
+ if (delta.newlyFixed.length === 0) {
8927
+ lines.push(" (none)");
8928
+ } else {
8929
+ for (const name of delta.newlyFixed) lines.push(` - ${name}`);
8930
+ }
8931
+ lines.push("");
8932
+ lines.push(`Still failing (unchanged): ${delta.stillFailing.length}`);
8933
+ lines.push(
8934
+ `
8935
+ [${runner}] ${delta.newlyFailing.length} newly failing, ${delta.newlyFixed.length} newly fixed, ${delta.stillFailing.length} still failing`
8936
+ );
8937
+ return lines.join("\n");
8938
+ }
8939
+ function formatFailureDeltaJson(delta, runner) {
8940
+ return JSON.stringify(
8941
+ {
8942
+ runner,
8943
+ hasBaseline: delta.hasBaseline,
8944
+ newlyFailing: delta.newlyFailing,
8945
+ newlyFixed: delta.newlyFixed,
8946
+ stillFailingCount: delta.stillFailing.length
8947
+ },
8948
+ null,
8949
+ 2
8950
+ );
8951
+ }
8952
+
8953
+ // src/failures_state.ts
8954
+ init_define_import_meta_env();
8955
+ import * as fs13 from "node:fs";
8956
+ import * as path13 from "node:path";
8957
+ var KEY_RE = /^[A-Za-z0-9_-]{1,80}$/;
8958
+ var DEFAULT_FAILURES_STATE_KEY = "default";
8959
+ function validateKey(key) {
8960
+ if (!KEY_RE.test(key)) {
8961
+ throw new Error(
8962
+ `Invalid --key ${JSON.stringify(key)}: use only letters, digits, hyphens, underscores (max 80 chars)`
8963
+ );
8964
+ }
8965
+ }
8966
+ function failuresStatePath(projectHash, key) {
8967
+ validateKey(key);
8968
+ return path13.join(dataDir(), "projects", `${projectHash}_failures_${key}.json`);
8969
+ }
8970
+ function loadFailureSnapshot(projectHash, key) {
8971
+ const p = failuresStatePath(projectHash, key);
8972
+ try {
8973
+ if (!fs13.existsSync(p)) return null;
8974
+ const parsed = JSON.parse(fs13.readFileSync(p, "utf-8"));
8975
+ if (typeof parsed !== "object" || parsed === null) return null;
8976
+ const obj = parsed;
8977
+ if (!Array.isArray(obj.signatures)) return null;
8978
+ return {
8979
+ signatures: obj.signatures.filter((s) => typeof s === "string"),
8980
+ runner: typeof obj.runner === "string" ? obj.runner : "unknown",
8981
+ storedAt: typeof obj.storedAt === "number" ? obj.storedAt : 0
8982
+ };
8983
+ } catch {
8984
+ return null;
8985
+ }
8986
+ }
8987
+ function saveFailureSnapshot(projectHash, key, snapshot) {
8988
+ const p = failuresStatePath(projectHash, key);
8989
+ const dir = path13.dirname(p);
8990
+ ensureDirSync(dir);
8991
+ const content = JSON.stringify(snapshot);
8992
+ const doSave = () => {
8993
+ atomicWriteText(p, content);
8994
+ return true;
8995
+ };
8996
+ if (withFileLock(`${p}.lock`, doSave) === void 0) doSave();
8997
+ }
8998
+
8999
+ // src/text_commands.ts
9000
+ init_define_import_meta_env();
9001
+ import * as fs15 from "fs";
9002
+ import * as path15 from "path";
9003
+
9004
+ // src/project_memory.ts
9005
+ init_define_import_meta_env();
9006
+ import * as fs14 from "node:fs";
9007
+ import * as path14 from "node:path";
9008
+ var MAX_ENTRIES = 30;
9009
+ var KEY_RE2 = /^[A-Za-z0-9_-]{1,80}$/;
9010
+ function ordinal(a, b) {
9011
+ return a < b ? -1 : a > b ? 1 : 0;
9012
+ }
9013
+ function memoryPath(projectHash) {
9014
+ return path14.join(dataDir(), "projects", `${projectHash}_memory.toml`);
9015
+ }
9016
+ function validateKey2(key) {
9017
+ if (!KEY_RE2.test(key)) {
9018
+ throw new Error(
9019
+ `Invalid memory key ${JSON.stringify(key)}: use only letters, digits, hyphens, underscores (max 80 chars)`
9020
+ );
9021
+ }
9022
+ }
9023
+ function parseTOML(content) {
9024
+ const result = {};
9025
+ for (const line of content.split("\n")) {
9026
+ const trimmed = line.trim();
9027
+ if (!trimmed || trimmed.startsWith("#")) {
9028
+ continue;
9029
+ }
9030
+ const match2 = trimmed.match(/^([A-Za-z0-9_-]+)\s*=\s*"(.*)"\s*$/);
9031
+ if (match2) {
9032
+ const [, key, value] = match2;
9033
+ if (key && value !== void 0) {
9034
+ const unescaped = value.replace(/\\([\\nrt"])/g, (_, c) => {
9035
+ switch (c) {
9036
+ case "\\":
9037
+ return "\\";
9038
+ case "n":
9039
+ return "\n";
9040
+ case "r":
9041
+ return "\r";
9042
+ case '"':
9043
+ return '"';
9044
+ default:
9045
+ return _;
9046
+ }
9047
+ });
9048
+ result[key] = unescaped;
9049
+ }
9050
+ }
9051
+ }
9052
+ return result;
9053
+ }
9054
+ function loadRaw(filePath) {
9055
+ try {
9056
+ if (!fs14.existsSync(filePath)) {
9057
+ return {};
9058
+ }
9059
+ const content = fs14.readFileSync(filePath, "utf-8");
9060
+ return parseTOML(content);
9061
+ } catch {
9062
+ return {};
9063
+ }
9064
+ }
9065
+ function save(filePath, entries) {
9066
+ const lines = [];
9067
+ const sorted = Object.entries(entries).sort(([a], [b]) => ordinal(a, b));
9068
+ for (const [k, v] of sorted) {
9069
+ const escaped = v.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\r/g, "\\r").replace(/\n/g, "\\n");
9070
+ lines.push(`${k} = "${escaped}"`);
9071
+ }
9072
+ const content = lines.length > 0 ? lines.join("\n") + "\n" : "";
9073
+ const dir = path14.dirname(filePath);
9074
+ ensureDirSync(dir);
9075
+ atomicWriteText(filePath, content);
9076
+ }
9077
+ function loadEntries(projectHash) {
9078
+ return loadRaw(memoryPath(projectHash));
9079
+ }
9080
+ function setEntry(projectHash, key, value) {
9081
+ validateKey2(key);
9082
+ const p = memoryPath(projectHash);
9083
+ const dir = path14.dirname(p);
9084
+ ensureDirSync(dir);
9085
+ const doSet = () => {
9086
+ const entries = loadRaw(p);
9087
+ const isNewKey = !(key in entries);
9088
+ if (isNewKey && Object.keys(entries).length >= MAX_ENTRIES) {
9089
+ const keysToKeep = MAX_ENTRIES - 1;
9090
+ const allKeys = Object.keys(entries).sort((a, b) => ordinal(a, b));
9091
+ for (const k of allKeys.slice(keysToKeep)) {
9092
+ delete entries[k];
9093
+ }
9094
+ }
9095
+ entries[key] = value;
9096
+ save(p, entries);
9097
+ return true;
9098
+ };
9099
+ if (withFileLock(`${p}.lock`, doSet) === void 0) doSet();
9100
+ }
9101
+ function unsetEntry(projectHash, key) {
9102
+ validateKey2(key);
9103
+ const p = memoryPath(projectHash);
9104
+ const doUnset = () => {
9105
+ const entries = loadRaw(p);
9106
+ if (key in entries) {
9107
+ delete entries[key];
9108
+ save(p, entries);
9109
+ }
9110
+ return true;
9111
+ };
9112
+ if (withFileLock(`${p}.lock`, doUnset) === void 0) doUnset();
9113
+ }
9114
+ function clearAll(projectHash) {
9115
+ const p = memoryPath(projectHash);
9116
+ const doClear = () => {
9117
+ if (fs14.existsSync(p)) {
9118
+ save(p, {});
9119
+ }
9120
+ return true;
9121
+ };
9122
+ if (withFileLock(`${p}.lock`, doClear) === void 0) doClear();
9123
+ }
9124
+
9125
+ // src/text_commands.ts
9126
+ function readInput(src) {
9127
+ if (src !== void 0) return fs15.readFileSync(src, "utf8");
9128
+ return fs15.readFileSync(0, "utf8");
9129
+ }
9130
+ function splitLines(text) {
9131
+ return text.split(/\r?\n/);
9132
+ }
9133
+ var DEFAULT_KINDS = ["TODO", "FIXME", "HACK", "XXX", "NOTE"];
9134
+ function countUnescapedQuotes(text, quoteChar) {
9135
+ let count = 0;
9136
+ let backslashes = 0;
9137
+ for (const ch of text) {
9138
+ if (ch === "\\") {
9139
+ backslashes++;
9140
+ continue;
9141
+ }
9142
+ if (ch === quoteChar && backslashes % 2 === 0) count++;
9143
+ backslashes = 0;
9144
+ }
9145
+ return count;
9146
+ }
9147
+ function isInsideStringLiteral2(line, markerIndex) {
9148
+ const before = line.slice(0, markerIndex);
9149
+ const dqCount = countUnescapedQuotes(before, '"');
9150
+ return dqCount % 2 !== 0;
9151
+ }
9152
+ function isMarkerOccurrence(matched, hasColon) {
9153
+ return hasColon || matched === matched.toUpperCase();
9154
+ }
9155
+ function scanFileForTodos(filePath, kindSet) {
9156
+ let text;
9157
+ try {
9158
+ text = fs15.readFileSync(filePath, "utf8");
9159
+ } catch {
9160
+ return [];
9161
+ }
9162
+ const kindPattern = [...kindSet].map(escapeRegExp).join("|");
9163
+ const re = new RegExp(`\\b(${kindPattern})\\b(\\s*:)?\\s*(.*)`, "i");
9164
+ const items = [];
9165
+ const lines = splitLines(text);
9166
+ for (let i = 0; i < lines.length; i++) {
9167
+ const line = lines[i] ?? "";
9168
+ const m = re.exec(line);
9169
+ if (m === null) continue;
9170
+ if (!isMarkerOccurrence(m[1] ?? "", m[2] !== void 0)) continue;
9171
+ const idx = m.index ?? 0;
9172
+ if (isInsideStringLiteral2(line, idx)) continue;
9173
+ items.push({ file: filePath, line: i + 1, kind: m[1]?.toUpperCase() ?? "", text: (m[3] ?? "").trim() });
9174
+ }
9175
+ return items;
9176
+ }
9177
+ function collectTodoFiles(patterns) {
9178
+ if (patterns.length > 0) {
9179
+ const results = [];
9180
+ for (const p of patterns) {
9181
+ const abs = path15.resolve(p);
9182
+ try {
9183
+ const stat = fs15.statSync(abs);
9184
+ if (stat.isDirectory()) {
9185
+ results.push(...walkProject(abs).files);
9186
+ } else {
9187
+ results.push(abs);
9188
+ }
9189
+ } catch {
9190
+ }
9191
+ }
9192
+ return results;
9193
+ }
9194
+ return walkProject(process.cwd()).files;
9195
+ }
9196
+ function cmdTodo(patterns, opts) {
9197
+ const kindSet = new Set(
9198
+ opts.kinds !== void 0 && opts.kinds.length > 0 ? opts.kinds.split(",").map((k) => k.trim().toUpperCase()) : DEFAULT_KINDS
9199
+ );
9200
+ const files = collectTodoFiles(patterns);
9201
+ const items = [];
9202
+ for (const f of files) {
9203
+ items.push(...scanFileForTodos(f, kindSet));
9204
+ }
9205
+ if (opts.json === true) {
9206
+ process.stdout.write(JSON.stringify({ items }, null, 2) + "\n");
9207
+ return;
9208
+ }
9209
+ if (items.length === 0) {
9210
+ process.stdout.write("No TODO markers found.\n");
9211
+ return;
9212
+ }
9213
+ const groupBy = opts.group ?? "file";
9214
+ if (groupBy === "kind") {
9215
+ const byKind = /* @__PURE__ */ new Map();
9216
+ for (const item of items) {
9217
+ const arr = byKind.get(item.kind) ?? [];
9218
+ arr.push(item);
9219
+ byKind.set(item.kind, arr);
9220
+ }
9221
+ for (const [kind, group] of byKind) {
9222
+ process.stdout.write(`
9223
+ [${kind}]
9224
+ `);
9225
+ for (const item of group) {
9226
+ process.stdout.write(` ${item.file}:${item.line} ${item.text}
9227
+ `);
9228
+ }
9229
+ }
9230
+ } else {
9231
+ const byFile = /* @__PURE__ */ new Map();
9232
+ for (const item of items) {
9233
+ const arr = byFile.get(item.file) ?? [];
9234
+ arr.push(item);
9235
+ byFile.set(item.file, arr);
9236
+ }
9237
+ for (const [file, group] of byFile) {
9238
+ for (const item of group) {
9239
+ process.stdout.write(`${file}:${item.line} ${item.kind} ${item.text}
9240
+ `);
9241
+ }
9242
+ }
9243
+ }
9244
+ }
9245
+ function parsePythonBlock(lines, start) {
9246
+ if (!/^Traceback \(most recent call last\):/.test(lines[start] ?? "")) return null;
9247
+ let i = start + 1;
9248
+ const frames = [];
9249
+ let block = null;
9250
+ while (i < lines.length) {
9251
+ const fl = lines[i] ?? "";
9252
+ const fm = /^\s{2}File "([^"]+)", line (\d+), in (\S+)/.exec(fl);
9253
+ if (fm !== null) {
9254
+ i++;
9255
+ const peek = lines[i]?.trim();
9256
+ const hasContext = peek !== void 0 && !peek.startsWith("File ") && !peek.startsWith("Traceback");
9257
+ if (hasContext) i++;
9258
+ frames.push({ file: fm[1] ?? "", lineNo: Number.parseInt(fm[2] ?? "0", 10), func: fm[3] ?? "", context: hasContext ? peek ?? "" : "" });
9259
+ continue;
9260
+ }
9261
+ if (/^Traceback \(most recent call last\):/.test(fl)) {
9262
+ break;
9263
+ }
9264
+ if (!/^\s/.test(fl) && fl.trim() !== "") {
9265
+ block = { frames, exception: fl.trim() };
9266
+ i++;
9267
+ break;
9268
+ }
9269
+ i++;
9270
+ }
9271
+ if (frames.length > 0 && block === null) {
9272
+ block = { frames, exception: "" };
9273
+ }
9274
+ return { block, nextIndex: i };
9275
+ }
9276
+ var NODE_FRAME_WITH_FUNC_RE = /^\s+at\s+(.+?)\s+\(([^)]+)\)\s*$/;
9277
+ var NODE_FRAME_ANON_RE = /^\s+at\s+([^\s()][^()]*)\s*$/;
9278
+ var NODE_LOC_RE = /^(.*):(\d+):(\d+)$/;
9279
+ function parseNodeFrameLine(line) {
9280
+ const withFunc = NODE_FRAME_WITH_FUNC_RE.exec(line);
9281
+ if (withFunc !== null) {
9282
+ const loc = NODE_LOC_RE.exec(withFunc[2] ?? "");
9283
+ if (loc === null) return null;
9284
+ return { file: loc[1] ?? "", lineNo: Number.parseInt(loc[2] ?? "0", 10), func: withFunc[1] ?? "" };
9285
+ }
9286
+ const anon = NODE_FRAME_ANON_RE.exec(line);
9287
+ if (anon !== null) {
9288
+ const loc = NODE_LOC_RE.exec((anon[1] ?? "").trim());
9289
+ if (loc === null) return null;
9290
+ return { file: loc[1] ?? "", lineNo: Number.parseInt(loc[2] ?? "0", 10), func: "" };
9291
+ }
9292
+ return null;
9293
+ }
9294
+ function parseNodeBlock(lines, start) {
9295
+ const header = (lines[start] ?? "").trim();
9296
+ if (header === "") return null;
9297
+ const firstFrame = parseNodeFrameLine(lines[start + 1] ?? "");
9298
+ if (firstFrame === null) return null;
9299
+ const frames = [firstFrame];
9300
+ let i = start + 2;
9301
+ while (i < lines.length) {
9302
+ const f = parseNodeFrameLine(lines[i] ?? "");
9303
+ if (f === null) break;
9304
+ frames.push(f);
9305
+ i++;
9306
+ }
9307
+ return { block: { frames, exception: header }, nextIndex: i };
9308
+ }
9309
+ var RUST_PANIC_HEADER_RE = /^thread '[^']*' panicked at (.+):(\d+):(\d+):\s*$/;
9310
+ var RUST_BACKTRACE_NOTE_RE = /^note: run with `RUST_BACKTRACE=1`/;
9311
+ var RUST_BACKTRACE_NUM_RE = /^\s*\d+:\s+(.+)$/;
9312
+ var RUST_BACKTRACE_AT_RE = /^\s+at\s+(.+):(\d+):(\d+)\s*$/;
9313
+ function parseRustBlock(lines, start) {
9314
+ const header = lines[start] ?? "";
9315
+ const hm = RUST_PANIC_HEADER_RE.exec(header);
9316
+ if (hm === null) return null;
9317
+ const panicFrame = { file: hm[1] ?? "", lineNo: Number.parseInt(hm[2] ?? "0", 10), func: "" };
9318
+ let i = start + 1;
9319
+ const msgLines = [];
9320
+ while (i < lines.length) {
9321
+ const l = lines[i] ?? "";
9322
+ if (l.trim() === "") {
9323
+ i++;
9324
+ break;
9325
+ }
9326
+ if (RUST_BACKTRACE_NOTE_RE.test(l)) {
9327
+ i++;
9328
+ break;
9329
+ }
9330
+ if (l.trim() === "stack backtrace:") break;
9331
+ msgLines.push(l.trim());
9332
+ i++;
9333
+ }
9334
+ const exception = msgLines.join(" ");
9335
+ if ((lines[i] ?? "").trim() === "stack backtrace:") {
9336
+ i++;
9337
+ const frames = [];
9338
+ while (i < lines.length) {
9339
+ const numMatch = RUST_BACKTRACE_NUM_RE.exec(lines[i] ?? "");
9340
+ if (numMatch === null) break;
9341
+ const atMatch = RUST_BACKTRACE_AT_RE.exec(lines[i + 1] ?? "");
9342
+ if (atMatch === null) {
9343
+ i += 1;
9344
+ continue;
9345
+ }
9346
+ frames.push({ file: atMatch[1] ?? "", lineNo: Number.parseInt(atMatch[2] ?? "0", 10), func: (numMatch[1] ?? "").trim() });
9347
+ i += 2;
9348
+ }
9349
+ if (frames.length > 0) {
9350
+ return { block: { frames, exception }, nextIndex: i };
9351
+ }
9352
+ }
9353
+ return { block: { frames: [panicFrame], exception }, nextIndex: i };
9354
+ }
9355
+ var JVM_HEADER_RE = /^(?:Exception in thread "[^"]*" )?(?:Caused by: )?((?:[A-Za-z_$][\w$]*\.)+[A-Za-z_$][\w$]*): (.*)$/;
9356
+ var JVM_FRAME_RE = /^\s+at\s+(\S+)\(([^)]*)\)\s*$/;
9357
+ var JVM_MORE_RE = /^\s*\.\.\.\s+\d+\s+more\s*$/;
9358
+ function parseJvmFrameLine(line) {
9359
+ const m = JVM_FRAME_RE.exec(line);
9360
+ if (m === null) return null;
9361
+ const func = m[1] ?? "";
9362
+ const inner = m[2] ?? "";
9363
+ if (inner === "Native Method" || inner === "Unknown Source") {
9364
+ return { file: inner, lineNo: 0, func };
9365
+ }
9366
+ const lm = /^(.+):(\d+)$/.exec(inner);
9367
+ if (lm === null) return null;
9368
+ return { file: lm[1] ?? "", lineNo: Number.parseInt(lm[2] ?? "0", 10), func };
9369
+ }
9370
+ function parseJvmBlock(lines, start) {
9371
+ const header = lines[start] ?? "";
9372
+ if (JVM_HEADER_RE.exec(header) === null) return null;
9373
+ const firstFrame = parseJvmFrameLine(lines[start + 1] ?? "");
9374
+ if (firstFrame === null) return null;
9375
+ const frames = [firstFrame];
9376
+ let i = start + 2;
9377
+ while (i < lines.length) {
9378
+ const l = lines[i] ?? "";
9379
+ if (JVM_MORE_RE.test(l)) {
9380
+ i++;
9381
+ break;
9382
+ }
9383
+ const f = parseJvmFrameLine(l);
9384
+ if (f === null) break;
9385
+ frames.push(f);
9386
+ i++;
9387
+ }
9388
+ return { block: { frames, exception: header.trim() }, nextIndex: i };
9389
+ }
9390
+ var DOTNET_HEADER_RE = /^(?:Unhandled exception\. )?((?:[A-Za-z_][\w]*\.)+[A-Za-z_][\w]*): (.*)$/;
9391
+ var DOTNET_FRAME_WITH_LOC_RE = /^\s+at\s+(.+?)\s+in\s+(.+):line\s+(\d+)\s*$/;
9392
+ var DOTNET_FRAME_NO_LOC_RE = /^\s+at\s+(.+)$/;
9393
+ function parseDotnetFrameLine(line) {
9394
+ const withLoc = DOTNET_FRAME_WITH_LOC_RE.exec(line);
9395
+ if (withLoc !== null) {
9396
+ return { file: withLoc[2] ?? "", lineNo: Number.parseInt(withLoc[3] ?? "0", 10), func: withLoc[1] ?? "" };
9397
+ }
9398
+ const noLoc = DOTNET_FRAME_NO_LOC_RE.exec(line);
9399
+ if (noLoc !== null) {
9400
+ return { file: "", lineNo: 0, func: (noLoc[1] ?? "").trim() };
9401
+ }
9402
+ return null;
9403
+ }
9404
+ function parseDotnetBlock(lines, start) {
9405
+ const header = lines[start] ?? "";
9406
+ if (DOTNET_HEADER_RE.exec(header) === null) return null;
9407
+ const firstFrame = DOTNET_FRAME_WITH_LOC_RE.exec(lines[start + 1] ?? "") !== null ? parseDotnetFrameLine(lines[start + 1] ?? "") : null;
9408
+ if (firstFrame === null) return null;
9409
+ const frames = [firstFrame];
9410
+ let i = start + 2;
9411
+ while (i < lines.length) {
9412
+ const f = parseDotnetFrameLine(lines[i] ?? "");
9413
+ if (f === null) break;
9414
+ frames.push(f);
9415
+ i++;
9416
+ }
9417
+ return { block: { frames, exception: header.trim() }, nextIndex: i };
9418
+ }
9419
+ function parseTracebacks(text) {
9420
+ const lines = splitLines(text);
9421
+ const blocks = [];
9422
+ let i = 0;
9423
+ while (i < lines.length) {
9424
+ const result = parsePythonBlock(lines, i) ?? parseRustBlock(lines, i) ?? parseNodeBlock(lines, i) ?? parseJvmBlock(lines, i) ?? parseDotnetBlock(lines, i);
9425
+ if (result === null) {
9426
+ i++;
9427
+ continue;
9428
+ }
9429
+ if (result.block !== null) blocks.push(result.block);
9430
+ i = result.nextIndex;
9431
+ }
9432
+ return blocks;
9433
+ }
9434
+ function isPathUnderRoot(normalPath, normalRoot) {
9435
+ if (!normalPath.startsWith(normalRoot)) return false;
9436
+ if (normalPath === normalRoot) return true;
9437
+ const nextChar = normalPath[normalRoot.length];
9438
+ return nextChar === "/" || nextChar === "\\";
9439
+ }
9440
+ function isProjectFrame(framePath, cwd) {
9441
+ if (framePath.startsWith("node:")) return false;
9442
+ if (framePath === "" || framePath === "Native Method" || framePath === "Unknown Source") return false;
9443
+ const normalCwd = normalizeDarwinSystemAlias(foldPath(canonicalize(cwd)));
9444
+ const normalAbs = normalizeDarwinSystemAlias(foldPath(canonicalize(framePath, cwd)));
9445
+ if (isPathUnderRoot(normalAbs, normalCwd)) {
9446
+ return true;
9447
+ }
9448
+ if (framePath.includes("site-packages") || framePath.includes("lib/python")) return false;
9449
+ if (framePath.includes("/rustc/")) return false;
9450
+ if (framePath.includes(".cargo/registry") || framePath.includes(".cargo\\registry")) return false;
9451
+ if (/^<.+>$/.test(framePath)) return false;
9452
+ if (!path15.isAbsolute(framePath) && !framePath.startsWith("..")) return true;
9453
+ return false;
9454
+ }
9455
+ function resolveFrameSymbol(frame, projectRoot) {
9456
+ const filePath = resolveIndexPath(frame.file, projectRoot);
9457
+ const syms = querySymbols({ filePath, limit: ALL_SYMBOLS_IN_FILE_LIMIT });
9458
+ const match2 = enclosingSymbol(syms, frame.lineNo);
9459
+ if (match2 === null) return null;
9460
+ return {
9461
+ key: `${match2.filePath}::${match2.name}`,
9462
+ name: match2.name,
9463
+ kind: match2.kind,
9464
+ filePath: match2.filePath,
9465
+ lineStart: match2.lineStart,
9466
+ lineEnd: match2.lineEnd,
9467
+ body: resolveBody(match2)
9468
+ };
9469
+ }
9470
+ function formatFrameBody(frame, projectRoot, seen) {
9471
+ const resolved = resolveFrameSymbol(frame, projectRoot);
9472
+ if (resolved === null) {
9473
+ return [` # body: No symbols enclosing line ${frame.lineNo} in '${frame.file}'`];
9474
+ }
9475
+ const header = ` # body: ${resolved.name} ${resolved.kind} ${resolved.filePath}:${resolved.lineStart}-${resolved.lineEnd}`;
9476
+ if (seen.has(resolved.key)) {
9477
+ return [`${header} (same as above)`];
9478
+ }
9479
+ seen.set(resolved.key, true);
9480
+ return [header, resolved.body];
9481
+ }
9482
+ function cmdTrace(src, opts) {
9483
+ const text = readInput(src);
9484
+ const blocks = parseTracebacks(text);
9485
+ if (blocks.length === 0) {
9486
+ process.stderr.write("token-goat: no traceback found\n");
9487
+ process.exitCode = 0;
9488
+ return;
9489
+ }
9490
+ const cwd = process.cwd();
9491
+ const keepN = opts.keep !== void 0 ? requireNonNegativeStrictInt("--keep", opts.keep) : 0;
9492
+ const filtered = blocks.map((b) => {
9493
+ let frames = b.frames.filter((f) => isProjectFrame(f.file, cwd));
9494
+ if (keepN > 0 && frames.length > keepN) frames = frames.slice(frames.length - keepN);
9495
+ return { ...b, frames };
9496
+ });
9497
+ if (opts.json === true) {
9498
+ if (opts.bodies !== true) {
9499
+ process.stdout.write(JSON.stringify({ tracebacks: filtered }, null, 2) + "\n");
9500
+ return;
9501
+ }
9502
+ const seenJson = /* @__PURE__ */ new Map();
9503
+ const withBodies = filtered.map((b) => ({
9504
+ ...b,
9505
+ frames: b.frames.map((f) => {
9506
+ const resolved = resolveFrameSymbol(f, cwd);
9507
+ if (resolved === null) return { ...f };
9508
+ if (seenJson.has(resolved.key)) {
9509
+ return { ...f, bodyDuplicateOf: resolved.key };
9510
+ }
9511
+ seenJson.set(resolved.key, true);
9512
+ return {
9513
+ ...f,
9514
+ bodySymbol: { name: resolved.name, kind: resolved.kind, filePath: resolved.filePath, lineStart: resolved.lineStart, lineEnd: resolved.lineEnd },
9515
+ body: resolved.body
9516
+ };
9517
+ })
9518
+ }));
9519
+ process.stdout.write(JSON.stringify({ tracebacks: withBodies }, null, 2) + "\n");
9520
+ return;
9521
+ }
9522
+ const seenBodies = /* @__PURE__ */ new Map();
9523
+ for (const [blockIndex, block] of filtered.entries()) {
9524
+ process.stdout.write("Traceback (most recent call last):\n");
9525
+ const projectFrameCount = blocks[blockIndex]?.frames.filter((f) => isProjectFrame(f.file, cwd)).length ?? 0;
9526
+ const droppedByKeep = projectFrameCount - block.frames.length;
9527
+ if (droppedByKeep > 0) {
9528
+ const noun = droppedByKeep === 1 ? "frame" : "frames";
9529
+ process.stdout.write(` ...(${droppedByKeep} more ${noun} elided; use a higher --keep to see more)
9530
+ `);
9531
+ }
9532
+ for (const f of block.frames) {
9533
+ process.stdout.write(` File "${f.file}", line ${f.lineNo}, in ${f.func}
9534
+ `);
9535
+ if (f.context) process.stdout.write(` ${f.context}
9536
+ `);
9537
+ if (opts.bodies === true) {
9538
+ for (const line of formatFrameBody(f, cwd, seenBodies)) process.stdout.write(`${line}
9539
+ `);
9540
+ }
9541
+ }
9542
+ if (block.frames.length === 0) {
9543
+ const preFilterCount = blocks[blockIndex]?.frames.length ?? 0;
9544
+ const noun = preFilterCount === 1 ? "frame" : "frames";
9545
+ const verb = preFilterCount === 1 ? "was" : "were";
9546
+ process.stdout.write(` (all ${preFilterCount} ${noun} ${verb} filtered out as non-project -- this traceback runs entirely through dependency or runtime code)
9547
+ `);
9548
+ }
9549
+ if (block.exception) process.stdout.write(`${block.exception}
9550
+ `);
9551
+ process.stdout.write("\n");
9552
+ }
9553
+ }
9554
+ var VOLATILE_SUBS = [
9555
+ { re: /\b\d{1,2}:\d{2}:\d{2}(?:\.\d+)?\b/g, placeholder: "[HH:MM:SS]" },
9556
+ { re: /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, placeholder: "[UUID]" },
9557
+ { re: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g, placeholder: "[IP]" },
9558
+ { re: /\b[0-9a-f]{8,}\b/g, placeholder: "[HEX]" },
9559
+ // Bare integers (counters, PIDs, ports, line numbers, byte counts, elapsed-ms values). Must run last: it operates on a string that has already had HH:MM:SS/UUID/IP/HEX replaced with their placeholders, so it never competes with those rules for the same digits. The lookbehind/lookahead exclude letters, digits AND underscore (not just \b, which treats underscore as a word character) so a digit run embedded in a longer token like v2, utf8, x86_64, sha256, or test_3_case is left untouched.
9560
+ { re: /(?<![A-Za-z0-9_])\d+(?![A-Za-z0-9_])/g, placeholder: "[N]" }
9561
+ ];
9562
+ function normalizeVolatile(line) {
9563
+ let out2 = line;
9564
+ for (const sub of VOLATILE_SUBS) {
9565
+ out2 = out2.replace(sub.re, sub.placeholder);
9566
+ }
9567
+ return out2;
9568
+ }
9569
+ var MAX_FOLD_REPEATS_KEYS = 2e4;
9570
+ function applyFiltersAndFold(lines, noNormalize, foldRepeats) {
9571
+ const dropped = [];
9572
+ for (const raw of lines) {
9573
+ let cur = raw;
9574
+ for (const filter2 of FILTERS) {
9575
+ if (cur === null) break;
9576
+ if (filter2.pattern !== null && !filter2.pattern.test(cur)) continue;
9577
+ cur = filter2.replacer(cur);
9578
+ break;
9579
+ }
9580
+ if (cur !== null) dropped.push(cur);
9581
+ }
9582
+ const folded = [];
9583
+ if (!foldRepeats) {
9584
+ let prevKey2 = null;
9585
+ let lastText = "";
9586
+ let count = 0;
9587
+ for (const line of dropped) {
9588
+ const key = noNormalize ? line : normalizeVolatile(line);
9589
+ if (key === prevKey2) {
9590
+ count++;
9591
+ } else {
9592
+ if (prevKey2 !== null) folded.push({ text: lastText, count });
9593
+ prevKey2 = key;
9594
+ lastText = line;
9595
+ count = 1;
9596
+ }
9597
+ }
9598
+ if (prevKey2 !== null) folded.push({ text: lastText, count });
9599
+ return folded;
9600
+ }
9601
+ const keyIndex = /* @__PURE__ */ new Map();
9602
+ let prevKey = null;
9603
+ for (const line of dropped) {
9604
+ const key = noNormalize ? line : normalizeVolatile(line);
9605
+ const seenIdx = keyIndex.get(key);
9606
+ if (seenIdx !== void 0) {
9607
+ const entry = folded[seenIdx];
9608
+ if (entry !== void 0) entry.count++;
9609
+ } else if (key === prevKey && folded.length > 0) {
9610
+ const entry = folded[folded.length - 1];
9611
+ if (entry !== void 0) entry.count++;
9612
+ } else {
9613
+ folded.push({ text: line, count: 1 });
9614
+ if (keyIndex.size < MAX_FOLD_REPEATS_KEYS) keyIndex.set(key, folded.length - 1);
9615
+ }
9616
+ prevKey = key;
9617
+ }
9618
+ return folded;
9619
+ }
9620
+ function cmdLogfold(src, opts) {
9621
+ const text = readInput(src);
9622
+ let lines = splitLines(text);
9623
+ if (opts.tail !== void 0) {
9624
+ const n = requireNonNegativeStrictInt("--tail", opts.tail);
9625
+ lines = lines.slice(Math.max(0, lines.length - n));
9626
+ }
9627
+ const folded = applyFiltersAndFold(lines, opts.noNormalize === true, opts.foldRepeats === true);
9628
+ if (opts.json === true) {
9629
+ process.stdout.write(JSON.stringify({ lines: folded }, null, 2) + "\n");
9630
+ return;
9631
+ }
9632
+ for (const item of folded) {
9633
+ if (item.count > 1) {
9634
+ process.stdout.write(`${item.text} (x${item.count})
9635
+ `);
9636
+ } else {
9637
+ process.stdout.write(`${item.text}
9638
+ `);
9639
+ }
9640
+ }
9641
+ }
9642
+ var LOCK_PRIORITY = [
9643
+ "package-lock.json",
9644
+ "yarn.lock",
9645
+ "pnpm-lock.yaml",
9646
+ "poetry.lock",
9647
+ "uv.lock",
9648
+ "Pipfile.lock",
9649
+ "Cargo.lock"
9650
+ ];
9651
+ function findLockfile(startPath) {
9652
+ const stat = fs15.statSync(startPath, { throwIfNoEntry: false });
9653
+ if (stat !== void 0 && stat.isFile()) {
9654
+ return { file: startPath, others: [] };
9655
+ }
9656
+ const dir = stat !== void 0 && stat.isDirectory() ? startPath : path15.dirname(startPath);
9657
+ const found = [];
9658
+ for (const name of LOCK_PRIORITY) {
9659
+ const candidate = path15.join(dir, name);
9660
+ if (fs15.existsSync(candidate)) found.push(candidate);
9661
+ }
9662
+ try {
9663
+ const entries = fs15.readdirSync(dir);
9664
+ for (const e of entries) {
9665
+ if (/^requirements.*\.txt$/.test(e)) {
9666
+ const p = path15.join(dir, e);
9667
+ if (!found.includes(p)) found.push(p);
9668
+ }
9669
+ }
9670
+ } catch {
9671
+ }
9672
+ if (found.length === 0) return null;
9673
+ const primary = found[0];
9674
+ return { file: primary, others: found.slice(1) };
9675
+ }
9676
+ function collectV1Dependencies(deps, isDirect, out2) {
9677
+ for (const [name, val] of Object.entries(deps)) {
9678
+ out2.push({ name, version: val.version ?? "", kind: isDirect ? "direct" : "transitive" });
9679
+ if (val.dependencies !== void 0) collectV1Dependencies(val.dependencies, false, out2);
9680
+ }
9681
+ }
9682
+ function parsePackageLockJson(content) {
9683
+ const raw = JSON.parse(content);
9684
+ if (raw.packages === void 0 && raw.dependencies !== void 0) {
9685
+ const deps2 = [];
9686
+ collectV1Dependencies(raw.dependencies, true, deps2);
9687
+ return deps2;
9688
+ }
9689
+ const pkgs = raw.packages ?? {};
9690
+ const directDeps = pkgs[""] ?? {};
9691
+ const allDirect = /* @__PURE__ */ new Set([
9692
+ ...Object.keys(directDeps.dependencies ?? {}),
9693
+ ...Object.keys(directDeps.devDependencies ?? {}),
9694
+ ...Object.keys(directDeps.optionalDependencies ?? {})
9695
+ ]);
9696
+ const deps = [];
9697
+ for (const [key, val] of Object.entries(pkgs)) {
9698
+ if (key === "") continue;
9699
+ const name = key.split("node_modules/").pop() ?? key;
9700
+ const version = val.version ?? "";
9701
+ const isTopLevel = key === `node_modules/${name}`;
9702
+ deps.push({ name, version, kind: isTopLevel && allDirect.has(name) ? "direct" : "transitive" });
9703
+ }
9704
+ return deps;
9705
+ }
9706
+ function parseYarnLock(content) {
9707
+ const deps = [];
9708
+ const headerRe = /^"?(@?[^@"]+)@/;
9709
+ const versionRe = /^\s{2}version[:\s]+"?([^"\s]+)"?/;
9710
+ const lines = splitLines(content);
9711
+ let pendingName = null;
9712
+ for (const line of lines) {
9713
+ if (line.trim() === "" || line.startsWith("#")) {
9714
+ pendingName = null;
9715
+ continue;
9716
+ }
9717
+ if (!line.startsWith(" ") && !line.startsWith(" ")) {
9718
+ const m = headerRe.exec(line);
9719
+ if (m !== null) pendingName = m[1]?.trim() ?? null;
9720
+ continue;
9721
+ }
9722
+ if (pendingName !== null) {
9723
+ const vm = versionRe.exec(line);
9724
+ if (vm !== null) {
9725
+ deps.push({ name: pendingName, version: vm[1] ?? "", kind: "unknown" });
9726
+ pendingName = null;
9727
+ }
9728
+ }
9729
+ }
9730
+ return deps;
9731
+ }
9732
+ function parseTomlPackages(content) {
9733
+ const deps = [];
9734
+ const lines = splitLines(content);
9735
+ let inPackage = false;
9736
+ let name = "";
9737
+ let version = "";
9738
+ for (const line of lines) {
9739
+ if (/^\[\[package\]\]/.test(line)) {
9740
+ if (inPackage && name) deps.push({ name, version, kind: "unknown" });
9741
+ inPackage = true;
9742
+ name = "";
9743
+ version = "";
9744
+ continue;
9745
+ }
9746
+ if (inPackage && /^name\s*=/.test(line)) {
9747
+ const m = /=\s*"([^"]+)"/.exec(line);
9748
+ if (m !== null) name = m[1] ?? "";
9749
+ }
9750
+ if (inPackage && /^version\s*=/.test(line)) {
9751
+ const m = /=\s*"([^"]+)"/.exec(line);
9752
+ if (m !== null) version = m[1] ?? "";
9753
+ }
9754
+ }
9755
+ if (inPackage && name) deps.push({ name, version, kind: "unknown" });
9756
+ return deps;
9757
+ }
9758
+ function parsePipfileLock(content) {
9759
+ const raw = JSON.parse(content);
9760
+ const deps = [];
9761
+ for (const [name, meta] of Object.entries(raw.default ?? {})) {
9762
+ deps.push({ name, version: (meta.version ?? "").replace(/^==/, ""), kind: "unknown" });
9763
+ }
9764
+ for (const [name, meta] of Object.entries(raw.develop ?? {})) {
9765
+ deps.push({ name, version: (meta.version ?? "").replace(/^==/, ""), kind: "unknown" });
9766
+ }
9767
+ return deps;
9768
+ }
9769
+ function parseRequirementsTxt(content) {
9770
+ const deps = [];
9771
+ for (const raw of splitLines(content)) {
9772
+ if (/^\s*#/.test(raw)) continue;
9773
+ const eggMatch = /^\s*(?:-e\s+|--editable[\s=]+)?(?:git|hg|svn|bzr)\+.*#egg=([A-Za-z0-9_.-]+)/.exec(raw);
9774
+ if (eggMatch !== null) {
9775
+ deps.push({ name: eggMatch[1] ?? "", version: "", kind: "unknown" });
9776
+ continue;
9777
+ }
9778
+ const line = raw.split("#")[0]?.trim() ?? "";
9779
+ if (!line || line.startsWith("-")) continue;
9780
+ const m = /^([A-Za-z0-9_.-]+)(?:\[[^\]]*\])?\s*(?:[>=!~<]+\s*([^\s,;]+))?/.exec(line);
9781
+ if (m !== null) {
9782
+ deps.push({ name: m[1] ?? "", version: m[2] ?? "", kind: "unknown" });
9783
+ }
9784
+ }
9785
+ return deps;
9786
+ }
9787
+ function stripPnpmPeerSuffix(value) {
9788
+ let end = value.length;
9789
+ while (end > 0 && value[end - 1] === ")") {
9790
+ let depth = 0;
9791
+ let i = end - 1;
9792
+ for (; i >= 0; i--) {
9793
+ const ch = value[i];
9794
+ if (ch === ")") depth++;
9795
+ else if (ch === "(") {
9796
+ depth--;
9797
+ if (depth === 0) break;
9798
+ }
9799
+ }
9800
+ if (depth !== 0 || i < 0) break;
9801
+ end = i;
9802
+ }
9803
+ return value.slice(0, end);
9804
+ }
9805
+ function stripPnpmLegacyPeerHash(value) {
9806
+ const idx = value.indexOf("_");
9807
+ return idx === -1 ? value : value.slice(0, idx);
9808
+ }
9809
+ function splitPnpmPackageKey(rawKey) {
9810
+ const key = stripPnpmPeerSuffix(rawKey.startsWith("/") ? rawKey.slice(1) : rawKey);
9811
+ const scoped = key.startsWith("@");
9812
+ const nameSearchStart = scoped ? key.indexOf("/") : 0;
9813
+ if (scoped && nameSearchStart === -1) return null;
9814
+ const additionalSlashIdx = key.indexOf("/", nameSearchStart + 1);
9815
+ if (additionalSlashIdx !== -1) {
9816
+ const name2 = key.slice(0, additionalSlashIdx);
9817
+ const version2 = stripPnpmLegacyPeerHash(key.slice(additionalSlashIdx + 1));
9818
+ if (!name2 || !version2) return null;
9819
+ return { name: name2, version: version2 };
9820
+ }
9821
+ const versionSepIdx = key.indexOf("@", nameSearchStart + 1);
9822
+ if (versionSepIdx === -1) return null;
9823
+ const name = key.slice(0, versionSepIdx);
9824
+ const version = key.slice(versionSepIdx + 1);
9825
+ if (!name || !version) return null;
9826
+ return { name, version };
9827
+ }
9828
+ function collectPnpmDirectVersions(section, out2) {
9829
+ if (section === null || typeof section !== "object") return;
9830
+ for (const [name, val] of Object.entries(section)) {
9831
+ if (typeof val === "string" && val) {
9832
+ out2.set(name, stripPnpmLegacyPeerHash(stripPnpmPeerSuffix(val)));
9833
+ continue;
9834
+ }
9835
+ const version = val?.version;
9836
+ if (typeof version === "string" && version) out2.set(name, stripPnpmPeerSuffix(version));
9837
+ }
9838
+ }
9839
+ function parsePnpmLock(content) {
9840
+ let raw;
9841
+ try {
9842
+ raw = load(content);
9843
+ } catch {
9844
+ return [];
9845
+ }
9846
+ if (raw === null || typeof raw !== "object") return [];
9847
+ const doc = raw;
9848
+ const directVersions = /* @__PURE__ */ new Map();
9849
+ const rootSections = doc.importers !== void 0 ? doc.importers["."] : doc;
9850
+ collectPnpmDirectVersions(rootSections?.dependencies, directVersions);
9851
+ collectPnpmDirectVersions(rootSections?.devDependencies, directVersions);
9852
+ collectPnpmDirectVersions(rootSections?.optionalDependencies, directVersions);
9853
+ const deps = [];
9854
+ for (const key of Object.keys(doc.packages ?? {})) {
9855
+ const parsed = splitPnpmPackageKey(key);
9856
+ if (parsed === null) continue;
9857
+ const isDirect = directVersions.get(parsed.name) === parsed.version;
9858
+ deps.push({ name: parsed.name, version: parsed.version, kind: isDirect ? "direct" : "transitive" });
9859
+ }
9860
+ return deps;
9861
+ }
9862
+ function parseLockFile(filePath) {
9863
+ const base = path15.basename(filePath);
9864
+ const content = fs15.readFileSync(filePath, "utf8");
9865
+ if (base === "package-lock.json") return { format: "npm", deps: parsePackageLockJson(content) };
9866
+ if (base === "yarn.lock") return { format: "yarn", deps: parseYarnLock(content) };
9867
+ if (base === "pnpm-lock.yaml") return { format: "pnpm", deps: parsePnpmLock(content) };
9868
+ if (base === "poetry.lock") return { format: "poetry", deps: parseTomlPackages(content) };
9869
+ if (base === "uv.lock") return { format: "uv", deps: parseTomlPackages(content) };
9870
+ if (base === "Cargo.lock") return { format: "cargo", deps: parseTomlPackages(content) };
9871
+ if (base === "Pipfile.lock") return { format: "pipfile", deps: parsePipfileLock(content) };
9872
+ return { format: "requirements", deps: parseRequirementsTxt(content) };
9873
+ }
9874
+ function buildNpmEdges(content) {
9875
+ const raw = JSON.parse(content);
9876
+ if (raw.packages === void 0 && raw.dependencies !== void 0) {
9877
+ const edges2 = /* @__PURE__ */ new Map();
9878
+ const directNames2 = new Set(Object.keys(raw.dependencies));
9879
+ const walk = (deps) => {
9880
+ for (const [name, val] of Object.entries(deps)) {
9881
+ if (!edges2.has(name)) edges2.set(name, val.dependencies !== void 0 ? Object.keys(val.dependencies) : []);
9882
+ if (val.dependencies !== void 0) walk(val.dependencies);
9883
+ }
9884
+ };
9885
+ walk(raw.dependencies);
9886
+ return { edges: edges2, directNames: directNames2 };
9887
+ }
9888
+ const pkgs = raw.packages;
9889
+ if (pkgs === void 0) return null;
9890
+ const rootEntry = pkgs[""];
9891
+ const directNames = /* @__PURE__ */ new Set([
9892
+ ...Object.keys(rootEntry?.dependencies ?? {}),
9893
+ ...Object.keys(rootEntry?.devDependencies ?? {}),
9894
+ ...Object.keys(rootEntry?.optionalDependencies ?? {})
9895
+ ]);
9896
+ const edges = /* @__PURE__ */ new Map();
9897
+ for (const [key, val] of Object.entries(pkgs)) {
9898
+ if (key === "") continue;
9899
+ const name = key.split("node_modules/").pop() ?? key;
9900
+ if (edges.has(name)) continue;
9901
+ edges.set(name, [
9902
+ .../* @__PURE__ */ new Set([...Object.keys(val.dependencies ?? {}), ...Object.keys(val.devDependencies ?? {}), ...Object.keys(val.optionalDependencies ?? {})])
9903
+ ]);
9904
+ }
9905
+ return { edges, directNames };
9906
+ }
9907
+ function findReverseDirectDeps(target, edges, directNames) {
9908
+ const result = [];
9909
+ for (const direct of directNames) {
9910
+ if (direct === target) continue;
9911
+ const seen = /* @__PURE__ */ new Set([direct]);
9912
+ const stack = [...edges.get(direct) ?? []];
9913
+ let reaches = false;
9914
+ while (stack.length > 0) {
9915
+ const cur = stack.pop();
9916
+ if (seen.has(cur)) continue;
9917
+ seen.add(cur);
9918
+ if (cur === target) {
9919
+ reaches = true;
9920
+ break;
9921
+ }
9922
+ for (const next of edges.get(cur) ?? []) stack.push(next);
9923
+ }
9924
+ if (reaches) result.push(direct);
9925
+ }
9926
+ return result.sort();
9927
+ }
9928
+ function cmdLockdepsPackage(lockfile, format, deps, query, json) {
9929
+ const matches2 = deps.filter((d) => d.name === query);
9930
+ if (matches2.length === 0) {
9931
+ const suggestions = suggestPackageNames(query, deps.map((d) => d.name));
9932
+ const hint = suggestions.length > 0 ? ` (did you mean: ${suggestions.join(", ")}?)` : "";
9933
+ throw new Error(`Package '${query}' not found in ${lockfile}${hint}`);
9934
+ }
9935
+ const primary = matches2.find((d) => d.kind === "direct") ?? matches2[0];
9936
+ const otherVersions = [...new Set(matches2.filter((d) => d.version !== primary.version).map((d) => d.version))];
9937
+ const graph = format === "npm" ? buildNpmEdges(fs15.readFileSync(lockfile, "utf8")) : null;
9938
+ const graphAvailable = graph !== null;
9939
+ const dependsOn = graph !== null ? [...graph.edges.get(query) ?? []].sort() : [];
9940
+ const dependedOnBy = graph !== null ? findReverseDirectDeps(query, graph.edges, graph.directNames) : [];
9941
+ if (json) {
9942
+ process.stdout.write(
9943
+ JSON.stringify(
9944
+ { file: lockfile, format, package: primary.name, version: primary.version, kind: primary.kind, otherVersions, graphAvailable, dependsOn, dependedOnBy },
9945
+ null,
9946
+ 2
9947
+ ) + "\n"
9948
+ );
9949
+ return;
9950
+ }
9951
+ process.stdout.write(`Lockfile: ${lockfile} (${format})
9952
+ `);
9953
+ process.stdout.write(`Package: ${primary.name}
9954
+ `);
9955
+ process.stdout.write(`Version: ${primary.version}${primary.kind === "unknown" ? "" : ` (${primary.kind})`}
9956
+ `);
9957
+ if (otherVersions.length > 0) process.stdout.write(`Other versions in lockfile: ${otherVersions.join(", ")}
9958
+ `);
9959
+ if (!graphAvailable) {
9960
+ process.stdout.write(`
9961
+ Dependency graph not available for format '${format}' (only npm package-lock.json exposes package-to-package edges).
9962
+ `);
9963
+ return;
9964
+ }
9965
+ process.stdout.write(`
9966
+ Depends on (${dependsOn.length}):
9967
+ `);
9968
+ if (dependsOn.length === 0) process.stdout.write(" (none)\n");
9969
+ for (const n of dependsOn) process.stdout.write(` ${n}
9970
+ `);
9971
+ process.stdout.write(`
9972
+ Depended on by direct/top-level deps (${dependedOnBy.length}):
9973
+ `);
9974
+ if (dependedOnBy.length === 0) process.stdout.write(" (none)\n");
9975
+ for (const n of dependedOnBy) process.stdout.write(` ${n}
9976
+ `);
9977
+ }
9978
+ function cmdLockdeps(filePath, opts) {
9979
+ const target = filePath !== void 0 ? path15.resolve(filePath) : process.cwd();
9980
+ const found = findLockfile(target);
9981
+ if (found === null) {
9982
+ throw new Error("No lockfile found. Expected one of: " + LOCK_PRIORITY.join(", ") + ", requirements*.txt");
9983
+ }
9984
+ const { deps, format } = parseLockFile(found.file);
9985
+ const others = found.others;
9986
+ if (opts.package !== void 0) {
9987
+ cmdLockdepsPackage(found.file, format, deps, opts.package, opts.json === true);
9988
+ return;
9989
+ }
9990
+ if (opts.json === true) {
9991
+ process.stdout.write(JSON.stringify({ file: found.file, format, total: deps.length, others, deps }, null, 2) + "\n");
9992
+ return;
9993
+ }
9994
+ process.stdout.write(`Lockfile: ${found.file} (${format})
9995
+ `);
9996
+ if (others.length > 0) process.stdout.write(`Other lockfiles found (not parsed): ${others.join(", ")}
9997
+ `);
9998
+ process.stdout.write(`Total: ${deps.length} packages
9999
+
10000
+ `);
10001
+ for (const dep of deps) {
10002
+ const kindLabel = dep.kind === "unknown" ? "" : ` (${dep.kind})`;
10003
+ process.stdout.write(`${dep.name} ${dep.version}${kindLabel}
10004
+ `);
10005
+ }
8273
10006
  }
8274
- function extractGeneric(lines) {
8275
- const result = {
8276
- runner: "unknown",
8277
- blocks: [],
8278
- summaryLines: [],
8279
- statsLine: ""
8280
- };
8281
- for (const line of lines) {
8282
- const s = line.trimEnd();
8283
- if (/\b(FAILED|FAILURE|ERROR)\b/i.test(s)) {
8284
- result.summaryLines.push(s);
10007
+ function resolveProjectHash() {
10008
+ const project = findProject(process.cwd());
10009
+ if (project === null) throw new Error("No project root found from cwd. Is this inside a project (git repo, package.json, etc.)?");
10010
+ return project.hash;
10011
+ }
10012
+ function cmdNote(action, key, value, opts) {
10013
+ const act = action.toLowerCase();
10014
+ if (act === "list") {
10015
+ const hash = resolveProjectHash();
10016
+ const entries = loadEntries(hash);
10017
+ if (opts.json === true) {
10018
+ process.stdout.write(JSON.stringify(entries, null, 2) + "\n");
10019
+ } else {
10020
+ const pairs = Object.entries(entries);
10021
+ if (pairs.length === 0) {
10022
+ process.stdout.write("(no notes set)\n");
10023
+ } else {
10024
+ for (const [k, v] of pairs) {
10025
+ process.stdout.write(`${k} = ${v}
10026
+ `);
10027
+ }
10028
+ }
8285
10029
  }
10030
+ return;
8286
10031
  }
8287
- return result;
8288
- }
8289
- function extractFailures(text, options) {
8290
- const cleaned = stripAnsi(text);
8291
- const lines = cleaned.split("\n");
8292
- const detected = options?.runner ?? detectRunner(cleaned);
8293
- if (detected === "pytest") {
8294
- return extractPytest(lines);
10032
+ if (act === "clear") {
10033
+ const hash = resolveProjectHash();
10034
+ clearAll(hash);
10035
+ process.stdout.write("Cleared all notes for this project.\n");
10036
+ return;
8295
10037
  }
8296
- if (detected === "jest") {
8297
- return extractJest(lines);
10038
+ if (act === "get") {
10039
+ if (key === void 0) throw new Error("note get requires a key");
10040
+ const hash = resolveProjectHash();
10041
+ const entries = loadEntries(hash);
10042
+ const v = entries[key];
10043
+ if (v === void 0) throw new Error(`Key not found: ${key}`);
10044
+ process.stdout.write(v + "\n");
10045
+ return;
8298
10046
  }
8299
- if (detected === "go") {
8300
- return extractGo(lines);
10047
+ if (act === "unset") {
10048
+ if (key === void 0) throw new Error("note unset requires a key");
10049
+ const hash = resolveProjectHash();
10050
+ unsetEntry(hash, key);
10051
+ process.stdout.write(`Unset: ${key}
10052
+ `);
10053
+ return;
8301
10054
  }
8302
- if (detected === "cargo") {
8303
- return extractCargo(lines);
10055
+ if (act === "set") {
10056
+ if (key === void 0) throw new Error("note set requires a key");
10057
+ if (value === void 0) throw new Error("note set requires a value");
10058
+ const hash = resolveProjectHash();
10059
+ setEntry(hash, key, value);
10060
+ process.stdout.write(`Set: ${key} = ${value}
10061
+ `);
10062
+ return;
8304
10063
  }
8305
- return extractGeneric(lines);
10064
+ throw new Error(`Unknown note action: ${action}. Use: set, get, unset, list, clear`);
8306
10065
  }
8307
- function formatFailuresText(result) {
8308
- if (result.blocks.length === 0 && result.summaryLines.length === 0) {
8309
- return "No failures found.";
10066
+ function loadAllSessionReadCounts() {
10067
+ const sessionsDir = path15.join(tokenGoatHome(), "sessions");
10068
+ const totals = /* @__PURE__ */ new Map();
10069
+ let entries;
10070
+ try {
10071
+ entries = fs15.readdirSync(sessionsDir, { withFileTypes: true });
10072
+ } catch {
10073
+ return totals;
8310
10074
  }
8311
- const parts = [];
8312
- const sep5 = "\u2500".repeat(60);
8313
- for (const block of result.blocks) {
8314
- parts.push(sep5, `FAIL ${block.name}`, sep5, block.body, "");
10075
+ for (const entry of entries) {
10076
+ if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
10077
+ const filePath = path15.join(sessionsDir, entry.name);
10078
+ let raw;
10079
+ try {
10080
+ raw = JSON.parse(fs15.readFileSync(filePath, "utf8"));
10081
+ } catch {
10082
+ continue;
10083
+ }
10084
+ if (raw === null || typeof raw !== "object") continue;
10085
+ const files = raw["files"];
10086
+ if (!Array.isArray(files)) continue;
10087
+ for (const f of files) {
10088
+ if (f === null || typeof f !== "object") continue;
10089
+ const fp = f["path"];
10090
+ const rc = f["readCount"];
10091
+ if (typeof fp !== "string" || typeof rc !== "number") continue;
10092
+ const key = foldPath(fp);
10093
+ const existing = totals.get(key);
10094
+ totals.set(key, { path: existing?.path ?? fp, readCount: (existing?.readCount ?? 0) + rc });
10095
+ }
10096
+ }
10097
+ return totals;
10098
+ }
10099
+ function cmdHot(opts) {
10100
+ const limit = opts.limit !== void 0 ? requireNonNegativeStrictInt("--limit", opts.limit) : 20;
10101
+ if (opts.limit !== void 0 && limit === 0) {
10102
+ throw new Error(`--limit must be a positive number, got: "${opts.limit}"`);
10103
+ }
10104
+ const totals = loadAllSessionReadCounts();
10105
+ let entries = [...totals.values()].map(({ path: p, readCount: rc }) => ({ path: p, readCount: rc }));
10106
+ const preProjectCount = entries.length;
10107
+ if (opts.project === true) {
10108
+ const project = findProject(process.cwd());
10109
+ if (project !== null) {
10110
+ const root = foldPath(project.root);
10111
+ entries = entries.filter((e) => isPathUnderRoot(foldPath(e.path), root));
10112
+ }
10113
+ }
10114
+ entries.sort((a, b) => b.readCount - a.readCount);
10115
+ entries = entries.slice(0, limit);
10116
+ if (opts.json === true) {
10117
+ process.stdout.write(JSON.stringify({ entries }, null, 2) + "\n");
10118
+ return;
8315
10119
  }
8316
- if (result.summaryLines.length > 0) {
8317
- if (parts.length > 0) {
8318
- parts.push(sep5);
10120
+ if (entries.length === 0) {
10121
+ if (opts.project === true && preProjectCount > 0) {
10122
+ const noun = preProjectCount === 1 ? "file" : "files";
10123
+ process.stdout.write(`No session read data under this project root (${preProjectCount} ${noun} recorded outside it).
10124
+ `);
10125
+ return;
8319
10126
  }
8320
- parts.push(...result.summaryLines);
10127
+ process.stdout.write("No session read data found.\n");
10128
+ return;
8321
10129
  }
8322
- if (result.statsLine) {
8323
- parts.push(result.statsLine);
10130
+ const hotDisplayRoot = getDisplayRoot();
10131
+ for (const e of entries) {
10132
+ process.stdout.write(`${e.readCount} ${toDisplayPath(hotDisplayRoot, e.path)}
10133
+ `);
8324
10134
  }
8325
- const n = getFailureCount(result);
8326
- parts.push(`
8327
- ${n} failure(s) [${result.runner}]`);
8328
- return parts.join("\n");
8329
- }
8330
- function formatFailuresJson(result) {
8331
- return JSON.stringify(
8332
- {
8333
- runner: result.runner,
8334
- count: getFailureCount(result),
8335
- failures: result.blocks.map((b) => ({ name: b.name, body: b.body })),
8336
- summary: result.summaryLines,
8337
- stats: result.statsLine
8338
- },
8339
- null,
8340
- 2
8341
- );
8342
- }
8343
- function failureSignatures(result) {
8344
- const raw = result.blocks.length > 0 ? result.blocks.map((b) => b.name.trim()) : result.summaryLines.map((s) => s.trim()).filter((s) => s !== "");
8345
- return Array.from(new Set(raw));
8346
10135
  }
8347
- function computeFailureDelta(prevSignatures, currSignatures) {
8348
- if (prevSignatures === null) {
8349
- return { hasBaseline: false, newlyFailing: [...currSignatures], newlyFixed: [], stillFailing: [] };
10136
+ function cmdRecent(nStr, opts) {
10137
+ const n = nStr !== void 0 ? requireNonNegativeStrictInt("recent", nStr) : 20;
10138
+ if (nStr !== void 0 && n === 0) {
10139
+ throw new Error(`recent: limit must be a positive number, got: "${nStr}"`);
10140
+ }
10141
+ const sessionFiles = getSessionFiles();
10142
+ const entries = [...sessionFiles.values()].map((e) => ({ path: e.path, readCount: e.readCount, lastReadAt: e.lastReadAt, wasEdited: e.wasEdited })).sort((a, b) => b.lastReadAt - a.lastReadAt).slice(0, n);
10143
+ if (opts.json === true) {
10144
+ process.stdout.write(JSON.stringify({ entries, scope: "current-session" }, null, 2) + "\n");
10145
+ return;
10146
+ }
10147
+ process.stdout.write("# Recent files (current session only \u2014 use `token-goat hot` for cross-session)\n");
10148
+ if (entries.length === 0) {
10149
+ process.stdout.write("No files read in this session yet.\n");
10150
+ return;
10151
+ }
10152
+ const recentDisplayRoot = getDisplayRoot();
10153
+ for (const e of entries) {
10154
+ const edited = e.wasEdited ? " [edited]" : "";
10155
+ const ts = new Date(e.lastReadAt).toISOString();
10156
+ process.stdout.write(`${ts} ${toDisplayPath(recentDisplayRoot, e.path)}${edited}
10157
+ `);
8350
10158
  }
8351
- const prevSet = new Set(prevSignatures);
8352
- const currSet = new Set(currSignatures);
8353
- return {
8354
- hasBaseline: true,
8355
- newlyFailing: currSignatures.filter((s) => !prevSet.has(s)),
8356
- newlyFixed: prevSignatures.filter((s) => !currSet.has(s)),
8357
- stillFailing: currSignatures.filter((s) => prevSet.has(s))
8358
- };
8359
10159
  }
8360
- function formatFailureDeltaText(delta, runner) {
8361
- if (!delta.hasBaseline) {
8362
- const lines2 = [
8363
- `No baseline yet for this project/key -- showing all ${delta.newlyFailing.length} current failure(s) as new. [${runner}]`
8364
- ];
8365
- if (delta.newlyFailing.length === 0) {
8366
- lines2.push("(no failures)");
8367
- } else {
8368
- for (const name of delta.newlyFailing) lines2.push(`+ ${name}`);
8369
- }
8370
- return lines2.join("\n");
10160
+ function cmdIgnores(opts) {
10161
+ const cwd = process.cwd();
10162
+ const cfg = loadConfig();
10163
+ const walkMode = detectWalkMode(cwd);
10164
+ const walkIndexBuiltinExclusions = [".env", ".env.*", "*.d.ts"];
10165
+ const report = {
10166
+ walkMode,
10167
+ indexRespectsGitignore: walkMode === "git",
10168
+ walkIndexBuiltinExclusions,
10169
+ skipDirs: [...SKIP_DIRS].sort(),
10170
+ blockedRoots: cfg.worker.blocked_roots,
10171
+ excludeTests: cfg.repomap.exclude_tests,
10172
+ packIgnorePatterns: countPackIgnorePatterns(cwd)
10173
+ };
10174
+ if (opts.json === true) {
10175
+ process.stdout.write(JSON.stringify(report, null, 2) + "\n");
10176
+ return;
8371
10177
  }
8372
- const lines = [];
8373
- lines.push(`Newly failing (${delta.newlyFailing.length}):`);
8374
- if (delta.newlyFailing.length === 0) {
8375
- lines.push(" (none)");
10178
+ process.stdout.write(`Walk mode: ${walkMode}
10179
+ `);
10180
+ if (walkMode === "git") {
10181
+ process.stdout.write("token-goat index: .gitignore exclusions are active (via git ls-files).\n");
8376
10182
  } else {
8377
- for (const name of delta.newlyFailing) lines.push(` + ${name}`);
10183
+ process.stdout.write(
10184
+ `token-goat index --walk: built-in exclusions ${walkIndexBuiltinExclusions.join(", ")}.
10185
+ `
10186
+ );
8378
10187
  }
8379
- lines.push("");
8380
- lines.push(`Newly fixed (${delta.newlyFixed.length}):`);
8381
- if (delta.newlyFixed.length === 0) {
8382
- lines.push(" (none)");
10188
+ process.stdout.write(
10189
+ `map/todo/conflicts (and hot --project) ignore neither .gitignore nor ${walkIndexBuiltinExclusions.join(", ")} in either mode -- they only skip: ${report.skipDirs.join(", ")} (and hidden directories).
10190
+ `
10191
+ );
10192
+ if (cfg.worker.blocked_roots.length > 0) {
10193
+ process.stdout.write(`Blocked roots (config, enforced by token-goat index / index --walk / worker only): ${cfg.worker.blocked_roots.join(", ")}
10194
+ `);
8383
10195
  } else {
8384
- for (const name of delta.newlyFixed) lines.push(` - ${name}`);
10196
+ process.stdout.write("Blocked roots (config, enforced by token-goat index / index --walk / worker only): none\n");
8385
10197
  }
8386
- lines.push("");
8387
- lines.push(`Still failing (unchanged): ${delta.stillFailing.length}`);
8388
- lines.push(
8389
- `
8390
- [${runner}] ${delta.newlyFailing.length} newly failing, ${delta.newlyFixed.length} newly fixed, ${delta.stillFailing.length} still failing`
8391
- );
8392
- return lines.join("\n");
8393
- }
8394
- function formatFailureDeltaJson(delta, runner) {
8395
- return JSON.stringify(
8396
- {
8397
- runner,
8398
- hasBaseline: delta.hasBaseline,
8399
- newlyFailing: delta.newlyFailing,
8400
- newlyFixed: delta.newlyFixed,
8401
- stillFailingCount: delta.stillFailing.length
8402
- },
8403
- null,
8404
- 2
10198
+ process.stdout.write(`Exclude tests from repomap: ${cfg.repomap.exclude_tests}
10199
+ `);
10200
+ process.stdout.write(
10201
+ report.packIgnorePatterns > 0 ? `.tokengoatignore: ${report.packIgnorePatterns} pattern${report.packIgnorePatterns === 1 ? "" : "s"}, applied by token-goat pack only -- it does not exclude anything from the symbol index. To keep a path out of the index, run "token-goat project exclude <path>".
10202
+ ` : '.tokengoatignore: not present. It would apply to token-goat pack only -- to keep a path out of the symbol index, run "token-goat project exclude <path>".\n'
8405
10203
  );
8406
10204
  }
8407
-
8408
- // src/failures_state.ts
8409
- init_define_import_meta_env();
8410
- import * as fs9 from "node:fs";
8411
- import * as path11 from "node:path";
8412
- var KEY_RE = /^[A-Za-z0-9_-]{1,80}$/;
8413
- var DEFAULT_FAILURES_STATE_KEY = "default";
8414
- function validateKey(key) {
8415
- if (!KEY_RE.test(key)) {
8416
- throw new Error(
8417
- `Invalid --key ${JSON.stringify(key)}: use only letters, digits, hyphens, underscores (max 80 chars)`
8418
- );
8419
- }
8420
- }
8421
- function failuresStatePath(projectHash, key) {
8422
- validateKey(key);
8423
- return path11.join(dataDir(), "projects", `${projectHash}_failures_${key}.json`);
8424
- }
8425
- function loadFailureSnapshot(projectHash, key) {
8426
- const p = failuresStatePath(projectHash, key);
10205
+ function countPackIgnorePatterns(root) {
8427
10206
  try {
8428
- if (!fs9.existsSync(p)) return null;
8429
- const parsed = JSON.parse(fs9.readFileSync(p, "utf-8"));
8430
- if (typeof parsed !== "object" || parsed === null) return null;
8431
- const obj = parsed;
8432
- if (!Array.isArray(obj.signatures)) return null;
8433
- return {
8434
- signatures: obj.signatures.filter((s) => typeof s === "string"),
8435
- runner: typeof obj.runner === "string" ? obj.runner : "unknown",
8436
- storedAt: typeof obj.storedAt === "number" ? obj.storedAt : 0
8437
- };
10207
+ return fs15.readFileSync(path15.join(root, ".tokengoatignore"), "utf8").split("\n").map((ln) => ln.trim()).filter((ln) => ln.length > 0 && !ln.startsWith("#")).length;
8438
10208
  } catch {
8439
- return null;
10209
+ return 0;
8440
10210
  }
8441
10211
  }
8442
- function saveFailureSnapshot(projectHash, key, snapshot) {
8443
- const p = failuresStatePath(projectHash, key);
8444
- const dir = path11.dirname(p);
8445
- ensureDirSync(dir);
8446
- const content = JSON.stringify(snapshot);
8447
- const doSave = () => {
8448
- atomicWriteText(p, content);
8449
- return true;
8450
- };
8451
- if (withFileLock(`${p}.lock`, doSave) === void 0) doSave();
8452
- }
8453
10212
 
8454
10213
  // src/dep_docs.ts
8455
10214
  init_define_import_meta_env();
8456
10215
  import { createRequire } from "node:module";
8457
- import * as fs10 from "node:fs";
8458
- import * as path12 from "node:path";
10216
+ import * as fs16 from "node:fs";
10217
+ import * as path16 from "node:path";
8459
10218
  var _require = createRequire(import.meta.url);
8460
10219
  var _ts = null;
8461
10220
  var _tsLoadAttempted = false;
@@ -8474,21 +10233,21 @@ function loadTs() {
8474
10233
  }
8475
10234
  function dirExists(p) {
8476
10235
  try {
8477
- return fs10.statSync(p).isDirectory();
10236
+ return fs16.statSync(p).isDirectory();
8478
10237
  } catch {
8479
10238
  return false;
8480
10239
  }
8481
10240
  }
8482
10241
  function fileExists(p) {
8483
10242
  try {
8484
- return fs10.statSync(p).isFile();
10243
+ return fs16.statSync(p).isFile();
8485
10244
  } catch {
8486
10245
  return false;
8487
10246
  }
8488
10247
  }
8489
10248
  function readFileTextOrNull(p) {
8490
10249
  try {
8491
- return fs10.readFileSync(p, "utf8");
10250
+ return fs16.readFileSync(p, "utf8");
8492
10251
  } catch {
8493
10252
  return null;
8494
10253
  }
@@ -8496,7 +10255,7 @@ function readFileTextOrNull(p) {
8496
10255
  function listInstalledPackageNames(nodeModulesDir) {
8497
10256
  let entries;
8498
10257
  try {
8499
- entries = fs10.readdirSync(nodeModulesDir, { withFileTypes: true });
10258
+ entries = fs16.readdirSync(nodeModulesDir, { withFileTypes: true });
8500
10259
  } catch {
8501
10260
  return [];
8502
10261
  }
@@ -8506,7 +10265,7 @@ function listInstalledPackageNames(nodeModulesDir) {
8506
10265
  if (entry.name.startsWith("@")) {
8507
10266
  let scoped;
8508
10267
  try {
8509
- scoped = fs10.readdirSync(path12.join(nodeModulesDir, entry.name), { withFileTypes: true });
10268
+ scoped = fs16.readdirSync(path16.join(nodeModulesDir, entry.name), { withFileTypes: true });
8510
10269
  } catch {
8511
10270
  continue;
8512
10271
  }
@@ -8523,12 +10282,12 @@ var README_NAME_RE = /^readme(\.(md|markdown|txt))?$/i;
8523
10282
  function findReadmeFile(pkgDir) {
8524
10283
  const direct = findReadmeIn(pkgDir);
8525
10284
  if (direct !== null) return direct;
8526
- return findReadmeIn(path12.join(pkgDir, "docs"));
10285
+ return findReadmeIn(path16.join(pkgDir, "docs"));
8527
10286
  }
8528
10287
  function findReadmeIn(dir) {
8529
10288
  let entries;
8530
10289
  try {
8531
- entries = fs10.readdirSync(dir, { withFileTypes: true });
10290
+ entries = fs16.readdirSync(dir, { withFileTypes: true });
8532
10291
  } catch {
8533
10292
  return null;
8534
10293
  }
@@ -8540,7 +10299,7 @@ function findReadmeIn(dir) {
8540
10299
  return 1;
8541
10300
  };
8542
10301
  matches2.sort((a, b) => rank(a.name) - rank(b.name));
8543
- return path12.join(dir, matches2[0].name);
10302
+ return path16.join(dir, matches2[0].name);
8544
10303
  }
8545
10304
  function typesPackageDirName(pkgName) {
8546
10305
  if (pkgName.startsWith("@")) {
@@ -8567,30 +10326,30 @@ function resolveTypesLocation(pkgDir, pkgJson, nodeModulesDir, pkgName) {
8567
10326
  if (declared !== void 0) {
8568
10327
  const candidates = [declared, declared.endsWith(".d.ts") ? declared : `${declared}.d.ts`, declared.replace(/\.[cm]?[jt]s$/, ".d.ts")];
8569
10328
  for (const c of candidates) {
8570
- const p = path12.join(pkgDir, c);
10329
+ const p = path16.join(pkgDir, c);
8571
10330
  if (fileExists(p)) return { path: p, source: "bundled" };
8572
10331
  }
8573
10332
  }
8574
10333
  const main = typeof pkgJson["main"] === "string" ? pkgJson["main"] : "index.js";
8575
- const mainDts = path12.join(pkgDir, main.replace(/\.[cm]?js$/, ".d.ts"));
10334
+ const mainDts = path16.join(pkgDir, main.replace(/\.[cm]?js$/, ".d.ts"));
8576
10335
  if (fileExists(mainDts)) return { path: mainDts, source: "bundled" };
8577
- const indexDts = path12.join(pkgDir, "index.d.ts");
10336
+ const indexDts = path16.join(pkgDir, "index.d.ts");
8578
10337
  if (fileExists(indexDts)) return { path: indexDts, source: "bundled" };
8579
10338
  const typesDirName = typesPackageDirName(pkgName);
8580
- const typesPkgDir = path12.join(nodeModulesDir, "@types", typesDirName);
8581
- const typesPkgJsonRaw = readFileTextOrNull(path12.join(typesPkgDir, "package.json"));
10339
+ const typesPkgDir = path16.join(nodeModulesDir, "@types", typesDirName);
10340
+ const typesPkgJsonRaw = readFileTextOrNull(path16.join(typesPkgDir, "package.json"));
8582
10341
  if (typesPkgJsonRaw !== null) {
8583
10342
  try {
8584
10343
  const typesPkgJson = JSON.parse(typesPkgJsonRaw);
8585
10344
  const entry = (typeof typesPkgJson["types"] === "string" ? typesPkgJson["types"] : void 0) ?? (typeof typesPkgJson["main"] === "string" ? typesPkgJson["main"] : void 0) ?? "index.d.ts";
8586
10345
  const entryCandidates = [entry, entry.endsWith(".d.ts") ? entry : `${entry}.d.ts`, entry.replace(/\.[cm]?[jt]s$/, ".d.ts")];
8587
10346
  for (const c of entryCandidates) {
8588
- const entryPath = path12.join(typesPkgDir, c);
10347
+ const entryPath = path16.join(typesPkgDir, c);
8589
10348
  if (fileExists(entryPath)) return { path: entryPath, source: "@types" };
8590
10349
  }
8591
10350
  } catch {
8592
10351
  }
8593
- const fallback = path12.join(typesPkgDir, "index.d.ts");
10352
+ const fallback = path16.join(typesPkgDir, "index.d.ts");
8594
10353
  if (fileExists(fallback)) return { path: fallback, source: "@types" };
8595
10354
  }
8596
10355
  return null;
@@ -8657,16 +10416,16 @@ function recordDepDocsStat(fullSourceBytes, emittedText, packageName) {
8657
10416
  }
8658
10417
  function runDepDocs(opts) {
8659
10418
  const root = resolveProjectRoot({ project: opts.projectRoot ?? process.cwd() });
8660
- const nodeModulesDir = path12.join(root, "node_modules");
8661
- const pkgDir = path12.join(nodeModulesDir, opts.packageName);
8662
- const pkgJsonPath = path12.join(pkgDir, "package.json");
10419
+ const nodeModulesDir = path16.join(root, "node_modules");
10420
+ const pkgDir = path16.join(nodeModulesDir, opts.packageName);
10421
+ const pkgJsonPath = path16.join(pkgDir, "package.json");
8663
10422
  if (!dirExists(pkgDir) || !fileExists(pkgJsonPath)) {
8664
10423
  const known = listInstalledPackageNames(nodeModulesDir);
8665
10424
  const suggestions = suggestPackageNames(opts.packageName, known);
8666
10425
  const hint = suggestions.length > 0 ? ` (did you mean: ${suggestions.join(", ")}?)` : "";
8667
10426
  return { text: `Package '${opts.packageName}' not found under ${nodeModulesDir}${hint}`, code: 1 };
8668
10427
  }
8669
- const pkgJsonRaw = fs10.readFileSync(pkgJsonPath, "utf8");
10428
+ const pkgJsonRaw = fs16.readFileSync(pkgJsonPath, "utf8");
8670
10429
  let pkgJson;
8671
10430
  try {
8672
10431
  pkgJson = JSON.parse(pkgJsonRaw);
@@ -9170,14 +10929,14 @@ function cmdBaseline(opts) {
9170
10929
 
9171
10930
  // src/index_reclaim.ts
9172
10931
  init_define_import_meta_env();
9173
- import * as fs11 from "node:fs";
9174
- import * as path13 from "node:path";
10932
+ import * as fs17 from "node:fs";
10933
+ import * as path17 from "node:path";
9175
10934
  var DERIVED_TABLES = ["chunk_vectors", "chunks", "refs", "symbols", "files"];
9176
10935
  function indexSizeBytes(dbPath) {
9177
10936
  let total = 0;
9178
10937
  for (const p of [dbPath, `${dbPath}-wal`]) {
9179
10938
  try {
9180
- total += fs11.statSync(p).size;
10939
+ total += fs17.statSync(p).size;
9181
10940
  } catch {
9182
10941
  }
9183
10942
  }
@@ -9239,7 +10998,7 @@ function mb(bytes) {
9239
10998
  }
9240
10999
  function cmdReclaimIndex(opts) {
9241
11000
  const dbPath = opts.dbPath ?? globalDbPath();
9242
- if (opts.force !== true && isWorkerRunning(path13.dirname(dbPath))) {
11001
+ if (opts.force !== true && isWorkerRunning(path17.dirname(dbPath))) {
9243
11002
  throw new Error(
9244
11003
  "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)."
9245
11004
  );
@@ -9274,7 +11033,7 @@ function cmdReclaimIndex(opts) {
9274
11033
  }
9275
11034
  if (result.checkpointBusy) {
9276
11035
  process.stdout.write(
9277
- ` note: a concurrent reader blocked WAL truncation, so some space may still be held in ${path13.basename(dbPath)}-wal
11036
+ ` note: a concurrent reader blocked WAL truncation, so some space may still be held in ${path17.basename(dbPath)}-wal
9278
11037
  `
9279
11038
  );
9280
11039
  }
@@ -9282,9 +11041,9 @@ function cmdReclaimIndex(opts) {
9282
11041
 
9283
11042
  // src/config_commands.ts
9284
11043
  init_define_import_meta_env();
9285
- import * as fs12 from "node:fs";
11044
+ import * as fs18 from "node:fs";
9286
11045
  import * as os9 from "node:os";
9287
- import * as path14 from "node:path";
11046
+ import * as path18 from "node:path";
9288
11047
  function emit(text) {
9289
11048
  const payload = colorStdout() ? text : stripAnsi(text);
9290
11049
  process.stdout.write(ensureNewline(payload));
@@ -9293,7 +11052,7 @@ function emitErr2(text) {
9293
11052
  process.stderr.write(ensureNewline(text));
9294
11053
  }
9295
11054
  function saveConfigSafe(cfg) {
9296
- ensureDirSync(path14.dirname(configPath()));
11055
+ ensureDirSync(path18.dirname(configPath()));
9297
11056
  saveConfig(cfg);
9298
11057
  }
9299
11058
  function levenshtein(a, b, cap = 3) {
@@ -9504,7 +11263,7 @@ function cmdConfig(opts) {
9504
11263
  const parseErrAtLoad = getLastConfigParseError();
9505
11264
  if (parseErrAtLoad !== null) {
9506
11265
  try {
9507
- fs12.copyFileSync(configPath(), `${configPath()}.bak`);
11266
+ fs18.copyFileSync(configPath(), `${configPath()}.bak`);
9508
11267
  emitErr2(`config set: warning: config.toml failed to parse (${parseErrAtLoad}); backed up the original to config.toml.bak and rewriting it from defaults`);
9509
11268
  } catch {
9510
11269
  }
@@ -9540,8 +11299,8 @@ function cmdConfig(opts) {
9540
11299
  saveConfigSafe(cfg);
9541
11300
  return coercedValue;
9542
11301
  };
9543
- ensureDirSync(path14.dirname(configPath()));
9544
- const lockPath = path14.join(path14.dirname(configPath()), ".config.lock");
11302
+ ensureDirSync(path18.dirname(configPath()));
11303
+ const lockPath = path18.join(path18.dirname(configPath()), ".config.lock");
9545
11304
  const lockResult = withFileLock(lockPath, applySet, { waitMs: LOCK_WAIT_MS_HARDENED });
9546
11305
  const coerced = lockResult === void 0 ? applySet() : lockResult;
9547
11306
  invalidateConfigCache();
@@ -9570,7 +11329,7 @@ function cmdConfig(opts) {
9570
11329
  let raw = {};
9571
11330
  let parseErr = null;
9572
11331
  try {
9573
- const text = fs12.readFileSync(cfgFile, "utf8");
11332
+ const text = fs18.readFileSync(cfgFile, "utf8");
9574
11333
  raw = parse(text);
9575
11334
  } catch (e) {
9576
11335
  const code = e.code;
@@ -9677,7 +11436,7 @@ function cmdProject(opts) {
9677
11436
  emitErr2("project exclude requires a path argument");
9678
11437
  throw new Error("missing path");
9679
11438
  }
9680
- const target = path14.resolve(opts.pathArg);
11439
+ const target = path18.resolve(opts.pathArg);
9681
11440
  const cfg = loadPersistedConfig();
9682
11441
  const targetFolded = foldPath(normalizePath(target));
9683
11442
  if (cfg.worker.blocked_roots.some((r) => foldPath(normalizePath(r)) === targetFolded)) {
@@ -9699,7 +11458,7 @@ function cmdProject(opts) {
9699
11458
  const before = cfg.worker.blocked_roots;
9700
11459
  const after = before.filter((r) => {
9701
11460
  try {
9702
- return fs12.existsSync(r);
11461
+ return fs18.existsSync(r);
9703
11462
  } catch {
9704
11463
  return false;
9705
11464
  }
@@ -9746,14 +11505,14 @@ function recordCompactDocStat(fullSourceBytes, emittedText, detail) {
9746
11505
  recordStat("compact_doc", bytesSaved, Math.round(bytesSaved / 4), void 0, detail);
9747
11506
  }
9748
11507
  function cmdCompactDoc(opts) {
9749
- const resolved = path14.resolve(opts.filePath);
11508
+ const resolved = path18.resolve(opts.filePath);
9750
11509
  if (opts.heading !== void 0) {
9751
11510
  const result = compactDoc(resolved, opts.heading);
9752
11511
  if (result === null) {
9753
11512
  emitErr2(`compact-doc: could not read or compact '${resolved}'`);
9754
11513
  throw new Error(`could not compact: ${resolved}`);
9755
11514
  }
9756
- const legacyFullBytes = fs12.statSync(resolved).size;
11515
+ const legacyFullBytes = fs18.statSync(resolved).size;
9757
11516
  if (opts.json === true) {
9758
11517
  const jsonText = JSON.stringify({ path: resolved, compact: result }, null, 2);
9759
11518
  emit(jsonText);
@@ -9780,7 +11539,7 @@ function cmdCompactDoc(opts) {
9780
11539
  if (opts.force === true || opts.sentences !== void 0 || !fresh) {
9781
11540
  let sourceText;
9782
11541
  try {
9783
- sourceText = fs12.readFileSync(resolved, "utf-8");
11542
+ sourceText = fs18.readFileSync(resolved, "utf-8");
9784
11543
  } catch {
9785
11544
  emitErr2(`compact-doc: could not read or compact '${resolved}'`);
9786
11545
  throw new Error(`could not compact: ${resolved}`);
@@ -9796,7 +11555,7 @@ function cmdCompactDoc(opts) {
9796
11555
  }
9797
11556
  body = existing;
9798
11557
  }
9799
- const extractiveFullBytes = fs12.statSync(resolved).size;
11558
+ const extractiveFullBytes = fs18.statSync(resolved).size;
9800
11559
  if (opts.json === true) {
9801
11560
  const jsonText = JSON.stringify({ path: resolved, compactPath, rebuilt, compact: body }, null, 2);
9802
11561
  emit(jsonText);
@@ -9853,7 +11612,7 @@ async function cmdFetchImage(opts) {
9853
11612
  throw new Error(`fetch failed: ${opts.url}`, { cause: e });
9854
11613
  }
9855
11614
  const buf = fetched.body;
9856
- const outPath = opts.out ?? path14.join(os9.tmpdir(), `tg-fetch-${Date.now()}${extensionForContentType(fetched.contentType)}`);
11615
+ const outPath = opts.out ?? path18.join(os9.tmpdir(), `tg-fetch-${Date.now()}${extensionForContentType(fetched.contentType)}`);
9857
11616
  const originalBytes = buf.length;
9858
11617
  let shrunkBytes;
9859
11618
  let outData;
@@ -9935,9 +11694,9 @@ function cmdHistory(opts) {
9935
11694
 
9936
11695
  // src/cli_bootstrap_audit.ts
9937
11696
  init_define_import_meta_env();
9938
- import * as fs13 from "node:fs";
11697
+ import * as fs19 from "node:fs";
9939
11698
  import * as os10 from "node:os";
9940
- import * as path15 from "node:path";
11699
+ import * as path19 from "node:path";
9941
11700
  import * as readline from "node:readline";
9942
11701
  var FRONTMATTER_KEYS = /* @__PURE__ */ new Set(["description", "tools", "allowed-tools", "allowed_tools"]);
9943
11702
  function parseBudget(name, value) {
@@ -9946,7 +11705,7 @@ function parseBudget(name, value) {
9946
11705
  return value;
9947
11706
  }
9948
11707
  async function readFrontmatter(filePath) {
9949
- const stream = fs13.createReadStream(filePath, { encoding: "utf8" });
11708
+ const stream = fs19.createReadStream(filePath, { encoding: "utf8" });
9950
11709
  const lines = readline.createInterface({ input: stream, crlfDelay: Infinity });
9951
11710
  let first = true;
9952
11711
  let inFrontmatter = false;
@@ -9992,44 +11751,44 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
9992
11751
  let logicalRoot;
9993
11752
  let rootIsLink;
9994
11753
  try {
9995
- rootIsLink = (await fs13.promises.lstat(root)).isSymbolicLink();
9996
- logicalRoot = await fs13.promises.realpath(root);
11754
+ rootIsLink = (await fs19.promises.lstat(root)).isSymbolicLink();
11755
+ logicalRoot = await fs19.promises.realpath(root);
9997
11756
  } catch (error) {
9998
11757
  if (error.code === "ENOENT") return entries;
9999
- diagnostics.push({ path: path15.resolve(root), reason: "unreadable root" });
11758
+ diagnostics.push({ path: path19.resolve(root), reason: "unreadable root" });
10000
11759
  return entries;
10001
11760
  }
10002
11761
  if (rootIsLink && !followLinks) {
10003
- diagnostics.push({ path: path15.resolve(root), reason: "external root link skipped (use --follow-links)" });
11762
+ diagnostics.push({ path: path19.resolve(root), reason: "external root link skipped (use --follow-links)" });
10004
11763
  return entries;
10005
11764
  }
10006
11765
  const canonicalKey = (candidate) => process.platform === "win32" ? candidate.toLocaleLowerCase() : candidate;
10007
11766
  const isContained = (candidate, allowedRoot) => {
10008
- const relative3 = path15.relative(canonicalKey(allowedRoot), canonicalKey(candidate));
10009
- return relative3 === "" || relative3 !== ".." && !relative3.startsWith(`..${path15.sep}`) && !path15.isAbsolute(relative3);
11767
+ const relative3 = path19.relative(canonicalKey(allowedRoot), canonicalKey(candidate));
11768
+ return relative3 === "" || relative3 !== ".." && !relative3.startsWith(`..${path19.sep}`) && !path19.isAbsolute(relative3);
10010
11769
  };
10011
11770
  async function visit(candidate, allowedRoot, topLevel = false) {
10012
11771
  let real;
10013
11772
  let candidateIsLink;
10014
11773
  try {
10015
- candidateIsLink = (await fs13.promises.lstat(candidate)).isSymbolicLink();
10016
- real = await fs13.promises.realpath(candidate);
11774
+ candidateIsLink = (await fs19.promises.lstat(candidate)).isSymbolicLink();
11775
+ real = await fs19.promises.realpath(candidate);
10017
11776
  } catch {
10018
- diagnostics.push({ path: path15.resolve(candidate), reason: "broken or unreadable link" });
11777
+ diagnostics.push({ path: path19.resolve(candidate), reason: "broken or unreadable link" });
10019
11778
  return;
10020
11779
  }
10021
11780
  const withinAllowedRoot = allowedRoot === void 0 ? isContained(real, logicalRoot) : isContained(real, allowedRoot);
10022
11781
  const canFollowExternal = followLinks && topLevel;
10023
11782
  if (!withinAllowedRoot && !canFollowExternal) {
10024
- diagnostics.push({ path: path15.resolve(candidate), reason: candidateIsLink ? "external link skipped (use --follow-links)" : "canonical target outside scan root" });
11783
+ diagnostics.push({ path: path19.resolve(candidate), reason: candidateIsLink ? "external link skipped (use --follow-links)" : "canonical target outside scan root" });
10025
11784
  return;
10026
11785
  }
10027
11786
  const trustedRoot = !withinAllowedRoot && canFollowExternal ? real : allowedRoot ?? real;
10028
11787
  let stat;
10029
11788
  try {
10030
- stat = await fs13.promises.stat(real);
11789
+ stat = await fs19.promises.stat(real);
10031
11790
  } catch {
10032
- diagnostics.push({ path: path15.resolve(candidate), reason: "unreadable" });
11791
+ diagnostics.push({ path: path19.resolve(candidate), reason: "unreadable" });
10033
11792
  return;
10034
11793
  }
10035
11794
  const canonical = process.platform === "win32" ? real.toLocaleLowerCase() : real;
@@ -10038,16 +11797,16 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
10038
11797
  visitedDirs.add(canonical);
10039
11798
  let children;
10040
11799
  try {
10041
- children = await fs13.promises.readdir(real, { withFileTypes: true });
11800
+ children = await fs19.promises.readdir(real, { withFileTypes: true });
10042
11801
  } catch {
10043
- diagnostics.push({ path: path15.resolve(candidate), reason: "unreadable directory" });
11802
+ diagnostics.push({ path: path19.resolve(candidate), reason: "unreadable directory" });
10044
11803
  return;
10045
11804
  }
10046
11805
  children.sort((a, b) => a.name.localeCompare(b.name));
10047
- for (const child of children) await visit(path15.join(real, child.name), trustedRoot);
11806
+ for (const child of children) await visit(path19.join(real, child.name), trustedRoot);
10048
11807
  return;
10049
11808
  }
10050
- if (!stat.isFile() || path15.extname(real).toLowerCase() !== ".md") return;
11809
+ if (!stat.isFile() || path19.extname(real).toLowerCase() !== ".md") return;
10051
11810
  if (seenFiles.has(canonical)) return;
10052
11811
  seenFiles.add(canonical);
10053
11812
  try {
@@ -10057,35 +11816,35 @@ async function scanMetadataRoot(root, kind, diagnostics, visitedDirs = /* @__PUR
10057
11816
  const toolsBytes = Buffer.byteLength(metadata.tools, "utf8");
10058
11817
  entries.push({
10059
11818
  kind,
10060
- path: path15.resolve(real),
11819
+ path: path19.resolve(real),
10061
11820
  description_bytes: descriptionBytes,
10062
11821
  tools_bytes: toolsBytes,
10063
11822
  metadata_bytes: descriptionBytes + toolsBytes,
10064
11823
  estimated_tokens: Math.floor((descriptionBytes + toolsBytes) / 4)
10065
11824
  });
10066
11825
  } catch {
10067
- diagnostics.push({ path: path15.resolve(candidate), reason: "unreadable file" });
11826
+ diagnostics.push({ path: path19.resolve(candidate), reason: "unreadable file" });
10068
11827
  }
10069
11828
  }
10070
11829
  try {
10071
- const children = await fs13.promises.readdir(logicalRoot, { withFileTypes: true });
11830
+ const children = await fs19.promises.readdir(logicalRoot, { withFileTypes: true });
10072
11831
  children.sort((a, b) => a.name.localeCompare(b.name));
10073
- for (const child of children) await visit(path15.join(logicalRoot, child.name), void 0, true);
11832
+ for (const child of children) await visit(path19.join(logicalRoot, child.name), void 0, true);
10074
11833
  } catch {
10075
- diagnostics.push({ path: path15.resolve(root), reason: "unreadable root" });
11834
+ diagnostics.push({ path: path19.resolve(root), reason: "unreadable root" });
10076
11835
  }
10077
11836
  return entries;
10078
11837
  }
10079
11838
  async function buildBootstrapAudit(opts = {}) {
10080
11839
  const project = resolveProjectRoot(opts.project === void 0 ? {} : { project: opts.project });
10081
- const home = path15.resolve(opts.home ?? os10.homedir());
11840
+ const home = path19.resolve(opts.home ?? os10.homedir());
10082
11841
  const context = buildStats(project, home, opts.project === void 0 ? [] : [opts.project]);
10083
11842
  const diagnostics = [];
10084
11843
  const top = opts.top === void 0 ? 10 : parseBudget("--top", opts.top) ?? 10;
10085
11844
  const visitedDirs = /* @__PURE__ */ new Set();
10086
11845
  const seenFiles = /* @__PURE__ */ new Set();
10087
- const agents = await scanMetadataRoot(path15.join(home, ".claude", "agents"), "agent", diagnostics, visitedDirs, seenFiles, opts.followLinks === true);
10088
- const skills = await scanMetadataRoot(path15.join(home, ".claude", "skills"), "skill", diagnostics, visitedDirs, seenFiles, opts.followLinks === true);
11846
+ const agents = await scanMetadataRoot(path19.join(home, ".claude", "agents"), "agent", diagnostics, visitedDirs, seenFiles, opts.followLinks === true);
11847
+ const skills = await scanMetadataRoot(path19.join(home, ".claude", "skills"), "skill", diagnostics, visitedDirs, seenFiles, opts.followLinks === true);
10089
11848
  const largest = [...agents, ...skills].sort((a, b) => b.metadata_bytes - a.metadata_bytes || a.path.localeCompare(b.path)).slice(0, top);
10090
11849
  const metadataBytes = [...agents, ...skills].reduce((sum, entry) => sum + entry.metadata_bytes, 0);
10091
11850
  const totalTokens = context.total_tokens + Math.floor(metadataBytes / 4);
@@ -10146,8 +11905,8 @@ async function runBootstrapAudit(opts = {}) {
10146
11905
 
10147
11906
  // src/cli_memory.ts
10148
11907
  init_define_import_meta_env();
10149
- import * as fs14 from "node:fs";
10150
- import * as path16 from "node:path";
11908
+ import * as fs20 from "node:fs";
11909
+ import * as path20 from "node:path";
10151
11910
  function removeExactDupLines(text, report) {
10152
11911
  if (report.exactDupLines.length === 0) return text;
10153
11912
  const toRemove = new Set(report.exactDupLines.map(([, dupIdx]) => dupIdx));
@@ -10205,7 +11964,7 @@ function printReport(reports, clusters) {
10205
11964
  } else {
10206
11965
  for (const cluster of clusters) {
10207
11966
  w(
10208
- ` [${cluster.method}, similarity ${cluster.similarity}, ~${cluster.tokens} tok] ${cluster.members.map((m) => path16.basename(m)).join(", ")}
11967
+ ` [${cluster.method}, similarity ${cluster.similarity}, ~${cluster.tokens} tok] ${cluster.members.map((m) => path20.basename(m)).join(", ")}
10209
11968
  `
10210
11969
  );
10211
11970
  }
@@ -10220,7 +11979,7 @@ async function runMemoryCommand(opts = {}) {
10220
11979
  const claudeMds = findClaudeMdFiles(projectRoot);
10221
11980
  const reports = auditClaudeMd(claudeMds);
10222
11981
  const memoryMdPath = findMemoryMd(projectRoot);
10223
- const clusters = memoryMdPath !== null ? await findContentDuplicates(path16.dirname(memoryMdPath)) : [];
11982
+ const clusters = memoryMdPath !== null ? await findContentDuplicates(path20.dirname(memoryMdPath)) : [];
10224
11983
  printReport(reports, clusters);
10225
11984
  if (opts.fix !== true) return;
10226
11985
  const changes = [];
@@ -10228,7 +11987,7 @@ async function runMemoryCommand(opts = {}) {
10228
11987
  if (report.exactDupLines.length === 0) continue;
10229
11988
  let before;
10230
11989
  try {
10231
- before = fs14.readFileSync(report.path, "utf-8");
11990
+ before = fs20.readFileSync(report.path, "utf-8");
10232
11991
  } catch {
10233
11992
  continue;
10234
11993
  }
@@ -10266,33 +12025,33 @@ async function runMemoryCommand(opts = {}) {
10266
12025
 
10267
12026
  // src/cli_waste.ts
10268
12027
  init_define_import_meta_env();
10269
- import * as fs16 from "node:fs";
10270
- import * as path18 from "node:path";
12028
+ import * as fs22 from "node:fs";
12029
+ import * as path22 from "node:path";
10271
12030
 
10272
12031
  // src/waste.ts
10273
12032
  init_define_import_meta_env();
10274
- import * as fs15 from "node:fs";
12033
+ import * as fs21 from "node:fs";
10275
12034
  import * as os11 from "node:os";
10276
- import * as path17 from "node:path";
12035
+ import * as path21 from "node:path";
10277
12036
  function projectTranscriptsDir(projectRoot) {
10278
- const rootStr = path17.resolve(projectRoot);
12037
+ const rootStr = path21.resolve(projectRoot);
10279
12038
  const slug = rootStr.replace(/[^A-Za-z0-9]/g, "-");
10280
- return path17.join(os11.homedir(), ".claude", "projects", slug);
12039
+ return path21.join(os11.homedir(), ".claude", "projects", slug);
10281
12040
  }
10282
12041
  function findLatestTranscript(projectRoot) {
10283
12042
  const dir = projectTranscriptsDir(projectRoot);
10284
12043
  let entries;
10285
12044
  try {
10286
- entries = fs15.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
12045
+ entries = fs21.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
10287
12046
  } catch {
10288
12047
  return null;
10289
12048
  }
10290
12049
  let best = null;
10291
12050
  for (const name of entries) {
10292
- const full = path17.join(dir, name);
12051
+ const full = path21.join(dir, name);
10293
12052
  let stat;
10294
12053
  try {
10295
- stat = fs15.statSync(full);
12054
+ stat = fs21.statSync(full);
10296
12055
  } catch {
10297
12056
  continue;
10298
12057
  }
@@ -10349,7 +12108,7 @@ function extractResultText(content) {
10349
12108
  return "";
10350
12109
  }
10351
12110
  function parseTranscript2(transcriptPath) {
10352
- const raw = fs15.readFileSync(transcriptPath, "utf-8");
12111
+ const raw = fs21.readFileSync(transcriptPath, "utf-8");
10353
12112
  const calls = [];
10354
12113
  const resultTextById = /* @__PURE__ */ new Map();
10355
12114
  const assistantTurns = [];
@@ -10570,7 +12329,7 @@ function printReport2(report) {
10570
12329
  }
10571
12330
  async function runWasteCommand(opts = {}) {
10572
12331
  const projectRoot = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
10573
- const transcriptPath = opts.transcript !== void 0 ? path18.resolve(opts.transcript) : findLatestTranscript(projectRoot);
12332
+ const transcriptPath = opts.transcript !== void 0 ? path22.resolve(opts.transcript) : findLatestTranscript(projectRoot);
10574
12333
  if (transcriptPath === null) {
10575
12334
  if (opts.json === true) {
10576
12335
  process.stdout.write(`${JSON.stringify({ error: "no session transcript found", project: projectRoot })}
@@ -10584,7 +12343,7 @@ async function runWasteCommand(opts = {}) {
10584
12343
  process.exitCode = 1;
10585
12344
  return;
10586
12345
  }
10587
- if (!fs16.existsSync(transcriptPath)) {
12346
+ if (!fs22.existsSync(transcriptPath)) {
10588
12347
  process.stderr.write(`token-goat: transcript not found: ${transcriptPath}
10589
12348
  `);
10590
12349
  process.exitCode = 1;
@@ -10601,17 +12360,17 @@ async function runWasteCommand(opts = {}) {
10601
12360
 
10602
12361
  // src/session_read.ts
10603
12362
  init_define_import_meta_env();
10604
- import * as fs17 from "node:fs";
10605
- import * as path19 from "node:path";
12363
+ import * as fs23 from "node:fs";
12364
+ import * as path23 from "node:path";
10606
12365
  import * as readline2 from "node:readline";
10607
12366
  function resolveSessionTranscript(arg, opts = {}) {
10608
12367
  if (arg !== void 0 && arg !== "") {
10609
- if (fs17.existsSync(arg) && fs17.statSync(arg).isFile()) return arg;
12368
+ if (fs23.existsSync(arg) && fs23.statSync(arg).isFile()) return arg;
10610
12369
  const projectRoot2 = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
10611
12370
  const dir = projectTranscriptsDir(projectRoot2);
10612
12371
  const byId = arg.endsWith(".jsonl") ? arg : `${arg}.jsonl`;
10613
- const candidate = path19.join(dir, byId);
10614
- if (fs17.existsSync(candidate) && fs17.statSync(candidate).isFile()) return candidate;
12372
+ const candidate = path23.join(dir, byId);
12373
+ if (fs23.existsSync(candidate) && fs23.statSync(candidate).isFile()) return candidate;
10615
12374
  return null;
10616
12375
  }
10617
12376
  const projectRoot = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
@@ -10669,7 +12428,7 @@ function toolCallsForBlocks(blocks) {
10669
12428
  return blocks.filter((b) => b.type === "tool_use" && b.name !== void 0).map((b) => b.name);
10670
12429
  }
10671
12430
  async function* streamTurns(transcriptPath) {
10672
- const input = fs17.createReadStream(transcriptPath, { encoding: "utf8" });
12431
+ const input = fs23.createReadStream(transcriptPath, { encoding: "utf8" });
10673
12432
  const rl = readline2.createInterface({ input, crlfDelay: Infinity });
10674
12433
  try {
10675
12434
  let lineNumber = 0;
@@ -10768,15 +12527,15 @@ function formatSessionSlice(turns) {
10768
12527
 
10769
12528
  // src/cli_mcp_audit.ts
10770
12529
  init_define_import_meta_env();
10771
- import * as fs18 from "node:fs";
10772
- import * as path20 from "node:path";
12530
+ import * as fs24 from "node:fs";
12531
+ import * as path24 from "node:path";
10773
12532
  function readMcpConfig(projectRoot) {
10774
- const configPath2 = path20.join(projectRoot, ".mcp.json");
12533
+ const configPath2 = path24.join(projectRoot, ".mcp.json");
10775
12534
  try {
10776
- if (!fs18.existsSync(configPath2)) {
12535
+ if (!fs24.existsSync(configPath2)) {
10777
12536
  return null;
10778
12537
  }
10779
- const content = fs18.readFileSync(configPath2, "utf-8");
12538
+ const content = fs24.readFileSync(configPath2, "utf-8");
10780
12539
  const parsed = JSON.parse(content);
10781
12540
  return parsed.mcpServers || parsed;
10782
12541
  } catch {
@@ -10791,8 +12550,9 @@ function analyzeMcpCache() {
10791
12550
  const entry = value;
10792
12551
  const command = typeof entry["command"] === "string" ? entry["command"] : "";
10793
12552
  const sizeBytes = typeof entry["sizeBytes"] === "number" ? entry["sizeBytes"] : 0;
10794
- const toolMatch = command.match(/^mcp:mcp__([a-z0-9_-]+)__/i);
10795
- const toolName = toolMatch?.[1] ?? "unknown";
12553
+ const toolMatch = command.match(/^mcp:mcp__(.+?)__/i);
12554
+ if (!toolMatch) continue;
12555
+ const toolName = toolMatch[1];
10796
12556
  if (!serverMetrics.has(toolName)) {
10797
12557
  serverMetrics.set(toolName, { callCount: 0, perCallEstimate: 0, totalBytes: 0 });
10798
12558
  }
@@ -11073,10 +12833,10 @@ function readBoundedText(text, file) {
11073
12833
  if (text === void 0 && file === void 0) throw new CliError("provide text or --file");
11074
12834
  let value;
11075
12835
  if (file !== void 0) {
11076
- if (fs19.statSync(file).size > CONTENT_MAX_INPUT_CHARS) {
12836
+ if (fs25.statSync(file).size > CONTENT_MAX_INPUT_CHARS) {
11077
12837
  throw new CliError(`file exceeds the ${CONTENT_MAX_INPUT_CHARS}-byte safety limit`);
11078
12838
  }
11079
- value = fs19.readFileSync(file, "utf8");
12839
+ value = fs25.readFileSync(file, "utf8");
11080
12840
  } else {
11081
12841
  if (text === void 0) throw new CliError("provide text or --file");
11082
12842
  value = text;
@@ -11293,7 +13053,7 @@ async function cmdMcpServe() {
11293
13053
  let StdioServerTransport;
11294
13054
  try {
11295
13055
  ;
11296
- ({ createMcpServer } = await import("./token-goat-hook-chunk-FMQ37OQD.mjs"));
13056
+ ({ createMcpServer } = await import("./token-goat-hook-chunk-K6HLYRRI.mjs"));
11297
13057
  ({ StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js"));
11298
13058
  } catch (err2) {
11299
13059
  process.stderr.write(
@@ -11306,14 +13066,15 @@ async function cmdMcpServe() {
11306
13066
  const server = await createMcpServer();
11307
13067
  const transport = new StdioServerTransport();
11308
13068
  await server.connect(transport);
11309
- await new Promise((resolve10) => {
11310
- server.server.onclose = resolve10;
13069
+ await new Promise((resolve12) => {
13070
+ server.server.onclose = resolve12;
11311
13071
  });
11312
13072
  }
11313
13073
  async function cmdHook(event, opts) {
11314
13074
  if (typeof opts.harness === "string" && opts.harness.length > 0) {
11315
13075
  process.env[ENV_KEYS.HARNESS_OVERRIDE] = opts.harness;
11316
13076
  }
13077
+ const { relay } = await import("./token-goat-hook-chunk-ULGSXGZK.mjs");
11317
13078
  await relay(event);
11318
13079
  }
11319
13080
  async function cmdInstall(opts) {
@@ -11409,16 +13170,16 @@ async function cmdInstall(opts) {
11409
13170
  );
11410
13171
  }
11411
13172
  try {
11412
- const skillDir = path21.join(homedir10(), ".claude", "skills");
11413
- if (fs19.existsSync(skillDir)) {
11414
- const entries = fs19.readdirSync(skillDir, { withFileTypes: true });
13173
+ const skillDir = path25.join(homedir11(), ".claude", "skills");
13174
+ if (fs25.existsSync(skillDir)) {
13175
+ const entries = fs25.readdirSync(skillDir, { withFileTypes: true });
11415
13176
  const skillNames = [];
11416
13177
  const sessionId = getSessionId();
11417
13178
  for (const entry of entries) {
11418
13179
  if (!entry.isDirectory()) continue;
11419
- const skillFile = path21.join(skillDir, entry.name, "SKILL.md");
11420
- if (fs19.existsSync(skillFile)) {
11421
- const body = fs19.readFileSync(skillFile, "utf-8");
13180
+ const skillFile = path25.join(skillDir, entry.name, "SKILL.md");
13181
+ if (fs25.existsSync(skillFile)) {
13182
+ const body = fs25.readFileSync(skillFile, "utf-8");
11422
13183
  const compact = extractCompactFromMarker(body);
11423
13184
  if (compact === null) continue;
11424
13185
  const sourceSha = contentHash(body);
@@ -11428,10 +13189,10 @@ async function cmdInstall(opts) {
11428
13189
  }
11429
13190
  if (skillNames.length > 0) {
11430
13191
  const dir = skillOutputsDir();
11431
- await fs19.promises.mkdir(dir, { recursive: true });
11432
- const pregenPath = path21.join(dir, "pregen.json");
13192
+ await fs25.promises.mkdir(dir, { recursive: true });
13193
+ const pregenPath = path25.join(dir, "pregen.json");
11433
13194
  const pregenData = { ts: Date.now(), names: skillNames };
11434
- await fs19.promises.writeFile(pregenPath, JSON.stringify(pregenData, null, 2));
13195
+ await fs25.promises.writeFile(pregenPath, JSON.stringify(pregenData, null, 2));
11435
13196
  out(`Pre-generated ${skillNames.length} skill compacts.`);
11436
13197
  }
11437
13198
  }
@@ -11472,6 +13233,9 @@ function cmdUninstall(opts) {
11472
13233
  const removed2 = removal.run();
11473
13234
  out(removed2 ? `Removed token-goat ${removal.label}.` : `No token-goat ${removal.label} to remove.`);
11474
13235
  }
13236
+ for (const leftover of leftoverIntegrations(opts)) {
13237
+ out(`NOTE: the token-goat ${leftover.label} is still installed. Run "token-goat uninstall ${leftover.flag}" to remove it.`);
13238
+ }
11475
13239
  if (opts.vscode === true && otherScopeHasManagedServer({ project: opts.project === true })) {
11476
13240
  const otherScope = opts.project === true ? "user" : "project";
11477
13241
  out(`NOTE: token-goat is still registered in VS Code ${otherScope} scope. Run "token-goat uninstall --vscode${otherScope === "project" ? " --project" : ""}" to remove it too.`);
@@ -11479,6 +13243,43 @@ function cmdUninstall(opts) {
11479
13243
  if (opts.hermes === true) {
11480
13244
  out("No separate Hermes integration to remove (it shares the Claude Code hook entries).");
11481
13245
  }
13246
+ if (opts.purge === true) runPurge();
13247
+ }
13248
+ function leftoverIntegrations(opts) {
13249
+ const candidates = [
13250
+ { skipped: opts.codex !== true, present: isCodexInstalled, flag: "--codex", label: "Codex CLI integration" },
13251
+ { skipped: opts.gemini !== true, present: isGeminiInstalled, flag: "--gemini", label: "Gemini CLI integration" },
13252
+ { skipped: opts.qwen !== true, present: isQwenInstalled, flag: "--qwen", label: "Qwen Code integration" },
13253
+ { skipped: opts.pi !== true, present: () => isPiInstalled() || isPiInstalled({ local: true }), flag: "--pi", label: "pi extension" },
13254
+ { skipped: opts.openclaw !== true, present: isOpenclawInstalled, flag: "--openclaw", label: "OpenClaw integration" },
13255
+ {
13256
+ skipped: opts.copilot !== true,
13257
+ present: () => isCopilotCliInstalled() || isCopilotCliInstalled({ local: true }),
13258
+ flag: "--copilot",
13259
+ label: "Copilot CLI integration"
13260
+ },
13261
+ { skipped: opts.opencode !== true, present: isOpencodeInstalled, flag: "--opencode", label: "opencode plugin" },
13262
+ { skipped: opts.grok !== true, present: isGrokInstalled, flag: "--grok", label: "Grok CLI integration" }
13263
+ ];
13264
+ const found = [];
13265
+ for (const candidate of candidates) {
13266
+ if (!candidate.skipped) continue;
13267
+ try {
13268
+ if (candidate.present()) found.push({ flag: candidate.flag, label: candidate.label });
13269
+ } catch {
13270
+ }
13271
+ }
13272
+ return found;
13273
+ }
13274
+ function runPurge() {
13275
+ if (isWorkerRunning()) {
13276
+ err('token-goat: the background worker is running, so --purge would delete files it is about to rewrite. Run "token-goat worker stop" first.');
13277
+ return;
13278
+ }
13279
+ const result = purgeDataDirectories();
13280
+ for (const root of result.absent) out(`Nothing to purge at ${root}.`);
13281
+ for (const removed of result.removed) out(`Purged ${removed.path} (${formatBytes(removed.bytes)} reclaimed).`);
13282
+ for (const failure of result.failed) err(`token-goat: could not purge ${failure.path}: ${failure.reason}`);
11482
13283
  }
11483
13284
  function cmdWorkerStart() {
11484
13285
  if (isWorkerRunning()) {
@@ -11583,7 +13384,7 @@ ${formatSessionOutline(turns)}`;
11583
13384
  }
11584
13385
  function sessionTranscriptSize(transcriptPath) {
11585
13386
  try {
11586
- return fs19.statSync(transcriptPath).size;
13387
+ return fs25.statSync(transcriptPath).size;
11587
13388
  } catch {
11588
13389
  return 0;
11589
13390
  }
@@ -11640,7 +13441,27 @@ function cmdHintStats(opts = {}) {
11640
13441
  ...opts.markIneffective !== void 0 && isHintCategory(opts.markIneffective) ? { markIneffective: opts.markIneffective } : {}
11641
13442
  });
11642
13443
  }
11643
- function _applyFiltersAndPrint(content, opts) {
13444
+ function _applyFiltersAndPrint(content, opts, fenceUntrusted = false, fenceTag = UNTRUSTED_WEB_TAG) {
13445
+ const emit2 = (text) => {
13446
+ if (!fenceUntrusted || text === "") {
13447
+ out(text);
13448
+ return text;
13449
+ }
13450
+ let matches2 = [];
13451
+ try {
13452
+ if (loadConfig().injection.enabled) matches2 = scanForInjectionPatterns(text);
13453
+ } catch {
13454
+ matches2 = [];
13455
+ }
13456
+ if (matches2.length === 0) {
13457
+ out(text);
13458
+ return text;
13459
+ }
13460
+ recordStat("injection_detected", 0, 0, void 0, matches2.join(","));
13461
+ const fenced = fenceUntrustedContent(text, matches2, fenceTag);
13462
+ out(fenced);
13463
+ return fenced;
13464
+ };
11644
13465
  if (opts.section !== void 0) {
11645
13466
  const sectionResult = extractSection(content, opts.section);
11646
13467
  if (sectionResult === null) {
@@ -11669,9 +13490,7 @@ function _applyFiltersAndPrint(content, opts) {
11669
13490
  }
11670
13491
  const lines = content.split(/\r?\n/);
11671
13492
  if (opts.full === true) {
11672
- const printedFull = lines.join("\n");
11673
- out(printedFull);
11674
- return printedFull;
13493
+ return emit2(lines.join("\n"));
11675
13494
  }
11676
13495
  const headN = opts.head !== void 0 ? requireNonNegativeInt("--head", opts.head) : 30;
11677
13496
  const tailN = opts.tail !== void 0 ? requireNonNegativeInt("--tail", opts.tail) : 80;
@@ -11686,13 +13505,11 @@ function _applyFiltersAndPrint(content, opts) {
11686
13505
  } else if (opts.tail !== void 0) {
11687
13506
  result = lines.slice(Math.max(0, lines.length - tailN));
11688
13507
  }
11689
- const printed = result.join("\n");
11690
- out(printed);
11691
- return printed;
13508
+ return emit2(result.join("\n"));
11692
13509
  }
11693
13510
  function fileSizeOrZero(filePath) {
11694
13511
  try {
11695
- return fs19.statSync(filePath).size;
13512
+ return fs25.statSync(filePath).size;
11696
13513
  } catch {
11697
13514
  return 0;
11698
13515
  }
@@ -11707,16 +13524,16 @@ function cmdBashOutput(id, opts) {
11707
13524
  }
11708
13525
  let content;
11709
13526
  try {
11710
- const st = fs19.statSync(opts.file);
13527
+ const st = fs25.statSync(opts.file);
11711
13528
  if (st.isFIFO() || st.isSocket()) {
11712
13529
  throw new CliError(`--file '${opts.file}' is a special file (FIFO or socket) \u2014 only regular files are supported`);
11713
13530
  }
11714
- content = fs19.readFileSync(opts.file, "utf-8");
13531
+ content = fs25.readFileSync(opts.file, "utf-8");
11715
13532
  } catch (e) {
11716
13533
  if (e instanceof CliError) throw e;
11717
13534
  throw new CliError(`cannot read file: ${opts.file}`);
11718
13535
  }
11719
- _applyFiltersAndPrint(opts.transcript === true ? extractTranscriptText(content) : content, opts);
13536
+ _applyFiltersAndPrint(opts.transcript === true ? extractTranscriptText(content) : content, opts, true, UNTRUSTED_TOOL_TAG);
11720
13537
  return;
11721
13538
  }
11722
13539
  if (id === void 0) {
@@ -11726,7 +13543,7 @@ function cmdBashOutput(id, opts) {
11726
13543
  if (entry === null) {
11727
13544
  throw new CliError(`no cached bash output for id: ${id}. If this id is from a background task, recall its output file directly with: token-goat bash-output --file <path-to-output-file>`);
11728
13545
  }
11729
- _applyFiltersAndPrint(entry.output, opts);
13546
+ _applyFiltersAndPrint(entry.output, opts, true, UNTRUSTED_TOOL_TAG);
11730
13547
  }
11731
13548
  function cmdWebOutput(id, opts) {
11732
13549
  if (id === void 0) {
@@ -11736,7 +13553,7 @@ function cmdWebOutput(id, opts) {
11736
13553
  if (content === null) {
11737
13554
  throw new CliError(`no cached web output for id: ${id}. The cache may have expired; re-run the WebFetch to repopulate it.`);
11738
13555
  }
11739
- _applyFiltersAndPrint(content, opts);
13556
+ _applyFiltersAndPrint(content, opts, true);
11740
13557
  }
11741
13558
  function cmdMcpOutput(id, opts) {
11742
13559
  if (id === void 0) {
@@ -11749,7 +13566,7 @@ function cmdMcpOutput(id, opts) {
11749
13566
  if (entry === null) {
11750
13567
  throw new CliError(`no cached mcp output for id: ${id}. The cache may have expired; re-run the MCP tool call to repopulate it.`);
11751
13568
  }
11752
- _applyFiltersAndPrint(entry.output, opts);
13569
+ _applyFiltersAndPrint(entry.output, opts, true, UNTRUSTED_TOOL_TAG);
11753
13570
  }
11754
13571
  async function cmdPdfExtract(file, opts) {
11755
13572
  const text = await runPdfExtractText(file, opts.pages, opts.layout === true);
@@ -12092,16 +13909,17 @@ function emitExtraFileArgsNote(command, first, extras, opts = {}) {
12092
13909
  if (extras === void 0 || extras.length === 0) return;
12093
13910
  out(extraFileArgsNote(command, first, extras, opts));
12094
13911
  }
12095
- function cmdCompress(opts) {
13912
+ async function cmdCompress(opts) {
12096
13913
  try {
13914
+ const bashRunner = await import("./token-goat-hook-chunk-3OJELLK5.mjs");
12097
13915
  if (opts.compress === false) {
12098
- process.exitCode = runRaw(opts.cmd, parseTimeout(opts.timeout));
13916
+ process.exitCode = bashRunner.runRaw(opts.cmd, parseTimeout(opts.timeout, bashRunner.DEFAULT_TIMEOUT_SECONDS));
12099
13917
  return;
12100
13918
  }
12101
13919
  const maxTokens = opts.maxTokens !== void 0 ? requireNonNegativeInt("--max-tokens", opts.maxTokens) : 0;
12102
- process.exitCode = run(opts.cmd, {
13920
+ process.exitCode = bashRunner.run(opts.cmd, {
12103
13921
  filterName: opts.filter,
12104
- timeout: parseTimeout(opts.timeout),
13922
+ timeout: parseTimeout(opts.timeout, bashRunner.DEFAULT_TIMEOUT_SECONDS),
12105
13923
  maxTokens,
12106
13924
  ...opts.profile !== void 0 ? { compressionProfile: opts.profile } : {}
12107
13925
  });
@@ -12110,16 +13928,16 @@ function cmdCompress(opts) {
12110
13928
  process.exitCode = 1;
12111
13929
  }
12112
13930
  }
12113
- function parseTimeout(raw) {
13931
+ function parseTimeout(raw, fallbackSeconds) {
12114
13932
  const sec = raw ? parseInt(raw, 10) : 0;
12115
- return Number.isFinite(sec) && sec > 0 ? sec : DEFAULT_TIMEOUT_SECONDS;
13933
+ return Number.isFinite(sec) && sec > 0 ? sec : fallbackSeconds;
12116
13934
  }
12117
13935
  async function cmdSkillBody(name, opts) {
12118
13936
  const filePath = await getSkillFilePath(name);
12119
13937
  if (filePath === null) {
12120
13938
  throw new CliError(`skill '${name}' not found`);
12121
13939
  }
12122
- const body = fs19.readFileSync(filePath, "utf-8");
13940
+ const body = fs25.readFileSync(filePath, "utf-8");
12123
13941
  if (opts.compact === true) {
12124
13942
  out(extractCompactFromMarker(body) ?? body);
12125
13943
  } else {
@@ -12136,7 +13954,7 @@ async function cmdSkillCompact(name, opts) {
12136
13954
  for (const skill of skills) {
12137
13955
  const filePath = await getSkillFilePath(skill.name);
12138
13956
  if (!filePath) continue;
12139
- const body2 = fs19.readFileSync(filePath, "utf-8");
13957
+ const body2 = fs25.readFileSync(filePath, "utf-8");
12140
13958
  const compact2 = extractCompactFromMarker(body2);
12141
13959
  if (compact2 === null) continue;
12142
13960
  const sourceSha2 = contentHash(body2);
@@ -12157,19 +13975,19 @@ async function cmdSkillCompact(name, opts) {
12157
13975
  if (!opts.path.trim()) {
12158
13976
  throw new CliError("--path cannot be empty");
12159
13977
  }
12160
- if (!fs19.existsSync(opts.path)) {
13978
+ if (!fs25.existsSync(opts.path)) {
12161
13979
  throw new CliError(`skill file not found: ${opts.path}`);
12162
13980
  }
12163
13981
  try {
12164
- body = fs19.readFileSync(opts.path, "utf-8");
13982
+ body = fs25.readFileSync(opts.path, "utf-8");
12165
13983
  } catch (e) {
12166
13984
  if (e.code === "ENOENT") {
12167
13985
  throw new CliError(`skill file not found: ${opts.path}`);
12168
13986
  }
12169
13987
  throw new CliError(`failed to read skill file '${opts.path}': ${extractErrorMessage(e)}`);
12170
13988
  }
12171
- cacheName = name ?? path21.basename(path21.dirname(path21.resolve(opts.path)));
12172
- sourcePath = path21.resolve(opts.path);
13989
+ cacheName = name ?? path25.basename(path25.dirname(path25.resolve(opts.path)));
13990
+ sourcePath = path25.resolve(opts.path);
12173
13991
  } else {
12174
13992
  if (name === void 0 || !name.trim()) {
12175
13993
  throw new CliError("skill-compact requires a <name> or --path <file>");
@@ -12178,7 +13996,7 @@ async function cmdSkillCompact(name, opts) {
12178
13996
  if (filePath === null) {
12179
13997
  throw new CliError(`skill '${name}' not found`);
12180
13998
  }
12181
- body = fs19.readFileSync(filePath, "utf-8");
13999
+ body = fs25.readFileSync(filePath, "utf-8");
12182
14000
  cacheName = name;
12183
14001
  sourcePath = filePath;
12184
14002
  }
@@ -12310,8 +14128,8 @@ async function cmdSkillDiff(name) {
12310
14128
  }
12311
14129
  const newer = versions[0];
12312
14130
  const older = versions[1];
12313
- const newerBody = await fs19.promises.readFile(path21.resolve(dir, `${newer.outputId}.txt`), "utf-8").catch(() => null);
12314
- const olderBody = await fs19.promises.readFile(path21.resolve(dir, `${older.outputId}.txt`), "utf-8").catch(() => null);
14131
+ const newerBody = await fs25.promises.readFile(path25.resolve(dir, `${newer.outputId}.txt`), "utf-8").catch(() => null);
14132
+ const olderBody = await fs25.promises.readFile(path25.resolve(dir, `${older.outputId}.txt`), "utf-8").catch(() => null);
12315
14133
  if (newerBody === null || olderBody === null) {
12316
14134
  out(`a cached version of '${name}' was evicted while diffing -- try again`);
12317
14135
  return;
@@ -12340,7 +14158,7 @@ async function cmdSkillSection(nameHeading, headingArg) {
12340
14158
  if (!filePath) {
12341
14159
  throw new CliError(`skill '${skillName}' not found`);
12342
14160
  }
12343
- const body = fs19.readFileSync(filePath, "utf-8");
14161
+ const body = fs25.readFileSync(filePath, "utf-8");
12344
14162
  const extracted = extractNamedSection(body, heading);
12345
14163
  if (!extracted) {
12346
14164
  process.exitCode = 1;
@@ -12350,7 +14168,7 @@ async function cmdSkillSection(nameHeading, headingArg) {
12350
14168
  }
12351
14169
  function atomicWriteBuffer(dest, data) {
12352
14170
  try {
12353
- if (fs19.statSync(dest).isDirectory()) {
14171
+ if (fs25.statSync(dest).isDirectory()) {
12354
14172
  const e = Object.assign(new Error(`EISDIR: illegal operation on a directory, open '${dest}'`), { code: "EISDIR", path: dest });
12355
14173
  throw e;
12356
14174
  }
@@ -12358,23 +14176,23 @@ function atomicWriteBuffer(dest, data) {
12358
14176
  if (e.code !== "ENOENT") throw e;
12359
14177
  }
12360
14178
  const rnd = Math.random().toString(36).slice(2, 8);
12361
- const tmp = path21.join(path21.dirname(path21.resolve(dest)), `.tmp.${process.pid}.${rnd}`);
14179
+ const tmp = path25.join(path25.dirname(path25.resolve(dest)), `.tmp.${process.pid}.${rnd}`);
12362
14180
  try {
12363
- fs19.writeFileSync(tmp, data, { mode: 384 });
14181
+ fs25.writeFileSync(tmp, data, { mode: 384 });
12364
14182
  try {
12365
- const destMode = fs19.statSync(dest).mode;
12366
- fs19.chmodSync(tmp, destMode);
14183
+ const destMode = fs25.statSync(dest).mode;
14184
+ fs25.chmodSync(tmp, destMode);
12367
14185
  } catch (e) {
12368
14186
  if (e.code !== "ENOENT") throw e;
12369
14187
  }
12370
14188
  withRetryOnLock(() => {
12371
14189
  try {
12372
- fs19.renameSync(tmp, dest);
14190
+ fs25.renameSync(tmp, dest);
12373
14191
  } catch (e) {
12374
14192
  if (e.code === "EXDEV") {
12375
- fs19.copyFileSync(tmp, dest);
14193
+ fs25.copyFileSync(tmp, dest);
12376
14194
  try {
12377
- fs19.unlinkSync(tmp);
14195
+ fs25.unlinkSync(tmp);
12378
14196
  } catch (ue) {
12379
14197
  process.stderr.write(`token-goat write-file: warning: could not remove temp file ${tmp}: ${ue.message}
12380
14198
  `);
@@ -12386,7 +14204,7 @@ function atomicWriteBuffer(dest, data) {
12386
14204
  });
12387
14205
  } catch (e) {
12388
14206
  try {
12389
- fs19.unlinkSync(tmp);
14207
+ fs25.unlinkSync(tmp);
12390
14208
  } catch {
12391
14209
  }
12392
14210
  throw e;
@@ -12396,9 +14214,9 @@ function mapFsError(e, src, dest, srcLabel = "source") {
12396
14214
  const fe = e;
12397
14215
  if (fe.code === "ENOENT") {
12398
14216
  const errPath = fe.path ?? "";
12399
- const isSource = src !== void 0 && path21.resolve(errPath) === path21.resolve(src);
14217
+ const isSource = src !== void 0 && path25.resolve(errPath) === path25.resolve(src);
12400
14218
  if (isSource) throw new CliError(`${srcLabel} file not found: ${src}`);
12401
- const destDir = dest ? path21.dirname(path21.resolve(dest)) : path21.dirname(path21.resolve(errPath || "."));
14219
+ const destDir = dest ? path25.dirname(path25.resolve(dest)) : path25.dirname(path25.resolve(errPath || "."));
12402
14220
  throw new CliError(`destination directory does not exist: ${destDir}`);
12403
14221
  }
12404
14222
  if (fe.code === "ENOTDIR") {
@@ -12409,7 +14227,7 @@ function mapFsError(e, src, dest, srcLabel = "source") {
12409
14227
  }
12410
14228
  if (fe.code === "EISDIR") {
12411
14229
  const errPath = fe.path ?? "";
12412
- const isSource = src !== void 0 && (errPath === "" || path21.resolve(errPath) === path21.resolve(src));
14230
+ const isSource = src !== void 0 && (errPath === "" || path25.resolve(errPath) === path25.resolve(src));
12413
14231
  if (isSource) throw new CliError(`source is a directory, not a file: ${src}`);
12414
14232
  throw new CliError(`destination is a directory, not a file: ${dest ?? (errPath || "(unknown)")}`);
12415
14233
  }
@@ -12478,7 +14296,7 @@ function validateWritablePath(dest, label) {
12478
14296
  throw new CliError(`${label} path contains a null byte`);
12479
14297
  }
12480
14298
  if (isWindows()) {
12481
- const base = path21.basename(dest);
14299
+ const base = path25.basename(dest);
12482
14300
  const stem = base.replace(/\.[^.]*$/, "").toUpperCase();
12483
14301
  if (WIN_RESERVED.has(stem)) {
12484
14302
  throw new CliError(`${label} '${base}' is a reserved Windows device name`);
@@ -12508,7 +14326,7 @@ function readFileBoundedRaw(filePath, label, allowStdIn = false) {
12508
14326
  throw new CliError(`${label} ${filePath} requires piped input; use ${altLabel} for interactive use`);
12509
14327
  }
12510
14328
  try {
12511
- const st = fs19.statSync(filePath);
14329
+ const st = fs25.statSync(filePath);
12512
14330
  if (st.isFIFO() || st.isSocket()) {
12513
14331
  throw new CliError(`${label} '${filePath}' is a special file (FIFO or socket) \u2014 only regular files are supported`);
12514
14332
  }
@@ -12516,7 +14334,7 @@ function readFileBoundedRaw(filePath, label, allowStdIn = false) {
12516
14334
  if (st.size > maxBytes) {
12517
14335
  throw new CliError(`${label} '${filePath}' exceeds size limit (${Math.round(st.size / 1024 / 1024)} MB); set TOKEN_GOAT_MAX_STDIN_MB to override`);
12518
14336
  }
12519
- return fs19.readFileSync(filePath);
14337
+ return fs25.readFileSync(filePath);
12520
14338
  } catch (e) {
12521
14339
  if (e instanceof CliError) throw e;
12522
14340
  mapFsError(e, filePath, void 0, label);
@@ -12560,7 +14378,7 @@ function cmdNoteAdd(file, opts) {
12560
14378
  throw new CliError("note content must be valid UTF-8 text");
12561
14379
  }
12562
14380
  const resolvedPath = resolveIndexPath(file);
12563
- if (!fs19.existsSync(resolvedPath)) {
14381
+ if (!fs25.existsSync(resolvedPath)) {
12564
14382
  throw new CliError(`File not found: '${resolvedPath}'`);
12565
14383
  }
12566
14384
  healStaleIndex(resolvedPath);
@@ -12619,7 +14437,7 @@ function cmdWriteFile(dest, opts) {
12619
14437
  throw new CliError(`TOKEN_GOAT_MAX_STDIN_MB must be a positive integer; got '${process.env["TOKEN_GOAT_MAX_STDIN_MB"] ?? ""}'`);
12620
14438
  }
12621
14439
  const maxBytes = maxMB * 1024 * 1024;
12622
- return new Promise((resolve10, reject) => {
14440
+ return new Promise((resolve12, reject) => {
12623
14441
  const chunks = [];
12624
14442
  let totalBytes = 0;
12625
14443
  let settled = false;
@@ -12643,7 +14461,7 @@ function cmdWriteFile(dest, opts) {
12643
14461
  try {
12644
14462
  atomicWriteBuffer(dest, Buffer.concat(chunks));
12645
14463
  enqueueDirtyPathSafe(dest);
12646
- resolve10();
14464
+ resolve12();
12647
14465
  } catch (e) {
12648
14466
  try {
12649
14467
  mapFsError(e, void 0, dest);
@@ -12775,7 +14593,7 @@ function writeReplacedBuffer(file, replacedBuf, preWriteStat) {
12775
14593
  }
12776
14594
  let preRenameStat;
12777
14595
  try {
12778
- preRenameStat = fs19.statSync(file);
14596
+ preRenameStat = fs25.statSync(file);
12779
14597
  } catch {
12780
14598
  }
12781
14599
  if (preRenameStat !== void 0 && (preRenameStat.mtimeMs !== preWriteStat.mtimeMs || preRenameStat.size !== preWriteStat.size)) {
@@ -12816,7 +14634,7 @@ function cmdReplace(file, opts) {
12816
14634
  const targetBuf = readFileBoundedRaw(file, "target file", true);
12817
14635
  let preWriteStat;
12818
14636
  try {
12819
- preWriteStat = fs19.statSync(file);
14637
+ preWriteStat = fs25.statSync(file);
12820
14638
  } catch {
12821
14639
  }
12822
14640
  const usingFrom = opts.oldFrom !== void 0 || opts.newFrom !== void 0;
@@ -12910,7 +14728,7 @@ function cmdInsertSection(file, opts) {
12910
14728
  }
12911
14729
  let preWriteStat;
12912
14730
  try {
12913
- preWriteStat = fs19.statSync(file);
14731
+ preWriteStat = fs25.statSync(file);
12914
14732
  } catch {
12915
14733
  }
12916
14734
  const result = readSection(file, opts.after);
@@ -12924,7 +14742,7 @@ function cmdInsertSection(file, opts) {
12924
14742
  }
12925
14743
  let rawText;
12926
14744
  try {
12927
- rawText = fs19.readFileSync(file, "utf-8");
14745
+ rawText = fs25.readFileSync(file, "utf-8");
12928
14746
  } catch (e) {
12929
14747
  mapFsError(e, void 0, file);
12930
14748
  }
@@ -12939,7 +14757,7 @@ function cmdInsertSection(file, opts) {
12939
14757
  if (preWriteStat !== void 0) {
12940
14758
  let preRenameStat;
12941
14759
  try {
12942
- preRenameStat = fs19.statSync(file);
14760
+ preRenameStat = fs25.statSync(file);
12943
14761
  } catch {
12944
14762
  }
12945
14763
  if (preRenameStat !== void 0 && (preRenameStat.mtimeMs !== preWriteStat.mtimeMs || preRenameStat.size !== preWriteStat.size)) {
@@ -12956,6 +14774,9 @@ function cmdInsertSection(file, opts) {
12956
14774
  out(`inserted after '${result.heading}'${redirectNote} in ${file}`);
12957
14775
  }
12958
14776
  async function cmdGdriveSections(fileId, opts) {
14777
+ if (!loadConfig().gdrive.enabled) {
14778
+ throw new CliError("gdrive-sections is disabled by gdrive.enabled = false in this install");
14779
+ }
12959
14780
  const fetchOpts = { fresh: opts.fresh === true };
12960
14781
  const text = await fetchDoc(fileId, fetchOpts);
12961
14782
  const fullSourceBytes = Buffer.byteLength(text, "utf8");
@@ -12977,25 +14798,25 @@ ${content}`;
12977
14798
  }
12978
14799
  function expandGlobs(root, patterns, globFnOverride) {
12979
14800
  const out2 = [];
12980
- const globFn = globFnOverride ?? fs19["globSync"];
14801
+ const globFn = globFnOverride ?? fs25["globSync"];
12981
14802
  for (const p of patterns) {
12982
14803
  if (globFn !== void 0 && (p.includes("*") || p.includes("?") || p.includes("{"))) {
12983
14804
  try {
12984
14805
  const hits = globFn(p, { cwd: root });
12985
- for (const h of hits) out2.push(path21.isAbsolute(h) ? h : path21.join(root, h));
14806
+ for (const h of hits) out2.push(path25.isAbsolute(h) ? h : path25.join(root, h));
12986
14807
  continue;
12987
14808
  } catch {
12988
14809
  }
12989
14810
  }
12990
- out2.push(path21.isAbsolute(p) ? p : path21.join(root, p));
14811
+ out2.push(path25.isAbsolute(p) ? p : path25.join(root, p));
12991
14812
  }
12992
14813
  return out2;
12993
14814
  }
12994
14815
  function readIgnoreFile(root) {
12995
- const ignorePath = path21.join(root, ".tokengoatignore");
14816
+ const ignorePath = path25.join(root, ".tokengoatignore");
12996
14817
  let raw;
12997
14818
  try {
12998
- raw = fs19.readFileSync(ignorePath, "utf8");
14819
+ raw = fs25.readFileSync(ignorePath, "utf8");
12999
14820
  } catch {
13000
14821
  return void 0;
13001
14822
  }
@@ -13036,14 +14857,14 @@ function cmdPack(patterns, opts) {
13036
14857
  }
13037
14858
  let instruction;
13038
14859
  if (opts.instructionFile !== void 0) {
13039
- instruction = fs19.readFileSync(opts.instructionFile, "utf8");
14860
+ instruction = fs25.readFileSync(opts.instructionFile, "utf8");
13040
14861
  }
13041
14862
  const formatted = formatPack(result, style, {
13042
14863
  ...opts.lineNumbers === true ? { line_numbers: true } : {},
13043
14864
  ...instruction !== void 0 ? { instruction } : {}
13044
14865
  });
13045
14866
  if (opts.output !== void 0) {
13046
- fs19.writeFileSync(opts.output, formatted, "utf8");
14867
+ fs25.writeFileSync(opts.output, formatted, "utf8");
13047
14868
  } else {
13048
14869
  out(formatted);
13049
14870
  }
@@ -13061,7 +14882,7 @@ function cmdTokens(patterns, opts) {
13061
14882
  if (opts.tree === true) {
13062
14883
  const dirs = /* @__PURE__ */ new Map();
13063
14884
  for (const e of entries) {
13064
- const dir = path21.dirname(e.rel_path);
14885
+ const dir = path25.dirname(e.rel_path);
13065
14886
  if (!dirs.has(dir)) dirs.set(dir, []);
13066
14887
  dirs.get(dir).push(e);
13067
14888
  }
@@ -13071,7 +14892,7 @@ function cmdTokens(patterns, opts) {
13071
14892
  const pct = result.total_tokens > 0 ? Math.round(dirTokens / result.total_tokens * 100) : 0;
13072
14893
  lines2.push(`${dir}/ (${dirTokens} tokens, ${pct}%)`);
13073
14894
  for (const e of dirEntries) {
13074
- lines2.push(` ${path21.basename(e.rel_path).padEnd(30)} ${String(e.tokens).padStart(8)} tokens`);
14895
+ lines2.push(` ${path25.basename(e.rel_path).padEnd(30)} ${String(e.tokens).padStart(8)} tokens`);
13075
14896
  }
13076
14897
  }
13077
14898
  out(lines2.join("\n"));
@@ -13102,7 +14923,7 @@ function cmdBudget(patterns, opts) {
13102
14923
  }
13103
14924
  }
13104
14925
  function cmdFailures(src, opts) {
13105
- const text = src !== void 0 ? fs19.readFileSync(src, "utf8") : fs19.readFileSync(0, "utf8");
14926
+ const text = src !== void 0 ? fs25.readFileSync(src, "utf8") : fs25.readFileSync(0, "utf8");
13106
14927
  const result = extractFailures(text, opts.runner !== void 0 ? { runner: opts.runner } : {});
13107
14928
  if (opts.delta !== true) {
13108
14929
  out(opts.json === true ? formatFailuresJson(result) : formatFailuresText(result));
@@ -13143,6 +14964,12 @@ function buildProgram() {
13143
14964
  if (projectParseErr !== null) {
13144
14965
  err(`token-goat: .token-goat.toml failed to parse (${projectParseErr}); ignoring project override`);
13145
14966
  }
14967
+ const lockedKeys = lastProjectConfigLockedKeys();
14968
+ if (lockedKeys.length > 0) {
14969
+ err(
14970
+ `token-goat: .token-goat.toml may not set ${lockedKeys.join(", ")}; these are security settings and come from the global config or the environment only`
14971
+ );
14972
+ }
13146
14973
  try {
13147
14974
  await fn(...args);
13148
14975
  if (process.exitCode === void 0) {
@@ -13289,7 +15116,7 @@ function buildProgram() {
13289
15116
  program2.command("handoff-resolve <name>").description("resolve a project-local handoff compactly, or return it in full").option("--full", "return the full handoff text").action(guard(cmdHandoffResolve));
13290
15117
  program2.command("hook <event>").description("hook relay entrypoint (reads JSON on stdin)").option("--harness <name>", "override harness detection for this invocation (sets TOKEN_GOAT_HARNESS_OVERRIDE)").action(guard(cmdHook));
13291
15118
  program2.command("install").description("install hooks into Claude Code settings").option("-p, --project", "install into project scope instead of user scope").option("--codex", "also patch Codex CLI (~/.codex/config.toml, ~/.codex/AGENTS.md)").option("--gemini", "also patch Gemini CLI (~/.gemini/settings.json)").option("--qwen", "also patch Qwen Code (~/.qwen/settings.json)").option("--pi", "also drop a pi (pi-coding-agent) extension (~/.pi/agent/extensions/token-goat.ts)").option("--opencode", "also drop an opencode plugin (~/.config/opencode/plugins/token-goat.ts, %APPDATA%\\opencode\\plugins\\token-goat.ts on Windows)").option("--hermes", "verify token-goat hooks are present for Hermes Agent (writes nothing new)").option("--openclaw", "also register an OpenClaw plugin (~/.openclaw/openclaw.json, ~/.openclaw/plugins/token-goat.ts)").option("--copilot", "also register a Copilot CLI hook config and routing block (~/.copilot/hooks/token-goat.json, ~/.copilot/hooks/token-goat-shim.js, ~/.copilot/copilot-instructions.md; with --local, <project>/.github/hooks/token-goat.json, <project>/.github/hooks/token-goat-shim.js, <project>/.github/copilot-instructions.md)").option("--grok", "also register a Grok CLI (xAI Grok Build) hook config (~/.grok/hooks/token-goat.json, ~/.grok/hooks/token-goat-shim.js)").option("--vscode", "also configure a VS Code MCP server (user-profile mcp.json by default; -p/--project for the workspace .vscode/mcp.json) and Copilot routing guidance").option("--local", "with --pi, install the project-local extension (<project>/.pi/extensions/token-goat.ts) instead of the global one").action(guard(cmdInstall));
13292
- program2.command("uninstall").description("remove token-goat hooks from Claude Code settings").option("-p, --project", "uninstall from project scope instead of user scope").option("--codex", "also strip the Codex CLI integration (~/.codex/config.toml, ~/.codex/AGENTS.md)").option("--gemini", "also strip the Gemini CLI integration (~/.gemini/settings.json)").option("--qwen", "also strip the Qwen Code integration (~/.qwen/settings.json)").option("--pi", "also remove the pi (pi-coding-agent) extension").option("--opencode", "also remove the opencode plugin").option("--hermes", "no-op verification flag for symmetry with install (removes no files)").option("--openclaw", "also remove the OpenClaw plugin and config entry").option("--copilot", "also remove the Copilot CLI hook config and shim script, and strip the token-goat block from ~/.copilot/copilot-instructions.md (or <project>/.github/copilot-instructions.md with --local)").option("--grok", "also remove the Grok CLI hook config and shim script").option("--vscode", "also remove the VS Code MCP server (user scope by default; -p/--project for the workspace one) and routing guidance").option("--local", "with --pi, remove the project-local extension instead of the global one").action(guard(cmdUninstall));
15119
+ program2.command("uninstall").description("remove token-goat hooks from Claude Code settings").option("-p, --project", "uninstall from project scope instead of user scope").option("--codex", "also strip the Codex CLI integration (~/.codex/config.toml, ~/.codex/AGENTS.md)").option("--gemini", "also strip the Gemini CLI integration (~/.gemini/settings.json)").option("--qwen", "also strip the Qwen Code integration (~/.qwen/settings.json)").option("--pi", "also remove the pi (pi-coding-agent) extension").option("--opencode", "also remove the opencode plugin").option("--hermes", "no-op verification flag for symmetry with install (removes no files)").option("--openclaw", "also remove the OpenClaw plugin and config entry").option("--copilot", "also remove the Copilot CLI hook config and shim script, and strip the token-goat block from ~/.copilot/copilot-instructions.md (or <project>/.github/copilot-instructions.md with --local)").option("--grok", "also remove the Grok CLI hook config and shim script").option("--vscode", "also remove the VS Code MCP server (user scope by default; -p/--project for the workspace one) and routing guidance").option("--local", "with --pi, remove the project-local extension instead of the global one").option("--purge", "also delete the data directories (index, caches, session state, logs); refuses while the worker is running").action(guard(cmdUninstall));
13293
15120
  program2.command("mcp-status").description("check whether an MCP integration is already configured (used by the VS Code extension)").option("--vscode", "check VS Code mcp.json (user scope, plus workspace scope with --project)").option("-p, --project", "also check the workspace .vscode/mcp.json (relative to --cwd or the real cwd)").action(guard(cmdMcpStatus));
13294
15121
  const worker = program2.command("worker").description("background indexer lifecycle");
13295
15122
  worker.command("start").description("start the background indexer").action(guard(cmdWorkerStart));
@@ -13674,14 +15501,14 @@ function applyExitOverride(command) {
13674
15501
  command.exitOverride();
13675
15502
  for (const sub of command.commands) applyExitOverride(sub);
13676
15503
  }
13677
- async function run2(argv = process.argv) {
15504
+ async function run(argv = process.argv) {
13678
15505
  if (argv[2] === "--worker-daemon") {
13679
15506
  runDetachedWorkerDaemon();
13680
15507
  return;
13681
15508
  }
13682
15509
  if (argv[2] === "--batch-serve" && typeof argv[3] === "string") {
13683
- const { serveBatch } = await import("./token-goat-hook-chunk-ZRJUZZQE.mjs");
13684
- serveBatch(argv[3], (a) => run2(a));
15510
+ const { serveBatch } = await import("./token-goat-hook-chunk-5AQYD2SZ.mjs");
15511
+ serveBatch(argv[3], (a) => run(a));
13685
15512
  return;
13686
15513
  }
13687
15514
  const program2 = buildProgram();
@@ -13713,5 +15540,6 @@ export {
13713
15540
  buildProgram,
13714
15541
  cmdIndex,
13715
15542
  expandGlobs,
13716
- run2 as run
15543
+ leftoverIntegrations,
15544
+ run
13717
15545
  };