agentwaste-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/tools.js ADDED
@@ -0,0 +1,803 @@
1
+ import { toReportJson } from "./report.js";
2
+
3
+ export const TOOL_IDS = ["suckytraces", "keybarf", "tokengoblin", "tooltantrum"];
4
+
5
+ const TOOL_CONFIGS = {
6
+ suckytraces: {
7
+ id: "suckytraces",
8
+ packageName: "suckytraces",
9
+ binName: "suckytraces",
10
+ metricKey: "trace_coverage",
11
+ emoji: "🕵️",
12
+ npc: "Trace Goblin",
13
+ tagline: "sniffs out agent runs that cannot be replayed, explained, or defended in standup.",
14
+ good: "traceable little angel",
15
+ bad: "all vibes, no receipts",
16
+ commandHint: "npx suckytraces",
17
+ score(metric) {
18
+ if (metric.session_log_files === 0) return 20;
19
+ return clamp(100 - metric.session_file_coverage_percent);
20
+ },
21
+ findings(metric) {
22
+ return [
23
+ finding("coverage", `${metric.session_file_coverage_percent}% replayable`, metric.level),
24
+ finding("traced files", `${formatNumber(metric.traced_session_log_files)} / ${formatNumber(metric.session_log_files)} session logs`),
25
+ finding("trace crumbs", `${formatNumber(metric.structured_trace_signals)} structured signals`),
26
+ ];
27
+ },
28
+ roast(metric) {
29
+ if (metric.session_log_files === 0) return "No session logs found. The goblin is unemployed but suspicious.";
30
+ if (metric.session_file_coverage_percent >= 80) return "Your traces have shoes, a map, and a tiny flashlight. Respect.";
31
+ if (metric.session_file_coverage_percent >= 30) return "Some traces exist, but the murder board still has yarn gaps.";
32
+ return "Debugging these runs is basically reading tea leaves in a server room.";
33
+ },
34
+ next(metric) {
35
+ if (metric.session_log_files === 0) return "run an agent once, then re-run this goblin";
36
+ if (metric.session_file_coverage_percent >= 80) return "keep run_id / trace_id attached to every meaningful tool call";
37
+ return "add run_id, trace_id, span_id, retry metadata, and final artifacts to agent/tool logs";
38
+ },
39
+ evidence(report) {
40
+ return report.evidence.trace_files_sample;
41
+ },
42
+ explain: [
43
+ "coverage = session log files with structured trace evidence / scanned session log files * 100",
44
+ "trace evidence includes trace_id, span_id, run_id, OpenTelemetry, or otel",
45
+ "goal: when an agent breaks prod, you can replay the plot instead of interviewing a ghost",
46
+ ],
47
+ },
48
+ keybarf: {
49
+ id: "keybarf",
50
+ packageName: "keybarf",
51
+ binName: "keybarf",
52
+ metricKey: "credential_exposure",
53
+ emoji: "🤮",
54
+ npc: "Secret Raccoon",
55
+ tagline: "rummages through local agent logs for API keys that should not be cosplay props.",
56
+ good: "vault goblin satisfied",
57
+ bad: "credential confetti cannon",
58
+ commandHint: "npx keybarf",
59
+ score(metric) {
60
+ return clamp(metric.score ?? 0);
61
+ },
62
+ findings(metric) {
63
+ const kinds = Object.entries(metric.by_kind ?? {})
64
+ .filter(([, count]) => count > 0)
65
+ .sort((a, b) => b[1] - a[1])
66
+ .slice(0, 4)
67
+ .map(([kind, count]) => `${kind}:${count}`)
68
+ .join(", ");
69
+ return [
70
+ finding("secret fp", `${formatNumber(metric.unique_secret_fingerprints)} unique redacted fingerprints`, metric.level),
71
+ finding("model-facing", `${formatNumber(metric.model_facing_secret_fingerprints)} fingerprints reached prompt/tool context`, metric.model_facing_secret_fingerprints > 0 ? "CRITICAL" : "LOW"),
72
+ finding("kinds", kinds || "none spotted"),
73
+ finding("risk score", `${formatNumber(metric.score)} / 100`, metric.level),
74
+ ];
75
+ },
76
+ roast(metric) {
77
+ if (metric.model_facing_secret_fingerprints > 0) return "A model saw secret-shaped material. The raccoon is screaming into a paper bag.";
78
+ if (metric.unique_secret_fingerprints > 0) return "Secrets are sitting in logs/config like snacks on a sidewalk.";
79
+ return "No obvious key barf. The raccoon found crumbs, not crimes.";
80
+ },
81
+ next(metric) {
82
+ if (metric.model_facing_secret_fingerprints > 0) return "rotate exposed keys, purge logs, and vault credentials before the next agent run";
83
+ if (metric.unique_secret_fingerprints > 0) return "move keys into a vault/env boundary and stop printing them into logs";
84
+ return "keep secrets out of prompts, tool args, pasted stack traces, and debug logs";
85
+ },
86
+ evidence(report) {
87
+ return report.evidence.secret_files_sample;
88
+ },
89
+ explain: [
90
+ "raw keys = unique redacted fingerprints matched by known API-key patterns",
91
+ "model-facing keys = those fingerprints inside user/assistant/tool-call records",
92
+ "level = critical for model-facing material, high for raw secret material in logs/config",
93
+ ],
94
+ },
95
+ tokengoblin: {
96
+ id: "tokengoblin",
97
+ packageName: "tokengoblin",
98
+ binName: "tokengoblin",
99
+ metricKey: "context_efficiency",
100
+ emoji: "🧌",
101
+ npc: "Token Goblin",
102
+ tagline: "counts prompt calories, cache snacks, and context bloat burps.",
103
+ good: "lean prompt machine",
104
+ bad: "context buffet disaster",
105
+ commandHint: "npx tokengoblin",
106
+ score(metric) {
107
+ const levelBase = scoreFromLevel(metric.level);
108
+ const excessBoost = Math.min(35, metric.excess_fresh_input_percent || 0);
109
+ const cachePenalty = metric.cache_hit_percent < 20 && metric.fresh_input_tokens > 0 ? 10 : 0;
110
+ return clamp(levelBase + excessBoost + cachePenalty);
111
+ },
112
+ findings(metric, report) {
113
+ return [
114
+ finding("fresh/tool", `${formatNumber(metric.fresh_input_tokens_per_tool_call)} tokens`, metric.level),
115
+ finding("cache hit", `${metric.cache_hit_percent}%`, metric.cache_hit_percent >= 50 ? "LOW" : "MEDIUM"),
116
+ finding("excess", `${formatNumber(metric.excess_fresh_input_tokens)} fresh tokens over review line`),
117
+ finding("30d burn", `${formatNumber(report.monthly_impact_estimate.active_tokens_30d)} active tokens`),
118
+ ];
119
+ },
120
+ roast(metric) {
121
+ if (metric.level === "NO_DATA") return "No token footprints yet. Either pristine, empty, or wearing tiny socks.";
122
+ if (metric.fresh_input_tokens_per_tool_call >= metric.review_threshold_fresh_input_tokens_per_tool_call) return "Every tool call is dragging a suitcase full of prompt soup.";
123
+ if (metric.cache_hit_percent < 20 && metric.fresh_input_tokens > 0) return "The cache is basically a decorative bowl.";
124
+ return "Token goblin nibbled, but did not find a buffet catastrophe.";
125
+ },
126
+ next(metric) {
127
+ if (metric.level === "NO_DATA") return "run agents with token usage enabled, then ask the goblin again";
128
+ if (metric.fresh_input_tokens_per_tool_call >= metric.review_threshold_fresh_input_tokens_per_tool_call) return "trim repeated context, summarize durable state, and cache stable prompt chunks";
129
+ return "keep fresh input per tool low and move repeat loops into scripts/jobs";
130
+ },
131
+ evidence() {
132
+ return [];
133
+ },
134
+ explain: [
135
+ "fresh input = model input minus cached reads where providers expose caching",
136
+ "fresh/tool = sum(fresh input tokens) / tool calls",
137
+ "excess = fresh input above a conservative 4,000-token-per-tool review line",
138
+ ],
139
+ },
140
+ tooltantrum: {
141
+ id: "tooltantrum",
142
+ packageName: "tooltantrum",
143
+ binName: "tooltantrum",
144
+ metricKey: "tool_reliability",
145
+ emoji: "🛠️",
146
+ npc: "Retry Possum",
147
+ tagline: "tool reliability: failure rate, retry recovery, and unresolved calls.",
148
+ good: "healthy tool reliability",
149
+ bad: "unresolved tool failures",
150
+ commandHint: "npx tooltantrum",
151
+ score(metric) {
152
+ return clamp(scoreFromLevel(metric.level) + Math.min(40, metric.failure_rate_percent));
153
+ },
154
+ findings(metric) {
155
+ return [
156
+ finding("failure rate", `${metric.failure_rate_percent}%`, metric.level),
157
+ finding("failed", `${formatNumber(metric.failed_tool_calls)} / ${formatNumber(metric.tool_calls)} tool calls`),
158
+ finding("unresolved", `${formatNumber(metric.unresolved_failures)} failures never recovered`, metric.unresolved_failures > 0 ? metric.level : "LOW"),
159
+ finding("retry rescue", `${metric.retry_recovery_percent}% recovered after retry`),
160
+ ];
161
+ },
162
+ roast(metric) {
163
+ if (metric.tool_calls === 0) return "No tool calls found, so there is nothing to score yet.";
164
+ if (metric.failure_rate_percent >= 30) return "Very high tool failure rate. Agents are losing too many actions before finishing.";
165
+ if (metric.unresolved_failures > 0) return "Most failed tool calls did not show a later recovery signal.";
166
+ return "Tool calls look stable; retry behavior is boring in the good way.";
167
+ },
168
+ next(metric) {
169
+ if (metric.tool_calls === 0) return "run an agent with tool usage, then scan again";
170
+ if (metric.unresolved_failures > 0) return "classify retryable errors, make actions idempotent, cap retries, and save failure artifacts";
171
+ return "keep retries boring: classify failures, cap attempts, and log recovery";
172
+ },
173
+ evidence(report) {
174
+ return report.evidence.diagnostics;
175
+ },
176
+ explain: [
177
+ "failure rate = failed_tool_calls / tool_calls * 100",
178
+ "retry recovery = retry-after-failure signals / failed_tool_calls * 100",
179
+ "unresolved failures = failed tool calls that did not show a later retry signal",
180
+ ],
181
+ },
182
+ };
183
+
184
+ export function listToolConfigs() {
185
+ return TOOL_IDS.map((id) => TOOL_CONFIGS[id]);
186
+ }
187
+
188
+ export function getToolConfig(toolId) {
189
+ const config = TOOL_CONFIGS[toolId];
190
+ if (!config) throw new Error(`unknown tool ${toolId}`);
191
+ return config;
192
+ }
193
+
194
+ export function buildToolReport(stats, options) {
195
+ const config = getToolConfig(options.toolId);
196
+ const color = colorizer(options.color);
197
+ const full = toReportJson(stats, options);
198
+ const metric = full.metrics[config.metricKey];
199
+ const chaosScore = config.score(metric, full);
200
+ const verdict = verdictFor(config, metric, chaosScore);
201
+ const findings = config.findings(metric, full);
202
+ const evidence = config.evidence(full).slice(0, 5);
203
+ const json = {
204
+ version: options.version,
205
+ package: config.packageName,
206
+ command: config.commandHint,
207
+ npc: config.npc,
208
+ emoji: config.emoji,
209
+ tagline: config.tagline,
210
+ scanned: full.scanned,
211
+ metric_key: config.metricKey,
212
+ metric,
213
+ chaos_score: chaosScore,
214
+ verdict,
215
+ findings: findings.map(({ label, value }) => ({ label, value })),
216
+ roast: config.roast(metric, full),
217
+ evidence_sample: evidence,
218
+ };
219
+ return {
220
+ json,
221
+ text: renderToolText(config, full, metric, findings, evidence, verdict, chaosScore, color),
222
+ };
223
+ }
224
+
225
+ export function explainTool(toolId, options = {}) {
226
+ const config = getToolConfig(toolId);
227
+ const payload = {
228
+ version: options.version ?? "0.1.0",
229
+ package: config.packageName,
230
+ command: config.commandHint,
231
+ npc: config.npc,
232
+ metric_key: config.metricKey,
233
+ formulas: config.explain,
234
+ };
235
+ if (options.json) return JSON.stringify(payload, null, 2);
236
+ return [
237
+ `${config.packageName} explain v${payload.version}`,
238
+ `${config.emoji} ${config.npc}: ${config.tagline}`,
239
+ "",
240
+ ...config.explain.map((line) => `- ${line}`),
241
+ ].join("\n");
242
+ }
243
+
244
+ function renderToolText(config, full, metric, findings, evidence, verdict, chaosScore, color) {
245
+ const rows = [
246
+ reportText(`${config.packageName} postmortem`, color.title(`${config.packageName} postmortem`)),
247
+ reportText("──────────────────────────────", color.rule("──────────────────────────────")),
248
+ reportText(""),
249
+ reportSection("finding", color),
250
+ ...paragraphRows(toolFinding(config.id, metric), color, { level: metric.level ?? verdict.level }),
251
+ reportText(""),
252
+ reportSection("impact", color),
253
+ ...paragraphRows(toolImpact(config.id, full, metric), color),
254
+ reportText(""),
255
+ reportSection("agent leaderboard", color),
256
+ ...toolAgentLeaderboard(config.id, full, color),
257
+ reportText(""),
258
+ reportSection("numbers", color),
259
+ ...toolNumbers(config.id, full, metric, color),
260
+ ];
261
+
262
+ return rows.map((row) => row.painted).join("\n");
263
+ }
264
+
265
+ function toolFinding(toolId, metric) {
266
+ switch (toolId) {
267
+ case "suckytraces":
268
+ if (metric.session_log_files === 0) return "No session log files were found.";
269
+ return `Only ${formatPercent(metric.session_file_coverage_percent)} of scanned session logs include trace, run, or span IDs.`;
270
+ case "keybarf":
271
+ if (metric.model_facing_secret_fingerprints > 0) return "Secret fingerprints appeared in model-facing records.";
272
+ if (metric.unique_secret_fingerprints > 0) return "Secret fingerprints were found in local logs or config.";
273
+ return "No known secret fingerprints were found.";
274
+ case "tokengoblin":
275
+ if (metric.level === "NO_DATA") return "No token usage records were found.";
276
+ return `Fresh input averages ${formatNumber(metric.fresh_input_tokens_per_tool_call)} tokens per tool call.`;
277
+ case "tooltantrum":
278
+ if (metric.tool_calls === 0) return "No tool-call records were found.";
279
+ return `${formatPercent(metric.failure_rate_percent)} of tool calls failed; ${formatPercent(metric.retry_recovery_percent)} of failures showed retry recovery.`;
280
+ default:
281
+ return "No finding available.";
282
+ }
283
+ }
284
+
285
+ function toolImpact(toolId, full, metric) {
286
+ switch (toolId) {
287
+ case "suckytraces": {
288
+ const missing = Math.max(0, metric.session_log_files - metric.traced_session_log_files);
289
+ if (metric.session_log_files === 0) return "There are no runs to assess yet.";
290
+ return `${formatNumber(missing)} session logs may be hard to replay or debug after the fact.`;
291
+ }
292
+ case "keybarf":
293
+ if (metric.model_facing_secret_fingerprints > 0) return "Credentials may need rotation because secret-like data reached model-facing context.";
294
+ if (metric.unique_secret_fingerprints > 0) return "Secret material is present where local tools or logs can retain it.";
295
+ return "No credential exposure was detected by the current patterns.";
296
+ case "tokengoblin":
297
+ if (metric.excess_fresh_input_tokens > 0) return `${formatNumber(metric.excess_fresh_input_tokens)} fresh input tokens exceeded the review threshold in the scanned window.`;
298
+ return "Fresh input stayed under the review threshold for the scanned window.";
299
+ case "tooltantrum":
300
+ if (metric.unresolved_failures > 0) return `${formatNumber(metric.unresolved_failures)} failed tool calls did not show a later recovery signal.`;
301
+ if (metric.failed_tool_calls > 0) return "Failures occurred, but scanned retry signals recovered them.";
302
+ return "No failed tool calls were detected in the scanned window.";
303
+ default:
304
+ return "No impact available.";
305
+ }
306
+ }
307
+
308
+ function toolNumbers(toolId, full, metric, color) {
309
+ switch (toolId) {
310
+ case "suckytraces":
311
+ return [
312
+ metricLine("session logs scanned", formatNumber(metric.session_log_files), color),
313
+ metricLine("logs with trace/run ids", `${formatNumber(metric.traced_session_log_files)} / ${formatNumber(metric.session_log_files)}`, color, { level: metric.level }),
314
+ metricLine("trace coverage", formatPercent(metric.session_file_coverage_percent), color, { level: metric.level }),
315
+ metricLine("trace/run/span markers", formatNumber(metric.structured_trace_signals), color),
316
+ ];
317
+ case "keybarf":
318
+ return [
319
+ metricLine("unique fingerprints", formatNumber(metric.unique_secret_fingerprints), color, { level: metric.level }),
320
+ metricLine("model-facing", formatNumber(metric.model_facing_secret_fingerprints), color, { level: metric.model_facing_secret_fingerprints > 0 ? "CRITICAL" : "LOW" }),
321
+ metricLine("detected kinds", compactKinds(metric.by_kind), color),
322
+ ];
323
+ case "tokengoblin":
324
+ return [
325
+ metricLine("fresh input/tool", `${formatNumber(metric.fresh_input_tokens_per_tool_call)} tokens`, color, { level: metric.level }),
326
+ metricLine("review threshold", `${formatNumber(metric.review_threshold_fresh_input_tokens_per_tool_call)} tokens/tool`, color),
327
+ metricLine("cache hit rate", formatPercent(metric.cache_hit_percent), color, { level: metric.cache_hit_percent >= 50 ? "LOW" : "MEDIUM" }),
328
+ metricLine("excess fresh input", `${formatNumber(metric.excess_fresh_input_tokens)} tokens`, color, { level: metric.excess_fresh_input_tokens > 0 ? metric.level : "LOW" }),
329
+ metricLine("30d active tokens", formatNumber(full.monthly_impact_estimate.active_tokens_30d), color),
330
+ ];
331
+ case "tooltantrum": {
332
+ const recovered = Math.max(0, metric.failed_tool_calls - metric.unresolved_failures);
333
+ return [
334
+ metricLine("tool calls", formatNumber(metric.tool_calls), color),
335
+ metricLine("failed calls", formatNumber(metric.failed_tool_calls), color, { level: metric.failed_tool_calls > 0 ? metric.level : "LOW" }),
336
+ metricLine("failure rate", formatPercent(metric.failure_rate_percent), color, { level: metric.level }),
337
+ metricLine("recovered after retry", formatNumber(recovered), color, { level: recovered > 0 ? "LOW" : undefined }),
338
+ metricLine("unresolved failures", formatNumber(metric.unresolved_failures), color, { level: metric.unresolved_failures > 0 ? metric.level : "LOW" }),
339
+ metricLine("retry recovery rate", formatPercent(metric.retry_recovery_percent), color, { level: metric.retry_recovery_percent >= 80 ? "LOW" : "HIGH" }),
340
+ ];
341
+ }
342
+ default:
343
+ return [];
344
+ }
345
+ }
346
+
347
+ function toolNextStep(toolId, metric) {
348
+ switch (toolId) {
349
+ case "suckytraces":
350
+ return "Write a run_id, trace_id, or span_id into each tool execution log.";
351
+ case "keybarf":
352
+ if (metric.model_facing_secret_fingerprints > 0) return "Rotate exposed credentials and move secrets behind environment or secret-manager boundaries.";
353
+ if (metric.unique_secret_fingerprints > 0) return "Move secrets out of logs/config and keep only redacted values in agent context.";
354
+ return "Keep secrets out of prompts, tool arguments, stack traces, and debug logs.";
355
+ case "tokengoblin":
356
+ if (metric.excess_fresh_input_tokens > 0) return "Trim repeated prompt context and cache stable input before tool-heavy runs.";
357
+ return "Keep fresh input per tool call below the review threshold.";
358
+ case "tooltantrum":
359
+ if (metric.unresolved_failures > 0) return "Classify retryable errors, make tool actions idempotent, and persist final failure details.";
360
+ return "Keep retry behavior explicit: classify errors, cap attempts, and log recovery.";
361
+ default:
362
+ return "Review the finding above.";
363
+ }
364
+ }
365
+
366
+ function toolAgentLeaderboard(toolId, full, color) {
367
+ const ranked = rankAgents(toolId, full).slice(0, 5);
368
+ if (ranked.length === 0) return paragraphRows("No agent had enough data for a leaderboard.", color);
369
+ const best = ranked[0];
370
+ return ranked.map((entry) => {
371
+ const winner = sameRank(entry, best);
372
+ return leaderboardLine(winner ? `🏆 ${entry.label}` : ` ${entry.label}`, entry.value, color, winner);
373
+ });
374
+ }
375
+
376
+ function rankAgents(toolId, full) {
377
+ const sources = Object.values(full.scanned.sources ?? {})
378
+ .filter((source) => source.label !== "Agent config" && source.unavailable !== true);
379
+
380
+ if (toolId === "suckytraces") {
381
+ return sources
382
+ .filter((source) => eligibleAgent(toolId, source))
383
+ .map((source) => {
384
+ const percent = source.files > 0 ? Math.round(((source.traced_session_log_files ?? 0) / source.files) * 100) : 0;
385
+ return {
386
+ label: source.label,
387
+ sort: -percent,
388
+ value: `${percent}% coverage (${formatNumber(source.traced_session_log_files ?? 0)} / ${formatNumber(source.files)})`,
389
+ };
390
+ })
391
+ .sort(compareAgentRank);
392
+ }
393
+
394
+ if (toolId === "keybarf") {
395
+ return sources
396
+ .filter((source) => eligibleAgent(toolId, source))
397
+ .map((source) => {
398
+ const modelFacing = source.model_facing_secret_fingerprints ?? 0;
399
+ const total = Math.max(source.secret_fingerprints ?? 0, modelFacing);
400
+ return {
401
+ label: source.label,
402
+ sort: modelFacing,
403
+ tiebreaker: total,
404
+ value: `${formatNumber(modelFacing)} model-facing · ${formatNumber(total)} total`,
405
+ };
406
+ })
407
+ .sort(compareAgentRank);
408
+ }
409
+
410
+ if (toolId === "tokengoblin") {
411
+ return sources
412
+ .filter((source) => eligibleAgent(toolId, source))
413
+ .map((source) => {
414
+ const freshPerTool = Math.round((source.fresh_input_tokens ?? 0) / source.tool_calls);
415
+ return {
416
+ label: source.label,
417
+ sort: freshPerTool,
418
+ value: `${formatNumber(freshPerTool)} fresh input/tool`,
419
+ };
420
+ })
421
+ .sort(compareAgentRank);
422
+ }
423
+
424
+ if (toolId === "tooltantrum") {
425
+ return sources
426
+ .filter((source) => eligibleAgent(toolId, source))
427
+ .map((source) => {
428
+ const failureRate = round(((source.failed_tool_calls ?? 0) / source.tool_calls) * 100, 1);
429
+ return {
430
+ label: source.label,
431
+ sort: failureRate,
432
+ tiebreaker: source.failed_tool_calls ?? 0,
433
+ value: `${failureRate}% failure (${formatNumber(source.failed_tool_calls ?? 0)} / ${formatNumber(source.tool_calls)})`,
434
+ };
435
+ })
436
+ .sort(compareAgentRank);
437
+ }
438
+
439
+ return [];
440
+ }
441
+
442
+ function eligibleAgent(toolId, source) {
443
+ switch (toolId) {
444
+ case "suckytraces":
445
+ return (source.files ?? 0) >= 20;
446
+ case "keybarf":
447
+ return (source.files ?? 0) >= 20 || (source.sessions ?? 0) >= 20;
448
+ case "tokengoblin":
449
+ return (source.tool_calls ?? 0) >= 50 && (source.fresh_input_tokens ?? 0) > 0;
450
+ case "tooltantrum":
451
+ return (source.tool_calls ?? 0) >= 50;
452
+ default:
453
+ return false;
454
+ }
455
+ }
456
+
457
+ function compareAgentRank(a, b) {
458
+ if (a.sort !== b.sort) return a.sort - b.sort;
459
+ return (a.tiebreaker ?? 0) - (b.tiebreaker ?? 0);
460
+ }
461
+
462
+ function sameRank(a, b) {
463
+ return a.sort === b.sort && (a.tiebreaker ?? 0) === (b.tiebreaker ?? 0);
464
+ }
465
+
466
+ function leaderboardLine(label, value, color, winner) {
467
+ const safeLabel = shortText(label, 26).padEnd(26);
468
+ const paintLabel = winner ? color.solution(safeLabel) : color.label(safeLabel);
469
+ const paintValue = winner ? color.solution(value) : color.value(value);
470
+ return { raw: ` ${safeLabel} ${value}`, painted: ` ${paintLabel} ${paintValue}` };
471
+ }
472
+
473
+ function sectionTitle(toolId) {
474
+ switch (toolId) {
475
+ case "suckytraces":
476
+ return "trace coverage";
477
+ case "keybarf":
478
+ return "credential exposure";
479
+ case "tokengoblin":
480
+ return "context usage";
481
+ case "tooltantrum":
482
+ return "tool reliability";
483
+ default:
484
+ return "metrics";
485
+ }
486
+ }
487
+
488
+ function compactStatus(toolId, metric) {
489
+ switch (toolId) {
490
+ case "suckytraces":
491
+ if (metric.session_log_files === 0) return "no session log files were found";
492
+ if (metric.session_file_coverage_percent >= 80) return "most session logs contain trace evidence";
493
+ if (metric.session_file_coverage_percent >= 30) return "some session logs contain trace evidence";
494
+ return "fewer than 30% of session logs contain trace evidence";
495
+ case "keybarf":
496
+ if (metric.model_facing_secret_fingerprints > 0) return "secret fingerprints reached model-facing records";
497
+ if (metric.unique_secret_fingerprints > 0) return "secret fingerprints were found in logs or config";
498
+ return "no secret fingerprints matched known patterns";
499
+ case "tokengoblin":
500
+ if (metric.level === "NO_DATA") return "no token usage records were found";
501
+ if (metric.fresh_input_tokens_per_tool_call >= metric.review_threshold_fresh_input_tokens_per_tool_call) return "fresh input per tool call exceeds the review threshold";
502
+ if (metric.cache_hit_percent < 20 && metric.fresh_input_tokens > 0) return "cache hit rate is low for observed input";
503
+ return "fresh input per tool call is within the review threshold";
504
+ case "tooltantrum":
505
+ return toolReliabilityStatus(metric);
506
+ default:
507
+ return "ok";
508
+ }
509
+ }
510
+
511
+ function toolReliabilityStatus(metric) {
512
+ if (metric.tool_calls === 0) return "no tool-call records were found";
513
+ if (metric.unresolved_failures > 0 && metric.retry_recovery_percent < 20) return "failed calls rarely show retry recovery";
514
+ if (metric.unresolved_failures > 0) return "some failed calls did not show recovery";
515
+ if (metric.failure_rate_percent >= 15) return "tool-call failure rate is high";
516
+ if (metric.failure_rate_percent >= 5) return "tool-call failure rate is moderate";
517
+ return "tool-call failure rate is low";
518
+ }
519
+
520
+ function verdictFor(config, metric, score) {
521
+ const level = metric.level ?? scoreLevel(score);
522
+ if (score >= 75 || level === "CRITICAL" || level === "MISSING") return { level, label: config.bad, mood: "bad" };
523
+ if (score >= 45 || level === "HIGH") return { level, label: "spicy enough to investigate", mood: "spicy" };
524
+ if (score >= 20 || level === "MEDIUM" || level === "PARTIAL") return { level, label: "a little haunted", mood: "haunted" };
525
+ return { level, label: config.good, mood: "good" };
526
+ }
527
+
528
+ function scoreLevel(score) {
529
+ if (score >= 75) return "CRITICAL";
530
+ if (score >= 45) return "HIGH";
531
+ if (score >= 20) return "MEDIUM";
532
+ return "LOW";
533
+ }
534
+
535
+ function finding(label, value, level) {
536
+ return { label, value, level };
537
+ }
538
+
539
+ function pairRow(label, value, color, options = {}) {
540
+ const safeLabel = String(label).slice(0, 12);
541
+ const raw = `${safeLabel.padEnd(12)} ${value}`;
542
+ const paintValue = options.solution ? color.solution(value) : options.level ? color.level(value, options.level) : color.value(value);
543
+ return { raw, painted: `${color.label(safeLabel.padEnd(12))} ${paintValue}` };
544
+ }
545
+
546
+ function reportLine(label, value, color, options = {}) {
547
+ const labelWidth = options.labelWidth ?? 14;
548
+ const indent = " ".repeat(options.indent ?? 2);
549
+ const safeLabel = String(label).toLowerCase().slice(0, labelWidth);
550
+ const raw = `${indent}${safeLabel.padEnd(labelWidth)} ${value}`;
551
+ const paintValue = options.solution ? color.solution(value) : options.level ? color.level(value, options.level) : color.value(value);
552
+ return { raw, painted: `${indent}${color.label(safeLabel.padEnd(labelWidth))} ${paintValue}` };
553
+ }
554
+
555
+ function metricLine(label, value, color, options = {}) {
556
+ return reportLine(label, value, color, { ...options, indent: 4, labelWidth: 28 });
557
+ }
558
+
559
+ function reportSection(label, color) {
560
+ return reportText(String(label).toLowerCase(), color.title(String(label).toLowerCase()));
561
+ }
562
+
563
+ function paragraphRows(value, color, options = {}) {
564
+ const indent = " ";
565
+ return wrapText(String(value), 76).map((line) => {
566
+ const paintValue = options.solution ? color.solution(line) : options.level ? color.level(line, options.level) : color.value(line);
567
+ return { raw: `${indent}${line}`, painted: `${indent}${paintValue}` };
568
+ });
569
+ }
570
+
571
+ function reportText(raw, painted = raw) {
572
+ return { raw, painted: raw ? ` ${painted}` : "" };
573
+ }
574
+
575
+ function wrappedPairRows(label, value, color, options = {}) {
576
+ const labelWidth = 12;
577
+ const width = options.width ?? 74;
578
+ const textWidth = Math.max(20, width - labelWidth - 1);
579
+ const lines = wrapText(String(value), textWidth);
580
+ return lines.map((line, index) => {
581
+ const visibleLabel = index === 0 ? String(label).slice(0, labelWidth) : "";
582
+ const paintValue = options.solution ? color.solution(line) : options.level ? color.level(line, options.level) : color.value(line);
583
+ return {
584
+ raw: `${visibleLabel.padEnd(labelWidth)} ${line}`,
585
+ painted: `${color.label(visibleLabel.padEnd(labelWidth))} ${paintValue}`,
586
+ };
587
+ });
588
+ }
589
+
590
+ function ruleRow() {
591
+ return { rule: true, raw: "", painted: "" };
592
+ }
593
+
594
+ function textRow(raw, painted = raw) {
595
+ return { raw, painted };
596
+ }
597
+
598
+ function blankRow() {
599
+ return { raw: "", painted: "" };
600
+ }
601
+
602
+ function box(title, rows, color) {
603
+ const width = Math.max(48, title.length + 4, ...rows.map((row) => row.raw.length));
604
+ const topFill = "─".repeat(Math.max(1, width - title.length - 1));
605
+ const bottomFill = "─".repeat(width + 2);
606
+ return [
607
+ `${color.border("╭─")} ${color.title(title)} ${color.border(`${topFill}╮`)}`,
608
+ ...rows.map((row) => boxRow(row, width, color)),
609
+ `${color.border("╰")}${color.border(bottomFill)}${color.border("╯")}`,
610
+ ].join("\n");
611
+ }
612
+
613
+ function boxRow(row, width, color) {
614
+ const padding = " ".repeat(Math.max(0, width - row.raw.length));
615
+ return `${color.border("│")} ${row.painted}${padding} ${color.border("│")}`;
616
+ }
617
+
618
+ function fixedBox(title, rows, color, width) {
619
+ const topFill = "─".repeat(Math.max(1, width - title.length - 1));
620
+ const bottomFill = "─".repeat(width + 2);
621
+ return [
622
+ `${color.border("╭─")} ${color.title(title)} ${color.border(`${topFill}╮`)}`,
623
+ ...rows.map((row) => fixedBoxRow(row, width, color)),
624
+ `${color.border("╰")}${color.border(bottomFill)}${color.border("╯")}`,
625
+ ].join("\n");
626
+ }
627
+
628
+ function fixedBoxRow(row, width, color) {
629
+ if (row.rule) return `${color.border("├")}${color.border("─".repeat(width + 2))}${color.border("┤")}`;
630
+ const raw = row.raw.length > width ? `${row.raw.slice(0, Math.max(0, width - 1))}…` : row.raw;
631
+ const painted = row.raw.length > width ? raw : row.painted;
632
+ const padding = " ".repeat(Math.max(0, width - raw.length));
633
+ return `${color.border("│")} ${painted}${padding} ${color.border("│")}`;
634
+ }
635
+
636
+ function largestFileLine(files) {
637
+ if (!Array.isArray(files) || files.length === 0) return "none";
638
+ const file = files[0];
639
+ if (typeof file === "string") return compactPath(file);
640
+ return `${compactPath(file.path ?? file.file ?? "unknown")} (${formatNumber(file.lines ?? 0)} lines)`;
641
+ }
642
+
643
+ function compactPath(value) {
644
+ const text = String(value ?? "unknown");
645
+ if (text.length <= 30) return text;
646
+ const tail = text.split(/[\\/]/).slice(-2).join("/");
647
+ return `…/${tail}`;
648
+ }
649
+
650
+ function compactKinds(kinds) {
651
+ const value = Object.entries(kinds ?? {})
652
+ .filter(([, count]) => count > 0)
653
+ .sort((a, b) => b[1] - a[1])
654
+ .slice(0, 4)
655
+ .map(([kind, count]) => `${kind}:${count}`)
656
+ .join(", ");
657
+ return value || "none";
658
+ }
659
+
660
+ function rootSummary(roots) {
661
+ if (!Array.isArray(roots) || roots.length === 0) return "0";
662
+ return shortText(`${roots.length} (${roots.slice(0, 3).join(", ")}${roots.length > 3 ? ", …" : ""})`, 44);
663
+ }
664
+
665
+ function shortText(value, max) {
666
+ const text = String(value ?? "");
667
+ if (text.length <= max) return text;
668
+ return `${text.slice(0, Math.max(0, max - 1))}…`;
669
+ }
670
+
671
+ function scoreFromLevel(level) {
672
+ switch (String(level).toUpperCase()) {
673
+ case "CRITICAL":
674
+ case "MISSING":
675
+ return 82;
676
+ case "HIGH":
677
+ return 62;
678
+ case "MEDIUM":
679
+ case "PARTIAL":
680
+ return 34;
681
+ case "CONFIG_ONLY":
682
+ case "NO_DATA":
683
+ return 12;
684
+ default:
685
+ return 5;
686
+ }
687
+ }
688
+
689
+ function clamp(value) {
690
+ return Math.max(0, Math.min(100, Math.round(Number(value) || 0)));
691
+ }
692
+
693
+ function round(value, digits) {
694
+ const factor = 10 ** digits;
695
+ return Math.round(value * factor) / factor;
696
+ }
697
+
698
+ function scanWindow(days) {
699
+ return days === "all" ? "all sessions" : `last ${days}d`;
700
+ }
701
+
702
+ function formatNumber(value) {
703
+ return Number(value || 0).toLocaleString("en-US");
704
+ }
705
+
706
+ function formatPercent(value) {
707
+ const number = Number(value || 0);
708
+ return `${number.toFixed(Number.isInteger(number) ? 0 : 1)}%`;
709
+ }
710
+
711
+ function progressBar(value, max, width) {
712
+ const ratio = Math.max(0, Math.min(1, Number(value || 0) / Math.max(1, max)));
713
+ const filled = Math.max(value > 0 ? 1 : 0, Math.round(ratio * width));
714
+ return `${"█".repeat(Math.min(width, filled))}${"░".repeat(Math.max(0, width - filled))}`;
715
+ }
716
+
717
+ function scoreSparkline(score) {
718
+ return progressBar(score, 100, 12);
719
+ }
720
+
721
+ function wrapText(value, width) {
722
+ const words = String(value).split(/\s+/).filter(Boolean);
723
+ const lines = [];
724
+ let line = "";
725
+ for (const word of words) {
726
+ if (word.length > width) {
727
+ if (line) {
728
+ lines.push(line);
729
+ line = "";
730
+ }
731
+ lines.push(word.slice(0, width));
732
+ continue;
733
+ }
734
+ if (!line) {
735
+ line = word;
736
+ continue;
737
+ }
738
+ if (`${line} ${word}`.length > width) {
739
+ lines.push(line);
740
+ line = word;
741
+ } else {
742
+ line = `${line} ${word}`;
743
+ }
744
+ }
745
+ if (line) lines.push(line);
746
+ return lines.length > 0 ? lines : [""];
747
+ }
748
+
749
+ function formatSeconds(ms) {
750
+ const seconds = Math.max(0.1, Number(ms || 0) / 1000);
751
+ return `${seconds.toFixed(seconds >= 10 ? 0 : 1)}s`;
752
+ }
753
+
754
+ function colorizer(enabled) {
755
+ if (!enabled) return plainColors();
756
+ const paint = (code, value) => `\x1b[${code}m${value}\x1b[0m`;
757
+ return {
758
+ accent: (value) => paint("38;2;147;197;253", value),
759
+ border: (value) => paint("38;2;100;116;139", value),
760
+ label: (value) => paint("38;2;186;230;253", value),
761
+ muted: (value) => paint("38;2;148;163;184", value),
762
+ rule: (value) => paint("38;2;71;85;105", value),
763
+ solution: (value) => paint("38;2;125;211;252", value),
764
+ title: (value) => paint("38;2;147;197;253;1", value),
765
+ value: (value) => paint("38;2;226;232;240", value),
766
+ level: (value, level) => paint(levelColor(level), value),
767
+ };
768
+ }
769
+
770
+ function plainColors() {
771
+ return {
772
+ accent: (value) => value,
773
+ border: (value) => value,
774
+ label: (value) => value,
775
+ muted: (value) => value,
776
+ rule: (value) => value,
777
+ solution: (value) => value,
778
+ title: (value) => value,
779
+ value: (value) => value,
780
+ level: (value) => value,
781
+ };
782
+ }
783
+
784
+ function levelColor(level) {
785
+ switch (String(level).toLowerCase()) {
786
+ case "critical":
787
+ case "missing":
788
+ return "38;2;251;113;133";
789
+ case "high":
790
+ return "38;2;253;186;116";
791
+ case "medium":
792
+ case "partial":
793
+ return "38;2;147;197;253";
794
+ case "good":
795
+ case "low":
796
+ return "38;2;125;211;252";
797
+ case "config_only":
798
+ case "no_data":
799
+ return "38;2;148;163;184";
800
+ default:
801
+ return "90";
802
+ }
803
+ }