@kevinrabun/judges 3.111.0 → 3.112.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 (42) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +63 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/finding-fix-estimate.d.ts +2 -0
  6. package/dist/commands/finding-fix-estimate.d.ts.map +1 -0
  7. package/dist/commands/finding-fix-estimate.js +96 -0
  8. package/dist/commands/finding-fix-estimate.js.map +1 -0
  9. package/dist/commands/finding-noise-score.d.ts +2 -0
  10. package/dist/commands/finding-noise-score.d.ts.map +1 -0
  11. package/dist/commands/finding-noise-score.js +94 -0
  12. package/dist/commands/finding-noise-score.js.map +1 -0
  13. package/dist/commands/finding-repeat-detect.d.ts +2 -0
  14. package/dist/commands/finding-repeat-detect.d.ts.map +1 -0
  15. package/dist/commands/finding-repeat-detect.js +93 -0
  16. package/dist/commands/finding-repeat-detect.js.map +1 -0
  17. package/dist/commands/finding-scope-impact.d.ts +2 -0
  18. package/dist/commands/finding-scope-impact.d.ts.map +1 -0
  19. package/dist/commands/finding-scope-impact.js +84 -0
  20. package/dist/commands/finding-scope-impact.js.map +1 -0
  21. package/dist/commands/finding-top-offender.d.ts +2 -0
  22. package/dist/commands/finding-top-offender.d.ts.map +1 -0
  23. package/dist/commands/finding-top-offender.js +76 -0
  24. package/dist/commands/finding-top-offender.js.map +1 -0
  25. package/dist/commands/review-health-trend.d.ts +2 -0
  26. package/dist/commands/review-health-trend.d.ts.map +1 -0
  27. package/dist/commands/review-health-trend.js +108 -0
  28. package/dist/commands/review-health-trend.js.map +1 -0
  29. package/dist/commands/review-readiness-check.d.ts +2 -0
  30. package/dist/commands/review-readiness-check.d.ts.map +1 -0
  31. package/dist/commands/review-readiness-check.js +99 -0
  32. package/dist/commands/review-readiness-check.js.map +1 -0
  33. package/dist/commands/review-team-skill-map.d.ts +2 -0
  34. package/dist/commands/review-team-skill-map.d.ts.map +1 -0
  35. package/dist/commands/review-team-skill-map.js +103 -0
  36. package/dist/commands/review-team-skill-map.js.map +1 -0
  37. package/dist/commands/review-workflow-suggest.d.ts +2 -0
  38. package/dist/commands/review-workflow-suggest.d.ts.map +1 -0
  39. package/dist/commands/review-workflow-suggest.js +130 -0
  40. package/dist/commands/review-workflow-suggest.js.map +1 -0
  41. package/package.json +1 -1
  42. package/server.json +2 -2
@@ -0,0 +1,2 @@
1
+ export declare function runFindingFixEstimate(argv: string[]): void;
2
+ //# sourceMappingURL=finding-fix-estimate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding-fix-estimate.d.ts","sourceRoot":"","sources":["../../src/commands/finding-fix-estimate.ts"],"names":[],"mappings":"AAsDA,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CA2E1D"}
@@ -0,0 +1,96 @@
1
+ import { readFileSync, existsSync } from "fs";
2
+ import { join } from "path";
3
+ function estimateFixTime(finding) {
4
+ const hasPatch = finding.patch !== undefined && finding.patch !== null;
5
+ const recLength = finding.recommendation.length;
6
+ let baseMinutes;
7
+ if (finding.severity === "critical")
8
+ baseMinutes = 120;
9
+ else if (finding.severity === "high")
10
+ baseMinutes = 60;
11
+ else if (finding.severity === "medium")
12
+ baseMinutes = 30;
13
+ else if (finding.severity === "low")
14
+ baseMinutes = 15;
15
+ else
16
+ baseMinutes = 10;
17
+ if (hasPatch)
18
+ baseMinutes = Math.round(baseMinutes * 0.4);
19
+ if (recLength > 200)
20
+ baseMinutes = Math.round(baseMinutes * 1.2);
21
+ let label;
22
+ if (baseMinutes <= 10)
23
+ label = "trivial (~10 min)";
24
+ else if (baseMinutes <= 30)
25
+ label = "quick (~30 min)";
26
+ else if (baseMinutes <= 60)
27
+ label = "moderate (~1 hr)";
28
+ else if (baseMinutes <= 120)
29
+ label = "significant (~2 hr)";
30
+ else
31
+ label = "major (2+ hr)";
32
+ return { minutes: baseMinutes, label };
33
+ }
34
+ export function runFindingFixEstimate(argv) {
35
+ if (argv.includes("--help") || argv.includes("-h")) {
36
+ console.log(`Usage: judges finding-fix-estimate [options]
37
+
38
+ Estimate fix effort for each finding.
39
+
40
+ Options:
41
+ --report <path> Path to verdict JSON
42
+ --format <fmt> Output format: table (default) or json
43
+ -h, --help Show this help message`);
44
+ return;
45
+ }
46
+ const formatIdx = argv.indexOf("--format");
47
+ const format = formatIdx !== -1 && argv[formatIdx + 1] ? argv[formatIdx + 1] : "table";
48
+ const reportIdx = argv.indexOf("--report");
49
+ const reportPath = reportIdx !== -1 && argv[reportIdx + 1]
50
+ ? join(process.cwd(), argv[reportIdx + 1])
51
+ : join(process.cwd(), ".judges", "last-verdict.json");
52
+ if (!existsSync(reportPath)) {
53
+ console.log(`No report found at: ${reportPath}`);
54
+ return;
55
+ }
56
+ const data = JSON.parse(readFileSync(reportPath, "utf-8"));
57
+ const findings = data.findings ?? [];
58
+ const estimates = [];
59
+ let totalMinutes = 0;
60
+ for (const f of findings) {
61
+ const hasPatch = f.patch !== undefined && f.patch !== null;
62
+ const est = estimateFixTime(f);
63
+ totalMinutes += est.minutes;
64
+ estimates.push({
65
+ ruleId: f.ruleId,
66
+ title: f.title,
67
+ severity: f.severity,
68
+ hasPatch,
69
+ estimateMinutes: est.minutes,
70
+ estimateLabel: est.label,
71
+ recommendation: f.recommendation,
72
+ });
73
+ }
74
+ estimates.sort((a, b) => b.estimateMinutes - a.estimateMinutes);
75
+ const report = {
76
+ totalFindings: estimates.length,
77
+ totalMinutes,
78
+ totalHours: Math.round((totalMinutes / 60) * 10) / 10,
79
+ estimates,
80
+ };
81
+ if (format === "json") {
82
+ console.log(JSON.stringify(report, null, 2));
83
+ return;
84
+ }
85
+ console.log(`\n=== Fix Estimates (${report.totalFindings} findings, ~${report.totalHours} hours total) ===\n`);
86
+ if (estimates.length === 0) {
87
+ console.log("No findings to estimate.");
88
+ return;
89
+ }
90
+ for (const e of estimates) {
91
+ const patchTag = e.hasPatch ? " [patch]" : "";
92
+ console.log(` ${e.estimateLabel.padEnd(22)} [${e.severity}] ${e.ruleId}${patchTag}`);
93
+ console.log(` ${e.title}`);
94
+ }
95
+ }
96
+ //# sourceMappingURL=finding-fix-estimate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding-fix-estimate.js","sourceRoot":"","sources":["../../src/commands/finding-fix-estimate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function runFindingNoiseScore(argv: string[]): void;
2
+ //# sourceMappingURL=finding-noise-score.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding-noise-score.d.ts","sourceRoot":"","sources":["../../src/commands/finding-noise-score.ts"],"names":[],"mappings":"AA2DA,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAyEzD"}
@@ -0,0 +1,94 @@
1
+ import { readFileSync, existsSync } from "fs";
2
+ import { join } from "path";
3
+ function computeNoiseScore(finding) {
4
+ let score = 0;
5
+ const factors = [];
6
+ if (finding.confidence !== undefined && finding.confidence !== null && finding.confidence < 0.5) {
7
+ score += 30;
8
+ factors.push("low confidence");
9
+ }
10
+ if (finding.severity === "info") {
11
+ score += 20;
12
+ factors.push("info-level severity");
13
+ }
14
+ else if (finding.severity === "low") {
15
+ score += 10;
16
+ factors.push("low severity");
17
+ }
18
+ if (finding.patch === undefined || finding.patch === null) {
19
+ score += 15;
20
+ factors.push("no patch available");
21
+ }
22
+ if (finding.recommendation.length < 30) {
23
+ score += 15;
24
+ factors.push("vague recommendation");
25
+ }
26
+ if (factors.length === 0) {
27
+ factors.push("actionable finding");
28
+ }
29
+ return { score: Math.min(100, score), factors };
30
+ }
31
+ export function runFindingNoiseScore(argv) {
32
+ if (argv.includes("--help") || argv.includes("-h")) {
33
+ console.log(`Usage: judges finding-noise-score [options]
34
+
35
+ Score finding noise levels to identify low-signal findings.
36
+
37
+ Options:
38
+ --report <path> Path to verdict JSON
39
+ --threshold <n> Noise score threshold to flag (default: 40)
40
+ --format <fmt> Output format: table (default) or json
41
+ -h, --help Show this help message`);
42
+ return;
43
+ }
44
+ const formatIdx = argv.indexOf("--format");
45
+ const format = formatIdx !== -1 && argv[formatIdx + 1] ? argv[formatIdx + 1] : "table";
46
+ const threshIdx = argv.indexOf("--threshold");
47
+ const threshold = threshIdx !== -1 && argv[threshIdx + 1] ? parseInt(argv[threshIdx + 1], 10) : 40;
48
+ const reportIdx = argv.indexOf("--report");
49
+ const reportPath = reportIdx !== -1 && argv[reportIdx + 1]
50
+ ? join(process.cwd(), argv[reportIdx + 1])
51
+ : join(process.cwd(), ".judges", "last-verdict.json");
52
+ if (!existsSync(reportPath)) {
53
+ console.log(`No report found at: ${reportPath}`);
54
+ return;
55
+ }
56
+ const data = JSON.parse(readFileSync(reportPath, "utf-8"));
57
+ const findings = data.findings ?? [];
58
+ const entries = [];
59
+ for (const f of findings) {
60
+ const result = computeNoiseScore(f);
61
+ let noiseLevel;
62
+ if (result.score >= 60)
63
+ noiseLevel = "high noise";
64
+ else if (result.score >= 30)
65
+ noiseLevel = "moderate noise";
66
+ else
67
+ noiseLevel = "low noise";
68
+ entries.push({
69
+ ruleId: f.ruleId,
70
+ title: f.title,
71
+ severity: f.severity,
72
+ noiseScore: result.score,
73
+ noiseLevel,
74
+ factors: result.factors,
75
+ });
76
+ }
77
+ entries.sort((a, b) => b.noiseScore - a.noiseScore);
78
+ if (format === "json") {
79
+ console.log(JSON.stringify(entries, null, 2));
80
+ return;
81
+ }
82
+ const noisy = entries.filter((e) => e.noiseScore >= threshold);
83
+ console.log(`\n=== Noise Score (${noisy.length}/${entries.length} above threshold ${threshold}) ===\n`);
84
+ if (entries.length === 0) {
85
+ console.log("No findings to score.");
86
+ return;
87
+ }
88
+ for (const e of entries) {
89
+ const flag = e.noiseScore >= threshold ? " ⚠" : "";
90
+ console.log(` ${String(e.noiseScore).padStart(3)} ${e.noiseLevel.padEnd(16)} ${e.ruleId}${flag}`);
91
+ console.log(` ${e.factors.join(", ")}`);
92
+ }
93
+ }
94
+ //# sourceMappingURL=finding-noise-score.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding-noise-score.js","sourceRoot":"","sources":["../../src/commands/finding-noise-score.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAmB5B,SAAS,iBAAiB,CAAC,OAK1B;IACC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,IAAI,OAAO,CAAC,UAAU,KAAK,IAAI,IAAI,OAAO,CAAC,UAAU,GAAG,GAAG,EAAE,CAAC;QAChG,KAAK,IAAI,EAAE,CAAC;QACZ,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IACjC,CAAC;IAED,IAAI,OAAO,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;QAChC,KAAK,IAAI,EAAE,CAAC;QACZ,OAAO,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IACtC,CAAC;SAAM,IAAI,OAAO,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;QACtC,KAAK,IAAI,EAAE,CAAC;QACZ,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC/B,CAAC;IAED,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,OAAO,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;QAC1D,KAAK,IAAI,EAAE,CAAC;QACZ,OAAO,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;IACrC,CAAC;IAED,IAAI,OAAO,CAAC,cAAc,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QACvC,KAAK,IAAI,EAAE,CAAC;QACZ,OAAO,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IACvC,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;IACrC,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,IAAc;IACjD,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,OAAO,CAAC,GAAG,CAAC;;;;;;;;8CAQ8B,CAAC,CAAC;QAC5C,OAAO;IACT,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,SAAS,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAEvF,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAC9C,MAAM,SAAS,GAAG,SAAS,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAEnG,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3C,MAAM,UAAU,GACd,SAAS,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC;QACrC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;QAC1C,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,mBAAmB,CAAC,CAAC;IAE1D,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,OAAO,CAAC,GAAG,CAAC,uBAAuB,UAAU,EAAE,CAAC,CAAC;QACjD,OAAO;IACT,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAoB,CAAC;IAC9E,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;IAErC,MAAM,OAAO,GAAiB,EAAE,CAAC;IACjC,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC;QAEpC,IAAI,UAAkB,CAAC;QACvB,IAAI,MAAM,CAAC,KAAK,IAAI,EAAE;YAAE,UAAU,GAAG,YAAY,CAAC;aAC7C,IAAI,MAAM,CAAC,KAAK,IAAI,EAAE;YAAE,UAAU,GAAG,gBAAgB,CAAC;;YACtD,UAAU,GAAG,WAAW,CAAC;QAE9B,OAAO,CAAC,IAAI,CAAC;YACX,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,UAAU,EAAE,MAAM,CAAC,KAAK;YACxB,UAAU;YACV,OAAO,EAAE,MAAM,CAAC,OAAO;SACxB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC;IAEpD,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC9C,OAAO;IACT,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,IAAI,SAAS,CAAC,CAAC;IAC/D,OAAO,CAAC,GAAG,CAAC,sBAAsB,KAAK,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,oBAAoB,SAAS,SAAS,CAAC,CAAC;IAExG,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;QACrC,OAAO;IACT,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,CAAC,CAAC,UAAU,IAAI,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QACnD,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC,CAAC;QACpG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChD,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function runFindingRepeatDetect(argv: string[]): void;
2
+ //# sourceMappingURL=finding-repeat-detect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding-repeat-detect.d.ts","sourceRoot":"","sources":["../../src/commands/finding-repeat-detect.ts"],"names":[],"mappings":"AA4FA,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CA4C3D"}
@@ -0,0 +1,93 @@
1
+ import { readFileSync, existsSync, readdirSync } from "fs";
2
+ import { join } from "path";
3
+ function detectRepeats(historyDir) {
4
+ if (!existsSync(historyDir)) {
5
+ return { totalRepeats: 0, uniqueRepeatingRules: 0, entries: [] };
6
+ }
7
+ const files = readdirSync(historyDir);
8
+ const jsonFiles = files.filter((f) => String(f).endsWith(".json")).sort();
9
+ const ruleData = {};
10
+ for (const file of jsonFiles) {
11
+ const raw = readFileSync(join(historyDir, String(file)), "utf-8");
12
+ let verdict;
13
+ try {
14
+ verdict = JSON.parse(raw);
15
+ }
16
+ catch {
17
+ continue;
18
+ }
19
+ const period = String(file).replace(/\.json$/, "");
20
+ const seenInFile = new Set();
21
+ for (const f of verdict.findings ?? []) {
22
+ if (seenInFile.has(f.ruleId))
23
+ continue;
24
+ seenInFile.add(f.ruleId);
25
+ if (!ruleData[f.ruleId]) {
26
+ ruleData[f.ruleId] = {
27
+ occurrences: 0,
28
+ firstSeen: period,
29
+ lastSeen: period,
30
+ severity: f.severity,
31
+ title: f.title,
32
+ };
33
+ }
34
+ ruleData[f.ruleId].occurrences += 1;
35
+ ruleData[f.ruleId].lastSeen = period;
36
+ }
37
+ }
38
+ const entries = [];
39
+ for (const [ruleId, data] of Object.entries(ruleData)) {
40
+ if (data.occurrences > 1) {
41
+ entries.push({
42
+ ruleId,
43
+ occurrences: data.occurrences,
44
+ firstSeen: data.firstSeen,
45
+ lastSeen: data.lastSeen,
46
+ severity: data.severity,
47
+ sampleTitle: data.title,
48
+ });
49
+ }
50
+ }
51
+ entries.sort((a, b) => b.occurrences - a.occurrences);
52
+ return {
53
+ totalRepeats: entries.reduce((sum, e) => sum + e.occurrences, 0),
54
+ uniqueRepeatingRules: entries.length,
55
+ entries,
56
+ };
57
+ }
58
+ export function runFindingRepeatDetect(argv) {
59
+ if (argv.includes("--help") || argv.includes("-h")) {
60
+ console.log(`Usage: judges finding-repeat-detect [options]
61
+
62
+ Detect findings that repeat across multiple reviews.
63
+
64
+ Options:
65
+ --history <dir> Directory with verdict JSON files (default: .judges/history)
66
+ --format <fmt> Output format: table (default) or json
67
+ -h, --help Show this help message`);
68
+ return;
69
+ }
70
+ const formatIdx = argv.indexOf("--format");
71
+ const format = formatIdx !== -1 && argv[formatIdx + 1] ? argv[formatIdx + 1] : "table";
72
+ const histIdx = argv.indexOf("--history");
73
+ const historyDir = histIdx !== -1 && argv[histIdx + 1]
74
+ ? join(process.cwd(), argv[histIdx + 1])
75
+ : join(process.cwd(), ".judges", "history");
76
+ const report = detectRepeats(historyDir);
77
+ if (format === "json") {
78
+ console.log(JSON.stringify(report, null, 2));
79
+ return;
80
+ }
81
+ console.log(`\n=== Repeat Detection (${report.uniqueRepeatingRules} repeating rules, ${report.totalRepeats} total occurrences) ===\n`);
82
+ if (report.entries.length === 0) {
83
+ console.log("No repeating findings detected.");
84
+ return;
85
+ }
86
+ for (const e of report.entries) {
87
+ console.log(` ${e.ruleId} — ${e.occurrences} occurrences [${e.severity}]`);
88
+ console.log(` First: ${e.firstSeen} Last: ${e.lastSeen}`);
89
+ console.log(` ${e.sampleTitle}`);
90
+ console.log();
91
+ }
92
+ }
93
+ //# sourceMappingURL=finding-repeat-detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding-repeat-detect.js","sourceRoot":"","sources":["../../src/commands/finding-repeat-detect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function runFindingScopeImpact(argv: string[]): void;
2
+ //# sourceMappingURL=finding-scope-impact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding-scope-impact.d.ts","sourceRoot":"","sources":["../../src/commands/finding-scope-impact.ts"],"names":[],"mappings":"AA0DA,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAqD1D"}
@@ -0,0 +1,84 @@
1
+ import { readFileSync, existsSync } from "fs";
2
+ import { join } from "path";
3
+ function extractDomain(ruleId) {
4
+ const parts = ruleId.split("/");
5
+ return parts.length > 1 ? parts[0] : "general";
6
+ }
7
+ function analyseScope(verdict) {
8
+ const findings = verdict.findings ?? [];
9
+ const domainCounts = {};
10
+ for (const f of findings) {
11
+ const domain = extractDomain(f.ruleId);
12
+ domainCounts[domain] = (domainCounts[domain] ?? 0) + 1;
13
+ }
14
+ const entries = [];
15
+ for (const f of findings) {
16
+ const domain = extractDomain(f.ruleId);
17
+ const count = domainCounts[domain] ?? 1;
18
+ let impactScope;
19
+ if (count >= 10)
20
+ impactScope = "systemic";
21
+ else if (count >= 5)
22
+ impactScope = "widespread";
23
+ else if (count >= 2)
24
+ impactScope = "moderate";
25
+ else
26
+ impactScope = "isolated";
27
+ entries.push({
28
+ ruleId: f.ruleId,
29
+ title: f.title,
30
+ severity: f.severity,
31
+ domain,
32
+ domainCount: count,
33
+ impactScope,
34
+ });
35
+ }
36
+ entries.sort((a, b) => b.domainCount - a.domainCount);
37
+ return entries;
38
+ }
39
+ export function runFindingScopeImpact(argv) {
40
+ if (argv.includes("--help") || argv.includes("-h")) {
41
+ console.log(`Usage: judges finding-scope-impact [options]
42
+
43
+ Analyse the scope of finding impact across domains.
44
+
45
+ Options:
46
+ --report <path> Path to verdict JSON
47
+ --format <fmt> Output format: table (default) or json
48
+ -h, --help Show this help message`);
49
+ return;
50
+ }
51
+ const formatIdx = argv.indexOf("--format");
52
+ const format = formatIdx !== -1 && argv[formatIdx + 1] ? argv[formatIdx + 1] : "table";
53
+ const reportIdx = argv.indexOf("--report");
54
+ const reportPath = reportIdx !== -1 && argv[reportIdx + 1]
55
+ ? join(process.cwd(), argv[reportIdx + 1])
56
+ : join(process.cwd(), ".judges", "last-verdict.json");
57
+ if (!existsSync(reportPath)) {
58
+ console.log(`No report found at: ${reportPath}`);
59
+ return;
60
+ }
61
+ const data = JSON.parse(readFileSync(reportPath, "utf-8"));
62
+ const entries = analyseScope(data);
63
+ if (format === "json") {
64
+ console.log(JSON.stringify(entries, null, 2));
65
+ return;
66
+ }
67
+ console.log(`\n=== Finding Scope Impact (${entries.length} findings) ===\n`);
68
+ if (entries.length === 0) {
69
+ console.log("No findings to analyse.");
70
+ return;
71
+ }
72
+ const seen = new Set();
73
+ for (const e of entries) {
74
+ if (!seen.has(e.domain)) {
75
+ seen.add(e.domain);
76
+ console.log(` [${e.domain}] ${e.domainCount} findings — ${e.impactScope} scope`);
77
+ }
78
+ }
79
+ console.log();
80
+ for (const e of entries) {
81
+ console.log(` ${e.impactScope.padEnd(12)} [${e.severity}] ${e.ruleId}: ${e.title}`);
82
+ }
83
+ }
84
+ //# sourceMappingURL=finding-scope-impact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding-scope-impact.js","sourceRoot":"","sources":["../../src/commands/finding-scope-impact.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function runFindingTopOffender(argv: string[]): void;
2
+ //# sourceMappingURL=finding-top-offender.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding-top-offender.d.ts","sourceRoot":"","sources":["../../src/commands/finding-top-offender.ts"],"names":[],"mappings":"AA+CA,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAyD1D"}
@@ -0,0 +1,76 @@
1
+ import { readFileSync, existsSync } from "fs";
2
+ import { join } from "path";
3
+ function findTopOffenders(verdict) {
4
+ const findings = verdict.findings ?? [];
5
+ if (findings.length === 0)
6
+ return [];
7
+ const ruleMap = {};
8
+ for (const f of findings) {
9
+ if (!ruleMap[f.ruleId]) {
10
+ ruleMap[f.ruleId] = { count: 0, severities: {}, sampleTitle: f.title };
11
+ }
12
+ ruleMap[f.ruleId].count += 1;
13
+ ruleMap[f.ruleId].severities[f.severity] = (ruleMap[f.ruleId].severities[f.severity] ?? 0) + 1;
14
+ }
15
+ const entries = [];
16
+ for (const [ruleId, data] of Object.entries(ruleMap)) {
17
+ entries.push({
18
+ ruleId,
19
+ count: data.count,
20
+ severities: data.severities,
21
+ sampleTitle: data.sampleTitle,
22
+ percentage: Math.round((data.count / findings.length) * 100),
23
+ });
24
+ }
25
+ entries.sort((a, b) => b.count - a.count);
26
+ return entries;
27
+ }
28
+ export function runFindingTopOffender(argv) {
29
+ if (argv.includes("--help") || argv.includes("-h")) {
30
+ console.log(`Usage: judges finding-top-offender [options]
31
+
32
+ Identify the most frequently triggered rules.
33
+
34
+ Options:
35
+ --report <path> Path to verdict JSON
36
+ --top <n> Number of top offenders to show (default: 10)
37
+ --format <fmt> Output format: table (default) or json
38
+ -h, --help Show this help message`);
39
+ return;
40
+ }
41
+ const formatIdx = argv.indexOf("--format");
42
+ const format = formatIdx !== -1 && argv[formatIdx + 1] ? argv[formatIdx + 1] : "table";
43
+ const topIdx = argv.indexOf("--top");
44
+ const topN = topIdx !== -1 && argv[topIdx + 1] ? parseInt(argv[topIdx + 1], 10) : 10;
45
+ const reportIdx = argv.indexOf("--report");
46
+ const reportPath = reportIdx !== -1 && argv[reportIdx + 1]
47
+ ? join(process.cwd(), argv[reportIdx + 1])
48
+ : join(process.cwd(), ".judges", "last-verdict.json");
49
+ if (!existsSync(reportPath)) {
50
+ console.log(`No report found at: ${reportPath}`);
51
+ return;
52
+ }
53
+ const data = JSON.parse(readFileSync(reportPath, "utf-8"));
54
+ const offenders = findTopOffenders(data);
55
+ const shown = offenders.slice(0, topN);
56
+ if (format === "json") {
57
+ console.log(JSON.stringify(shown, null, 2));
58
+ return;
59
+ }
60
+ console.log(`\n=== Top Offenders (${shown.length}/${offenders.length} rules) ===\n`);
61
+ if (shown.length === 0) {
62
+ console.log("No findings to analyse.");
63
+ return;
64
+ }
65
+ for (let i = 0; i < shown.length; i++) {
66
+ const e = shown[i];
67
+ const severityStr = Object.entries(e.severities)
68
+ .map(([s, n]) => `${s}:${n}`)
69
+ .join(" ");
70
+ console.log(` #${i + 1} ${e.ruleId} — ${e.count} findings (${e.percentage}%)`);
71
+ console.log(` Severities: ${severityStr}`);
72
+ console.log(` Example: ${e.sampleTitle}`);
73
+ console.log();
74
+ }
75
+ }
76
+ //# sourceMappingURL=finding-top-offender.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding-top-offender.js","sourceRoot":"","sources":["../../src/commands/finding-top-offender.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function runReviewHealthTrend(argv: string[]): void;
2
+ //# sourceMappingURL=review-health-trend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-health-trend.d.ts","sourceRoot":"","sources":["../../src/commands/review-health-trend.ts"],"names":[],"mappings":"AAgGA,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CA0CzD"}
@@ -0,0 +1,108 @@
1
+ import { readFileSync, existsSync, readdirSync } from "fs";
2
+ import { join } from "path";
3
+ function computeHealthScore(verdict) {
4
+ const findings = verdict.findings ?? [];
5
+ if (findings.length === 0)
6
+ return 100;
7
+ let penalty = 0;
8
+ for (const f of findings) {
9
+ if (f.severity === "critical")
10
+ penalty += 15;
11
+ else if (f.severity === "high")
12
+ penalty += 10;
13
+ else if (f.severity === "medium")
14
+ penalty += 5;
15
+ else if (f.severity === "low")
16
+ penalty += 2;
17
+ else
18
+ penalty += 1;
19
+ }
20
+ return Math.max(0, 100 - penalty);
21
+ }
22
+ function buildTrend(historyDir) {
23
+ if (!existsSync(historyDir)) {
24
+ return { snapshots: [], trend: "unknown", currentScore: 0, averageScore: 0 };
25
+ }
26
+ const files = readdirSync(historyDir);
27
+ const jsonFiles = files.filter((f) => String(f).endsWith(".json")).sort();
28
+ const snapshots = [];
29
+ for (const file of jsonFiles) {
30
+ const raw = readFileSync(join(historyDir, String(file)), "utf-8");
31
+ let verdict;
32
+ try {
33
+ verdict = JSON.parse(raw);
34
+ }
35
+ catch {
36
+ continue;
37
+ }
38
+ const score = computeHealthScore(verdict);
39
+ const findings = verdict.findings ?? [];
40
+ const criticals = findings.filter((f) => f.severity === "critical").length;
41
+ const passRate = verdict.overallVerdict === "pass" ? 100 : 0;
42
+ snapshots.push({
43
+ period: String(file).replace(/\.json$/, ""),
44
+ score,
45
+ findingCount: findings.length,
46
+ criticalCount: criticals,
47
+ passRate,
48
+ });
49
+ }
50
+ if (snapshots.length === 0) {
51
+ return { snapshots, trend: "unknown", currentScore: 0, averageScore: 0 };
52
+ }
53
+ const currentScore = snapshots[snapshots.length - 1].score;
54
+ const averageScore = Math.round(snapshots.reduce((sum, s) => sum + s.score, 0) / snapshots.length);
55
+ let trend;
56
+ if (snapshots.length < 3) {
57
+ trend = "insufficient data";
58
+ }
59
+ else {
60
+ const recent = snapshots.slice(-3);
61
+ const first = recent[0].score;
62
+ const last = recent[recent.length - 1].score;
63
+ if (last > first + 5)
64
+ trend = "improving";
65
+ else if (last < first - 5)
66
+ trend = "declining";
67
+ else
68
+ trend = "stable";
69
+ }
70
+ return { snapshots, trend, currentScore, averageScore };
71
+ }
72
+ export function runReviewHealthTrend(argv) {
73
+ if (argv.includes("--help") || argv.includes("-h")) {
74
+ console.log(`Usage: judges review-health-trend [options]
75
+
76
+ Track review health over time with composite scoring.
77
+
78
+ Options:
79
+ --history <dir> Directory with verdict JSON files (default: .judges/history)
80
+ --format <fmt> Output format: table (default) or json
81
+ -h, --help Show this help message`);
82
+ return;
83
+ }
84
+ const formatIdx = argv.indexOf("--format");
85
+ const format = formatIdx !== -1 && argv[formatIdx + 1] ? argv[formatIdx + 1] : "table";
86
+ const histIdx = argv.indexOf("--history");
87
+ const historyDir = histIdx !== -1 && argv[histIdx + 1]
88
+ ? join(process.cwd(), argv[histIdx + 1])
89
+ : join(process.cwd(), ".judges", "history");
90
+ const result = buildTrend(historyDir);
91
+ if (format === "json") {
92
+ console.log(JSON.stringify(result, null, 2));
93
+ return;
94
+ }
95
+ console.log(`\n=== Review Health Trend ===\n`);
96
+ console.log(` Current Score: ${result.currentScore}/100`);
97
+ console.log(` Average Score: ${result.averageScore}/100`);
98
+ console.log(` Trend: ${result.trend}`);
99
+ console.log(` Data Points: ${result.snapshots.length}`);
100
+ if (result.snapshots.length > 0) {
101
+ console.log();
102
+ for (const s of result.snapshots) {
103
+ const bar = "█".repeat(Math.round(s.score / 5));
104
+ console.log(` ${s.period.padEnd(20)} ${String(s.score).padStart(3)}/100 ${bar}`);
105
+ }
106
+ }
107
+ }
108
+ //# sourceMappingURL=review-health-trend.js.map