@kevinrabun/judges 3.81.0 → 3.83.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 +26 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +126 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/finding-age-tracker.d.ts +8 -0
- package/dist/commands/finding-age-tracker.d.ts.map +1 -0
- package/dist/commands/finding-age-tracker.js +153 -0
- package/dist/commands/finding-age-tracker.js.map +1 -0
- package/dist/commands/finding-category-stats.d.ts +5 -0
- package/dist/commands/finding-category-stats.d.ts.map +1 -0
- package/dist/commands/finding-category-stats.js +105 -0
- package/dist/commands/finding-category-stats.js.map +1 -0
- package/dist/commands/finding-compare-runs.d.ts +5 -0
- package/dist/commands/finding-compare-runs.d.ts.map +1 -0
- package/dist/commands/finding-compare-runs.js +106 -0
- package/dist/commands/finding-compare-runs.js.map +1 -0
- package/dist/commands/finding-duplicate-rule.d.ts +5 -0
- package/dist/commands/finding-duplicate-rule.d.ts.map +1 -0
- package/dist/commands/finding-duplicate-rule.js +104 -0
- package/dist/commands/finding-duplicate-rule.js.map +1 -0
- package/dist/commands/finding-hotfix-suggest.d.ts +8 -0
- package/dist/commands/finding-hotfix-suggest.d.ts.map +1 -0
- package/dist/commands/finding-hotfix-suggest.js +171 -0
- package/dist/commands/finding-hotfix-suggest.js.map +1 -0
- package/dist/commands/finding-line-blame.d.ts +8 -0
- package/dist/commands/finding-line-blame.d.ts.map +1 -0
- package/dist/commands/finding-line-blame.js +133 -0
- package/dist/commands/finding-line-blame.js.map +1 -0
- package/dist/commands/finding-summary-digest.d.ts +8 -0
- package/dist/commands/finding-summary-digest.d.ts.map +1 -0
- package/dist/commands/finding-summary-digest.js +146 -0
- package/dist/commands/finding-summary-digest.js.map +1 -0
- package/dist/commands/review-approval-gate.d.ts +8 -0
- package/dist/commands/review-approval-gate.d.ts.map +1 -0
- package/dist/commands/review-approval-gate.js +191 -0
- package/dist/commands/review-approval-gate.js.map +1 -0
- package/dist/commands/review-branch-compare.d.ts +5 -0
- package/dist/commands/review-branch-compare.d.ts.map +1 -0
- package/dist/commands/review-branch-compare.js +114 -0
- package/dist/commands/review-branch-compare.js.map +1 -0
- package/dist/commands/review-changelog-entry.d.ts +8 -0
- package/dist/commands/review-changelog-entry.d.ts.map +1 -0
- package/dist/commands/review-changelog-entry.js +110 -0
- package/dist/commands/review-changelog-entry.js.map +1 -0
- package/dist/commands/review-code-owner.d.ts +8 -0
- package/dist/commands/review-code-owner.d.ts.map +1 -0
- package/dist/commands/review-code-owner.js +165 -0
- package/dist/commands/review-code-owner.js.map +1 -0
- package/dist/commands/review-export-pdf.d.ts +8 -0
- package/dist/commands/review-export-pdf.d.ts.map +1 -0
- package/dist/commands/review-export-pdf.js +132 -0
- package/dist/commands/review-export-pdf.js.map +1 -0
- package/dist/commands/review-finding-link.d.ts +8 -0
- package/dist/commands/review-finding-link.d.ts.map +1 -0
- package/dist/commands/review-finding-link.js +116 -0
- package/dist/commands/review-finding-link.js.map +1 -0
- package/dist/commands/review-parallel-files.d.ts +8 -0
- package/dist/commands/review-parallel-files.d.ts.map +1 -0
- package/dist/commands/review-parallel-files.js +135 -0
- package/dist/commands/review-parallel-files.js.map +1 -0
- package/dist/commands/review-scope-lock.d.ts +8 -0
- package/dist/commands/review-scope-lock.d.ts.map +1 -0
- package/dist/commands/review-scope-lock.js +139 -0
- package/dist/commands/review-scope-lock.js.map +1 -0
- package/dist/commands/review-skip-list.d.ts +5 -0
- package/dist/commands/review-skip-list.d.ts.map +1 -0
- package/dist/commands/review-skip-list.js +136 -0
- package/dist/commands/review-skip-list.js.map +1 -0
- package/dist/commands/review-team-assign.d.ts +8 -0
- package/dist/commands/review-team-assign.d.ts.map +1 -0
- package/dist/commands/review-team-assign.js +212 -0
- package/dist/commands/review-team-assign.js.map +1 -0
- package/dist/commands/review-watch-mode.d.ts +8 -0
- package/dist/commands/review-watch-mode.d.ts.map +1 -0
- package/dist/commands/review-watch-mode.js +133 -0
- package/dist/commands/review-watch-mode.js.map +1 -0
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review-approval-gate — Gate reviews with configurable approval criteria.
|
|
3
|
+
*
|
|
4
|
+
* Evaluates whether a review verdict meets predefined quality gates
|
|
5
|
+
* and provides pass/fail determination for CI/CD integration.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
8
|
+
import { dirname, join } from "path";
|
|
9
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
10
|
+
function gateFile() {
|
|
11
|
+
return join(process.cwd(), ".judges", "approval-gates.json");
|
|
12
|
+
}
|
|
13
|
+
function loadGates() {
|
|
14
|
+
const f = gateFile();
|
|
15
|
+
if (!existsSync(f))
|
|
16
|
+
return [];
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(readFileSync(f, "utf-8"));
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function saveGates(gates) {
|
|
25
|
+
const f = gateFile();
|
|
26
|
+
const d = dirname(f);
|
|
27
|
+
if (!existsSync(d))
|
|
28
|
+
mkdirSync(d, { recursive: true });
|
|
29
|
+
writeFileSync(f, JSON.stringify(gates, null, 2));
|
|
30
|
+
}
|
|
31
|
+
function evaluateGate(verdict, gate) {
|
|
32
|
+
const reasons = [];
|
|
33
|
+
let passed = true;
|
|
34
|
+
if (verdict.criticalCount > gate.maxCritical) {
|
|
35
|
+
passed = false;
|
|
36
|
+
reasons.push(`Critical findings: ${verdict.criticalCount} > max ${gate.maxCritical}`);
|
|
37
|
+
}
|
|
38
|
+
if (verdict.highCount > gate.maxHigh) {
|
|
39
|
+
passed = false;
|
|
40
|
+
reasons.push(`High findings: ${verdict.highCount} > max ${gate.maxHigh}`);
|
|
41
|
+
}
|
|
42
|
+
if (verdict.overallScore < gate.minScore) {
|
|
43
|
+
passed = false;
|
|
44
|
+
reasons.push(`Score: ${verdict.overallScore} < min ${gate.minScore}`);
|
|
45
|
+
}
|
|
46
|
+
if (verdict.findings.length > gate.maxTotal) {
|
|
47
|
+
passed = false;
|
|
48
|
+
reasons.push(`Total findings: ${verdict.findings.length} > max ${gate.maxTotal}`);
|
|
49
|
+
}
|
|
50
|
+
if (gate.requiredVerdict !== "any" && verdict.overallVerdict !== gate.requiredVerdict) {
|
|
51
|
+
passed = false;
|
|
52
|
+
reasons.push(`Verdict: ${verdict.overallVerdict} !== required ${gate.requiredVerdict}`);
|
|
53
|
+
}
|
|
54
|
+
if (passed)
|
|
55
|
+
reasons.push("All criteria met");
|
|
56
|
+
return { gateName: gate.name, passed, reasons };
|
|
57
|
+
}
|
|
58
|
+
// ─── CLI ────────────────────────────────────────────────────────────────────
|
|
59
|
+
export function runReviewApprovalGate(argv) {
|
|
60
|
+
const sub = argv[0];
|
|
61
|
+
if (!sub || sub === "--help" || sub === "-h") {
|
|
62
|
+
console.log(`
|
|
63
|
+
judges review-approval-gate — Configurable review approval gates
|
|
64
|
+
|
|
65
|
+
Usage:
|
|
66
|
+
judges review-approval-gate add --name <name> [gate options]
|
|
67
|
+
judges review-approval-gate remove --name <name>
|
|
68
|
+
judges review-approval-gate list
|
|
69
|
+
judges review-approval-gate check --file <verdict.json> [--format table|json]
|
|
70
|
+
judges review-approval-gate clear
|
|
71
|
+
|
|
72
|
+
Gate Options:
|
|
73
|
+
--max-critical <n> Max critical findings allowed (default: 0)
|
|
74
|
+
--max-high <n> Max high findings allowed (default: 5)
|
|
75
|
+
--min-score <n> Minimum score required (default: 50)
|
|
76
|
+
--max-total <n> Max total findings allowed (default: 50)
|
|
77
|
+
--require-verdict <v> Required verdict: pass, fail, any (default: any)
|
|
78
|
+
--help, -h Show this help
|
|
79
|
+
`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const args = argv.slice(1);
|
|
83
|
+
if (sub === "add") {
|
|
84
|
+
const nameIdx = args.indexOf("--name");
|
|
85
|
+
const name = nameIdx >= 0 ? args[nameIdx + 1] : undefined;
|
|
86
|
+
if (!name) {
|
|
87
|
+
console.error("Error: --name required");
|
|
88
|
+
process.exitCode = 1;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const maxCritIdx = args.indexOf("--max-critical");
|
|
92
|
+
const maxHighIdx = args.indexOf("--max-high");
|
|
93
|
+
const minScoreIdx = args.indexOf("--min-score");
|
|
94
|
+
const maxTotalIdx = args.indexOf("--max-total");
|
|
95
|
+
const reqIdx = args.indexOf("--require-verdict");
|
|
96
|
+
const gate = {
|
|
97
|
+
name,
|
|
98
|
+
maxCritical: maxCritIdx >= 0 ? parseInt(args[maxCritIdx + 1], 10) : 0,
|
|
99
|
+
maxHigh: maxHighIdx >= 0 ? parseInt(args[maxHighIdx + 1], 10) : 5,
|
|
100
|
+
minScore: minScoreIdx >= 0 ? parseInt(args[minScoreIdx + 1], 10) : 50,
|
|
101
|
+
maxTotal: maxTotalIdx >= 0 ? parseInt(args[maxTotalIdx + 1], 10) : 50,
|
|
102
|
+
requiredVerdict: reqIdx >= 0 ? args[reqIdx + 1] : "any",
|
|
103
|
+
};
|
|
104
|
+
const gates = loadGates().filter((g) => g.name !== name);
|
|
105
|
+
gates.push(gate);
|
|
106
|
+
saveGates(gates);
|
|
107
|
+
console.log(`Gate added: ${name}`);
|
|
108
|
+
}
|
|
109
|
+
else if (sub === "remove") {
|
|
110
|
+
const nameIdx = args.indexOf("--name");
|
|
111
|
+
const name = nameIdx >= 0 ? args[nameIdx + 1] : undefined;
|
|
112
|
+
if (!name) {
|
|
113
|
+
console.error("Error: --name required");
|
|
114
|
+
process.exitCode = 1;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const gates = loadGates().filter((g) => g.name !== name);
|
|
118
|
+
saveGates(gates);
|
|
119
|
+
console.log(`Gate removed: ${name}`);
|
|
120
|
+
}
|
|
121
|
+
else if (sub === "list") {
|
|
122
|
+
const gates = loadGates();
|
|
123
|
+
if (gates.length === 0) {
|
|
124
|
+
console.log("No approval gates configured.");
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
console.log(`\nApproval Gates (${gates.length})`);
|
|
128
|
+
console.log("═".repeat(60));
|
|
129
|
+
for (const g of gates) {
|
|
130
|
+
console.log(` ${g.name}: max-crit=${g.maxCritical} max-high=${g.maxHigh} min-score=${g.minScore} max-total=${g.maxTotal} verdict=${g.requiredVerdict}`);
|
|
131
|
+
}
|
|
132
|
+
console.log("═".repeat(60));
|
|
133
|
+
}
|
|
134
|
+
else if (sub === "check") {
|
|
135
|
+
const fileIdx = args.indexOf("--file");
|
|
136
|
+
const formatIdx = args.indexOf("--format");
|
|
137
|
+
const filePath = fileIdx >= 0 ? args[fileIdx + 1] : undefined;
|
|
138
|
+
const format = formatIdx >= 0 ? args[formatIdx + 1] : "table";
|
|
139
|
+
if (!filePath) {
|
|
140
|
+
console.error("Error: --file required");
|
|
141
|
+
process.exitCode = 1;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (!existsSync(filePath)) {
|
|
145
|
+
console.error(`Error: not found: ${filePath}`);
|
|
146
|
+
process.exitCode = 1;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const gates = loadGates();
|
|
150
|
+
if (gates.length === 0) {
|
|
151
|
+
console.error("Error: no gates configured. Use 'add' first.");
|
|
152
|
+
process.exitCode = 1;
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
let verdict;
|
|
156
|
+
try {
|
|
157
|
+
verdict = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
console.error("Error: invalid JSON");
|
|
161
|
+
process.exitCode = 1;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const results = gates.map((g) => evaluateGate(verdict, g));
|
|
165
|
+
if (format === "json") {
|
|
166
|
+
console.log(JSON.stringify(results, null, 2));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const allPassed = results.every((r) => r.passed);
|
|
170
|
+
console.log(`\nApproval Gate Results: ${allPassed ? "APPROVED" : "BLOCKED"}`);
|
|
171
|
+
console.log("═".repeat(60));
|
|
172
|
+
for (const r of results) {
|
|
173
|
+
const status = r.passed ? "PASS" : "FAIL";
|
|
174
|
+
console.log(`\n [${status}] ${r.gateName}`);
|
|
175
|
+
for (const reason of r.reasons)
|
|
176
|
+
console.log(` ${reason}`);
|
|
177
|
+
}
|
|
178
|
+
console.log("\n" + "═".repeat(60));
|
|
179
|
+
if (!allPassed)
|
|
180
|
+
process.exitCode = 1;
|
|
181
|
+
}
|
|
182
|
+
else if (sub === "clear") {
|
|
183
|
+
saveGates([]);
|
|
184
|
+
console.log("Approval gates cleared.");
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
console.error(`Unknown subcommand: ${sub}. Use --help for usage.`);
|
|
188
|
+
process.exitCode = 1;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=review-approval-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-approval-gate.js","sourceRoot":"","sources":["../../src/commands/review-approval-gate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-branch-compare.d.ts","sourceRoot":"","sources":["../../src/commands/review-branch-compare.ts"],"names":[],"mappings":"AAAA;;GAEG;AAoCH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAwF3D"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review-branch-compare — Compare review results between git branches.
|
|
3
|
+
*/
|
|
4
|
+
import { readFileSync, existsSync } from "fs";
|
|
5
|
+
import { execSync } from "child_process";
|
|
6
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
7
|
+
function getCurrentBranch() {
|
|
8
|
+
try {
|
|
9
|
+
return execSync("git branch --show-current", { encoding: "utf-8" }).trim();
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return "unknown";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function getBranchDiff(base, head) {
|
|
16
|
+
try {
|
|
17
|
+
const output = execSync(`git diff --name-only ${base}...${head}`, { encoding: "utf-8" });
|
|
18
|
+
return output.trim().split("\n").filter(Boolean);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function loadVerdict(path) {
|
|
25
|
+
if (!existsSync(path))
|
|
26
|
+
return null;
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// ─── CLI ────────────────────────────────────────────────────────────────────
|
|
35
|
+
export function runReviewBranchCompare(argv) {
|
|
36
|
+
const baseIdx = argv.indexOf("--base");
|
|
37
|
+
const headIdx = argv.indexOf("--head");
|
|
38
|
+
const baseVerdictIdx = argv.indexOf("--base-verdict");
|
|
39
|
+
const headVerdictIdx = argv.indexOf("--head-verdict");
|
|
40
|
+
const formatIdx = argv.indexOf("--format");
|
|
41
|
+
const base = baseIdx >= 0 ? argv[baseIdx + 1] : "main";
|
|
42
|
+
const head = headIdx >= 0 ? argv[headIdx + 1] : getCurrentBranch();
|
|
43
|
+
const baseVerdictPath = baseVerdictIdx >= 0 ? argv[baseVerdictIdx + 1] : undefined;
|
|
44
|
+
const headVerdictPath = headVerdictIdx >= 0 ? argv[headVerdictIdx + 1] : undefined;
|
|
45
|
+
const format = formatIdx >= 0 ? argv[formatIdx + 1] : "table";
|
|
46
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
47
|
+
console.log(`
|
|
48
|
+
judges review-branch-compare — Compare reviews between branches
|
|
49
|
+
|
|
50
|
+
Usage:
|
|
51
|
+
judges review-branch-compare [--base <branch>] [--head <branch>]
|
|
52
|
+
[--base-verdict <path>] [--head-verdict <path>]
|
|
53
|
+
[--format table|json]
|
|
54
|
+
|
|
55
|
+
Options:
|
|
56
|
+
--base <branch> Base branch (default: main)
|
|
57
|
+
--head <branch> Head branch (default: current branch)
|
|
58
|
+
--base-verdict <path> Verdict JSON for base branch
|
|
59
|
+
--head-verdict <path> Verdict JSON for head branch
|
|
60
|
+
--format <fmt> Output format: table (default), json
|
|
61
|
+
--help, -h Show this help
|
|
62
|
+
`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const changedFiles = getBranchDiff(base, head);
|
|
66
|
+
const result = {
|
|
67
|
+
base,
|
|
68
|
+
head,
|
|
69
|
+
changedFiles: changedFiles.length,
|
|
70
|
+
fileList: changedFiles,
|
|
71
|
+
};
|
|
72
|
+
// If verdicts provided, compare them
|
|
73
|
+
if (baseVerdictPath && headVerdictPath) {
|
|
74
|
+
const baseVerdict = loadVerdict(baseVerdictPath);
|
|
75
|
+
const headVerdict = loadVerdict(headVerdictPath);
|
|
76
|
+
if (baseVerdict && headVerdict) {
|
|
77
|
+
const baseKeys = new Set(baseVerdict.findings.map((f) => `${f.ruleId}:${f.title}`));
|
|
78
|
+
const headKeys = new Set(headVerdict.findings.map((f) => `${f.ruleId}:${f.title}`));
|
|
79
|
+
const newFindings = headVerdict.findings.filter((f) => !baseKeys.has(`${f.ruleId}:${f.title}`));
|
|
80
|
+
const resolved = baseVerdict.findings.filter((f) => !headKeys.has(`${f.ruleId}:${f.title}`));
|
|
81
|
+
result.scoreChange = headVerdict.overallScore - baseVerdict.overallScore;
|
|
82
|
+
result.newFindings = newFindings.length;
|
|
83
|
+
result.resolvedFindings = resolved.length;
|
|
84
|
+
result.baseTotal = baseVerdict.findings.length;
|
|
85
|
+
result.headTotal = headVerdict.findings.length;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (format === "json") {
|
|
89
|
+
console.log(JSON.stringify(result, null, 2));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
console.log(`\nBranch Comparison: ${base} → ${head}`);
|
|
93
|
+
console.log("═".repeat(60));
|
|
94
|
+
console.log(`Changed files: ${changedFiles.length}`);
|
|
95
|
+
if (changedFiles.length > 0) {
|
|
96
|
+
console.log("\nChanged files:");
|
|
97
|
+
for (const f of changedFiles.slice(0, 20))
|
|
98
|
+
console.log(` ${f}`);
|
|
99
|
+
if (changedFiles.length > 20)
|
|
100
|
+
console.log(` ... and ${changedFiles.length - 20} more`);
|
|
101
|
+
}
|
|
102
|
+
if (result.scoreChange !== undefined) {
|
|
103
|
+
console.log("\n" + "─".repeat(60));
|
|
104
|
+
console.log("Verdict Comparison:");
|
|
105
|
+
const dir = result.scoreChange >= 0 ? "+" : "";
|
|
106
|
+
console.log(` Score change: ${dir}${result.scoreChange}`);
|
|
107
|
+
console.log(` Base findings: ${result.baseTotal}`);
|
|
108
|
+
console.log(` Head findings: ${result.headTotal}`);
|
|
109
|
+
console.log(` New findings: ${result.newFindings}`);
|
|
110
|
+
console.log(` Resolved: ${result.resolvedFindings}`);
|
|
111
|
+
}
|
|
112
|
+
console.log("═".repeat(60));
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=review-branch-compare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-branch-compare.js","sourceRoot":"","sources":["../../src/commands/review-branch-compare.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review-changelog-entry — Generate changelog entries from review findings.
|
|
3
|
+
*
|
|
4
|
+
* Creates structured changelog content from verdict findings,
|
|
5
|
+
* suitable for inclusion in project CHANGELOG files.
|
|
6
|
+
*/
|
|
7
|
+
export declare function runReviewChangelogEntry(argv: string[]): void;
|
|
8
|
+
//# sourceMappingURL=review-changelog-entry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-changelog-entry.d.ts","sourceRoot":"","sources":["../../src/commands/review-changelog-entry.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA0DH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CA+D5D"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review-changelog-entry — Generate changelog entries from review findings.
|
|
3
|
+
*
|
|
4
|
+
* Creates structured changelog content from verdict findings,
|
|
5
|
+
* suitable for inclusion in project CHANGELOG files.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync, existsSync } from "fs";
|
|
8
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
9
|
+
function generateChangelog(verdict, version) {
|
|
10
|
+
const lines = [];
|
|
11
|
+
const date = new Date().toISOString().split("T")[0];
|
|
12
|
+
lines.push(`## [${version}] — ${date}`);
|
|
13
|
+
lines.push("");
|
|
14
|
+
// Group findings by category
|
|
15
|
+
const securityFindings = verdict.findings.filter((f) => ["critical", "high"].includes((f.severity || "medium").toLowerCase()));
|
|
16
|
+
const qualityFindings = verdict.findings.filter((f) => ["medium", "low"].includes((f.severity || "medium").toLowerCase()));
|
|
17
|
+
if (securityFindings.length > 0) {
|
|
18
|
+
lines.push("### Security Fixes");
|
|
19
|
+
lines.push("");
|
|
20
|
+
for (const f of securityFindings) {
|
|
21
|
+
lines.push(`- Fixed: ${f.title} (${f.ruleId})`);
|
|
22
|
+
if (f.recommendation) {
|
|
23
|
+
const rec = f.recommendation.length > 80 ? f.recommendation.slice(0, 80) + "…" : f.recommendation;
|
|
24
|
+
lines.push(` - ${rec}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
lines.push("");
|
|
28
|
+
}
|
|
29
|
+
if (qualityFindings.length > 0) {
|
|
30
|
+
lines.push("### Quality Improvements");
|
|
31
|
+
lines.push("");
|
|
32
|
+
for (const f of qualityFindings) {
|
|
33
|
+
lines.push(`- Improved: ${f.title} (${f.ruleId})`);
|
|
34
|
+
}
|
|
35
|
+
lines.push("");
|
|
36
|
+
}
|
|
37
|
+
// Summary stats
|
|
38
|
+
lines.push("### Review Summary");
|
|
39
|
+
lines.push("");
|
|
40
|
+
lines.push(`- Score: ${verdict.overallScore}`);
|
|
41
|
+
lines.push(`- Verdict: ${verdict.overallVerdict}`);
|
|
42
|
+
lines.push(`- Findings addressed: ${verdict.findings.length}`);
|
|
43
|
+
if (verdict.criticalCount > 0)
|
|
44
|
+
lines.push(`- Critical issues fixed: ${verdict.criticalCount}`);
|
|
45
|
+
if (verdict.highCount > 0)
|
|
46
|
+
lines.push(`- High issues fixed: ${verdict.highCount}`);
|
|
47
|
+
return lines.join("\n");
|
|
48
|
+
}
|
|
49
|
+
// ─── CLI ────────────────────────────────────────────────────────────────────
|
|
50
|
+
export function runReviewChangelogEntry(argv) {
|
|
51
|
+
const fileIdx = argv.indexOf("--file");
|
|
52
|
+
const versionIdx = argv.indexOf("--version");
|
|
53
|
+
const formatIdx = argv.indexOf("--format");
|
|
54
|
+
const filePath = fileIdx >= 0 ? argv[fileIdx + 1] : undefined;
|
|
55
|
+
const version = versionIdx >= 0 ? argv[versionIdx + 1] : "unreleased";
|
|
56
|
+
const format = formatIdx >= 0 ? argv[formatIdx + 1] : "markdown";
|
|
57
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
58
|
+
console.log(`
|
|
59
|
+
judges review-changelog-entry — Generate changelog from findings
|
|
60
|
+
|
|
61
|
+
Usage:
|
|
62
|
+
judges review-changelog-entry --file <verdict.json> [--version <ver>]
|
|
63
|
+
[--format markdown|json]
|
|
64
|
+
|
|
65
|
+
Options:
|
|
66
|
+
--file <path> Path to verdict JSON file (required)
|
|
67
|
+
--version <ver> Version string (default: "unreleased")
|
|
68
|
+
--format <fmt> Output format: markdown (default), json
|
|
69
|
+
--help, -h Show this help
|
|
70
|
+
`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!filePath) {
|
|
74
|
+
console.error("Error: --file required");
|
|
75
|
+
process.exitCode = 1;
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (!existsSync(filePath)) {
|
|
79
|
+
console.error(`Error: not found: ${filePath}`);
|
|
80
|
+
process.exitCode = 1;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
let verdict;
|
|
84
|
+
try {
|
|
85
|
+
verdict = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
console.error("Error: invalid JSON");
|
|
89
|
+
process.exitCode = 1;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (format === "json") {
|
|
93
|
+
const entry = {
|
|
94
|
+
version,
|
|
95
|
+
date: new Date().toISOString().split("T")[0],
|
|
96
|
+
securityFixes: verdict.findings
|
|
97
|
+
.filter((f) => ["critical", "high"].includes((f.severity || "medium").toLowerCase()))
|
|
98
|
+
.map((f) => ({ title: f.title, ruleId: f.ruleId, severity: f.severity })),
|
|
99
|
+
qualityImprovements: verdict.findings
|
|
100
|
+
.filter((f) => ["medium", "low"].includes((f.severity || "medium").toLowerCase()))
|
|
101
|
+
.map((f) => ({ title: f.title, ruleId: f.ruleId })),
|
|
102
|
+
score: verdict.overallScore,
|
|
103
|
+
verdict: verdict.overallVerdict,
|
|
104
|
+
};
|
|
105
|
+
console.log(JSON.stringify(entry, null, 2));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
console.log(generateChangelog(verdict, version));
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=review-changelog-entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-changelog-entry.js","sourceRoot":"","sources":["../../src/commands/review-changelog-entry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review-code-owner — Map findings to CODEOWNERS entries.
|
|
3
|
+
*
|
|
4
|
+
* Cross-references findings with CODEOWNERS file to identify
|
|
5
|
+
* responsible teams/individuals for each finding.
|
|
6
|
+
*/
|
|
7
|
+
export declare function runReviewCodeOwner(argv: string[]): void;
|
|
8
|
+
//# sourceMappingURL=review-code-owner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-code-owner.d.ts","sourceRoot":"","sources":["../../src/commands/review-code-owner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAkEH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CA0HvD"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review-code-owner — Map findings to CODEOWNERS entries.
|
|
3
|
+
*
|
|
4
|
+
* Cross-references findings with CODEOWNERS file to identify
|
|
5
|
+
* responsible teams/individuals for each finding.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync, existsSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
10
|
+
function parseCodeowners(content) {
|
|
11
|
+
const rules = [];
|
|
12
|
+
for (const line of content.split("\n")) {
|
|
13
|
+
const trimmed = line.trim();
|
|
14
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
15
|
+
continue;
|
|
16
|
+
const parts = trimmed.split(/\s+/);
|
|
17
|
+
if (parts.length >= 2) {
|
|
18
|
+
rules.push({ pattern: parts[0], owners: parts.slice(1) });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return rules;
|
|
22
|
+
}
|
|
23
|
+
function findCodeownersFile() {
|
|
24
|
+
const candidates = [
|
|
25
|
+
join(process.cwd(), "CODEOWNERS"),
|
|
26
|
+
join(process.cwd(), ".github", "CODEOWNERS"),
|
|
27
|
+
join(process.cwd(), "docs", "CODEOWNERS"),
|
|
28
|
+
];
|
|
29
|
+
for (const c of candidates) {
|
|
30
|
+
if (existsSync(c))
|
|
31
|
+
return c;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
function matchPattern(filePath, pattern) {
|
|
36
|
+
if (pattern === "*")
|
|
37
|
+
return true;
|
|
38
|
+
if (pattern.endsWith("/"))
|
|
39
|
+
return filePath.startsWith(pattern) || filePath.includes("/" + pattern);
|
|
40
|
+
if (pattern.startsWith("*."))
|
|
41
|
+
return filePath.endsWith(pattern.slice(1));
|
|
42
|
+
if (pattern.includes("*")) {
|
|
43
|
+
const prefix = pattern.split("*")[0];
|
|
44
|
+
return filePath.startsWith(prefix) || filePath.includes(prefix);
|
|
45
|
+
}
|
|
46
|
+
return filePath.includes(pattern);
|
|
47
|
+
}
|
|
48
|
+
function inferFilePath(title) {
|
|
49
|
+
const match = title.match(/in\s+[`']?(\S+\.\w{1,5})[`']?/i);
|
|
50
|
+
return match ? match[1] : "";
|
|
51
|
+
}
|
|
52
|
+
// ─── CLI ────────────────────────────────────────────────────────────────────
|
|
53
|
+
export function runReviewCodeOwner(argv) {
|
|
54
|
+
const verdictIdx = argv.indexOf("--verdict");
|
|
55
|
+
const ownersIdx = argv.indexOf("--owners");
|
|
56
|
+
const formatIdx = argv.indexOf("--format");
|
|
57
|
+
const verdictPath = verdictIdx >= 0 ? argv[verdictIdx + 1] : undefined;
|
|
58
|
+
const ownersPath = ownersIdx >= 0 ? argv[ownersIdx + 1] : findCodeownersFile();
|
|
59
|
+
const format = formatIdx >= 0 ? argv[formatIdx + 1] : "table";
|
|
60
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
61
|
+
console.log(`
|
|
62
|
+
judges review-code-owner — Map findings to CODEOWNERS
|
|
63
|
+
|
|
64
|
+
Usage:
|
|
65
|
+
judges review-code-owner --verdict <verdict.json> [--owners <CODEOWNERS>]
|
|
66
|
+
[--format table|json]
|
|
67
|
+
|
|
68
|
+
Options:
|
|
69
|
+
--verdict <path> Path to verdict JSON file (required)
|
|
70
|
+
--owners <path> Path to CODEOWNERS file (auto-detected)
|
|
71
|
+
--format <fmt> Output format: table (default), json
|
|
72
|
+
--help, -h Show this help
|
|
73
|
+
`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!verdictPath) {
|
|
77
|
+
console.error("Error: --verdict required");
|
|
78
|
+
process.exitCode = 1;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (!existsSync(verdictPath)) {
|
|
82
|
+
console.error(`Error: verdict not found: ${verdictPath}`);
|
|
83
|
+
process.exitCode = 1;
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (!ownersPath) {
|
|
87
|
+
console.error("Error: CODEOWNERS file not found. Use --owners to specify.");
|
|
88
|
+
process.exitCode = 1;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (!existsSync(ownersPath)) {
|
|
92
|
+
console.error(`Error: CODEOWNERS not found: ${ownersPath}`);
|
|
93
|
+
process.exitCode = 1;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
let verdict;
|
|
97
|
+
try {
|
|
98
|
+
verdict = JSON.parse(readFileSync(verdictPath, "utf-8"));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
console.error("Error: invalid verdict JSON");
|
|
102
|
+
process.exitCode = 1;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const rules = parseCodeowners(readFileSync(ownersPath, "utf-8"));
|
|
106
|
+
const results = [];
|
|
107
|
+
for (const f of verdict.findings) {
|
|
108
|
+
const filePath = inferFilePath(f.title);
|
|
109
|
+
let matched = false;
|
|
110
|
+
// Match against CODEOWNERS rules (last match wins, per GitHub convention)
|
|
111
|
+
for (let i = rules.length - 1; i >= 0; i--) {
|
|
112
|
+
if (filePath && matchPattern(filePath, rules[i].pattern)) {
|
|
113
|
+
results.push({
|
|
114
|
+
ruleId: f.ruleId,
|
|
115
|
+
title: f.title,
|
|
116
|
+
severity: f.severity || "medium",
|
|
117
|
+
owners: rules[i].owners,
|
|
118
|
+
matchedPattern: rules[i].pattern,
|
|
119
|
+
});
|
|
120
|
+
matched = true;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (!matched) {
|
|
125
|
+
// Check for wildcard default owner
|
|
126
|
+
const defaultRule = rules.find((r) => r.pattern === "*");
|
|
127
|
+
results.push({
|
|
128
|
+
ruleId: f.ruleId,
|
|
129
|
+
title: f.title,
|
|
130
|
+
severity: f.severity || "medium",
|
|
131
|
+
owners: defaultRule ? defaultRule.owners : ["unassigned"],
|
|
132
|
+
matchedPattern: defaultRule ? "*" : "none",
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (format === "json") {
|
|
137
|
+
console.log(JSON.stringify(results, null, 2));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
console.log(`\nFinding Ownership (${results.length} findings)`);
|
|
141
|
+
console.log("═".repeat(70));
|
|
142
|
+
console.log(`${"Owner(s)".padEnd(25)} ${"Severity".padEnd(10)} Title`);
|
|
143
|
+
console.log("─".repeat(70));
|
|
144
|
+
for (const r of results) {
|
|
145
|
+
const owners = r.owners.join(", ");
|
|
146
|
+
const ownerStr = owners.length > 23 ? owners.slice(0, 23) + "…" : owners;
|
|
147
|
+
const sev = (r.severity || "medium").padEnd(10);
|
|
148
|
+
const title = r.title.length > 30 ? r.title.slice(0, 30) + "…" : r.title;
|
|
149
|
+
console.log(`${ownerStr.padEnd(25)} ${sev} ${title}`);
|
|
150
|
+
}
|
|
151
|
+
// Owner summary
|
|
152
|
+
const ownerCounts = new Map();
|
|
153
|
+
for (const r of results) {
|
|
154
|
+
for (const o of r.owners) {
|
|
155
|
+
ownerCounts.set(o, (ownerCounts.get(o) || 0) + 1);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
console.log("\n" + "─".repeat(70));
|
|
159
|
+
console.log("Owner Summary:");
|
|
160
|
+
for (const [owner, count] of [...ownerCounts.entries()].sort((a, b) => b[1] - a[1])) {
|
|
161
|
+
console.log(` ${owner}: ${count} finding(s)`);
|
|
162
|
+
}
|
|
163
|
+
console.log("═".repeat(70));
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=review-code-owner.js.map
|