agentwaste-core 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -7
- package/package.json +2 -2
- package/src/cli.js +50 -175
- package/src/index.js +1 -2
- package/src/report.js +0 -495
- package/src/scanner.js +133 -45
- package/src/tools.js +238 -577
package/src/tools.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { toReportJson } from "./report.js";
|
|
2
2
|
|
|
3
|
-
export const TOOL_IDS = ["suckytraces", "
|
|
3
|
+
export const TOOL_IDS = ["suckytraces", "tooltantrum"];
|
|
4
4
|
|
|
5
5
|
const TOOL_CONFIGS = {
|
|
6
6
|
suckytraces: {
|
|
@@ -8,133 +8,39 @@ const TOOL_CONFIGS = {
|
|
|
8
8
|
packageName: "suckytraces",
|
|
9
9
|
binName: "suckytraces",
|
|
10
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
11
|
commandHint: "npx suckytraces",
|
|
12
|
+
tagline: "Trace coverage for local coding-agent sessions.",
|
|
13
|
+
good: "trace trail intact",
|
|
14
|
+
bad: "trace blackout",
|
|
17
15
|
score(metric) {
|
|
18
|
-
if (metric.session_log_files === 0) return
|
|
16
|
+
if (metric.session_log_files === 0) return 0;
|
|
19
17
|
return clamp(100 - metric.session_file_coverage_percent);
|
|
20
18
|
},
|
|
21
|
-
|
|
22
|
-
return
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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.";
|
|
19
|
+
headline(metric) {
|
|
20
|
+
if (metric.session_log_files === 0) return "NO FOOTAGE FOUND";
|
|
21
|
+
if (metric.session_file_coverage_percent < 30) return "THE PAPER TRAIL IS ON FIRE";
|
|
22
|
+
if (metric.session_file_coverage_percent < 80) return "THE ALIBI HAS HOLES";
|
|
23
|
+
return "THE TRACE TRAIL IS CLEAN";
|
|
33
24
|
},
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (metric.
|
|
37
|
-
return "
|
|
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);
|
|
25
|
+
summary(metric) {
|
|
26
|
+
const missing = Math.max(0, metric.session_log_files - metric.traced_session_log_files);
|
|
27
|
+
if (metric.session_log_files === 0) return "No session logs were found, so there is nothing to replay yet.";
|
|
28
|
+
if (missing === 0) return "Every scanned log has trace evidence. The replay trail is intact.";
|
|
29
|
+
return `Only ${formatPercent(metric.session_file_coverage_percent)} of session logs include trace/run/span IDs. ${formatCount(missing, "log")} would be hard to replay after an incident.`;
|
|
61
30
|
},
|
|
62
31
|
findings(metric) {
|
|
63
|
-
const
|
|
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(", ");
|
|
32
|
+
const missing = Math.max(0, metric.session_log_files - metric.traced_session_log_files);
|
|
69
33
|
return [
|
|
70
|
-
finding("
|
|
71
|
-
finding("
|
|
72
|
-
finding("
|
|
73
|
-
finding("
|
|
34
|
+
finding("coverage", `${formatPercent(metric.session_file_coverage_percent)} traced`, metric.level),
|
|
35
|
+
finding("with ids", `${formatNumber(metric.traced_session_log_files)} / ${formatNumber(metric.session_log_files)} logs`, metric.level),
|
|
36
|
+
finding("missing", `${formatNumber(missing)} logs`, missing > 0 ? metric.level : "LOW"),
|
|
37
|
+
finding("signals", `${formatNumber(metric.structured_trace_signals)} markers`),
|
|
74
38
|
];
|
|
75
39
|
},
|
|
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
40
|
explain: [
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"
|
|
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",
|
|
41
|
+
"coverage = session log files with structured trace evidence / scanned session log files * 100",
|
|
42
|
+
"trace evidence includes trace_id, span_id, run_id, OpenTelemetry, or otel",
|
|
43
|
+
"goal: make local agent runs replayable without reading the whole transcript",
|
|
138
44
|
],
|
|
139
45
|
},
|
|
140
46
|
tooltantrum: {
|
|
@@ -142,37 +48,39 @@ const TOOL_CONFIGS = {
|
|
|
142
48
|
packageName: "tooltantrum",
|
|
143
49
|
binName: "tooltantrum",
|
|
144
50
|
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
51
|
commandHint: "npx tooltantrum",
|
|
52
|
+
tagline: "Tool-call reliability for local coding-agent sessions.",
|
|
53
|
+
good: "boringly stable",
|
|
54
|
+
bad: "tool meltdown",
|
|
151
55
|
score(metric) {
|
|
56
|
+
if (metric.tool_calls === 0) return 0;
|
|
152
57
|
return clamp(scoreFromLevel(metric.level) + Math.min(40, metric.failure_rate_percent));
|
|
153
58
|
},
|
|
59
|
+
headline(metric) {
|
|
60
|
+
if (metric.tool_calls === 0) return "NO TOOLS ENTERED THE ARENA";
|
|
61
|
+
if (metric.unresolved_failures > 0) return "TOOLS ARE THROWING HANDS";
|
|
62
|
+
if (metric.failure_rate_percent >= 15) return "THE TOOLS ARE BLEEDING";
|
|
63
|
+
if (metric.failed_tool_calls > 0) return "A FEW TOOLS TRIPPED";
|
|
64
|
+
return "THE TOOLS BEHAVED";
|
|
65
|
+
},
|
|
66
|
+
summary(metric) {
|
|
67
|
+
if (metric.tool_calls === 0) return "No tool-call records were found, so there is nothing to score yet.";
|
|
68
|
+
if (metric.unresolved_failures > 0) {
|
|
69
|
+
return `${formatPercent(metric.failure_rate_percent)} of tool calls failed. Only ${formatPercent(metric.retry_recovery_percent)} of failures showed retry recovery, leaving ${formatCount(metric.unresolved_failures, "failure")} unresolved.`;
|
|
70
|
+
}
|
|
71
|
+
if (metric.failed_tool_calls > 0) {
|
|
72
|
+
return `${formatCount(metric.failed_tool_calls, "call")} failed, but the scanned retry signals recovered them.`;
|
|
73
|
+
}
|
|
74
|
+
return "No failed tool calls were detected in the scanned window.";
|
|
75
|
+
},
|
|
154
76
|
findings(metric) {
|
|
155
77
|
return [
|
|
156
|
-
finding("failure
|
|
157
|
-
finding("failed", `${formatNumber(metric.failed_tool_calls)} / ${formatNumber(metric.tool_calls)}
|
|
158
|
-
finding("unresolved", `${formatNumber(metric.unresolved_failures)}
|
|
159
|
-
finding("
|
|
78
|
+
finding("failure", formatPercent(metric.failure_rate_percent), metric.level),
|
|
79
|
+
finding("failed", `${formatNumber(metric.failed_tool_calls)} / ${formatNumber(metric.tool_calls)} calls`, metric.level),
|
|
80
|
+
finding("unresolved", `${formatNumber(metric.unresolved_failures)} unresolved`, metric.unresolved_failures > 0 ? metric.level : "LOW"),
|
|
81
|
+
finding("rescued", `${formatPercent(metric.retry_recovery_percent)} after retry`),
|
|
160
82
|
];
|
|
161
83
|
},
|
|
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
84
|
explain: [
|
|
177
85
|
"failure rate = failed_tool_calls / tool_calls * 100",
|
|
178
86
|
"retry recovery = retry-after-failure signals / failed_tool_calls * 100",
|
|
@@ -196,29 +104,35 @@ export function buildToolReport(stats, options) {
|
|
|
196
104
|
const color = colorizer(options.color);
|
|
197
105
|
const full = toReportJson(stats, options);
|
|
198
106
|
const metric = full.metrics[config.metricKey];
|
|
199
|
-
const
|
|
200
|
-
const verdict = verdictFor(config, metric,
|
|
107
|
+
const score = config.score(metric, full);
|
|
108
|
+
const verdict = verdictFor(config, metric, score);
|
|
109
|
+
const headline = config.headline(metric, full);
|
|
110
|
+
const summary = config.summary(metric, full);
|
|
201
111
|
const findings = config.findings(metric, full);
|
|
202
|
-
const
|
|
112
|
+
const sources = sourceEntries(config.id, full);
|
|
113
|
+
const share = renderShareCard(config, full, metric, score, headline, sources);
|
|
114
|
+
|
|
203
115
|
const json = {
|
|
204
116
|
version: options.version,
|
|
205
117
|
package: config.packageName,
|
|
206
118
|
command: config.commandHint,
|
|
207
|
-
npc: config.npc,
|
|
208
|
-
emoji: config.emoji,
|
|
209
119
|
tagline: config.tagline,
|
|
210
120
|
scanned: full.scanned,
|
|
211
121
|
metric_key: config.metricKey,
|
|
212
122
|
metric,
|
|
213
|
-
|
|
123
|
+
score,
|
|
214
124
|
verdict,
|
|
125
|
+
headline,
|
|
126
|
+
summary,
|
|
215
127
|
findings: findings.map(({ label, value }) => ({ label, value })),
|
|
216
|
-
|
|
217
|
-
|
|
128
|
+
share,
|
|
129
|
+
source_breakdown: sources.map((entry) => entry.json),
|
|
218
130
|
};
|
|
131
|
+
|
|
219
132
|
return {
|
|
220
133
|
json,
|
|
221
|
-
|
|
134
|
+
share,
|
|
135
|
+
text: renderToolText(config, full, metric, verdict, score, headline, summary, color),
|
|
222
136
|
};
|
|
223
137
|
}
|
|
224
138
|
|
|
@@ -228,446 +142,172 @@ export function explainTool(toolId, options = {}) {
|
|
|
228
142
|
version: options.version ?? "0.1.0",
|
|
229
143
|
package: config.packageName,
|
|
230
144
|
command: config.commandHint,
|
|
231
|
-
npc: config.npc,
|
|
232
145
|
metric_key: config.metricKey,
|
|
233
146
|
formulas: config.explain,
|
|
234
147
|
};
|
|
235
148
|
if (options.json) return JSON.stringify(payload, null, 2);
|
|
236
149
|
return [
|
|
237
150
|
`${config.packageName} explain v${payload.version}`,
|
|
238
|
-
|
|
151
|
+
config.tagline,
|
|
239
152
|
"",
|
|
240
153
|
...config.explain.map((line) => `- ${line}`),
|
|
241
154
|
].join("\n");
|
|
242
155
|
}
|
|
243
156
|
|
|
244
|
-
function renderToolText(config, full, metric,
|
|
157
|
+
function renderToolText(config, full, metric, verdict, score, headline, summary, color) {
|
|
158
|
+
const sources = sourceEntries(config.id, full).filter((entry) => entry.showInText);
|
|
159
|
+
const sourceLabelWidth = Math.max(11, ...sources.map((entry) => entry.label.toLowerCase().length));
|
|
245
160
|
const rows = [
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
...toolAgentLeaderboard(config.id, full, color),
|
|
257
|
-
reportText(""),
|
|
258
|
-
reportSection("numbers", color),
|
|
259
|
-
...toolNumbers(config.id, full, metric, color),
|
|
161
|
+
...heroRows(config, full, metric, score, headline, verdict, color),
|
|
162
|
+
blankRow(),
|
|
163
|
+
sectionRow("🚨 WORST OFFENDERS", color),
|
|
164
|
+
...(sources.length > 0 ? sources.map((entry, index) => sourceRow(entry, color, index === 0, sourceLabelWidth)) : [detailRow("none", "no active sources found", color)]),
|
|
165
|
+
blankRow(),
|
|
166
|
+
sectionRow("STATS", color),
|
|
167
|
+
...statRows(config.id, metric, verdict, score, color),
|
|
168
|
+
blankRow(),
|
|
169
|
+
sectionRow("SUMMARY", color),
|
|
170
|
+
...paragraphRows(summary, color, { level: verdict.level }),
|
|
260
171
|
];
|
|
261
172
|
|
|
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
|
-
}
|
|
173
|
+
return `\n${rows.map((row) => (row.raw ? ` ${row.painted}` : "")).join("\n")}`;
|
|
283
174
|
}
|
|
284
175
|
|
|
285
|
-
function
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
}
|
|
176
|
+
function heroRows(config, full, metric, score, headline, verdict, color) {
|
|
177
|
+
const hero = `${heroEmoji(config.id, score)} ${headline}`;
|
|
178
|
+
return [
|
|
179
|
+
textRow(hero, color.level(hero, verdict.level)),
|
|
180
|
+
textRow(heroStatLine(config.id, metric, score), color.value(heroStatLine(config.id, metric, score))),
|
|
181
|
+
textRow(
|
|
182
|
+
`scanned ${formatNumber(full.scanned.sessions)} sessions · ${formatNumber(full.scanned.files)} files · ${scanWindow(full.scanned.days)}`,
|
|
183
|
+
color.muted(`scanned ${formatNumber(full.scanned.sessions)} sessions · ${formatNumber(full.scanned.files)} files · ${scanWindow(full.scanned.days)}`),
|
|
184
|
+
),
|
|
185
|
+
ruleRow(color),
|
|
186
|
+
];
|
|
306
187
|
}
|
|
307
188
|
|
|
308
|
-
function
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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 [];
|
|
189
|
+
function statRows(toolId, metric, verdict, score, color) {
|
|
190
|
+
if (toolId === "suckytraces") {
|
|
191
|
+
const missing = Math.max(0, metric.session_log_files - metric.traced_session_log_files);
|
|
192
|
+
return [
|
|
193
|
+
detailRow("severity", `${score}/100 · ${verdict.label}`, color, { level: verdict.level }),
|
|
194
|
+
detailRow("coverage", formatPercent(metric.session_file_coverage_percent), color, { level: metric.level }),
|
|
195
|
+
detailRow("with ids", `${formatNumber(metric.traced_session_log_files)} / ${formatNumber(metric.session_log_files)}`, color, { level: metric.level }),
|
|
196
|
+
detailRow("missing", formatNumber(missing), color, { level: missing > 0 ? metric.level : "LOW" }),
|
|
197
|
+
detailRow("signals", formatNumber(metric.structured_trace_signals), color),
|
|
198
|
+
];
|
|
344
199
|
}
|
|
345
|
-
}
|
|
346
200
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
}
|
|
201
|
+
const recovered = Math.max(0, metric.failed_tool_calls - metric.unresolved_failures);
|
|
202
|
+
return [
|
|
203
|
+
detailRow("severity", `${score}/100 · ${verdict.label}`, color, { level: verdict.level }),
|
|
204
|
+
detailRow("calls", formatNumber(metric.tool_calls), color),
|
|
205
|
+
detailRow("failed", `${formatNumber(metric.failed_tool_calls)} (${formatPercent(metric.failure_rate_percent)})`, color, { level: metric.level }),
|
|
206
|
+
detailRow("rescued", `${formatNumber(recovered)} (${formatPercent(metric.retry_recovery_percent)})`, color),
|
|
207
|
+
detailRow("unresolved", formatNumber(metric.unresolved_failures), color, { level: metric.unresolved_failures > 0 ? metric.level : "LOW" }),
|
|
208
|
+
];
|
|
364
209
|
}
|
|
365
210
|
|
|
366
|
-
function
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
return ranked.map((entry) => {
|
|
371
|
-
const winner = sameRank(entry, best);
|
|
372
|
-
return leaderboardLine(winner ? `🏆 ${entry.label}` : ` ${entry.label}`, entry.value, color, winner);
|
|
373
|
-
});
|
|
211
|
+
function sourceEntries(toolId, full) {
|
|
212
|
+
return activeSources(full)
|
|
213
|
+
.map((source) => sourceEntry(toolId, source))
|
|
214
|
+
.sort((a, b) => b.sort - a.sort || a.label.localeCompare(b.label));
|
|
374
215
|
}
|
|
375
216
|
|
|
376
|
-
function
|
|
377
|
-
const
|
|
378
|
-
|
|
217
|
+
function sourceEntry(toolId, source) {
|
|
218
|
+
const files = source.files ?? 0;
|
|
219
|
+
const sessions = source.sessions ?? 0;
|
|
379
220
|
|
|
380
221
|
if (toolId === "suckytraces") {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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);
|
|
222
|
+
const traced = source.traced_session_log_files ?? 0;
|
|
223
|
+
const missing = Math.max(0, files - traced);
|
|
224
|
+
const coverage = files > 0 ? round((traced / files) * 100, 1) : 0;
|
|
225
|
+
return {
|
|
226
|
+
label: source.label,
|
|
227
|
+
sort: ((100 - coverage) * 1_000_000) + files,
|
|
228
|
+
showInText: files > 0,
|
|
229
|
+
detail: `${formatPercent(coverage)} traced · ${formatNumber(missing)} missing · ${formatNumber(files)} files`,
|
|
230
|
+
json: { source: source.label, files, sessions, trace_coverage_percent: coverage, traced_session_log_files: traced, missing_trace_files: missing },
|
|
231
|
+
};
|
|
437
232
|
}
|
|
438
233
|
|
|
439
|
-
|
|
234
|
+
const calls = source.tool_calls ?? 0;
|
|
235
|
+
const failed = source.failed_tool_calls ?? 0;
|
|
236
|
+
const failureRate = calls > 0 ? round((failed / calls) * 100, 1) : 0;
|
|
237
|
+
return {
|
|
238
|
+
label: source.label,
|
|
239
|
+
sort: (failureRate * 1_000_000) + failed,
|
|
240
|
+
showInText: calls > 0,
|
|
241
|
+
detail: `${formatPercent(failureRate)} fail · ${formatNumber(failed)}/${formatNumber(calls)} · ${formatNumber(sessions)} sessions`,
|
|
242
|
+
json: { source: source.label, files, sessions, tool_calls: calls, failed_tool_calls: failed, failure_rate_percent: failureRate },
|
|
243
|
+
};
|
|
440
244
|
}
|
|
441
245
|
|
|
442
|
-
function
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
case "tooltantrum":
|
|
451
|
-
return (source.tool_calls ?? 0) >= 50;
|
|
452
|
-
default:
|
|
453
|
-
return false;
|
|
454
|
-
}
|
|
246
|
+
function activeSources(full) {
|
|
247
|
+
return Object.values(full.scanned.sources ?? {}).filter((source) => {
|
|
248
|
+
if (source.label === "Agent config" || source.unavailable === true) return false;
|
|
249
|
+
return (source.files ?? 0) > 0 ||
|
|
250
|
+
(source.sessions ?? 0) > 0 ||
|
|
251
|
+
(source.tool_calls ?? 0) > 0 ||
|
|
252
|
+
(source.trace_signals ?? 0) > 0;
|
|
253
|
+
});
|
|
455
254
|
}
|
|
456
255
|
|
|
457
|
-
function
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
}
|
|
256
|
+
function renderShareCard(config, full, metric, score, headline, sources) {
|
|
257
|
+
const top = sources.find((entry) => entry.showInText);
|
|
258
|
+
const lines = [
|
|
259
|
+
`${heroEmoji(config.id, score)} ${headline}`,
|
|
260
|
+
`Severity: ${score}/100`,
|
|
261
|
+
`Scanned: ${formatNumber(full.scanned.sessions)} sessions · ${formatNumber(full.scanned.files)} files · ${scanWindow(full.scanned.days)}`,
|
|
262
|
+
];
|
|
461
263
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}
|
|
264
|
+
if (config.id === "suckytraces") {
|
|
265
|
+
const missing = Math.max(0, metric.session_log_files - metric.traced_session_log_files);
|
|
266
|
+
lines.push(`Stats: ${formatPercent(metric.session_file_coverage_percent)} traced · ${formatNumber(metric.traced_session_log_files)}/${formatNumber(metric.session_log_files)} logs with IDs · ${formatNumber(missing)} missing`);
|
|
267
|
+
} else {
|
|
268
|
+
lines.push(`Stats: ${formatPercent(metric.failure_rate_percent)} failed · ${formatNumber(metric.failed_tool_calls)}/${formatNumber(metric.tool_calls)} calls · ${formatNumber(metric.unresolved_failures)} unresolved · ${formatPercent(metric.retry_recovery_percent)} rescued`);
|
|
269
|
+
}
|
|
465
270
|
|
|
466
|
-
|
|
467
|
-
|
|
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}` };
|
|
271
|
+
if (top) lines.push(`Worst: ${top.label} — ${top.detail}`);
|
|
272
|
+
return lines.join("\n");
|
|
471
273
|
}
|
|
472
274
|
|
|
473
|
-
function
|
|
474
|
-
|
|
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
|
-
}
|
|
275
|
+
function heroTitle(toolId) {
|
|
276
|
+
return toolId === "suckytraces" ? "SUCKY TRACES" : "TOOL TANTRUM";
|
|
486
277
|
}
|
|
487
278
|
|
|
488
|
-
function
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
-
}
|
|
279
|
+
function heroEmoji(toolId, score) {
|
|
280
|
+
if (score >= 75) return toolId === "suckytraces" ? "🕳️" : "🔥";
|
|
281
|
+
if (score >= 45) return "⚠️";
|
|
282
|
+
return "✅";
|
|
509
283
|
}
|
|
510
284
|
|
|
511
|
-
function
|
|
512
|
-
if (
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
return "tool-call failure rate is low";
|
|
285
|
+
function heroStatLine(toolId, metric, score) {
|
|
286
|
+
if (toolId === "suckytraces") {
|
|
287
|
+
const missing = Math.max(0, metric.session_log_files - metric.traced_session_log_files);
|
|
288
|
+
return `${score}/100 SEVERITY · ${formatPercent(metric.session_file_coverage_percent)} TRACED · ${formatNumber(missing)} MISSING`;
|
|
289
|
+
}
|
|
290
|
+
return `${score}/100 SEVERITY · ${formatPercent(metric.failure_rate_percent)} FAILED · ${formatNumber(metric.unresolved_failures)} UNRESOLVED`;
|
|
518
291
|
}
|
|
519
292
|
|
|
520
293
|
function verdictFor(config, metric, score) {
|
|
521
|
-
const level = metric.level
|
|
522
|
-
if (score >= 75 || level === "CRITICAL" || level === "MISSING") return { level, label: config.bad
|
|
523
|
-
if (score >= 45 || level === "HIGH") return { level, label: "
|
|
524
|
-
if (score >= 20 || level === "MEDIUM" || level === "PARTIAL") return { level, label: "
|
|
525
|
-
return { level, label: config.good
|
|
294
|
+
const level = scoreLevel(score, metric.level);
|
|
295
|
+
if (score >= 75 || level === "CRITICAL" || level === "MISSING") return { level, label: config.bad };
|
|
296
|
+
if (score >= 45 || level === "HIGH") return { level, label: "serious" };
|
|
297
|
+
if (score >= 20 || level === "MEDIUM" || level === "PARTIAL") return { level, label: "watch" };
|
|
298
|
+
return { level, label: config.good };
|
|
526
299
|
}
|
|
527
300
|
|
|
528
|
-
function scoreLevel(score) {
|
|
301
|
+
function scoreLevel(score, fallback) {
|
|
302
|
+
const normalized = String(fallback ?? "").toUpperCase();
|
|
303
|
+
if (normalized === "CRITICAL" || normalized === "MISSING") return normalized;
|
|
529
304
|
if (score >= 75) return "CRITICAL";
|
|
530
305
|
if (score >= 45) return "HIGH";
|
|
531
306
|
if (score >= 20) return "MEDIUM";
|
|
307
|
+
if (normalized === "NO_DATA" || normalized === "CONFIG_ONLY") return normalized;
|
|
532
308
|
return "LOW";
|
|
533
309
|
}
|
|
534
310
|
|
|
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
311
|
function scoreFromLevel(level) {
|
|
672
312
|
switch (String(level).toUpperCase()) {
|
|
673
313
|
case "CRITICAL":
|
|
@@ -678,44 +318,57 @@ function scoreFromLevel(level) {
|
|
|
678
318
|
case "MEDIUM":
|
|
679
319
|
case "PARTIAL":
|
|
680
320
|
return 34;
|
|
681
|
-
case "CONFIG_ONLY":
|
|
682
|
-
case "NO_DATA":
|
|
683
|
-
return 12;
|
|
684
321
|
default:
|
|
685
322
|
return 5;
|
|
686
323
|
}
|
|
687
324
|
}
|
|
688
325
|
|
|
689
|
-
function
|
|
690
|
-
return
|
|
326
|
+
function finding(label, value, level) {
|
|
327
|
+
return { label, value, level };
|
|
691
328
|
}
|
|
692
329
|
|
|
693
|
-
function
|
|
694
|
-
|
|
695
|
-
return Math.round(value * factor) / factor;
|
|
330
|
+
function textRow(raw, painted = raw) {
|
|
331
|
+
return { raw, painted };
|
|
696
332
|
}
|
|
697
333
|
|
|
698
|
-
function
|
|
699
|
-
return
|
|
334
|
+
function blankRow() {
|
|
335
|
+
return { raw: "", painted: "" };
|
|
700
336
|
}
|
|
701
337
|
|
|
702
|
-
function
|
|
703
|
-
|
|
338
|
+
function ruleRow(color) {
|
|
339
|
+
const rule = "━".repeat(58);
|
|
340
|
+
return textRow(rule, color.rule(rule));
|
|
704
341
|
}
|
|
705
342
|
|
|
706
|
-
function
|
|
707
|
-
|
|
708
|
-
|
|
343
|
+
function sectionRow(title, color) {
|
|
344
|
+
return textRow(title, color.title(title));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function detailRow(label, value, color, options = {}) {
|
|
348
|
+
const safeLabel = String(label).toLowerCase().padEnd(11);
|
|
349
|
+
const raw = ` ${safeLabel} ${value}`;
|
|
350
|
+
const paintValue = options.level ? color.level(value, options.level) : color.value(value);
|
|
351
|
+
return textRow(raw, ` ${color.label(safeLabel)} ${paintValue}`);
|
|
709
352
|
}
|
|
710
353
|
|
|
711
|
-
function
|
|
712
|
-
const
|
|
713
|
-
const
|
|
714
|
-
|
|
354
|
+
function sourceRow(entry, color, highlight = false, labelWidth = 11) {
|
|
355
|
+
const prefix = highlight ? "🚨 " : " ";
|
|
356
|
+
const label = `${prefix}${entry.label.toLowerCase()}`.padEnd(labelWidth + prefix.length);
|
|
357
|
+
const raw = ` ${label} ${entry.detail}`;
|
|
358
|
+
if (!highlight) return textRow(raw, ` ${color.label(label)} ${color.value(entry.detail)}`);
|
|
359
|
+
return textRow(raw, ` ${color.level(label, "CRITICAL")} ${color.level(entry.detail, "CRITICAL")}`);
|
|
715
360
|
}
|
|
716
361
|
|
|
717
|
-
function
|
|
718
|
-
return
|
|
362
|
+
function mutedRow(raw, color) {
|
|
363
|
+
return textRow(raw, color.muted(raw));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function paragraphRows(value, color, options = {}) {
|
|
367
|
+
return wrapText(String(value), 58).map((line) => {
|
|
368
|
+
const raw = ` ${line}`;
|
|
369
|
+
const paintedValue = options.level ? color.level(line, options.level) : color.value(line);
|
|
370
|
+
return textRow(raw, ` ${paintedValue}`);
|
|
371
|
+
});
|
|
719
372
|
}
|
|
720
373
|
|
|
721
374
|
function wrapText(value, width) {
|
|
@@ -723,14 +376,6 @@ function wrapText(value, width) {
|
|
|
723
376
|
const lines = [];
|
|
724
377
|
let line = "";
|
|
725
378
|
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
379
|
if (!line) {
|
|
735
380
|
line = word;
|
|
736
381
|
continue;
|
|
@@ -746,21 +391,40 @@ function wrapText(value, width) {
|
|
|
746
391
|
return lines.length > 0 ? lines : [""];
|
|
747
392
|
}
|
|
748
393
|
|
|
749
|
-
function
|
|
750
|
-
|
|
751
|
-
|
|
394
|
+
function scanWindow(days) {
|
|
395
|
+
return days === "all" ? "all sessions" : `last ${days}d`;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function clamp(value) {
|
|
399
|
+
return Math.max(0, Math.min(100, Math.round(Number(value) || 0)));
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function round(value, digits) {
|
|
403
|
+
const factor = 10 ** digits;
|
|
404
|
+
return Math.round(value * factor) / factor;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function formatNumber(value) {
|
|
408
|
+
return Number(value || 0).toLocaleString("en-US");
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function formatCount(value, singular, plural = `${singular}s`) {
|
|
412
|
+
const number = Number(value || 0);
|
|
413
|
+
return `${formatNumber(number)} ${number === 1 ? singular : plural}`;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function formatPercent(value) {
|
|
417
|
+
const number = Number(value || 0);
|
|
418
|
+
return `${number.toFixed(Number.isInteger(number) ? 0 : 1)}%`;
|
|
752
419
|
}
|
|
753
420
|
|
|
754
421
|
function colorizer(enabled) {
|
|
755
422
|
if (!enabled) return plainColors();
|
|
756
423
|
const paint = (code, value) => `\x1b[${code}m${value}\x1b[0m`;
|
|
757
424
|
return {
|
|
758
|
-
accent: (value) => paint("38;2;147;197;253", value),
|
|
759
|
-
border: (value) => paint("38;2;100;116;139", value),
|
|
760
425
|
label: (value) => paint("38;2;186;230;253", value),
|
|
761
426
|
muted: (value) => paint("38;2;148;163;184", value),
|
|
762
|
-
rule: (value) => paint("38;2;
|
|
763
|
-
solution: (value) => paint("38;2;125;211;252", value),
|
|
427
|
+
rule: (value) => paint("38;2;51;65;85", value),
|
|
764
428
|
title: (value) => paint("38;2;147;197;253;1", value),
|
|
765
429
|
value: (value) => paint("38;2;226;232;240", value),
|
|
766
430
|
level: (value, level) => paint(levelColor(level), value),
|
|
@@ -769,12 +433,9 @@ function colorizer(enabled) {
|
|
|
769
433
|
|
|
770
434
|
function plainColors() {
|
|
771
435
|
return {
|
|
772
|
-
accent: (value) => value,
|
|
773
|
-
border: (value) => value,
|
|
774
436
|
label: (value) => value,
|
|
775
437
|
muted: (value) => value,
|
|
776
438
|
rule: (value) => value,
|
|
777
|
-
solution: (value) => value,
|
|
778
439
|
title: (value) => value,
|
|
779
440
|
value: (value) => value,
|
|
780
441
|
level: (value) => value,
|