@kevinrabun/judges 3.38.0 → 3.40.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/CHANGELOG.md +46 -0
- package/README.md +5 -4
- package/dist/api.d.ts +5 -2
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +5 -1
- package/dist/api.js.map +1 -1
- package/dist/ast/structural-parser.js +3 -3
- package/dist/ast/structural-parser.js.map +1 -1
- package/dist/calibration.d.ts +35 -0
- package/dist/calibration.d.ts.map +1 -1
- package/dist/calibration.js +52 -0
- package/dist/calibration.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +307 -16
- package/dist/cli.js.map +1 -1
- package/dist/commands/benchmark-languages.js +4 -4
- package/dist/commands/benchmark.d.ts +2 -1
- package/dist/commands/benchmark.d.ts.map +1 -1
- package/dist/commands/benchmark.js +67 -2
- package/dist/commands/benchmark.js.map +1 -1
- package/dist/commands/calibration-dashboard.d.ts.map +1 -1
- package/dist/commands/calibration-dashboard.js +198 -0
- package/dist/commands/calibration-dashboard.js.map +1 -1
- package/dist/commands/calibration-share.d.ts +31 -0
- package/dist/commands/calibration-share.d.ts.map +1 -0
- package/dist/commands/calibration-share.js +183 -0
- package/dist/commands/calibration-share.js.map +1 -0
- package/dist/commands/compliance-report.d.ts +35 -0
- package/dist/commands/compliance-report.d.ts.map +1 -0
- package/dist/commands/compliance-report.js +162 -0
- package/dist/commands/compliance-report.js.map +1 -0
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +8 -3
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/feedback-rules.d.ts +29 -0
- package/dist/commands/feedback-rules.d.ts.map +1 -0
- package/dist/commands/feedback-rules.js +174 -0
- package/dist/commands/feedback-rules.js.map +1 -0
- package/dist/commands/feedback.d.ts +12 -0
- package/dist/commands/feedback.d.ts.map +1 -1
- package/dist/commands/feedback.js +16 -0
- package/dist/commands/feedback.js.map +1 -1
- package/dist/commands/fix.d.ts.map +1 -1
- package/dist/commands/fix.js +33 -1
- package/dist/commands/fix.js.map +1 -1
- package/dist/commands/governance.d.ts +32 -0
- package/dist/commands/governance.d.ts.map +1 -0
- package/dist/commands/governance.js +203 -0
- package/dist/commands/governance.js.map +1 -0
- package/dist/commands/help.d.ts +8 -0
- package/dist/commands/help.d.ts.map +1 -0
- package/dist/commands/help.js +303 -0
- package/dist/commands/help.js.map +1 -0
- package/dist/commands/hook.d.ts.map +1 -1
- package/dist/commands/hook.js +17 -20
- package/dist/commands/hook.js.map +1 -1
- package/dist/commands/llm-benchmark.d.ts +119 -0
- package/dist/commands/llm-benchmark.d.ts.map +1 -0
- package/dist/commands/llm-benchmark.js +396 -0
- package/dist/commands/llm-benchmark.js.map +1 -0
- package/dist/commands/metrics-dashboard.d.ts +22 -0
- package/dist/commands/metrics-dashboard.d.ts.map +1 -0
- package/dist/commands/metrics-dashboard.js +335 -0
- package/dist/commands/metrics-dashboard.js.map +1 -0
- package/dist/commands/metrics.d.ts +58 -0
- package/dist/commands/metrics.d.ts.map +1 -0
- package/dist/commands/metrics.js +242 -0
- package/dist/commands/metrics.js.map +1 -0
- package/dist/commands/onboard.d.ts +13 -0
- package/dist/commands/onboard.d.ts.map +1 -0
- package/dist/commands/onboard.js +179 -0
- package/dist/commands/onboard.js.map +1 -0
- package/dist/commands/org-metrics.d.ts +24 -0
- package/dist/commands/org-metrics.d.ts.map +1 -0
- package/dist/commands/org-metrics.js +238 -0
- package/dist/commands/org-metrics.js.map +1 -0
- package/dist/commands/override.d.ts +62 -0
- package/dist/commands/override.d.ts.map +1 -0
- package/dist/commands/override.js +264 -0
- package/dist/commands/override.js.map +1 -0
- package/dist/commands/parity.d.ts +31 -0
- package/dist/commands/parity.d.ts.map +1 -0
- package/dist/commands/parity.js +213 -0
- package/dist/commands/parity.js.map +1 -0
- package/dist/commands/plugin-search.d.ts +40 -0
- package/dist/commands/plugin-search.d.ts.map +1 -0
- package/dist/commands/plugin-search.js +328 -0
- package/dist/commands/plugin-search.js.map +1 -0
- package/dist/commands/plugins.d.ts +13 -0
- package/dist/commands/plugins.d.ts.map +1 -0
- package/dist/commands/plugins.js +105 -0
- package/dist/commands/plugins.js.map +1 -0
- package/dist/commands/review.js +1 -1
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/snapshot.d.ts +27 -0
- package/dist/commands/snapshot.d.ts.map +1 -1
- package/dist/commands/snapshot.js +99 -0
- package/dist/commands/snapshot.js.map +1 -1
- package/dist/commands/trace.d.ts +65 -0
- package/dist/commands/trace.d.ts.map +1 -0
- package/dist/commands/trace.js +246 -0
- package/dist/commands/trace.js.map +1 -0
- package/dist/commands/trust-ramp.d.ts +30 -0
- package/dist/commands/trust-ramp.d.ts.map +1 -0
- package/dist/commands/trust-ramp.js +190 -0
- package/dist/commands/trust-ramp.js.map +1 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +65 -0
- package/dist/config.js.map +1 -1
- package/dist/data-adapter.d.ts +124 -0
- package/dist/data-adapter.d.ts.map +1 -0
- package/dist/data-adapter.js +213 -0
- package/dist/data-adapter.js.map +1 -0
- package/dist/evaluators/accessibility.js +1 -1
- package/dist/evaluators/accessibility.js.map +1 -1
- package/dist/evaluators/ai-code-safety.d.ts.map +1 -1
- package/dist/evaluators/ai-code-safety.js +1 -4
- package/dist/evaluators/ai-code-safety.js.map +1 -1
- package/dist/evaluators/cost-effectiveness.js +1 -1
- package/dist/evaluators/cost-effectiveness.js.map +1 -1
- package/dist/evaluators/false-positive-review.js +4 -4
- package/dist/evaluators/false-positive-review.js.map +1 -1
- package/dist/evaluators/iac-security.js +1 -1
- package/dist/evaluators/iac-security.js.map +1 -1
- package/dist/evaluators/index.d.ts.map +1 -1
- package/dist/evaluators/index.js +59 -10
- package/dist/evaluators/index.js.map +1 -1
- package/dist/evaluators/intent-alignment.d.ts +4 -0
- package/dist/evaluators/intent-alignment.d.ts.map +1 -1
- package/dist/evaluators/intent-alignment.js +163 -0
- package/dist/evaluators/intent-alignment.js.map +1 -1
- package/dist/evaluators/logic-review.js +1 -1
- package/dist/evaluators/logic-review.js.map +1 -1
- package/dist/evaluators/maintainability.js +1 -1
- package/dist/evaluators/maintainability.js.map +1 -1
- package/dist/evaluators/over-engineering.js +3 -3
- package/dist/evaluators/over-engineering.js.map +1 -1
- package/dist/evaluators/project.d.ts +12 -0
- package/dist/evaluators/project.d.ts.map +1 -1
- package/dist/evaluators/project.js +86 -0
- package/dist/evaluators/project.js.map +1 -1
- package/dist/evaluators/security.js +2 -2
- package/dist/evaluators/security.js.map +1 -1
- package/dist/evaluators/ux.js +1 -1
- package/dist/evaluators/ux.js.map +1 -1
- package/dist/finding-lifecycle.d.ts +9 -0
- package/dist/finding-lifecycle.d.ts.map +1 -1
- package/dist/finding-lifecycle.js +15 -0
- package/dist/finding-lifecycle.js.map +1 -1
- package/dist/fix-history.d.ts +9 -0
- package/dist/fix-history.d.ts.map +1 -1
- package/dist/fix-history.js +15 -0
- package/dist/fix-history.js.map +1 -1
- package/dist/formatters/sarif.d.ts +3 -0
- package/dist/formatters/sarif.d.ts.map +1 -1
- package/dist/formatters/sarif.js +36 -12
- package/dist/formatters/sarif.js.map +1 -1
- package/dist/github-app.d.ts +16 -1
- package/dist/github-app.d.ts.map +1 -1
- package/dist/github-app.js +85 -2
- package/dist/github-app.js.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/judge-registry.d.ts +157 -0
- package/dist/judge-registry.d.ts.map +1 -0
- package/dist/judge-registry.js +273 -0
- package/dist/judge-registry.js.map +1 -0
- package/dist/judges/accessibility.d.ts.map +1 -1
- package/dist/judges/accessibility.js +4 -0
- package/dist/judges/accessibility.js.map +1 -1
- package/dist/judges/agent-instructions.d.ts.map +1 -1
- package/dist/judges/agent-instructions.js +4 -0
- package/dist/judges/agent-instructions.js.map +1 -1
- package/dist/judges/ai-code-safety.d.ts.map +1 -1
- package/dist/judges/ai-code-safety.js +4 -0
- package/dist/judges/ai-code-safety.js.map +1 -1
- package/dist/judges/api-contract.d.ts.map +1 -1
- package/dist/judges/api-contract.js +4 -0
- package/dist/judges/api-contract.js.map +1 -1
- package/dist/judges/api-design.d.ts.map +1 -1
- package/dist/judges/api-design.js +4 -0
- package/dist/judges/api-design.js.map +1 -1
- package/dist/judges/authentication.d.ts.map +1 -1
- package/dist/judges/authentication.js +4 -0
- package/dist/judges/authentication.js.map +1 -1
- package/dist/judges/backwards-compatibility.d.ts.map +1 -1
- package/dist/judges/backwards-compatibility.js +4 -0
- package/dist/judges/backwards-compatibility.js.map +1 -1
- package/dist/judges/caching.d.ts.map +1 -1
- package/dist/judges/caching.js +4 -0
- package/dist/judges/caching.js.map +1 -1
- package/dist/judges/ci-cd.d.ts.map +1 -1
- package/dist/judges/ci-cd.js +4 -0
- package/dist/judges/ci-cd.js.map +1 -1
- package/dist/judges/cloud-readiness.d.ts.map +1 -1
- package/dist/judges/cloud-readiness.js +4 -0
- package/dist/judges/cloud-readiness.js.map +1 -1
- package/dist/judges/code-structure.d.ts.map +1 -1
- package/dist/judges/code-structure.js +4 -0
- package/dist/judges/code-structure.js.map +1 -1
- package/dist/judges/compliance.d.ts.map +1 -1
- package/dist/judges/compliance.js +4 -0
- package/dist/judges/compliance.js.map +1 -1
- package/dist/judges/concurrency.d.ts.map +1 -1
- package/dist/judges/concurrency.js +4 -0
- package/dist/judges/concurrency.js.map +1 -1
- package/dist/judges/configuration-management.d.ts.map +1 -1
- package/dist/judges/configuration-management.js +4 -0
- package/dist/judges/configuration-management.js.map +1 -1
- package/dist/judges/cost-effectiveness.d.ts.map +1 -1
- package/dist/judges/cost-effectiveness.js +4 -0
- package/dist/judges/cost-effectiveness.js.map +1 -1
- package/dist/judges/cybersecurity.d.ts.map +1 -1
- package/dist/judges/cybersecurity.js +4 -0
- package/dist/judges/cybersecurity.js.map +1 -1
- package/dist/judges/data-security.d.ts.map +1 -1
- package/dist/judges/data-security.js +4 -0
- package/dist/judges/data-security.js.map +1 -1
- package/dist/judges/data-sovereignty.d.ts.map +1 -1
- package/dist/judges/data-sovereignty.js +4 -0
- package/dist/judges/data-sovereignty.js.map +1 -1
- package/dist/judges/database.d.ts.map +1 -1
- package/dist/judges/database.js +4 -0
- package/dist/judges/database.js.map +1 -1
- package/dist/judges/dependency-health.d.ts.map +1 -1
- package/dist/judges/dependency-health.js +4 -0
- package/dist/judges/dependency-health.js.map +1 -1
- package/dist/judges/documentation.d.ts.map +1 -1
- package/dist/judges/documentation.js +4 -0
- package/dist/judges/documentation.js.map +1 -1
- package/dist/judges/error-handling.d.ts.map +1 -1
- package/dist/judges/error-handling.js +4 -0
- package/dist/judges/error-handling.js.map +1 -1
- package/dist/judges/ethics-bias.d.ts.map +1 -1
- package/dist/judges/ethics-bias.js +4 -0
- package/dist/judges/ethics-bias.js.map +1 -1
- package/dist/judges/false-positive-review.d.ts.map +1 -1
- package/dist/judges/false-positive-review.js +2 -0
- package/dist/judges/false-positive-review.js.map +1 -1
- package/dist/judges/framework-safety.d.ts.map +1 -1
- package/dist/judges/framework-safety.js +4 -0
- package/dist/judges/framework-safety.js.map +1 -1
- package/dist/judges/hallucination-detection.d.ts.map +1 -1
- package/dist/judges/hallucination-detection.js +4 -0
- package/dist/judges/hallucination-detection.js.map +1 -1
- package/dist/judges/iac-security.d.ts.map +1 -1
- package/dist/judges/iac-security.js +4 -0
- package/dist/judges/iac-security.js.map +1 -1
- package/dist/judges/index.d.ts +59 -0
- package/dist/judges/index.d.ts.map +1 -1
- package/dist/judges/index.js +65 -189
- package/dist/judges/index.js.map +1 -1
- package/dist/judges/intent-alignment.d.ts.map +1 -1
- package/dist/judges/intent-alignment.js +4 -0
- package/dist/judges/intent-alignment.js.map +1 -1
- package/dist/judges/internationalization.d.ts.map +1 -1
- package/dist/judges/internationalization.js +4 -0
- package/dist/judges/internationalization.js.map +1 -1
- package/dist/judges/logging-privacy.d.ts.map +1 -1
- package/dist/judges/logging-privacy.js +4 -0
- package/dist/judges/logging-privacy.js.map +1 -1
- package/dist/judges/logic-review.d.ts.map +1 -1
- package/dist/judges/logic-review.js +4 -0
- package/dist/judges/logic-review.js.map +1 -1
- package/dist/judges/maintainability.d.ts.map +1 -1
- package/dist/judges/maintainability.js +4 -0
- package/dist/judges/maintainability.js.map +1 -1
- package/dist/judges/model-fingerprint.d.ts.map +1 -1
- package/dist/judges/model-fingerprint.js +4 -0
- package/dist/judges/model-fingerprint.js.map +1 -1
- package/dist/judges/multi-turn-coherence.d.ts.map +1 -1
- package/dist/judges/multi-turn-coherence.js +4 -0
- package/dist/judges/multi-turn-coherence.js.map +1 -1
- package/dist/judges/observability.d.ts.map +1 -1
- package/dist/judges/observability.js +4 -0
- package/dist/judges/observability.js.map +1 -1
- package/dist/judges/over-engineering.d.ts.map +1 -1
- package/dist/judges/over-engineering.js +4 -0
- package/dist/judges/over-engineering.js.map +1 -1
- package/dist/judges/performance.d.ts.map +1 -1
- package/dist/judges/performance.js +4 -0
- package/dist/judges/performance.js.map +1 -1
- package/dist/judges/portability.d.ts.map +1 -1
- package/dist/judges/portability.js +4 -0
- package/dist/judges/portability.js.map +1 -1
- package/dist/judges/rate-limiting.d.ts.map +1 -1
- package/dist/judges/rate-limiting.js +4 -0
- package/dist/judges/rate-limiting.js.map +1 -1
- package/dist/judges/reliability.d.ts.map +1 -1
- package/dist/judges/reliability.js +4 -0
- package/dist/judges/reliability.js.map +1 -1
- package/dist/judges/scalability.d.ts.map +1 -1
- package/dist/judges/scalability.js +4 -0
- package/dist/judges/scalability.js.map +1 -1
- package/dist/judges/security.d.ts.map +1 -1
- package/dist/judges/security.js +4 -0
- package/dist/judges/security.js.map +1 -1
- package/dist/judges/software-practices.d.ts.map +1 -1
- package/dist/judges/software-practices.js +4 -0
- package/dist/judges/software-practices.js.map +1 -1
- package/dist/judges/testing.d.ts.map +1 -1
- package/dist/judges/testing.js +4 -0
- package/dist/judges/testing.js.map +1 -1
- package/dist/judges/ux.d.ts.map +1 -1
- package/dist/judges/ux.js +4 -0
- package/dist/judges/ux.js.map +1 -1
- package/dist/plugins.d.ts +8 -51
- package/dist/plugins.d.ts.map +1 -1
- package/dist/plugins.js +16 -125
- package/dist/plugins.js.map +1 -1
- package/dist/security-ids.d.ts +24 -0
- package/dist/security-ids.d.ts.map +1 -0
- package/dist/security-ids.js +240 -0
- package/dist/security-ids.js.map +1 -0
- package/dist/tools/prompts.d.ts +4 -0
- package/dist/tools/prompts.d.ts.map +1 -1
- package/dist/tools/prompts.js +6 -4
- package/dist/tools/prompts.js.map +1 -1
- package/dist/tools/register-scaffold.d.ts +3 -0
- package/dist/tools/register-scaffold.d.ts.map +1 -0
- package/dist/tools/register-scaffold.js +399 -0
- package/dist/tools/register-scaffold.js.map +1 -0
- package/dist/tools/register.d.ts +1 -1
- package/dist/tools/register.d.ts.map +1 -1
- package/dist/tools/register.js +3 -1
- package/dist/tools/register.js.map +1 -1
- package/dist/types.d.ts +75 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -2
- package/server.json +2 -2
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `judges metrics-dashboard` — Interactive HTML metrics dashboard.
|
|
3
|
+
*
|
|
4
|
+
* Generates a self-contained HTML page with charts visualising:
|
|
5
|
+
* - Findings over time (from snapshot data)
|
|
6
|
+
* - Fix rates and time saved (from finding lifecycle data)
|
|
7
|
+
* - Severity distribution
|
|
8
|
+
* - Judge performance breakdown
|
|
9
|
+
* - Feedback-driven calibration improvements
|
|
10
|
+
*
|
|
11
|
+
* All data comes from the user's own local stores (or configured adapter).
|
|
12
|
+
* Judges never hosts or processes user data — dashboards are generated
|
|
13
|
+
* client-side from the user's own files.
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* judges metrics-dashboard # output HTML to stdout
|
|
17
|
+
* judges metrics-dashboard --output report.html # write to file
|
|
18
|
+
* judges metrics-dashboard --format json # raw data
|
|
19
|
+
*/
|
|
20
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
21
|
+
import { resolve } from "path";
|
|
22
|
+
import { computeMetrics } from "./metrics.js";
|
|
23
|
+
function loadSnapshots(dir) {
|
|
24
|
+
const p = resolve(dir, ".judges-snapshots.json");
|
|
25
|
+
if (!existsSync(p))
|
|
26
|
+
return { snapshots: [] };
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(readFileSync(p, "utf-8"));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return { snapshots: [] };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function loadFindingStoreLocal(dir) {
|
|
35
|
+
const p = resolve(dir, ".judges-findings.json");
|
|
36
|
+
if (!existsSync(p))
|
|
37
|
+
return undefined;
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(readFileSync(p, "utf-8"));
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function loadFeedbackLocal(dir) {
|
|
46
|
+
const p = resolve(dir, ".judges-feedback.json");
|
|
47
|
+
if (!existsSync(p))
|
|
48
|
+
return { entries: [] };
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(readFileSync(p, "utf-8"));
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return { entries: [] };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// ─── HTML Dashboard ─────────────────────────────────────────────────────────
|
|
57
|
+
export function renderMetricsDashboardHtml(dir) {
|
|
58
|
+
const metrics = computeMetrics(resolve(dir));
|
|
59
|
+
const snapshots = loadSnapshots(dir);
|
|
60
|
+
const _findingStore = loadFindingStoreLocal(dir);
|
|
61
|
+
const feedback = loadFeedbackLocal(dir);
|
|
62
|
+
const _snapshotJson = JSON.stringify(snapshots.snapshots.slice(-50));
|
|
63
|
+
const _metricsJson = JSON.stringify(metrics);
|
|
64
|
+
const _severityJson = JSON.stringify(metrics.findings.bySeverity);
|
|
65
|
+
const _feedbackJson = JSON.stringify(aggregateFeedbackByWeek(feedback));
|
|
66
|
+
return `<!DOCTYPE html>
|
|
67
|
+
<html lang="en">
|
|
68
|
+
<head>
|
|
69
|
+
<meta charset="utf-8">
|
|
70
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
71
|
+
<title>Judges — Metrics Dashboard</title>
|
|
72
|
+
<style>
|
|
73
|
+
:root {
|
|
74
|
+
--bg: #0d1117; --surface: #161b22; --border: #30363d;
|
|
75
|
+
--text: #e6edf3; --text2: #8b949e; --accent: #58a6ff;
|
|
76
|
+
--green: #3fb950; --yellow: #d29922; --red: #f85149; --orange: #db6d28;
|
|
77
|
+
}
|
|
78
|
+
@media (prefers-color-scheme: light) {
|
|
79
|
+
:root {
|
|
80
|
+
--bg: #f6f8fa; --surface: #ffffff; --border: #d0d7de;
|
|
81
|
+
--text: #1f2328; --text2: #656d76; --accent: #0969da;
|
|
82
|
+
--green: #1a7f37; --yellow: #9a6700; --red: #cf222e; --orange: #bc4c00;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
86
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
87
|
+
background: var(--bg); color: var(--text); padding: 2rem; line-height: 1.5; }
|
|
88
|
+
h1 { font-size: 1.8rem; margin-bottom: 0.5rem; }
|
|
89
|
+
h2 { font-size: 1.2rem; margin: 1.5rem 0 0.8rem; color: var(--text2); }
|
|
90
|
+
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; margin: 1rem 0; }
|
|
91
|
+
.card { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 1.2rem; }
|
|
92
|
+
.card .label { font-size: 0.8rem; color: var(--text2); text-transform: uppercase; letter-spacing: 0.05em; }
|
|
93
|
+
.card .value { font-size: 2rem; font-weight: 700; margin-top: 0.3rem; }
|
|
94
|
+
.card .sub { font-size: 0.85rem; color: var(--text2); margin-top: 0.2rem; }
|
|
95
|
+
.green { color: var(--green); } .yellow { color: var(--yellow); } .red { color: var(--red); }
|
|
96
|
+
.chart-container { background: var(--surface); border: 1px solid var(--border);
|
|
97
|
+
border-radius: 8px; padding: 1.2rem; margin: 1rem 0; }
|
|
98
|
+
svg { width: 100%; }
|
|
99
|
+
table { width: 100%; border-collapse: collapse; margin: 0.5rem 0; }
|
|
100
|
+
th, td { padding: 0.5rem 0.8rem; text-align: left; border-bottom: 1px solid var(--border); font-size: 0.9rem; }
|
|
101
|
+
th { color: var(--text2); font-weight: 600; }
|
|
102
|
+
.bar { height: 18px; border-radius: 3px; display: inline-block; vertical-align: middle; }
|
|
103
|
+
.legend { display: flex; gap: 1rem; flex-wrap: wrap; margin: 0.5rem 0; font-size: 0.85rem; }
|
|
104
|
+
.legend span::before { content: ''; display: inline-block; width: 10px; height: 10px;
|
|
105
|
+
border-radius: 2px; margin-right: 4px; vertical-align: middle; }
|
|
106
|
+
.trend-arrow { font-size: 1.5rem; }
|
|
107
|
+
footer { margin-top: 2rem; font-size: 0.8rem; color: var(--text2); text-align: center; }
|
|
108
|
+
</style>
|
|
109
|
+
</head>
|
|
110
|
+
<body>
|
|
111
|
+
<h1>Judges — Metrics Dashboard</h1>
|
|
112
|
+
<p style="color:var(--text2)">Period: ${metrics.period.from.slice(0, 10)} → ${metrics.period.to.slice(0, 10)}</p>
|
|
113
|
+
|
|
114
|
+
<h2>Overview</h2>
|
|
115
|
+
<div class="grid">
|
|
116
|
+
<div class="card">
|
|
117
|
+
<div class="label">Total Findings</div>
|
|
118
|
+
<div class="value">${metrics.findings.totalDetected}</div>
|
|
119
|
+
<div class="sub">${metrics.findings.totalOpen} open · ${metrics.findings.totalFixed} fixed</div>
|
|
120
|
+
</div>
|
|
121
|
+
<div class="card">
|
|
122
|
+
<div class="label">Fix Rate</div>
|
|
123
|
+
<div class="value ${metrics.findings.fixRate >= 0.7 ? "green" : metrics.findings.fixRate >= 0.4 ? "yellow" : "red"}">${(metrics.findings.fixRate * 100).toFixed(1)}%</div>
|
|
124
|
+
<div class="sub">${metrics.findings.totalFixed} of ${metrics.findings.totalDetected} resolved</div>
|
|
125
|
+
</div>
|
|
126
|
+
<div class="card">
|
|
127
|
+
<div class="label">Time Saved</div>
|
|
128
|
+
<div class="value green">~${metrics.timeSaved.estimatedHours}h</div>
|
|
129
|
+
<div class="sub">${metrics.timeSaved.estimatedMinutes} minutes total</div>
|
|
130
|
+
</div>
|
|
131
|
+
<div class="card">
|
|
132
|
+
<div class="label">Trend</div>
|
|
133
|
+
<div class="value">
|
|
134
|
+
<span class="trend-arrow ${metrics.trend.direction === "improving" ? "green" : metrics.trend.direction === "degrading" ? "red" : "yellow"}">${metrics.trend.direction === "improving" ? "↗" : metrics.trend.direction === "degrading" ? "↘" : "→"}</span>
|
|
135
|
+
</div>
|
|
136
|
+
<div class="sub">${metrics.trend.direction} — ${metrics.trend.newFindingsPerRun.toFixed(1)} new / ${metrics.trend.fixedFindingsPerRun.toFixed(1)} fixed per run</div>
|
|
137
|
+
</div>
|
|
138
|
+
<div class="card">
|
|
139
|
+
<div class="label">False Positives</div>
|
|
140
|
+
<div class="value ${metrics.findings.totalFalsePositive === 0 ? "green" : "yellow"}">${metrics.findings.totalFalsePositive}</div>
|
|
141
|
+
<div class="sub">${metrics.findings.totalAcceptedRisk} accepted risk</div>
|
|
142
|
+
</div>
|
|
143
|
+
<div class="card">
|
|
144
|
+
<div class="label">Feedback Entries</div>
|
|
145
|
+
<div class="value">${feedback.entries.length}</div>
|
|
146
|
+
<div class="sub">Used for calibration</div>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<h2>Severity Distribution</h2>
|
|
151
|
+
<div class="chart-container">
|
|
152
|
+
${renderSeverityBars(metrics)}
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<h2>Time Saved Breakdown</h2>
|
|
156
|
+
<div class="chart-container">
|
|
157
|
+
<table>
|
|
158
|
+
<thead><tr><th>Category</th><th>Count</th><th>Per Item</th><th>Total</th></tr></thead>
|
|
159
|
+
<tbody>${metrics.timeSaved.breakdown.map((b) => `<tr><td>${escapeHtml(b.category)}</td><td>${b.count}</td><td>${b.minutesPerItem} min</td><td>${b.totalMinutes} min</td></tr>`).join("")}</tbody>
|
|
160
|
+
</table>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
${snapshots.snapshots.length > 1
|
|
164
|
+
? `
|
|
165
|
+
<h2>Findings Over Time</h2>
|
|
166
|
+
<div class="chart-container">
|
|
167
|
+
${renderTrendChart(snapshots.snapshots)}
|
|
168
|
+
</div>`
|
|
169
|
+
: ""}
|
|
170
|
+
|
|
171
|
+
${feedback.entries.length > 0
|
|
172
|
+
? `
|
|
173
|
+
<h2>Feedback Activity</h2>
|
|
174
|
+
<div class="chart-container">
|
|
175
|
+
${renderFeedbackChart(feedback)}
|
|
176
|
+
</div>`
|
|
177
|
+
: ""}
|
|
178
|
+
|
|
179
|
+
<footer>
|
|
180
|
+
Generated by Judges Panel · ${new Date().toISOString().slice(0, 10)} ·
|
|
181
|
+
Data sourced from local project files — judges never hosts or processes your data
|
|
182
|
+
</footer>
|
|
183
|
+
</body>
|
|
184
|
+
</html>`;
|
|
185
|
+
}
|
|
186
|
+
// ─── Chart Renderers ────────────────────────────────────────────────────────
|
|
187
|
+
function renderSeverityBars(metrics) {
|
|
188
|
+
const severities = ["critical", "high", "medium", "low", "info"];
|
|
189
|
+
const colors = {
|
|
190
|
+
critical: "var(--red)",
|
|
191
|
+
high: "var(--orange)",
|
|
192
|
+
medium: "var(--yellow)",
|
|
193
|
+
low: "var(--accent)",
|
|
194
|
+
info: "var(--text2)",
|
|
195
|
+
};
|
|
196
|
+
const max = Math.max(1, ...severities.map((s) => metrics.findings.bySeverity[s]?.detected ?? 0));
|
|
197
|
+
return `<table>
|
|
198
|
+
<thead><tr><th>Severity</th><th>Detected</th><th>Fixed</th><th></th></tr></thead>
|
|
199
|
+
<tbody>${severities
|
|
200
|
+
.map((s) => {
|
|
201
|
+
const d = metrics.findings.bySeverity[s]?.detected ?? 0;
|
|
202
|
+
const f = metrics.findings.bySeverity[s]?.fixed ?? 0;
|
|
203
|
+
const pct = max > 0 ? (d / max) * 100 : 0;
|
|
204
|
+
return `<tr><td style="text-transform:capitalize">${s}</td><td>${d}</td><td>${f}</td><td><span class="bar" style="width:${pct}%;background:${colors[s]}"></span></td></tr>`;
|
|
205
|
+
})
|
|
206
|
+
.join("")}</tbody>
|
|
207
|
+
</table>`;
|
|
208
|
+
}
|
|
209
|
+
function renderTrendChart(snapshots) {
|
|
210
|
+
const pts = snapshots.slice(-30);
|
|
211
|
+
if (pts.length < 2)
|
|
212
|
+
return "<p>Not enough data points for trend chart.</p>";
|
|
213
|
+
const maxFindings = Math.max(1, ...pts.map((p) => p.totalFindings));
|
|
214
|
+
const w = 800;
|
|
215
|
+
const h = 200;
|
|
216
|
+
const pad = 40;
|
|
217
|
+
const xStep = (w - 2 * pad) / (pts.length - 1);
|
|
218
|
+
const points = pts.map((p, i) => ({
|
|
219
|
+
x: pad + i * xStep,
|
|
220
|
+
y: h - pad - (p.totalFindings / maxFindings) * (h - 2 * pad),
|
|
221
|
+
}));
|
|
222
|
+
const pathD = points.map((p, i) => `${i === 0 ? "M" : "L"} ${p.x.toFixed(1)} ${p.y.toFixed(1)}`).join(" ");
|
|
223
|
+
return `<svg viewBox="0 0 ${w} ${h}" preserveAspectRatio="xMidYMid meet">
|
|
224
|
+
<line x1="${pad}" y1="${h - pad}" x2="${w - pad}" y2="${h - pad}" stroke="var(--border)" />
|
|
225
|
+
<line x1="${pad}" y1="${pad}" x2="${pad}" y2="${h - pad}" stroke="var(--border)" />
|
|
226
|
+
<text x="${pad - 5}" y="${pad + 4}" fill="var(--text2)" font-size="11" text-anchor="end">${maxFindings}</text>
|
|
227
|
+
<text x="${pad - 5}" y="${h - pad + 4}" fill="var(--text2)" font-size="11" text-anchor="end">0</text>
|
|
228
|
+
<text x="${pad}" y="${h - pad + 16}" fill="var(--text2)" font-size="10">${pts[0].timestamp.slice(0, 10)}</text>
|
|
229
|
+
<text x="${w - pad}" y="${h - pad + 16}" fill="var(--text2)" font-size="10" text-anchor="end">${pts[pts.length - 1].timestamp.slice(0, 10)}</text>
|
|
230
|
+
<path d="${pathD}" fill="none" stroke="var(--accent)" stroke-width="2" />
|
|
231
|
+
${points.map((p, i) => `<circle cx="${p.x.toFixed(1)}" cy="${p.y.toFixed(1)}" r="3" fill="var(--accent)"><title>${pts[i].timestamp.slice(0, 10)}: ${pts[i].totalFindings} findings</title></circle>`).join("")}
|
|
232
|
+
</svg>
|
|
233
|
+
<div class="legend"><span style="color:var(--accent)">● Total findings over time (last ${pts.length} snapshots)</span></div>`;
|
|
234
|
+
}
|
|
235
|
+
function renderFeedbackChart(feedback) {
|
|
236
|
+
const weeks = aggregateFeedbackByWeek(feedback);
|
|
237
|
+
if (weeks.length === 0)
|
|
238
|
+
return "<p>No feedback data.</p>";
|
|
239
|
+
const max = Math.max(1, ...weeks.map((w) => w.tp + w.fp + w.wontfix));
|
|
240
|
+
const w = 800;
|
|
241
|
+
const h = 180;
|
|
242
|
+
const pad = 40;
|
|
243
|
+
const barWidth = Math.max(8, Math.min(40, (w - 2 * pad) / weeks.length - 4));
|
|
244
|
+
return `<svg viewBox="0 0 ${w} ${h}" preserveAspectRatio="xMidYMid meet">
|
|
245
|
+
<line x1="${pad}" y1="${h - pad}" x2="${w - pad}" y2="${h - pad}" stroke="var(--border)" />
|
|
246
|
+
${weeks
|
|
247
|
+
.map((wk, i) => {
|
|
248
|
+
const x = pad + i * ((w - 2 * pad) / weeks.length) + 2;
|
|
249
|
+
const totalH = ((wk.tp + wk.fp + wk.wontfix) / max) * (h - 2 * pad);
|
|
250
|
+
const tpH = (wk.tp / max) * (h - 2 * pad);
|
|
251
|
+
const fpH = (wk.fp / max) * (h - 2 * pad);
|
|
252
|
+
const wfH = (wk.wontfix / max) * (h - 2 * pad);
|
|
253
|
+
const baseY = h - pad;
|
|
254
|
+
return `
|
|
255
|
+
<rect x="${x}" y="${baseY - tpH}" width="${barWidth}" height="${tpH}" fill="var(--green)" rx="2"><title>Week ${wk.week}: ${wk.tp} TP</title></rect>
|
|
256
|
+
<rect x="${x}" y="${baseY - tpH - fpH}" width="${barWidth}" height="${fpH}" fill="var(--red)" rx="2"><title>Week ${wk.week}: ${wk.fp} FP</title></rect>
|
|
257
|
+
<rect x="${x}" y="${baseY - totalH}" width="${barWidth}" height="${wfH}" fill="var(--yellow)" rx="2"><title>Week ${wk.week}: ${wk.wontfix} Won't Fix</title></rect>`;
|
|
258
|
+
})
|
|
259
|
+
.join("")}
|
|
260
|
+
</svg>
|
|
261
|
+
<div class="legend">
|
|
262
|
+
<span style="color:var(--green)">● True Positive</span>
|
|
263
|
+
<span style="color:var(--red)">● False Positive</span>
|
|
264
|
+
<span style="color:var(--yellow)">● Won't Fix</span>
|
|
265
|
+
</div>`;
|
|
266
|
+
}
|
|
267
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
268
|
+
function escapeHtml(s) {
|
|
269
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
270
|
+
}
|
|
271
|
+
function aggregateFeedbackByWeek(feedback) {
|
|
272
|
+
const buckets = new Map();
|
|
273
|
+
for (const e of feedback.entries) {
|
|
274
|
+
const d = new Date(e.timestamp);
|
|
275
|
+
const weekStart = new Date(d);
|
|
276
|
+
weekStart.setDate(d.getDate() - d.getDay());
|
|
277
|
+
const key = weekStart.toISOString().slice(0, 10);
|
|
278
|
+
const b = buckets.get(key) ?? { week: key, tp: 0, fp: 0, wontfix: 0 };
|
|
279
|
+
if (e.verdict === "tp")
|
|
280
|
+
b.tp++;
|
|
281
|
+
else if (e.verdict === "fp")
|
|
282
|
+
b.fp++;
|
|
283
|
+
else
|
|
284
|
+
b.wontfix++;
|
|
285
|
+
buckets.set(key, b);
|
|
286
|
+
}
|
|
287
|
+
return [...buckets.values()].sort((a, b) => a.week.localeCompare(b.week)).slice(-12);
|
|
288
|
+
}
|
|
289
|
+
// ─── CLI Entry ──────────────────────────────────────────────────────────────
|
|
290
|
+
export function runMetricsDashboard(argv) {
|
|
291
|
+
let format = "html";
|
|
292
|
+
let outputPath;
|
|
293
|
+
let dir = ".";
|
|
294
|
+
for (let i = 0; i < argv.length; i++) {
|
|
295
|
+
const arg = argv[i];
|
|
296
|
+
if (arg === "--format" && argv[i + 1])
|
|
297
|
+
format = argv[++i];
|
|
298
|
+
else if ((arg === "--output" || arg === "-o") && argv[i + 1])
|
|
299
|
+
outputPath = argv[++i];
|
|
300
|
+
else if (arg === "--dir" && argv[i + 1])
|
|
301
|
+
dir = argv[++i];
|
|
302
|
+
else if (arg === "--help" || arg === "-h") {
|
|
303
|
+
console.log(`
|
|
304
|
+
judges metrics-dashboard — Interactive HTML metrics dashboard
|
|
305
|
+
|
|
306
|
+
Usage:
|
|
307
|
+
judges metrics-dashboard Output HTML to stdout
|
|
308
|
+
judges metrics-dashboard -o report.html Write to file
|
|
309
|
+
judges metrics-dashboard --format json Raw metrics data
|
|
310
|
+
judges metrics-dashboard --dir ./project Target project directory
|
|
311
|
+
|
|
312
|
+
Options:
|
|
313
|
+
--format <fmt> Output format: html (default), json
|
|
314
|
+
--output, -o Write to file instead of stdout
|
|
315
|
+
--dir <path> Project directory (default: cwd)
|
|
316
|
+
-h, --help Show this help
|
|
317
|
+
`);
|
|
318
|
+
process.exit(0);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (format === "json") {
|
|
322
|
+
const metrics = computeMetrics(resolve(dir));
|
|
323
|
+
console.log(JSON.stringify(metrics, null, 2));
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const html = renderMetricsDashboardHtml(dir);
|
|
327
|
+
if (outputPath) {
|
|
328
|
+
writeFileSync(outputPath, html, "utf-8");
|
|
329
|
+
console.log(` ✅ Metrics dashboard written to ${outputPath}`);
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
console.log(html);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
//# sourceMappingURL=metrics-dashboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics-dashboard.js","sourceRoot":"","sources":["../../src/commands/metrics-dashboard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `judges metrics` — Local-only ROI metrics.
|
|
3
|
+
*
|
|
4
|
+
* Computes time-saved, defect-catch, and adoption statistics from
|
|
5
|
+
* local finding lifecycle data and feedback stores. No data leaves
|
|
6
|
+
* the developer's machine.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* judges metrics # summary to stdout
|
|
10
|
+
* judges metrics --format json # machine-readable
|
|
11
|
+
* judges metrics --since 30d # last 30 days
|
|
12
|
+
*/
|
|
13
|
+
export interface RoiMetrics {
|
|
14
|
+
/** Period covered by the metrics */
|
|
15
|
+
period: {
|
|
16
|
+
from: string;
|
|
17
|
+
to: string;
|
|
18
|
+
};
|
|
19
|
+
/** Finding statistics */
|
|
20
|
+
findings: {
|
|
21
|
+
totalDetected: number;
|
|
22
|
+
totalFixed: number;
|
|
23
|
+
totalOpen: number;
|
|
24
|
+
totalAcceptedRisk: number;
|
|
25
|
+
totalFalsePositive: number;
|
|
26
|
+
fixRate: number;
|
|
27
|
+
bySeverity: Record<string, {
|
|
28
|
+
detected: number;
|
|
29
|
+
fixed: number;
|
|
30
|
+
}>;
|
|
31
|
+
};
|
|
32
|
+
/** Auto-fix statistics */
|
|
33
|
+
autoFix: {
|
|
34
|
+
available: number;
|
|
35
|
+
applied: number;
|
|
36
|
+
adoptionRate: number;
|
|
37
|
+
};
|
|
38
|
+
/** Estimated time savings */
|
|
39
|
+
timeSaved: {
|
|
40
|
+
estimatedMinutes: number;
|
|
41
|
+
estimatedHours: number;
|
|
42
|
+
breakdown: {
|
|
43
|
+
category: string;
|
|
44
|
+
count: number;
|
|
45
|
+
minutesPerItem: number;
|
|
46
|
+
totalMinutes: number;
|
|
47
|
+
}[];
|
|
48
|
+
};
|
|
49
|
+
/** Trend over the period */
|
|
50
|
+
trend: {
|
|
51
|
+
direction: "improving" | "stable" | "degrading";
|
|
52
|
+
newFindingsPerRun: number;
|
|
53
|
+
fixedFindingsPerRun: number;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export declare function computeMetrics(dir: string, sinceDays?: number): RoiMetrics;
|
|
57
|
+
export declare function runMetrics(argv: string[]): void;
|
|
58
|
+
//# sourceMappingURL=metrics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/commands/metrics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AASH,MAAM,WAAW,UAAU;IACzB,oCAAoC;IACpC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IACrC,yBAAyB;IACzB,QAAQ,EAAE;QACR,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KACjE,CAAC;IACF,0BAA0B;IAC1B,OAAO,EAAE;QACP,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,6BAA6B;IAC7B,SAAS,EAAE;QACT,gBAAgB,EAAE,MAAM,CAAC;QACzB,cAAc,EAAE,MAAM,CAAC;QACvB,SAAS,EAAE;YACT,QAAQ,EAAE,MAAM,CAAC;YACjB,KAAK,EAAE,MAAM,CAAC;YACd,cAAc,EAAE,MAAM,CAAC;YACvB,YAAY,EAAE,MAAM,CAAC;SACtB,EAAE,CAAC;KACL,CAAC;IACF,4BAA4B;IAC5B,KAAK,EAAE;QACL,SAAS,EAAE,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;QAChD,iBAAiB,EAAE,MAAM,CAAC;QAC1B,mBAAmB,EAAE,MAAM,CAAC;KAC7B,CAAC;CACH;AA2DD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,UAAU,CA8G1E;AAiDD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAsC/C"}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `judges metrics` — Local-only ROI metrics.
|
|
3
|
+
*
|
|
4
|
+
* Computes time-saved, defect-catch, and adoption statistics from
|
|
5
|
+
* local finding lifecycle data and feedback stores. No data leaves
|
|
6
|
+
* the developer's machine.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* judges metrics # summary to stdout
|
|
10
|
+
* judges metrics --format json # machine-readable
|
|
11
|
+
* judges metrics --since 30d # last 30 days
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, readFileSync } from "fs";
|
|
14
|
+
import { resolve } from "path";
|
|
15
|
+
// ─── Time Estimates ─────────────────────────────────────────────────────────
|
|
16
|
+
// Conservative estimates for manual review time saved per finding category.
|
|
17
|
+
// Based on industry averages for manual code review discovery.
|
|
18
|
+
const MINUTES_PER_FINDING = {
|
|
19
|
+
critical: 120, // Critical vulns take ~2 hours to discover manually
|
|
20
|
+
high: 60, // High-severity issues ~1 hour
|
|
21
|
+
medium: 30, // Medium issues ~30 minutes
|
|
22
|
+
low: 15, // Low issues ~15 minutes
|
|
23
|
+
info: 5, // Informational ~5 minutes
|
|
24
|
+
};
|
|
25
|
+
const MINUTES_PER_AUTOFIX = 20; // Each auto-fix saves ~20 min of manual patching
|
|
26
|
+
// ─── Data Loading ───────────────────────────────────────────────────────────
|
|
27
|
+
function loadFindingStore(dir) {
|
|
28
|
+
const storePath = resolve(dir, ".judges-findings.json");
|
|
29
|
+
if (!existsSync(storePath))
|
|
30
|
+
return undefined;
|
|
31
|
+
try {
|
|
32
|
+
const raw = JSON.parse(readFileSync(storePath, "utf-8"));
|
|
33
|
+
return raw;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function _loadFeedbackEntries(dir) {
|
|
40
|
+
const feedbackPath = resolve(dir, ".judges-feedback.json");
|
|
41
|
+
if (!existsSync(feedbackPath))
|
|
42
|
+
return [];
|
|
43
|
+
try {
|
|
44
|
+
const raw = JSON.parse(readFileSync(feedbackPath, "utf-8"));
|
|
45
|
+
return (raw.entries ?? []);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// ─── Metrics Computation ────────────────────────────────────────────────────
|
|
52
|
+
function parseSinceDays(since) {
|
|
53
|
+
if (!since)
|
|
54
|
+
return undefined;
|
|
55
|
+
const m = /^(\d+)d$/.exec(since);
|
|
56
|
+
return m ? parseInt(m[1], 10) : undefined;
|
|
57
|
+
}
|
|
58
|
+
function isWithinPeriod(timestamp, cutoff) {
|
|
59
|
+
if (!cutoff)
|
|
60
|
+
return true;
|
|
61
|
+
return new Date(timestamp) >= cutoff;
|
|
62
|
+
}
|
|
63
|
+
export function computeMetrics(dir, sinceDays) {
|
|
64
|
+
const store = loadFindingStore(dir);
|
|
65
|
+
const findings = store?.findings ?? [];
|
|
66
|
+
const now = new Date();
|
|
67
|
+
const cutoff = sinceDays ? new Date(now.getTime() - sinceDays * 86400000) : undefined;
|
|
68
|
+
// Filter by period
|
|
69
|
+
const relevant = cutoff
|
|
70
|
+
? findings.filter((f) => isWithinPeriod(f.lastSeen, cutoff) || isWithinPeriod(f.firstSeen, cutoff))
|
|
71
|
+
: findings;
|
|
72
|
+
const from = cutoff?.toISOString() ??
|
|
73
|
+
(relevant.length > 0
|
|
74
|
+
? relevant.reduce((a, b) => (a.firstSeen < b.firstSeen ? a : b)).firstSeen
|
|
75
|
+
: now.toISOString());
|
|
76
|
+
const to = now.toISOString();
|
|
77
|
+
// Categorize
|
|
78
|
+
const fixed = relevant.filter((f) => f.status === "fixed");
|
|
79
|
+
const open = relevant.filter((f) => f.status === "open");
|
|
80
|
+
const acceptedRisk = relevant.filter((f) => f.status === "accepted-risk" || f.status === "wont-fix" || f.status === "deferred");
|
|
81
|
+
const falsePositive = relevant.filter((f) => f.status === "false-positive");
|
|
82
|
+
// By severity
|
|
83
|
+
const bySeverity = {};
|
|
84
|
+
for (const f of relevant) {
|
|
85
|
+
if (!bySeverity[f.severity])
|
|
86
|
+
bySeverity[f.severity] = { detected: 0, fixed: 0 };
|
|
87
|
+
bySeverity[f.severity].detected++;
|
|
88
|
+
if (f.status === "fixed")
|
|
89
|
+
bySeverity[f.severity].fixed++;
|
|
90
|
+
}
|
|
91
|
+
// Auto-fix estimates: findings with fixes available approximate to findings with patches
|
|
92
|
+
// We count findings that were fixed quickly (< 1 day from first seen) as likely auto-fixed
|
|
93
|
+
const quickFixes = fixed.filter((f) => {
|
|
94
|
+
if (!f.fixedAt)
|
|
95
|
+
return false;
|
|
96
|
+
const openDuration = new Date(f.fixedAt).getTime() - new Date(f.firstSeen).getTime();
|
|
97
|
+
return openDuration < 86400000; // Fixed within 24 hours
|
|
98
|
+
});
|
|
99
|
+
// Time saved breakdown
|
|
100
|
+
const breakdown = [];
|
|
101
|
+
for (const sev of ["critical", "high", "medium", "low", "info"]) {
|
|
102
|
+
const count = bySeverity[sev]?.detected ?? 0;
|
|
103
|
+
if (count > 0) {
|
|
104
|
+
breakdown.push({
|
|
105
|
+
category: `${sev} findings detected`,
|
|
106
|
+
count,
|
|
107
|
+
minutesPerItem: MINUTES_PER_FINDING[sev],
|
|
108
|
+
totalMinutes: count * MINUTES_PER_FINDING[sev],
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (quickFixes.length > 0) {
|
|
113
|
+
breakdown.push({
|
|
114
|
+
category: "Auto-fixes applied",
|
|
115
|
+
count: quickFixes.length,
|
|
116
|
+
minutesPerItem: MINUTES_PER_AUTOFIX,
|
|
117
|
+
totalMinutes: quickFixes.length * MINUTES_PER_AUTOFIX,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
const totalMinutes = breakdown.reduce((sum, b) => sum + b.totalMinutes, 0);
|
|
121
|
+
// Trend: compare first half vs second half of findings by firstSeen
|
|
122
|
+
const sorted = [...relevant].sort((a, b) => a.firstSeen.localeCompare(b.firstSeen));
|
|
123
|
+
const mid = Math.floor(sorted.length / 2);
|
|
124
|
+
const firstHalfOpen = sorted.slice(0, mid).filter((f) => f.status === "open").length;
|
|
125
|
+
const secondHalfOpen = sorted.slice(mid).filter((f) => f.status === "open").length;
|
|
126
|
+
const trend = sorted.length < 4
|
|
127
|
+
? "stable"
|
|
128
|
+
: secondHalfOpen < firstHalfOpen * 0.8
|
|
129
|
+
? "improving"
|
|
130
|
+
: secondHalfOpen > firstHalfOpen * 1.2
|
|
131
|
+
? "degrading"
|
|
132
|
+
: "stable";
|
|
133
|
+
const runCount = store?.runNumber ?? 1;
|
|
134
|
+
return {
|
|
135
|
+
period: { from, to },
|
|
136
|
+
findings: {
|
|
137
|
+
totalDetected: relevant.length,
|
|
138
|
+
totalFixed: fixed.length,
|
|
139
|
+
totalOpen: open.length,
|
|
140
|
+
totalAcceptedRisk: acceptedRisk.length,
|
|
141
|
+
totalFalsePositive: falsePositive.length,
|
|
142
|
+
fixRate: relevant.length > 0 ? fixed.length / relevant.length : 0,
|
|
143
|
+
bySeverity,
|
|
144
|
+
},
|
|
145
|
+
autoFix: {
|
|
146
|
+
available: relevant.length, // All findings potentially have fixes
|
|
147
|
+
applied: quickFixes.length,
|
|
148
|
+
adoptionRate: relevant.length > 0 ? quickFixes.length / relevant.length : 0,
|
|
149
|
+
},
|
|
150
|
+
timeSaved: {
|
|
151
|
+
estimatedMinutes: totalMinutes,
|
|
152
|
+
estimatedHours: Math.round((totalMinutes / 60) * 10) / 10,
|
|
153
|
+
breakdown,
|
|
154
|
+
},
|
|
155
|
+
trend: {
|
|
156
|
+
direction: trend,
|
|
157
|
+
newFindingsPerRun: runCount > 0 ? relevant.length / runCount : 0,
|
|
158
|
+
fixedFindingsPerRun: runCount > 0 ? fixed.length / runCount : 0,
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
// ─── Formatters ─────────────────────────────────────────────────────────────
|
|
163
|
+
function formatMetricsText(m) {
|
|
164
|
+
const lines = [
|
|
165
|
+
"╔══════════════════════════════════════════════════════════════╗",
|
|
166
|
+
"║ Judges — ROI Metrics (Local Only) ║",
|
|
167
|
+
"╚══════════════════════════════════════════════════════════════╝",
|
|
168
|
+
"",
|
|
169
|
+
`Period: ${m.period.from.slice(0, 10)} → ${m.period.to.slice(0, 10)}`,
|
|
170
|
+
"",
|
|
171
|
+
"── Findings ────────────────────────────────────────────────────",
|
|
172
|
+
` Detected: ${m.findings.totalDetected}`,
|
|
173
|
+
` Fixed: ${m.findings.totalFixed}`,
|
|
174
|
+
` Open: ${m.findings.totalOpen}`,
|
|
175
|
+
` Accepted risk: ${m.findings.totalAcceptedRisk}`,
|
|
176
|
+
` False positive: ${m.findings.totalFalsePositive}`,
|
|
177
|
+
` Fix rate: ${(m.findings.fixRate * 100).toFixed(1)}%`,
|
|
178
|
+
"",
|
|
179
|
+
];
|
|
180
|
+
if (Object.keys(m.findings.bySeverity).length > 0) {
|
|
181
|
+
lines.push(" By severity:");
|
|
182
|
+
for (const [sev, s] of Object.entries(m.findings.bySeverity)) {
|
|
183
|
+
lines.push(` ${sev.padEnd(10)} ${s.detected} detected, ${s.fixed} fixed`);
|
|
184
|
+
}
|
|
185
|
+
lines.push("");
|
|
186
|
+
}
|
|
187
|
+
lines.push("── Time Saved (estimated) ──────────────────────────────────");
|
|
188
|
+
lines.push(` Total: ~${m.timeSaved.estimatedHours} hours`);
|
|
189
|
+
for (const b of m.timeSaved.breakdown) {
|
|
190
|
+
lines.push(` ${b.category}: ${b.count} × ${b.minutesPerItem}min = ${b.totalMinutes}min`);
|
|
191
|
+
}
|
|
192
|
+
lines.push("");
|
|
193
|
+
lines.push("── Trend ───────────────────────────────────────────────────");
|
|
194
|
+
const arrow = m.trend.direction === "improving" ? "↗" : m.trend.direction === "degrading" ? "↘" : "→";
|
|
195
|
+
lines.push(` Direction: ${arrow} ${m.trend.direction}`);
|
|
196
|
+
lines.push(` Findings/run: ${m.trend.newFindingsPerRun.toFixed(1)}`);
|
|
197
|
+
lines.push(` Fixed/run: ${m.trend.fixedFindingsPerRun.toFixed(1)}`);
|
|
198
|
+
lines.push("");
|
|
199
|
+
return lines.join("\n");
|
|
200
|
+
}
|
|
201
|
+
// ─── CLI Entry ──────────────────────────────────────────────────────────────
|
|
202
|
+
export function runMetrics(argv) {
|
|
203
|
+
let format = "text";
|
|
204
|
+
let since;
|
|
205
|
+
let dir = ".";
|
|
206
|
+
for (let i = 0; i < argv.length; i++) {
|
|
207
|
+
const arg = argv[i];
|
|
208
|
+
if (arg === "--format" && argv[i + 1])
|
|
209
|
+
format = argv[++i];
|
|
210
|
+
else if (arg === "--since" && argv[i + 1])
|
|
211
|
+
since = argv[++i];
|
|
212
|
+
else if (arg === "--dir" && argv[i + 1])
|
|
213
|
+
dir = argv[++i];
|
|
214
|
+
else if (arg === "--help" || arg === "-h") {
|
|
215
|
+
console.log(`
|
|
216
|
+
judges metrics — Local-only ROI metrics
|
|
217
|
+
|
|
218
|
+
Usage:
|
|
219
|
+
judges metrics Summary to stdout
|
|
220
|
+
judges metrics --format json Machine-readable output
|
|
221
|
+
judges metrics --since 30d Last 30 days only
|
|
222
|
+
judges metrics --dir ./project Target project directory
|
|
223
|
+
|
|
224
|
+
Options:
|
|
225
|
+
--format <fmt> Output format: text (default), json
|
|
226
|
+
--since <days> Time window, e.g. 30d, 90d
|
|
227
|
+
--dir <path> Project directory (default: cwd)
|
|
228
|
+
-h, --help Show this help
|
|
229
|
+
`);
|
|
230
|
+
process.exit(0);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const sinceDays = parseSinceDays(since);
|
|
234
|
+
const metrics = computeMetrics(resolve(dir), sinceDays);
|
|
235
|
+
if (format === "json") {
|
|
236
|
+
console.log(JSON.stringify(metrics, null, 2));
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
console.log(formatMetricsText(metrics));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
//# sourceMappingURL=metrics.js.map
|