@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.
Files changed (331) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +5 -4
  3. package/dist/api.d.ts +5 -2
  4. package/dist/api.d.ts.map +1 -1
  5. package/dist/api.js +5 -1
  6. package/dist/api.js.map +1 -1
  7. package/dist/ast/structural-parser.js +3 -3
  8. package/dist/ast/structural-parser.js.map +1 -1
  9. package/dist/calibration.d.ts +35 -0
  10. package/dist/calibration.d.ts.map +1 -1
  11. package/dist/calibration.js +52 -0
  12. package/dist/calibration.js.map +1 -1
  13. package/dist/cli.d.ts.map +1 -1
  14. package/dist/cli.js +307 -16
  15. package/dist/cli.js.map +1 -1
  16. package/dist/commands/benchmark-languages.js +4 -4
  17. package/dist/commands/benchmark.d.ts +2 -1
  18. package/dist/commands/benchmark.d.ts.map +1 -1
  19. package/dist/commands/benchmark.js +67 -2
  20. package/dist/commands/benchmark.js.map +1 -1
  21. package/dist/commands/calibration-dashboard.d.ts.map +1 -1
  22. package/dist/commands/calibration-dashboard.js +198 -0
  23. package/dist/commands/calibration-dashboard.js.map +1 -1
  24. package/dist/commands/calibration-share.d.ts +31 -0
  25. package/dist/commands/calibration-share.d.ts.map +1 -0
  26. package/dist/commands/calibration-share.js +183 -0
  27. package/dist/commands/calibration-share.js.map +1 -0
  28. package/dist/commands/compliance-report.d.ts +35 -0
  29. package/dist/commands/compliance-report.d.ts.map +1 -0
  30. package/dist/commands/compliance-report.js +162 -0
  31. package/dist/commands/compliance-report.js.map +1 -0
  32. package/dist/commands/diff.d.ts.map +1 -1
  33. package/dist/commands/diff.js +8 -3
  34. package/dist/commands/diff.js.map +1 -1
  35. package/dist/commands/feedback-rules.d.ts +29 -0
  36. package/dist/commands/feedback-rules.d.ts.map +1 -0
  37. package/dist/commands/feedback-rules.js +174 -0
  38. package/dist/commands/feedback-rules.js.map +1 -0
  39. package/dist/commands/feedback.d.ts +12 -0
  40. package/dist/commands/feedback.d.ts.map +1 -1
  41. package/dist/commands/feedback.js +16 -0
  42. package/dist/commands/feedback.js.map +1 -1
  43. package/dist/commands/fix.d.ts.map +1 -1
  44. package/dist/commands/fix.js +33 -1
  45. package/dist/commands/fix.js.map +1 -1
  46. package/dist/commands/governance.d.ts +32 -0
  47. package/dist/commands/governance.d.ts.map +1 -0
  48. package/dist/commands/governance.js +203 -0
  49. package/dist/commands/governance.js.map +1 -0
  50. package/dist/commands/help.d.ts +8 -0
  51. package/dist/commands/help.d.ts.map +1 -0
  52. package/dist/commands/help.js +303 -0
  53. package/dist/commands/help.js.map +1 -0
  54. package/dist/commands/hook.d.ts.map +1 -1
  55. package/dist/commands/hook.js +17 -20
  56. package/dist/commands/hook.js.map +1 -1
  57. package/dist/commands/llm-benchmark.d.ts +119 -0
  58. package/dist/commands/llm-benchmark.d.ts.map +1 -0
  59. package/dist/commands/llm-benchmark.js +396 -0
  60. package/dist/commands/llm-benchmark.js.map +1 -0
  61. package/dist/commands/metrics-dashboard.d.ts +22 -0
  62. package/dist/commands/metrics-dashboard.d.ts.map +1 -0
  63. package/dist/commands/metrics-dashboard.js +335 -0
  64. package/dist/commands/metrics-dashboard.js.map +1 -0
  65. package/dist/commands/metrics.d.ts +58 -0
  66. package/dist/commands/metrics.d.ts.map +1 -0
  67. package/dist/commands/metrics.js +242 -0
  68. package/dist/commands/metrics.js.map +1 -0
  69. package/dist/commands/onboard.d.ts +13 -0
  70. package/dist/commands/onboard.d.ts.map +1 -0
  71. package/dist/commands/onboard.js +179 -0
  72. package/dist/commands/onboard.js.map +1 -0
  73. package/dist/commands/org-metrics.d.ts +24 -0
  74. package/dist/commands/org-metrics.d.ts.map +1 -0
  75. package/dist/commands/org-metrics.js +238 -0
  76. package/dist/commands/org-metrics.js.map +1 -0
  77. package/dist/commands/override.d.ts +62 -0
  78. package/dist/commands/override.d.ts.map +1 -0
  79. package/dist/commands/override.js +264 -0
  80. package/dist/commands/override.js.map +1 -0
  81. package/dist/commands/parity.d.ts +31 -0
  82. package/dist/commands/parity.d.ts.map +1 -0
  83. package/dist/commands/parity.js +213 -0
  84. package/dist/commands/parity.js.map +1 -0
  85. package/dist/commands/plugin-search.d.ts +40 -0
  86. package/dist/commands/plugin-search.d.ts.map +1 -0
  87. package/dist/commands/plugin-search.js +328 -0
  88. package/dist/commands/plugin-search.js.map +1 -0
  89. package/dist/commands/plugins.d.ts +13 -0
  90. package/dist/commands/plugins.d.ts.map +1 -0
  91. package/dist/commands/plugins.js +105 -0
  92. package/dist/commands/plugins.js.map +1 -0
  93. package/dist/commands/review.js +1 -1
  94. package/dist/commands/review.js.map +1 -1
  95. package/dist/commands/snapshot.d.ts +27 -0
  96. package/dist/commands/snapshot.d.ts.map +1 -1
  97. package/dist/commands/snapshot.js +99 -0
  98. package/dist/commands/snapshot.js.map +1 -1
  99. package/dist/commands/trace.d.ts +65 -0
  100. package/dist/commands/trace.d.ts.map +1 -0
  101. package/dist/commands/trace.js +246 -0
  102. package/dist/commands/trace.js.map +1 -0
  103. package/dist/commands/trust-ramp.d.ts +30 -0
  104. package/dist/commands/trust-ramp.d.ts.map +1 -0
  105. package/dist/commands/trust-ramp.js +190 -0
  106. package/dist/commands/trust-ramp.js.map +1 -0
  107. package/dist/config.d.ts +5 -0
  108. package/dist/config.d.ts.map +1 -1
  109. package/dist/config.js +65 -0
  110. package/dist/config.js.map +1 -1
  111. package/dist/data-adapter.d.ts +124 -0
  112. package/dist/data-adapter.d.ts.map +1 -0
  113. package/dist/data-adapter.js +213 -0
  114. package/dist/data-adapter.js.map +1 -0
  115. package/dist/evaluators/accessibility.js +1 -1
  116. package/dist/evaluators/accessibility.js.map +1 -1
  117. package/dist/evaluators/ai-code-safety.d.ts.map +1 -1
  118. package/dist/evaluators/ai-code-safety.js +1 -4
  119. package/dist/evaluators/ai-code-safety.js.map +1 -1
  120. package/dist/evaluators/cost-effectiveness.js +1 -1
  121. package/dist/evaluators/cost-effectiveness.js.map +1 -1
  122. package/dist/evaluators/false-positive-review.js +4 -4
  123. package/dist/evaluators/false-positive-review.js.map +1 -1
  124. package/dist/evaluators/iac-security.js +1 -1
  125. package/dist/evaluators/iac-security.js.map +1 -1
  126. package/dist/evaluators/index.d.ts.map +1 -1
  127. package/dist/evaluators/index.js +59 -10
  128. package/dist/evaluators/index.js.map +1 -1
  129. package/dist/evaluators/intent-alignment.d.ts +4 -0
  130. package/dist/evaluators/intent-alignment.d.ts.map +1 -1
  131. package/dist/evaluators/intent-alignment.js +163 -0
  132. package/dist/evaluators/intent-alignment.js.map +1 -1
  133. package/dist/evaluators/logic-review.js +1 -1
  134. package/dist/evaluators/logic-review.js.map +1 -1
  135. package/dist/evaluators/maintainability.js +1 -1
  136. package/dist/evaluators/maintainability.js.map +1 -1
  137. package/dist/evaluators/over-engineering.js +3 -3
  138. package/dist/evaluators/over-engineering.js.map +1 -1
  139. package/dist/evaluators/project.d.ts +12 -0
  140. package/dist/evaluators/project.d.ts.map +1 -1
  141. package/dist/evaluators/project.js +86 -0
  142. package/dist/evaluators/project.js.map +1 -1
  143. package/dist/evaluators/security.js +2 -2
  144. package/dist/evaluators/security.js.map +1 -1
  145. package/dist/evaluators/ux.js +1 -1
  146. package/dist/evaluators/ux.js.map +1 -1
  147. package/dist/finding-lifecycle.d.ts +9 -0
  148. package/dist/finding-lifecycle.d.ts.map +1 -1
  149. package/dist/finding-lifecycle.js +15 -0
  150. package/dist/finding-lifecycle.js.map +1 -1
  151. package/dist/fix-history.d.ts +9 -0
  152. package/dist/fix-history.d.ts.map +1 -1
  153. package/dist/fix-history.js +15 -0
  154. package/dist/fix-history.js.map +1 -1
  155. package/dist/formatters/sarif.d.ts +3 -0
  156. package/dist/formatters/sarif.d.ts.map +1 -1
  157. package/dist/formatters/sarif.js +36 -12
  158. package/dist/formatters/sarif.js.map +1 -1
  159. package/dist/github-app.d.ts +16 -1
  160. package/dist/github-app.d.ts.map +1 -1
  161. package/dist/github-app.js +85 -2
  162. package/dist/github-app.js.map +1 -1
  163. package/dist/index.js +5 -0
  164. package/dist/index.js.map +1 -1
  165. package/dist/judge-registry.d.ts +157 -0
  166. package/dist/judge-registry.d.ts.map +1 -0
  167. package/dist/judge-registry.js +273 -0
  168. package/dist/judge-registry.js.map +1 -0
  169. package/dist/judges/accessibility.d.ts.map +1 -1
  170. package/dist/judges/accessibility.js +4 -0
  171. package/dist/judges/accessibility.js.map +1 -1
  172. package/dist/judges/agent-instructions.d.ts.map +1 -1
  173. package/dist/judges/agent-instructions.js +4 -0
  174. package/dist/judges/agent-instructions.js.map +1 -1
  175. package/dist/judges/ai-code-safety.d.ts.map +1 -1
  176. package/dist/judges/ai-code-safety.js +4 -0
  177. package/dist/judges/ai-code-safety.js.map +1 -1
  178. package/dist/judges/api-contract.d.ts.map +1 -1
  179. package/dist/judges/api-contract.js +4 -0
  180. package/dist/judges/api-contract.js.map +1 -1
  181. package/dist/judges/api-design.d.ts.map +1 -1
  182. package/dist/judges/api-design.js +4 -0
  183. package/dist/judges/api-design.js.map +1 -1
  184. package/dist/judges/authentication.d.ts.map +1 -1
  185. package/dist/judges/authentication.js +4 -0
  186. package/dist/judges/authentication.js.map +1 -1
  187. package/dist/judges/backwards-compatibility.d.ts.map +1 -1
  188. package/dist/judges/backwards-compatibility.js +4 -0
  189. package/dist/judges/backwards-compatibility.js.map +1 -1
  190. package/dist/judges/caching.d.ts.map +1 -1
  191. package/dist/judges/caching.js +4 -0
  192. package/dist/judges/caching.js.map +1 -1
  193. package/dist/judges/ci-cd.d.ts.map +1 -1
  194. package/dist/judges/ci-cd.js +4 -0
  195. package/dist/judges/ci-cd.js.map +1 -1
  196. package/dist/judges/cloud-readiness.d.ts.map +1 -1
  197. package/dist/judges/cloud-readiness.js +4 -0
  198. package/dist/judges/cloud-readiness.js.map +1 -1
  199. package/dist/judges/code-structure.d.ts.map +1 -1
  200. package/dist/judges/code-structure.js +4 -0
  201. package/dist/judges/code-structure.js.map +1 -1
  202. package/dist/judges/compliance.d.ts.map +1 -1
  203. package/dist/judges/compliance.js +4 -0
  204. package/dist/judges/compliance.js.map +1 -1
  205. package/dist/judges/concurrency.d.ts.map +1 -1
  206. package/dist/judges/concurrency.js +4 -0
  207. package/dist/judges/concurrency.js.map +1 -1
  208. package/dist/judges/configuration-management.d.ts.map +1 -1
  209. package/dist/judges/configuration-management.js +4 -0
  210. package/dist/judges/configuration-management.js.map +1 -1
  211. package/dist/judges/cost-effectiveness.d.ts.map +1 -1
  212. package/dist/judges/cost-effectiveness.js +4 -0
  213. package/dist/judges/cost-effectiveness.js.map +1 -1
  214. package/dist/judges/cybersecurity.d.ts.map +1 -1
  215. package/dist/judges/cybersecurity.js +4 -0
  216. package/dist/judges/cybersecurity.js.map +1 -1
  217. package/dist/judges/data-security.d.ts.map +1 -1
  218. package/dist/judges/data-security.js +4 -0
  219. package/dist/judges/data-security.js.map +1 -1
  220. package/dist/judges/data-sovereignty.d.ts.map +1 -1
  221. package/dist/judges/data-sovereignty.js +4 -0
  222. package/dist/judges/data-sovereignty.js.map +1 -1
  223. package/dist/judges/database.d.ts.map +1 -1
  224. package/dist/judges/database.js +4 -0
  225. package/dist/judges/database.js.map +1 -1
  226. package/dist/judges/dependency-health.d.ts.map +1 -1
  227. package/dist/judges/dependency-health.js +4 -0
  228. package/dist/judges/dependency-health.js.map +1 -1
  229. package/dist/judges/documentation.d.ts.map +1 -1
  230. package/dist/judges/documentation.js +4 -0
  231. package/dist/judges/documentation.js.map +1 -1
  232. package/dist/judges/error-handling.d.ts.map +1 -1
  233. package/dist/judges/error-handling.js +4 -0
  234. package/dist/judges/error-handling.js.map +1 -1
  235. package/dist/judges/ethics-bias.d.ts.map +1 -1
  236. package/dist/judges/ethics-bias.js +4 -0
  237. package/dist/judges/ethics-bias.js.map +1 -1
  238. package/dist/judges/false-positive-review.d.ts.map +1 -1
  239. package/dist/judges/false-positive-review.js +2 -0
  240. package/dist/judges/false-positive-review.js.map +1 -1
  241. package/dist/judges/framework-safety.d.ts.map +1 -1
  242. package/dist/judges/framework-safety.js +4 -0
  243. package/dist/judges/framework-safety.js.map +1 -1
  244. package/dist/judges/hallucination-detection.d.ts.map +1 -1
  245. package/dist/judges/hallucination-detection.js +4 -0
  246. package/dist/judges/hallucination-detection.js.map +1 -1
  247. package/dist/judges/iac-security.d.ts.map +1 -1
  248. package/dist/judges/iac-security.js +4 -0
  249. package/dist/judges/iac-security.js.map +1 -1
  250. package/dist/judges/index.d.ts +59 -0
  251. package/dist/judges/index.d.ts.map +1 -1
  252. package/dist/judges/index.js +65 -189
  253. package/dist/judges/index.js.map +1 -1
  254. package/dist/judges/intent-alignment.d.ts.map +1 -1
  255. package/dist/judges/intent-alignment.js +4 -0
  256. package/dist/judges/intent-alignment.js.map +1 -1
  257. package/dist/judges/internationalization.d.ts.map +1 -1
  258. package/dist/judges/internationalization.js +4 -0
  259. package/dist/judges/internationalization.js.map +1 -1
  260. package/dist/judges/logging-privacy.d.ts.map +1 -1
  261. package/dist/judges/logging-privacy.js +4 -0
  262. package/dist/judges/logging-privacy.js.map +1 -1
  263. package/dist/judges/logic-review.d.ts.map +1 -1
  264. package/dist/judges/logic-review.js +4 -0
  265. package/dist/judges/logic-review.js.map +1 -1
  266. package/dist/judges/maintainability.d.ts.map +1 -1
  267. package/dist/judges/maintainability.js +4 -0
  268. package/dist/judges/maintainability.js.map +1 -1
  269. package/dist/judges/model-fingerprint.d.ts.map +1 -1
  270. package/dist/judges/model-fingerprint.js +4 -0
  271. package/dist/judges/model-fingerprint.js.map +1 -1
  272. package/dist/judges/multi-turn-coherence.d.ts.map +1 -1
  273. package/dist/judges/multi-turn-coherence.js +4 -0
  274. package/dist/judges/multi-turn-coherence.js.map +1 -1
  275. package/dist/judges/observability.d.ts.map +1 -1
  276. package/dist/judges/observability.js +4 -0
  277. package/dist/judges/observability.js.map +1 -1
  278. package/dist/judges/over-engineering.d.ts.map +1 -1
  279. package/dist/judges/over-engineering.js +4 -0
  280. package/dist/judges/over-engineering.js.map +1 -1
  281. package/dist/judges/performance.d.ts.map +1 -1
  282. package/dist/judges/performance.js +4 -0
  283. package/dist/judges/performance.js.map +1 -1
  284. package/dist/judges/portability.d.ts.map +1 -1
  285. package/dist/judges/portability.js +4 -0
  286. package/dist/judges/portability.js.map +1 -1
  287. package/dist/judges/rate-limiting.d.ts.map +1 -1
  288. package/dist/judges/rate-limiting.js +4 -0
  289. package/dist/judges/rate-limiting.js.map +1 -1
  290. package/dist/judges/reliability.d.ts.map +1 -1
  291. package/dist/judges/reliability.js +4 -0
  292. package/dist/judges/reliability.js.map +1 -1
  293. package/dist/judges/scalability.d.ts.map +1 -1
  294. package/dist/judges/scalability.js +4 -0
  295. package/dist/judges/scalability.js.map +1 -1
  296. package/dist/judges/security.d.ts.map +1 -1
  297. package/dist/judges/security.js +4 -0
  298. package/dist/judges/security.js.map +1 -1
  299. package/dist/judges/software-practices.d.ts.map +1 -1
  300. package/dist/judges/software-practices.js +4 -0
  301. package/dist/judges/software-practices.js.map +1 -1
  302. package/dist/judges/testing.d.ts.map +1 -1
  303. package/dist/judges/testing.js +4 -0
  304. package/dist/judges/testing.js.map +1 -1
  305. package/dist/judges/ux.d.ts.map +1 -1
  306. package/dist/judges/ux.js +4 -0
  307. package/dist/judges/ux.js.map +1 -1
  308. package/dist/plugins.d.ts +8 -51
  309. package/dist/plugins.d.ts.map +1 -1
  310. package/dist/plugins.js +16 -125
  311. package/dist/plugins.js.map +1 -1
  312. package/dist/security-ids.d.ts +24 -0
  313. package/dist/security-ids.d.ts.map +1 -0
  314. package/dist/security-ids.js +240 -0
  315. package/dist/security-ids.js.map +1 -0
  316. package/dist/tools/prompts.d.ts +4 -0
  317. package/dist/tools/prompts.d.ts.map +1 -1
  318. package/dist/tools/prompts.js +6 -4
  319. package/dist/tools/prompts.js.map +1 -1
  320. package/dist/tools/register-scaffold.d.ts +3 -0
  321. package/dist/tools/register-scaffold.d.ts.map +1 -0
  322. package/dist/tools/register-scaffold.js +399 -0
  323. package/dist/tools/register-scaffold.js.map +1 -0
  324. package/dist/tools/register.d.ts +1 -1
  325. package/dist/tools/register.d.ts.map +1 -1
  326. package/dist/tools/register.js +3 -1
  327. package/dist/tools/register.js.map +1 -1
  328. package/dist/types.d.ts +75 -0
  329. package/dist/types.d.ts.map +1 -1
  330. package/package.json +3 -2
  331. 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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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