@kevinrabun/judges 3.66.0 → 3.67.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 +8 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +56 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/config-lint.d.ts +5 -0
- package/dist/commands/config-lint.d.ts.map +1 -0
- package/dist/commands/config-lint.js +188 -0
- package/dist/commands/config-lint.js.map +1 -0
- package/dist/commands/finding-age.d.ts +5 -0
- package/dist/commands/finding-age.d.ts.map +1 -0
- package/dist/commands/finding-age.js +146 -0
- package/dist/commands/finding-age.js.map +1 -0
- package/dist/commands/finding-rank.d.ts +5 -0
- package/dist/commands/finding-rank.d.ts.map +1 -0
- package/dist/commands/finding-rank.js +139 -0
- package/dist/commands/finding-rank.js.map +1 -0
- package/dist/commands/review-dashboard.d.ts +5 -0
- package/dist/commands/review-dashboard.d.ts.map +1 -0
- package/dist/commands/review-dashboard.js +141 -0
- package/dist/commands/review-dashboard.js.map +1 -0
- package/dist/commands/review-diff-summary.d.ts +5 -0
- package/dist/commands/review-diff-summary.d.ts.map +1 -0
- package/dist/commands/review-diff-summary.js +155 -0
- package/dist/commands/review-diff-summary.js.map +1 -0
- package/dist/commands/review-notify.d.ts +5 -0
- package/dist/commands/review-notify.d.ts.map +1 -0
- package/dist/commands/review-notify.js +144 -0
- package/dist/commands/review-notify.js.map +1 -0
- package/dist/commands/review-offline.d.ts +5 -0
- package/dist/commands/review-offline.d.ts.map +1 -0
- package/dist/commands/review-offline.js +126 -0
- package/dist/commands/review-offline.js.map +1 -0
- package/dist/commands/review-quota.d.ts +5 -0
- package/dist/commands/review-quota.d.ts.map +1 -0
- package/dist/commands/review-quota.js +127 -0
- package/dist/commands/review-quota.js.map +1 -0
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config-lint — Lint and validate .judgesrc configuration files.
|
|
3
|
+
*/
|
|
4
|
+
import { readFileSync, existsSync } from "fs";
|
|
5
|
+
// ─── Validation ─────────────────────────────────────────────────────────────
|
|
6
|
+
const VALID_PRESETS = [
|
|
7
|
+
"strict",
|
|
8
|
+
"lenient",
|
|
9
|
+
"security-only",
|
|
10
|
+
"startup",
|
|
11
|
+
"compliance",
|
|
12
|
+
"performance",
|
|
13
|
+
"react",
|
|
14
|
+
"express",
|
|
15
|
+
"fastapi",
|
|
16
|
+
"django",
|
|
17
|
+
"spring-boot",
|
|
18
|
+
"rails",
|
|
19
|
+
"nextjs",
|
|
20
|
+
"terraform",
|
|
21
|
+
"kubernetes",
|
|
22
|
+
];
|
|
23
|
+
const VALID_SEVERITIES = ["critical", "high", "medium", "low", "info"];
|
|
24
|
+
function lintConfig(config, issues) {
|
|
25
|
+
// Check preset
|
|
26
|
+
if (config["preset"] !== undefined) {
|
|
27
|
+
const preset = String(config["preset"]);
|
|
28
|
+
const presets = preset.split(",").map((p) => p.trim());
|
|
29
|
+
for (const p of presets) {
|
|
30
|
+
if (!VALID_PRESETS.includes(p)) {
|
|
31
|
+
issues.push({
|
|
32
|
+
level: "warning",
|
|
33
|
+
field: "preset",
|
|
34
|
+
message: `Unknown preset '${p}'. Valid: ${VALID_PRESETS.join(", ")}`,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Check minSeverity
|
|
40
|
+
if (config["minSeverity"] !== undefined) {
|
|
41
|
+
const sev = String(config["minSeverity"]).toLowerCase();
|
|
42
|
+
if (!VALID_SEVERITIES.includes(sev)) {
|
|
43
|
+
issues.push({
|
|
44
|
+
level: "error",
|
|
45
|
+
field: "minSeverity",
|
|
46
|
+
message: `Invalid severity '${sev}'. Valid: ${VALID_SEVERITIES.join(", ")}`,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Check disabledJudges
|
|
51
|
+
if (config["disabledJudges"] !== undefined) {
|
|
52
|
+
if (!Array.isArray(config["disabledJudges"])) {
|
|
53
|
+
issues.push({ level: "error", field: "disabledJudges", message: "Must be an array of strings" });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Check disabledRules
|
|
57
|
+
if (config["disabledRules"] !== undefined) {
|
|
58
|
+
if (!Array.isArray(config["disabledRules"])) {
|
|
59
|
+
issues.push({ level: "error", field: "disabledRules", message: "Must be an array of strings" });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Check ruleOverrides
|
|
63
|
+
if (config["ruleOverrides"] !== undefined) {
|
|
64
|
+
if (typeof config["ruleOverrides"] !== "object" || config["ruleOverrides"] === null) {
|
|
65
|
+
issues.push({ level: "error", field: "ruleOverrides", message: "Must be an object" });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Warn about unknown fields
|
|
69
|
+
const knownFields = new Set([
|
|
70
|
+
"preset",
|
|
71
|
+
"minSeverity",
|
|
72
|
+
"disabledJudges",
|
|
73
|
+
"disabledRules",
|
|
74
|
+
"ruleOverrides",
|
|
75
|
+
"format",
|
|
76
|
+
"failOnFindings",
|
|
77
|
+
"minScore",
|
|
78
|
+
"exclude",
|
|
79
|
+
"include",
|
|
80
|
+
"maxFiles",
|
|
81
|
+
"language",
|
|
82
|
+
"baseline",
|
|
83
|
+
]);
|
|
84
|
+
for (const key of Object.keys(config)) {
|
|
85
|
+
if (!knownFields.has(key)) {
|
|
86
|
+
issues.push({ level: "info", field: key, message: `Unknown field '${key}' — may be ignored` });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Check format
|
|
90
|
+
if (config["format"] !== undefined) {
|
|
91
|
+
const validFormats = ["text", "json", "sarif", "markdown", "html", "pdf", "junit", "codeclimate", "github-actions"];
|
|
92
|
+
if (!validFormats.includes(String(config["format"]))) {
|
|
93
|
+
issues.push({
|
|
94
|
+
level: "warning",
|
|
95
|
+
field: "format",
|
|
96
|
+
message: `Unknown format '${config["format"]}'. Valid: ${validFormats.join(", ")}`,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Check minScore
|
|
101
|
+
if (config["minScore"] !== undefined) {
|
|
102
|
+
const score = Number(config["minScore"]);
|
|
103
|
+
if (isNaN(score) || score < 0 || score > 100) {
|
|
104
|
+
issues.push({ level: "error", field: "minScore", message: "Must be a number between 0 and 100" });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// ─── CLI ────────────────────────────────────────────────────────────────────
|
|
109
|
+
export function runConfigLint(argv) {
|
|
110
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
111
|
+
console.log(`
|
|
112
|
+
judges config-lint — Lint and validate .judgesrc configuration
|
|
113
|
+
|
|
114
|
+
Usage:
|
|
115
|
+
judges config-lint Lint .judgesrc in current dir
|
|
116
|
+
judges config-lint --file custom.judgesrc Lint specific file
|
|
117
|
+
judges config-lint --strict Treat warnings as errors
|
|
118
|
+
judges config-lint --format json JSON output
|
|
119
|
+
|
|
120
|
+
Options:
|
|
121
|
+
--file <path> Configuration file to lint (default: .judgesrc)
|
|
122
|
+
--strict Treat warnings as errors
|
|
123
|
+
--format json JSON output
|
|
124
|
+
--help, -h Show this help
|
|
125
|
+
|
|
126
|
+
Validates configuration for correctness: preset names,
|
|
127
|
+
severity levels, field types, and unknown properties.
|
|
128
|
+
`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const format = argv.find((_a, i) => argv[i - 1] === "--format") || "text";
|
|
132
|
+
const configFile = argv.find((_a, i) => argv[i - 1] === "--file") || ".judgesrc";
|
|
133
|
+
const strict = argv.includes("--strict");
|
|
134
|
+
if (!existsSync(configFile)) {
|
|
135
|
+
console.error(`Error: Configuration file not found: ${configFile}`);
|
|
136
|
+
process.exitCode = 1;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
let config;
|
|
140
|
+
try {
|
|
141
|
+
config = JSON.parse(readFileSync(configFile, "utf-8"));
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
console.error(`Error: Invalid JSON in ${configFile}: ${e instanceof Error ? e.message : "parse error"}`);
|
|
145
|
+
process.exitCode = 1;
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const issues = [];
|
|
149
|
+
lintConfig(config, issues);
|
|
150
|
+
const errors = issues.filter((i) => i.level === "error");
|
|
151
|
+
const warnings = issues.filter((i) => i.level === "warning");
|
|
152
|
+
const infos = issues.filter((i) => i.level === "info");
|
|
153
|
+
const hasErrors = errors.length > 0 || (strict && warnings.length > 0);
|
|
154
|
+
if (format === "json") {
|
|
155
|
+
console.log(JSON.stringify({
|
|
156
|
+
file: configFile,
|
|
157
|
+
valid: !hasErrors,
|
|
158
|
+
errors: errors.length,
|
|
159
|
+
warnings: warnings.length,
|
|
160
|
+
infos: infos.length,
|
|
161
|
+
issues,
|
|
162
|
+
}, null, 2));
|
|
163
|
+
if (hasErrors)
|
|
164
|
+
process.exitCode = 1;
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
console.log(`\n Config Lint: ${configFile}\n ─────────────────────────────`);
|
|
168
|
+
if (issues.length === 0) {
|
|
169
|
+
console.log(" ✅ Configuration is valid. No issues found.");
|
|
170
|
+
console.log();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
for (const issue of issues) {
|
|
174
|
+
const icon = issue.level === "error" ? "❌" : issue.level === "warning" ? "⚠️" : "ℹ️";
|
|
175
|
+
console.log(` ${icon} [${issue.level.toUpperCase()}] ${issue.field}: ${issue.message}`);
|
|
176
|
+
}
|
|
177
|
+
console.log();
|
|
178
|
+
console.log(` Errors: ${errors.length}, Warnings: ${warnings.length}, Info: ${infos.length}`);
|
|
179
|
+
if (hasErrors) {
|
|
180
|
+
console.log(" ❌ Configuration has errors.");
|
|
181
|
+
process.exitCode = 1;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
console.log(" ✅ Configuration is valid (with warnings).");
|
|
185
|
+
}
|
|
186
|
+
console.log();
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=config-lint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-lint.js","sourceRoot":"","sources":["../../src/commands/config-lint.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"finding-age.d.ts","sourceRoot":"","sources":["../../src/commands/finding-age.ts"],"names":[],"mappings":"AAAA;;GAEG;AAsDH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAqIlD"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finding-age — Track how long findings have been unresolved.
|
|
3
|
+
*/
|
|
4
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
5
|
+
import { join, dirname } from "path";
|
|
6
|
+
// ─── Storage ────────────────────────────────────────────────────────────────
|
|
7
|
+
const AGE_FILE = join(".judges", "finding-age.json");
|
|
8
|
+
function loadAgeStore() {
|
|
9
|
+
if (!existsSync(AGE_FILE))
|
|
10
|
+
return { version: "1.0.0", records: [] };
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(readFileSync(AGE_FILE, "utf-8"));
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return { version: "1.0.0", records: [] };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function saveAgeStore(store) {
|
|
19
|
+
mkdirSync(dirname(AGE_FILE), { recursive: true });
|
|
20
|
+
writeFileSync(AGE_FILE, JSON.stringify(store, null, 2), "utf-8");
|
|
21
|
+
}
|
|
22
|
+
function findingFingerprint(f) {
|
|
23
|
+
return [f.ruleId || "", f.title || "", String(f.severity || "")].join("|").toLowerCase();
|
|
24
|
+
}
|
|
25
|
+
function daysBetween(a, b) {
|
|
26
|
+
const msPerDay = 86400000;
|
|
27
|
+
return Math.floor((new Date(b).getTime() - new Date(a).getTime()) / msPerDay);
|
|
28
|
+
}
|
|
29
|
+
// ─── CLI ────────────────────────────────────────────────────────────────────
|
|
30
|
+
export function runFindingAge(argv) {
|
|
31
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
32
|
+
console.log(`
|
|
33
|
+
judges finding-age — Track how long findings have been unresolved
|
|
34
|
+
|
|
35
|
+
Usage:
|
|
36
|
+
judges finding-age update --file verdict.json Update age records
|
|
37
|
+
judges finding-age show Show finding ages
|
|
38
|
+
judges finding-age show --stale 30 Show findings older than 30 days
|
|
39
|
+
judges finding-age clear Clear all records
|
|
40
|
+
|
|
41
|
+
Subcommands:
|
|
42
|
+
update Update records from a verdict file
|
|
43
|
+
show Show age report
|
|
44
|
+
clear Clear all tracking data
|
|
45
|
+
|
|
46
|
+
Options:
|
|
47
|
+
--file <path> Verdict JSON (for update)
|
|
48
|
+
--stale <days> Show only findings older than N days
|
|
49
|
+
--format json JSON output
|
|
50
|
+
--help, -h Show this help
|
|
51
|
+
|
|
52
|
+
Tracks first-seen dates and age of findings across reviews.
|
|
53
|
+
Data stored locally in .judges/finding-age.json.
|
|
54
|
+
`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const format = argv.find((_a, i) => argv[i - 1] === "--format") || "text";
|
|
58
|
+
const subcommand = argv.find((a) => ["update", "show", "clear"].includes(a)) || "show";
|
|
59
|
+
const store = loadAgeStore();
|
|
60
|
+
if (subcommand === "update") {
|
|
61
|
+
const file = argv.find((_a, i) => argv[i - 1] === "--file");
|
|
62
|
+
if (!file || !existsSync(file)) {
|
|
63
|
+
console.error("Error: --file with valid verdict JSON is required.");
|
|
64
|
+
process.exitCode = 1;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
let verdict;
|
|
68
|
+
try {
|
|
69
|
+
verdict = JSON.parse(readFileSync(file, "utf-8"));
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
console.error(`Error: Could not parse ${file}`);
|
|
73
|
+
process.exitCode = 1;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const now = new Date().toISOString();
|
|
77
|
+
const currentFingerprints = new Set();
|
|
78
|
+
for (const f of verdict.findings || []) {
|
|
79
|
+
const fp = findingFingerprint(f);
|
|
80
|
+
currentFingerprints.add(fp);
|
|
81
|
+
const existing = store.records.find((r) => r.fingerprint === fp);
|
|
82
|
+
if (existing) {
|
|
83
|
+
existing.lastSeen = now;
|
|
84
|
+
existing.ageInDays = daysBetween(existing.firstSeen, now);
|
|
85
|
+
existing.resolved = false;
|
|
86
|
+
existing.resolvedAt = "";
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
store.records.push({
|
|
90
|
+
fingerprint: fp,
|
|
91
|
+
ruleId: f.ruleId || "",
|
|
92
|
+
title: f.title || "",
|
|
93
|
+
severity: f.severity || "unknown",
|
|
94
|
+
firstSeen: now,
|
|
95
|
+
lastSeen: now,
|
|
96
|
+
resolved: false,
|
|
97
|
+
resolvedAt: "",
|
|
98
|
+
ageInDays: 0,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Mark resolved findings
|
|
103
|
+
for (const r of store.records) {
|
|
104
|
+
if (!currentFingerprints.has(r.fingerprint) && !r.resolved) {
|
|
105
|
+
r.resolved = true;
|
|
106
|
+
r.resolvedAt = now;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
saveAgeStore(store);
|
|
110
|
+
console.log(`Updated ${store.records.length} finding age records.`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (subcommand === "clear") {
|
|
114
|
+
saveAgeStore({ version: "1.0.0", records: [] });
|
|
115
|
+
console.log("Finding age records cleared.");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
// Show
|
|
119
|
+
const staleStr = argv.find((_a, i) => argv[i - 1] === "--stale");
|
|
120
|
+
const staleDays = staleStr ? parseInt(staleStr, 10) : 0;
|
|
121
|
+
let records = store.records.filter((r) => !r.resolved);
|
|
122
|
+
if (staleDays > 0) {
|
|
123
|
+
records = records.filter((r) => r.ageInDays >= staleDays);
|
|
124
|
+
}
|
|
125
|
+
records.sort((a, b) => b.ageInDays - a.ageInDays);
|
|
126
|
+
if (format === "json") {
|
|
127
|
+
console.log(JSON.stringify({ total: store.records.length, unresolved: records.length, staleDays, records }, null, 2));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
console.log(`\n Finding Age Report\n ═════════════════════════════`);
|
|
131
|
+
console.log(` Total tracked: ${store.records.length}`);
|
|
132
|
+
console.log(` Unresolved: ${records.length}`);
|
|
133
|
+
console.log(` Resolved: ${store.records.filter((r) => r.resolved).length}`);
|
|
134
|
+
if (staleDays > 0)
|
|
135
|
+
console.log(` Showing: older than ${staleDays} days`);
|
|
136
|
+
console.log();
|
|
137
|
+
if (records.length === 0) {
|
|
138
|
+
console.log(" No unresolved findings matching criteria.");
|
|
139
|
+
}
|
|
140
|
+
for (const r of records) {
|
|
141
|
+
const icon = r.ageInDays > 30 ? "🔴" : r.ageInDays > 7 ? "🟡" : "🟢";
|
|
142
|
+
console.log(` ${icon} ${r.ageInDays}d — [${r.severity.toUpperCase()}] ${r.title || r.ruleId} (since ${r.firstSeen.slice(0, 10)})`);
|
|
143
|
+
}
|
|
144
|
+
console.log();
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=finding-age.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"finding-age.js","sourceRoot":"","sources":["../../src/commands/finding-age.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"finding-rank.d.ts","sourceRoot":"","sources":["../../src/commands/finding-rank.ts"],"names":[],"mappings":"AAAA;;GAEG;AA2CH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CA+HnD"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finding-rank — Rank findings by business impact and fix effort.
|
|
3
|
+
*/
|
|
4
|
+
import { readFileSync, existsSync } from "fs";
|
|
5
|
+
// ─── Scoring ────────────────────────────────────────────────────────────────
|
|
6
|
+
const SEVERITY_IMPACT = {
|
|
7
|
+
critical: 100,
|
|
8
|
+
high: 75,
|
|
9
|
+
medium: 50,
|
|
10
|
+
low: 25,
|
|
11
|
+
info: 10,
|
|
12
|
+
};
|
|
13
|
+
function estimateEffort(f) {
|
|
14
|
+
// Estimated fix effort (1-10 scale): lower = easier to fix
|
|
15
|
+
if (f.patch)
|
|
16
|
+
return 2; // Has auto-fix
|
|
17
|
+
if (f.recommendation)
|
|
18
|
+
return 4; // Has guidance
|
|
19
|
+
const sev = (f.severity || "medium").toLowerCase();
|
|
20
|
+
if (sev === "low" || sev === "info")
|
|
21
|
+
return 3;
|
|
22
|
+
if (sev === "critical")
|
|
23
|
+
return 8;
|
|
24
|
+
return 5;
|
|
25
|
+
}
|
|
26
|
+
function computePriority(f) {
|
|
27
|
+
const impact = SEVERITY_IMPACT[(f.severity || "medium").toLowerCase()] || 50;
|
|
28
|
+
const conf = (f.confidence ?? 0.5) * 100;
|
|
29
|
+
const effort = estimateEffort(f);
|
|
30
|
+
// Priority = high impact + high confidence + low effort = higher score
|
|
31
|
+
return Math.round(impact * 0.5 + conf * 0.3 + (10 - effort) * 2);
|
|
32
|
+
}
|
|
33
|
+
// ─── CLI ────────────────────────────────────────────────────────────────────
|
|
34
|
+
export function runFindingRank(argv) {
|
|
35
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
36
|
+
console.log(`
|
|
37
|
+
judges finding-rank — Rank findings by business impact and fix effort
|
|
38
|
+
|
|
39
|
+
Usage:
|
|
40
|
+
judges finding-rank --file verdict.json Rank all findings
|
|
41
|
+
judges finding-rank --file verdict.json --top 10 Show top 10
|
|
42
|
+
judges finding-rank --file verdict.json --quick-wins Show easy high-impact fixes
|
|
43
|
+
|
|
44
|
+
Options:
|
|
45
|
+
--file <path> Verdict JSON to rank
|
|
46
|
+
--top <n> Show only top N findings
|
|
47
|
+
--quick-wins Show findings with high impact and low effort
|
|
48
|
+
--format json JSON output
|
|
49
|
+
--help, -h Show this help
|
|
50
|
+
|
|
51
|
+
Rankings prioritize by: severity (50%), confidence (30%),
|
|
52
|
+
and fix ease (20%). Quick-wins filter for high-impact, low-effort items.
|
|
53
|
+
`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const format = argv.find((_a, i) => argv[i - 1] === "--format") || "text";
|
|
57
|
+
const file = argv.find((_a, i) => argv[i - 1] === "--file");
|
|
58
|
+
const topN = parseInt(argv.find((_a, i) => argv[i - 1] === "--top") || "0", 10);
|
|
59
|
+
const quickWins = argv.includes("--quick-wins");
|
|
60
|
+
if (!file) {
|
|
61
|
+
console.error("Error: --file is required.");
|
|
62
|
+
process.exitCode = 1;
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (!existsSync(file)) {
|
|
66
|
+
console.error(`Error: File not found: ${file}`);
|
|
67
|
+
process.exitCode = 1;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
let verdict;
|
|
71
|
+
try {
|
|
72
|
+
verdict = JSON.parse(readFileSync(file, "utf-8"));
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
console.error(`Error: Could not parse ${file}`);
|
|
76
|
+
process.exitCode = 1;
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const findings = verdict.findings || [];
|
|
80
|
+
let ranked = findings.map((f) => ({
|
|
81
|
+
rank: 0,
|
|
82
|
+
priority: computePriority(f),
|
|
83
|
+
impact: SEVERITY_IMPACT[(f.severity || "medium").toLowerCase()] || 50,
|
|
84
|
+
effort: estimateEffort(f),
|
|
85
|
+
finding: f,
|
|
86
|
+
}));
|
|
87
|
+
// Sort by priority descending
|
|
88
|
+
ranked.sort((a, b) => b.priority - a.priority);
|
|
89
|
+
// Assign ranks
|
|
90
|
+
ranked.forEach((r, i) => {
|
|
91
|
+
r.rank = i + 1;
|
|
92
|
+
});
|
|
93
|
+
// Filter quick wins: high impact (>=50), low effort (<=4)
|
|
94
|
+
if (quickWins) {
|
|
95
|
+
ranked = ranked.filter((r) => r.impact >= 50 && r.effort <= 4);
|
|
96
|
+
}
|
|
97
|
+
// Limit
|
|
98
|
+
if (topN > 0) {
|
|
99
|
+
ranked = ranked.slice(0, topN);
|
|
100
|
+
}
|
|
101
|
+
if (format === "json") {
|
|
102
|
+
console.log(JSON.stringify({
|
|
103
|
+
total: findings.length,
|
|
104
|
+
shown: ranked.length,
|
|
105
|
+
rankings: ranked.map((r) => ({
|
|
106
|
+
rank: r.rank,
|
|
107
|
+
priority: r.priority,
|
|
108
|
+
impact: r.impact,
|
|
109
|
+
effort: r.effort,
|
|
110
|
+
ruleId: r.finding.ruleId,
|
|
111
|
+
title: r.finding.title,
|
|
112
|
+
severity: r.finding.severity,
|
|
113
|
+
})),
|
|
114
|
+
}, null, 2));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
console.log(`\n Finding Rankings${quickWins ? " (Quick Wins)" : ""}\n ═════════════════════════════`);
|
|
118
|
+
console.log(` Total findings: ${findings.length}`);
|
|
119
|
+
console.log(` Showing: ${ranked.length}`);
|
|
120
|
+
console.log();
|
|
121
|
+
if (ranked.length === 0) {
|
|
122
|
+
console.log(" No findings match criteria.");
|
|
123
|
+
console.log();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
console.log(" Rank Priority Impact Effort Finding");
|
|
127
|
+
console.log(" ──── ──────── ────── ────── ───────");
|
|
128
|
+
for (const r of ranked) {
|
|
129
|
+
const sev = (r.finding.severity || "").toUpperCase().slice(0, 4).padEnd(4);
|
|
130
|
+
const title = (r.finding.title || r.finding.ruleId || "").slice(0, 40);
|
|
131
|
+
const hasPatch = r.finding.patch ? " 🔧" : "";
|
|
132
|
+
console.log(` #${String(r.rank).padEnd(4)} ${String(r.priority).padStart(4)} ${String(r.impact).padStart(4)} ${String(r.effort).padStart(4)} [${sev}] ${title}${hasPatch}`);
|
|
133
|
+
}
|
|
134
|
+
if (quickWins && ranked.length > 0) {
|
|
135
|
+
console.log(`\n 💡 ${ranked.length} quick win(s) — high impact, low effort`);
|
|
136
|
+
}
|
|
137
|
+
console.log();
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=finding-rank.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"finding-rank.js","sourceRoot":"","sources":["../../src/commands/finding-rank.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-dashboard.d.ts","sourceRoot":"","sources":["../../src/commands/review-dashboard.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAqJvD"}
|