@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.
- package/CHANGELOG.md +13 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +63 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/finding-fix-estimate.d.ts +2 -0
- package/dist/commands/finding-fix-estimate.d.ts.map +1 -0
- package/dist/commands/finding-fix-estimate.js +96 -0
- package/dist/commands/finding-fix-estimate.js.map +1 -0
- package/dist/commands/finding-noise-score.d.ts +2 -0
- package/dist/commands/finding-noise-score.d.ts.map +1 -0
- package/dist/commands/finding-noise-score.js +94 -0
- package/dist/commands/finding-noise-score.js.map +1 -0
- package/dist/commands/finding-repeat-detect.d.ts +2 -0
- package/dist/commands/finding-repeat-detect.d.ts.map +1 -0
- package/dist/commands/finding-repeat-detect.js +93 -0
- package/dist/commands/finding-repeat-detect.js.map +1 -0
- package/dist/commands/finding-scope-impact.d.ts +2 -0
- package/dist/commands/finding-scope-impact.d.ts.map +1 -0
- package/dist/commands/finding-scope-impact.js +84 -0
- package/dist/commands/finding-scope-impact.js.map +1 -0
- package/dist/commands/finding-top-offender.d.ts +2 -0
- package/dist/commands/finding-top-offender.d.ts.map +1 -0
- package/dist/commands/finding-top-offender.js +76 -0
- package/dist/commands/finding-top-offender.js.map +1 -0
- package/dist/commands/review-health-trend.d.ts +2 -0
- package/dist/commands/review-health-trend.d.ts.map +1 -0
- package/dist/commands/review-health-trend.js +108 -0
- package/dist/commands/review-health-trend.js.map +1 -0
- package/dist/commands/review-readiness-check.d.ts +2 -0
- package/dist/commands/review-readiness-check.d.ts.map +1 -0
- package/dist/commands/review-readiness-check.js +99 -0
- package/dist/commands/review-readiness-check.js.map +1 -0
- package/dist/commands/review-team-skill-map.d.ts +2 -0
- package/dist/commands/review-team-skill-map.d.ts.map +1 -0
- package/dist/commands/review-team-skill-map.js +103 -0
- package/dist/commands/review-team-skill-map.js.map +1 -0
- package/dist/commands/review-workflow-suggest.d.ts +2 -0
- package/dist/commands/review-workflow-suggest.d.ts.map +1 -0
- package/dist/commands/review-workflow-suggest.js +130 -0
- package/dist/commands/review-workflow-suggest.js.map +1 -0
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -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 @@
|
|
|
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":"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"}
|
|
@@ -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":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,IAAI,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAwB5B,SAAS,aAAa,CAAC,UAAkB;IACvC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,YAAY,EAAE,CAAC,EAAE,oBAAoB,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACnE,CAAC;IAED,MAAM,KAAK,GAAG,WAAW,CAAC,UAAU,CAAwB,CAAC;IAC7D,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAE1E,MAAM,QAAQ,GAGV,EAAE,CAAC;IAEP,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;QAClE,IAAI,OAAwB,CAAC;QAC7B,IAAI,CAAC;YACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAoB,CAAC;QAC/C,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;QACnD,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;QAErC,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;YACvC,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;gBAAE,SAAS;YACvC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YAEzB,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;gBACxB,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG;oBACnB,WAAW,EAAE,CAAC;oBACd,SAAS,EAAE,MAAM;oBACjB,QAAQ,EAAE,MAAM;oBAChB,QAAQ,EAAE,CAAC,CAAC,QAAQ;oBACpB,KAAK,EAAE,CAAC,CAAC,KAAK;iBACf,CAAC;YACJ,CAAC;YAED,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC;YACpC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,GAAG,MAAM,CAAC;QACvC,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAkB,EAAE,CAAC;IAClC,KAAK,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtD,IAAI,IAAI,CAAC,WAAW,GAAG,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,IAAI,CAAC;gBACX,MAAM;gBACN,WAAW,EAAE,IAAI,CAAC,WAAW;gBAC7B,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,WAAW,EAAE,IAAI,CAAC,KAAK;aACxB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC;IAEtD,OAAO;QACL,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;QAChE,oBAAoB,EAAE,OAAO,CAAC,MAAM;QACpC,OAAO;KACR,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,IAAc;IACnD,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,OAAO,CAAC,GAAG,CAAC;;;;;;;8CAO8B,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,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC1C,MAAM,UAAU,GACd,OAAO,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC;QACjC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;QACxC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;IAEhD,MAAM,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;IAEzC,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7C,OAAO;IACT,CAAC;IAED,OAAO,CAAC,GAAG,CACT,2BAA2B,MAAM,CAAC,oBAAoB,qBAAqB,MAAM,CAAC,YAAY,2BAA2B,CAC1H,CAAC;IAEF,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChC,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;QAC/C,OAAO;IACT,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,MAAM,MAAM,CAAC,CAAC,WAAW,iBAAiB,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC;QAC5E,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,SAAS,WAAW,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;QACjE,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;QACvC,OAAO,CAAC,GAAG,EAAE,CAAC;IAChB,CAAC;AACH,CAAC"}
|
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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
|