@kevinrabun/judges 3.61.0 → 3.62.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 +7 -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/batch-review.d.ts +5 -0
- package/dist/commands/batch-review.d.ts.map +1 -0
- package/dist/commands/batch-review.js +181 -0
- package/dist/commands/batch-review.js.map +1 -0
- package/dist/commands/custom-rule.d.ts +5 -0
- package/dist/commands/custom-rule.d.ts.map +1 -0
- package/dist/commands/custom-rule.js +211 -0
- package/dist/commands/custom-rule.js.map +1 -0
- package/dist/commands/diff-review.d.ts +5 -0
- package/dist/commands/diff-review.d.ts.map +1 -0
- package/dist/commands/diff-review.js +191 -0
- package/dist/commands/diff-review.js.map +1 -0
- package/dist/commands/focus-area.d.ts +6 -0
- package/dist/commands/focus-area.d.ts.map +1 -0
- package/dist/commands/focus-area.js +193 -0
- package/dist/commands/focus-area.js.map +1 -0
- package/dist/commands/review-compare.d.ts +5 -0
- package/dist/commands/review-compare.d.ts.map +1 -0
- package/dist/commands/review-compare.js +201 -0
- package/dist/commands/review-compare.js.map +1 -0
- package/dist/commands/review-explain.d.ts +6 -0
- package/dist/commands/review-explain.d.ts.map +1 -0
- package/dist/commands/review-explain.js +195 -0
- package/dist/commands/review-explain.js.map +1 -0
- package/dist/commands/review-gate.d.ts +5 -0
- package/dist/commands/review-gate.d.ts.map +1 -0
- package/dist/commands/review-gate.js +213 -0
- package/dist/commands/review-gate.js.map +1 -0
- package/dist/commands/severity-tune.d.ts +5 -0
- package/dist/commands/severity-tune.d.ts.map +1 -0
- package/dist/commands/severity-tune.js +209 -0
- package/dist/commands/severity-tune.js.map +1 -0
- package/dist/commands/trend-report.d.ts +5 -0
- package/dist/commands/trend-report.d.ts.map +1 -0
- package/dist/commands/trend-report.js +149 -0
- package/dist/commands/trend-report.js.map +1 -0
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Focus-area — Identify high-risk areas that need the most review attention
|
|
3
|
+
* based on code complexity and pattern density.
|
|
4
|
+
*/
|
|
5
|
+
import { readFileSync, readdirSync, statSync } from "fs";
|
|
6
|
+
import { join, extname, relative } from "path";
|
|
7
|
+
// ─── Patterns ──────────────────────────────────────────────────────────────
|
|
8
|
+
const RISK_PATTERNS = [
|
|
9
|
+
{ name: "hardcoded-secret", weight: 10, regex: /(?:password|secret|api_key|token)\s*[:=]\s*["'][^"']{8,}/i },
|
|
10
|
+
{ name: "eval-usage", weight: 10, regex: /\beval\s*\(/ },
|
|
11
|
+
{ name: "sql-concat", weight: 10, regex: /(?:query|execute)\s*\(\s*["'`].*\+/ },
|
|
12
|
+
{ name: "xss-risk", weight: 8, regex: /innerHTML\s*=|document\.write\s*\(/ },
|
|
13
|
+
{ name: "command-injection", weight: 10, regex: /exec(?:Sync)?\s*\(\s*`[^`]*\$\{/ },
|
|
14
|
+
{ name: "empty-catch", weight: 3, regex: /catch\s*\([^)]*\)\s*\{\s*\}/ },
|
|
15
|
+
{ name: "any-type", weight: 2, regex: /:\s*any\b/ },
|
|
16
|
+
{ name: "unsafe-regex", weight: 7, regex: /new\s+RegExp\s*\([^)]*\+/ },
|
|
17
|
+
{ name: "nested-callback", weight: 4, regex: /\)\s*=>\s*\{[^}]*\)\s*=>\s*\{/ },
|
|
18
|
+
{ name: "deep-nesting", weight: 5, regex: /^\s{16,}\S/ },
|
|
19
|
+
{ name: "deprecated-api", weight: 3, regex: /new\s+Buffer\s*\(|\.substr\s*\(/ },
|
|
20
|
+
];
|
|
21
|
+
// ─── Complexity estimation ─────────────────────────────────────────────────
|
|
22
|
+
function estimateComplexity(content) {
|
|
23
|
+
const lines = content.split("\n");
|
|
24
|
+
let complexity = 0;
|
|
25
|
+
for (const line of lines) {
|
|
26
|
+
// Control flow
|
|
27
|
+
if (/\b(?:if|else if|switch|case|while|for|catch)\b/.test(line))
|
|
28
|
+
complexity++;
|
|
29
|
+
// Logical operators
|
|
30
|
+
if (/&&|\|\|/.test(line))
|
|
31
|
+
complexity++;
|
|
32
|
+
// Ternary
|
|
33
|
+
if (/[^?][?][^?]/.test(line))
|
|
34
|
+
complexity += 0.5;
|
|
35
|
+
// Nested functions
|
|
36
|
+
if (/(?:function\s+\w+|=>\s*\{)/.test(line))
|
|
37
|
+
complexity += 0.5;
|
|
38
|
+
}
|
|
39
|
+
return Math.round(complexity);
|
|
40
|
+
}
|
|
41
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
42
|
+
function collectSourceFiles(dir) {
|
|
43
|
+
const exts = new Set([".ts", ".js", ".tsx", ".jsx", ".py", ".java", ".go", ".rs", ".cs"]);
|
|
44
|
+
const files = [];
|
|
45
|
+
const skipDirs = new Set(["node_modules", ".git", "dist", "build", "coverage"]);
|
|
46
|
+
function walk(d) {
|
|
47
|
+
let entries;
|
|
48
|
+
try {
|
|
49
|
+
entries = readdirSync(d);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
for (const name of entries) {
|
|
55
|
+
if (skipDirs.has(name))
|
|
56
|
+
continue;
|
|
57
|
+
const full = join(d, name);
|
|
58
|
+
try {
|
|
59
|
+
const st = statSync(full);
|
|
60
|
+
if (st.isDirectory())
|
|
61
|
+
walk(full);
|
|
62
|
+
else if (exts.has(extname(name)))
|
|
63
|
+
files.push(full);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// skip
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
walk(dir);
|
|
71
|
+
return files;
|
|
72
|
+
}
|
|
73
|
+
function analyzeRisk(files, baseDir) {
|
|
74
|
+
const fileRisks = [];
|
|
75
|
+
for (const filePath of files) {
|
|
76
|
+
let content;
|
|
77
|
+
try {
|
|
78
|
+
content = readFileSync(filePath, "utf-8");
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const lines = content.split("\n");
|
|
84
|
+
const lineCount = lines.length;
|
|
85
|
+
const complexity = estimateComplexity(content);
|
|
86
|
+
const patternHits = new Map();
|
|
87
|
+
let totalWeight = 0;
|
|
88
|
+
for (const line of lines) {
|
|
89
|
+
for (const pat of RISK_PATTERNS) {
|
|
90
|
+
if (pat.regex.test(line)) {
|
|
91
|
+
totalWeight += pat.weight;
|
|
92
|
+
patternHits.set(pat.name, (patternHits.get(pat.name) || 0) + 1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const findingCount = [...patternHits.values()].reduce((a, b) => a + b, 0);
|
|
97
|
+
const patternDensity = lineCount > 0 ? Math.round((findingCount / lineCount) * 1000) / 10 : 0;
|
|
98
|
+
// Risk score: weighted sum of complexity, pattern weight, and density
|
|
99
|
+
const riskScore = Math.round(complexity * 0.3 + totalWeight * 2 + patternDensity * 10);
|
|
100
|
+
if (riskScore > 0) {
|
|
101
|
+
const topPatterns = [...patternHits.entries()]
|
|
102
|
+
.sort((a, b) => b[1] - a[1])
|
|
103
|
+
.slice(0, 3)
|
|
104
|
+
.map(([name]) => name);
|
|
105
|
+
fileRisks.push({
|
|
106
|
+
file: relative(baseDir, filePath),
|
|
107
|
+
riskScore,
|
|
108
|
+
complexity,
|
|
109
|
+
patternDensity,
|
|
110
|
+
lineCount,
|
|
111
|
+
findingCount,
|
|
112
|
+
topPatterns,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
fileRisks.sort((a, b) => b.riskScore - a.riskScore);
|
|
117
|
+
const distribution = { high: 0, medium: 0, low: 0 };
|
|
118
|
+
for (const fr of fileRisks) {
|
|
119
|
+
if (fr.riskScore >= 50)
|
|
120
|
+
distribution.high++;
|
|
121
|
+
else if (fr.riskScore >= 20)
|
|
122
|
+
distribution.medium++;
|
|
123
|
+
else
|
|
124
|
+
distribution.low++;
|
|
125
|
+
}
|
|
126
|
+
const summary = distribution.high > 0
|
|
127
|
+
? `${distribution.high} high-risk file(s) need immediate review attention.`
|
|
128
|
+
: fileRisks.length > 0
|
|
129
|
+
? `No critical risk areas. ${fileRisks.length} files have minor improvements available.`
|
|
130
|
+
: "No risk areas detected in scanned files.";
|
|
131
|
+
return {
|
|
132
|
+
filesAnalyzed: files.length,
|
|
133
|
+
highRiskFiles: fileRisks.slice(0, 20),
|
|
134
|
+
riskDistribution: distribution,
|
|
135
|
+
summary,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// ─── CLI ────────────────────────────────────────────────────────────────────
|
|
139
|
+
export function runFocusArea(argv) {
|
|
140
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
141
|
+
console.log(`
|
|
142
|
+
judges focus-area — Identify high-risk areas needing review attention
|
|
143
|
+
|
|
144
|
+
Usage:
|
|
145
|
+
judges focus-area [dir] Analyze risk areas
|
|
146
|
+
judges focus-area --top 10 Show top N files
|
|
147
|
+
judges focus-area --format json JSON output
|
|
148
|
+
|
|
149
|
+
Options:
|
|
150
|
+
[dir] Target directory (default: .)
|
|
151
|
+
--top <n> Number of files to show (default: 20)
|
|
152
|
+
--format json JSON output
|
|
153
|
+
--help, -h Show this help
|
|
154
|
+
|
|
155
|
+
Combines code complexity and security pattern density to rank files by
|
|
156
|
+
risk. Use this to prioritize review effort where it matters most.
|
|
157
|
+
`);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const format = argv.find((_a, i) => argv[i - 1] === "--format") || "text";
|
|
161
|
+
const dir = argv.find((a) => !a.startsWith("-") &&
|
|
162
|
+
a !== "focus-area" &&
|
|
163
|
+
argv[argv.indexOf(a) - 1] !== "--format" &&
|
|
164
|
+
argv[argv.indexOf(a) - 1] !== "--top") || ".";
|
|
165
|
+
const topN = parseInt(argv.find((_a, i) => argv[i - 1] === "--top") || "20", 10);
|
|
166
|
+
const files = collectSourceFiles(dir);
|
|
167
|
+
if (files.length === 0) {
|
|
168
|
+
console.log("No source files found.");
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const result = analyzeRisk(files, dir);
|
|
172
|
+
if (format === "json") {
|
|
173
|
+
console.log(JSON.stringify(result, null, 2));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
console.log(`\n Focus Area Analysis\n ─────────────────────────────`);
|
|
177
|
+
console.log(` Files analyzed: ${result.filesAnalyzed}`);
|
|
178
|
+
console.log(` Risk distribution: 🔴 ${result.riskDistribution.high} high 🟡 ${result.riskDistribution.medium} medium 🟢 ${result.riskDistribution.low} low`);
|
|
179
|
+
console.log(`\n ${result.summary}`);
|
|
180
|
+
const display = result.highRiskFiles.slice(0, topN);
|
|
181
|
+
if (display.length > 0) {
|
|
182
|
+
console.log("\n Highest risk files:");
|
|
183
|
+
for (const fr of display) {
|
|
184
|
+
const icon = fr.riskScore >= 50 ? "🔴" : fr.riskScore >= 20 ? "🟡" : "🟢";
|
|
185
|
+
console.log(` ${icon} [${fr.riskScore}] ${fr.file} (${fr.lineCount} lines, ${fr.findingCount} findings, complexity: ${fr.complexity})`);
|
|
186
|
+
if (fr.topPatterns.length > 0) {
|
|
187
|
+
console.log(` Top patterns: ${fr.topPatterns.join(", ")}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
console.log();
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=focus-area.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"focus-area.js","sourceRoot":"","sources":["../../src/commands/focus-area.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-compare.d.ts","sourceRoot":"","sources":["../../src/commands/review-compare.ts"],"names":[],"mappings":"AAAA;;GAEG;AA0FH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAuJrD"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review-compare — Compare two review runs to measure improvement.
|
|
3
|
+
*/
|
|
4
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
7
|
+
function getSnapshotsDir() {
|
|
8
|
+
return join(".", ".judges", "snapshots");
|
|
9
|
+
}
|
|
10
|
+
function loadSnapshot(id) {
|
|
11
|
+
const dir = getSnapshotsDir();
|
|
12
|
+
const filePath = join(dir, `${id}.json`);
|
|
13
|
+
if (!existsSync(filePath))
|
|
14
|
+
return null;
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(readFileSync(filePath, "utf-8"));
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function saveSnapshot(snapshot) {
|
|
23
|
+
const dir = getSnapshotsDir();
|
|
24
|
+
if (!existsSync(dir))
|
|
25
|
+
mkdirSync(dir, { recursive: true });
|
|
26
|
+
writeFileSync(join(dir, `${snapshot.id}.json`), JSON.stringify(snapshot, null, 2), "utf-8");
|
|
27
|
+
}
|
|
28
|
+
function listSnapshots() {
|
|
29
|
+
const dir = getSnapshotsDir();
|
|
30
|
+
if (!existsSync(dir))
|
|
31
|
+
return [];
|
|
32
|
+
try {
|
|
33
|
+
return readdirSync(dir)
|
|
34
|
+
.filter((f) => f.endsWith(".json"))
|
|
35
|
+
.map((f) => f.replace(".json", ""));
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function compareSnapshots(before, after) {
|
|
42
|
+
// Build finding signatures for comparison
|
|
43
|
+
const beforeSigs = new Set(before.findings.map((f) => `${f.pattern}:${f.file}:${f.line}`));
|
|
44
|
+
const afterSigs = new Set(after.findings.map((f) => `${f.pattern}:${f.file}:${f.line}`));
|
|
45
|
+
const newFindings = after.findings
|
|
46
|
+
.filter((f) => !beforeSigs.has(`${f.pattern}:${f.file}:${f.line}`))
|
|
47
|
+
.map((f) => ({ pattern: f.pattern, severity: f.severity, file: f.file }));
|
|
48
|
+
const fixedFindings = before.findings
|
|
49
|
+
.filter((f) => !afterSigs.has(`${f.pattern}:${f.file}:${f.line}`))
|
|
50
|
+
.map((f) => ({ pattern: f.pattern, severity: f.severity, file: f.file }));
|
|
51
|
+
const persisting = after.counts.total - newFindings.length;
|
|
52
|
+
const improvement = before.counts.total - after.counts.total;
|
|
53
|
+
const improvementPercent = before.counts.total > 0 ? Math.round((improvement / before.counts.total) * 100) : 0;
|
|
54
|
+
return {
|
|
55
|
+
before: { id: before.id, label: before.label, total: before.counts.total },
|
|
56
|
+
after: { id: after.id, label: after.label, total: after.counts.total },
|
|
57
|
+
improvement,
|
|
58
|
+
improvementPercent,
|
|
59
|
+
newFindings,
|
|
60
|
+
fixedFindings,
|
|
61
|
+
persistingFindings: Math.max(0, persisting),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// ─── CLI ────────────────────────────────────────────────────────────────────
|
|
65
|
+
export function runReviewCompare(argv) {
|
|
66
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
67
|
+
console.log(`
|
|
68
|
+
judges review-compare — Compare two review runs to measure improvement
|
|
69
|
+
|
|
70
|
+
Usage:
|
|
71
|
+
judges review-compare save --id run1 --label "Before refactor" --findings findings.json
|
|
72
|
+
judges review-compare diff --before run1 --after run2
|
|
73
|
+
judges review-compare list List saved snapshots
|
|
74
|
+
judges review-compare --format json JSON output
|
|
75
|
+
|
|
76
|
+
Subcommands:
|
|
77
|
+
save Save a review snapshot for later comparison
|
|
78
|
+
diff Compare two saved snapshots
|
|
79
|
+
list List all saved snapshots
|
|
80
|
+
|
|
81
|
+
Save Options:
|
|
82
|
+
--id <name> Snapshot identifier
|
|
83
|
+
--label <text> Human-readable label
|
|
84
|
+
--findings <path> JSON file with findings array
|
|
85
|
+
|
|
86
|
+
Diff Options:
|
|
87
|
+
--before <id> First snapshot ID
|
|
88
|
+
--after <id> Second snapshot ID
|
|
89
|
+
|
|
90
|
+
Snapshots are stored locally in .judges/snapshots/.
|
|
91
|
+
`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const format = argv.find((_a, i) => argv[i - 1] === "--format") || "text";
|
|
95
|
+
const subcommand = argv.find((a) => !a.startsWith("-") && a !== "review-compare") || "list";
|
|
96
|
+
if (subcommand === "save") {
|
|
97
|
+
const id = argv.find((_a, i) => argv[i - 1] === "--id");
|
|
98
|
+
const label = argv.find((_a, i) => argv[i - 1] === "--label") || id || "unlabeled";
|
|
99
|
+
const findingsPath = argv.find((_a, i) => argv[i - 1] === "--findings");
|
|
100
|
+
if (!id) {
|
|
101
|
+
console.error("Error: --id is required for save.");
|
|
102
|
+
process.exitCode = 1;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
let findings = [];
|
|
106
|
+
if (findingsPath && existsSync(findingsPath)) {
|
|
107
|
+
try {
|
|
108
|
+
const raw = JSON.parse(readFileSync(findingsPath, "utf-8"));
|
|
109
|
+
findings = Array.isArray(raw) ? raw : Array.isArray(raw.findings) ? raw.findings : [];
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
console.error("Error: Cannot parse findings file.");
|
|
113
|
+
process.exitCode = 1;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const counts = { critical: 0, high: 0, medium: 0, low: 0, total: findings.length };
|
|
118
|
+
for (const f of findings) {
|
|
119
|
+
if (f.severity === "critical")
|
|
120
|
+
counts.critical++;
|
|
121
|
+
else if (f.severity === "high")
|
|
122
|
+
counts.high++;
|
|
123
|
+
else if (f.severity === "medium")
|
|
124
|
+
counts.medium++;
|
|
125
|
+
else
|
|
126
|
+
counts.low++;
|
|
127
|
+
}
|
|
128
|
+
const snapshot = { id, timestamp: new Date().toISOString(), label, findings, counts };
|
|
129
|
+
saveSnapshot(snapshot);
|
|
130
|
+
console.log(`Saved snapshot '${id}' with ${findings.length} findings.`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (subcommand === "list") {
|
|
134
|
+
const ids = listSnapshots();
|
|
135
|
+
if (ids.length === 0) {
|
|
136
|
+
console.log("No snapshots saved. Use 'judges review-compare save' to create one.");
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
console.log(`\n Saved Snapshots:\n ─────────────────────────────`);
|
|
140
|
+
for (const id of ids) {
|
|
141
|
+
const snap = loadSnapshot(id);
|
|
142
|
+
if (snap) {
|
|
143
|
+
console.log(` ${snap.id} — ${snap.label} (${snap.counts.total} findings, ${snap.timestamp.slice(0, 10)})`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
console.log();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (subcommand === "diff") {
|
|
150
|
+
const beforeId = argv.find((_a, i) => argv[i - 1] === "--before");
|
|
151
|
+
const afterId = argv.find((_a, i) => argv[i - 1] === "--after");
|
|
152
|
+
if (!beforeId || !afterId) {
|
|
153
|
+
console.error("Error: Both --before and --after snapshot IDs are required.");
|
|
154
|
+
process.exitCode = 1;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const before = loadSnapshot(beforeId);
|
|
158
|
+
const after = loadSnapshot(afterId);
|
|
159
|
+
if (!before) {
|
|
160
|
+
console.error(`Error: Snapshot '${beforeId}' not found.`);
|
|
161
|
+
process.exitCode = 1;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (!after) {
|
|
165
|
+
console.error(`Error: Snapshot '${afterId}' not found.`);
|
|
166
|
+
process.exitCode = 1;
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const result = compareSnapshots(before, after);
|
|
170
|
+
if (format === "json") {
|
|
171
|
+
console.log(JSON.stringify(result, null, 2));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const trendIcon = result.improvement > 0 ? "📉" : result.improvement < 0 ? "📈" : "➡️";
|
|
175
|
+
console.log(`\n Review Comparison\n ─────────────────────────────`);
|
|
176
|
+
console.log(` Before: ${result.before.label} (${result.before.total} findings)`);
|
|
177
|
+
console.log(` After: ${result.after.label} (${result.after.total} findings)`);
|
|
178
|
+
console.log(` ${trendIcon} Change: ${result.improvement > 0 ? "-" : "+"}${Math.abs(result.improvement)} findings (${result.improvementPercent}%)`);
|
|
179
|
+
console.log(` Fixed: ${result.fixedFindings.length} New: ${result.newFindings.length} Persisting: ${result.persistingFindings}`);
|
|
180
|
+
if (result.fixedFindings.length > 0) {
|
|
181
|
+
console.log("\n Fixed findings:");
|
|
182
|
+
for (const f of result.fixedFindings.slice(0, 10)) {
|
|
183
|
+
console.log(` ✅ [${f.severity}] ${f.pattern} — ${f.file}`);
|
|
184
|
+
}
|
|
185
|
+
if (result.fixedFindings.length > 10)
|
|
186
|
+
console.log(` ... +${result.fixedFindings.length - 10} more`);
|
|
187
|
+
}
|
|
188
|
+
if (result.newFindings.length > 0) {
|
|
189
|
+
console.log("\n New findings:");
|
|
190
|
+
for (const f of result.newFindings.slice(0, 10)) {
|
|
191
|
+
console.log(` ❌ [${f.severity}] ${f.pattern} — ${f.file}`);
|
|
192
|
+
}
|
|
193
|
+
if (result.newFindings.length > 10)
|
|
194
|
+
console.log(` ... +${result.newFindings.length - 10} more`);
|
|
195
|
+
}
|
|
196
|
+
console.log();
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
console.log("Unknown subcommand. Use --help for usage.");
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=review-compare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-compare.js","sourceRoot":"","sources":["../../src/commands/review-compare.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-explain.d.ts","sourceRoot":"","sources":["../../src/commands/review-explain.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA6JH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CA8ErD"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review-explain — Generate plain-language explanation of review findings
|
|
3
|
+
* for non-technical stakeholders.
|
|
4
|
+
*/
|
|
5
|
+
import { readFileSync, existsSync } from "fs";
|
|
6
|
+
// ─── Plain-language explanations ───────────────────────────────────────────
|
|
7
|
+
const EXPLANATIONS = {
|
|
8
|
+
"hardcoded-secret": {
|
|
9
|
+
plain: "A password or secret key is written directly in the code, visible to anyone who can see the source.",
|
|
10
|
+
impact: "If code is shared or leaked, attackers can immediately access protected systems. This is a leading cause of data breaches.",
|
|
11
|
+
recommendation: "Move secrets to environment variables or a secrets manager. Never commit credentials to version control.",
|
|
12
|
+
},
|
|
13
|
+
"eval-usage": {
|
|
14
|
+
plain: "The code can execute arbitrary commands, potentially from untrusted input.",
|
|
15
|
+
impact: "An attacker could inject malicious code that runs with full application permissions, leading to data theft or system compromise.",
|
|
16
|
+
recommendation: "Replace with safer alternatives that don't allow arbitrary code execution.",
|
|
17
|
+
},
|
|
18
|
+
"sql-injection": {
|
|
19
|
+
plain: "User input is being inserted directly into database queries without protection.",
|
|
20
|
+
impact: "Attackers can manipulate queries to steal, modify, or delete data. SQL injection is consistently in the OWASP Top 10.",
|
|
21
|
+
recommendation: "Use parameterized queries or an ORM that handles input sanitization automatically.",
|
|
22
|
+
},
|
|
23
|
+
"sql-concat": {
|
|
24
|
+
plain: "Database queries are built by concatenating strings, which can allow injection attacks.",
|
|
25
|
+
impact: "Similar to SQL injection — attackers can manipulate queries through crafted input.",
|
|
26
|
+
recommendation: "Use parameterized queries instead of string concatenation.",
|
|
27
|
+
},
|
|
28
|
+
"xss-risk": {
|
|
29
|
+
plain: "The code inserts content directly into web pages without sanitization.",
|
|
30
|
+
impact: "Attackers can inject scripts that steal user credentials, redirect users, or deface the application.",
|
|
31
|
+
recommendation: "Always sanitize user content before displaying it. Use framework-provided escaping functions.",
|
|
32
|
+
},
|
|
33
|
+
"command-injection": {
|
|
34
|
+
plain: "The code runs system commands that include user-controllable data.",
|
|
35
|
+
impact: "An attacker could execute arbitrary commands on the server, potentially taking full control.",
|
|
36
|
+
recommendation: "Avoid passing user input to system commands. Use libraries with safe APIs instead.",
|
|
37
|
+
},
|
|
38
|
+
"empty-catch": {
|
|
39
|
+
plain: "Errors are being silently ignored — the code catches errors but does nothing with them.",
|
|
40
|
+
impact: "Issues go undetected in production, making debugging extremely difficult and potentially hiding security problems.",
|
|
41
|
+
recommendation: "Log errors appropriately and handle them based on their severity.",
|
|
42
|
+
},
|
|
43
|
+
"any-type": {
|
|
44
|
+
plain: "Code uses unsafe typing that bypasses compile-time safety checks.",
|
|
45
|
+
impact: "Bugs that would normally be caught before deployment can slip through to production.",
|
|
46
|
+
recommendation: "Use proper type definitions to catch errors during development rather than in production.",
|
|
47
|
+
},
|
|
48
|
+
"console-log": {
|
|
49
|
+
plain: "Debug logging statements are present in production code.",
|
|
50
|
+
impact: "Can leak sensitive information and create unnecessary noise in production logs.",
|
|
51
|
+
recommendation: "Use a proper logging framework with configurable levels.",
|
|
52
|
+
},
|
|
53
|
+
"deprecated-api": {
|
|
54
|
+
plain: "The code uses outdated functions that may be removed in future releases.",
|
|
55
|
+
impact: "Future updates could break the application. Deprecated APIs may also have known security vulnerabilities.",
|
|
56
|
+
recommendation: "Migrate to the recommended replacement APIs.",
|
|
57
|
+
},
|
|
58
|
+
"unsafe-regex": {
|
|
59
|
+
plain: "A pattern matching expression is built from user input, which could be exploited.",
|
|
60
|
+
impact: "Attackers can craft input that causes the application to hang or consume excessive resources.",
|
|
61
|
+
recommendation: "Validate and escape user input before using it in pattern matching.",
|
|
62
|
+
},
|
|
63
|
+
"todo-fixme": {
|
|
64
|
+
plain: "The code contains unfinished work markers that haven't been addressed.",
|
|
65
|
+
impact: "Incomplete implementations may have missing validation, error handling, or security checks.",
|
|
66
|
+
recommendation: "Track these in your project management tool and resolve before release.",
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
// ─── Analysis ──────────────────────────────────────────────────────────────
|
|
70
|
+
function explainFindings(findings) {
|
|
71
|
+
// Deduplicate by pattern
|
|
72
|
+
const patternSet = new Map();
|
|
73
|
+
for (const f of findings) {
|
|
74
|
+
const existing = patternSet.get(f.pattern);
|
|
75
|
+
if (existing) {
|
|
76
|
+
existing.count++;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
patternSet.set(f.pattern, { severity: f.severity, count: 1 });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const explanations = [];
|
|
83
|
+
for (const [pattern, data] of patternSet) {
|
|
84
|
+
const expl = EXPLANATIONS[pattern] || {
|
|
85
|
+
plain: `Found ${data.count} instance(s) of '${pattern}' pattern.`,
|
|
86
|
+
impact: "This finding may affect code quality or security depending on context.",
|
|
87
|
+
recommendation: "Review each instance and address based on your team's standards.",
|
|
88
|
+
};
|
|
89
|
+
explanations.push({
|
|
90
|
+
pattern,
|
|
91
|
+
severity: data.severity,
|
|
92
|
+
plainExplanation: expl.plain,
|
|
93
|
+
businessImpact: expl.impact,
|
|
94
|
+
recommendation: expl.recommendation,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
// Sort by severity
|
|
98
|
+
const sevRank = { critical: 4, high: 3, medium: 2, low: 1 };
|
|
99
|
+
explanations.sort((a, b) => (sevRank[b.severity] || 0) - (sevRank[a.severity] || 0));
|
|
100
|
+
const criticalCount = findings.filter((f) => f.severity === "critical").length;
|
|
101
|
+
const highCount = findings.filter((f) => f.severity === "high").length;
|
|
102
|
+
let riskLevel = "Low";
|
|
103
|
+
if (criticalCount > 0)
|
|
104
|
+
riskLevel = "Critical";
|
|
105
|
+
else if (highCount > 0)
|
|
106
|
+
riskLevel = "High";
|
|
107
|
+
else if (findings.length > 10)
|
|
108
|
+
riskLevel = "Medium";
|
|
109
|
+
let executiveSummary;
|
|
110
|
+
if (findings.length === 0) {
|
|
111
|
+
executiveSummary = "No issues were found in this review. The code appears to meet quality and security standards.";
|
|
112
|
+
}
|
|
113
|
+
else if (criticalCount > 0) {
|
|
114
|
+
executiveSummary = `This review found ${criticalCount} critical security issue(s) that must be resolved before deployment. These issues could lead to data breaches or system compromise if exploited.`;
|
|
115
|
+
}
|
|
116
|
+
else if (highCount > 0) {
|
|
117
|
+
executiveSummary = `This review found ${highCount} high-severity issue(s) that should be addressed promptly. While not immediately exploitable, they represent significant risk.`;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
executiveSummary = `This review found ${findings.length} issue(s), none critical. These represent code quality improvements that will reduce technical debt.`;
|
|
121
|
+
}
|
|
122
|
+
return { totalFindings: findings.length, explanations, executiveSummary, riskLevel };
|
|
123
|
+
}
|
|
124
|
+
// ─── CLI ────────────────────────────────────────────────────────────────────
|
|
125
|
+
export function runReviewExplain(argv) {
|
|
126
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
127
|
+
console.log(`
|
|
128
|
+
judges review-explain — Plain-language explanation of review findings
|
|
129
|
+
|
|
130
|
+
Usage:
|
|
131
|
+
judges review-explain --file findings.json Explain findings from JSON
|
|
132
|
+
judges review-explain --format json JSON output
|
|
133
|
+
|
|
134
|
+
Options:
|
|
135
|
+
--file <path> Path to findings JSON (array or {findings: [...]})
|
|
136
|
+
--format json JSON output
|
|
137
|
+
--help, -h Show this help
|
|
138
|
+
|
|
139
|
+
Translates technical security and quality findings into plain language
|
|
140
|
+
that non-technical stakeholders can understand. Includes business impact
|
|
141
|
+
and actionable recommendations.
|
|
142
|
+
`);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const format = argv.find((_a, i) => argv[i - 1] === "--format") || "text";
|
|
146
|
+
const findingsFile = argv.find((_a, i) => argv[i - 1] === "--file");
|
|
147
|
+
if (!findingsFile) {
|
|
148
|
+
console.error("Error: --file <path> is required. Provide a JSON file with findings.");
|
|
149
|
+
process.exitCode = 1;
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (!existsSync(findingsFile)) {
|
|
153
|
+
console.error(`Error: File '${findingsFile}' not found.`);
|
|
154
|
+
process.exitCode = 1;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
let findings;
|
|
158
|
+
try {
|
|
159
|
+
const raw = JSON.parse(readFileSync(findingsFile, "utf-8"));
|
|
160
|
+
findings = Array.isArray(raw) ? raw : Array.isArray(raw.findings) ? raw.findings : [];
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
console.error("Error: Cannot parse findings file.");
|
|
164
|
+
process.exitCode = 1;
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const result = explainFindings(findings);
|
|
168
|
+
if (format === "json") {
|
|
169
|
+
console.log(JSON.stringify(result, null, 2));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
console.log(`\n Review Explanation\n ─────────────────────────────`);
|
|
173
|
+
console.log(` Risk Level: ${result.riskLevel}`);
|
|
174
|
+
console.log(` Total Findings: ${result.totalFindings}\n`);
|
|
175
|
+
console.log(` Executive Summary:`);
|
|
176
|
+
console.log(` ${result.executiveSummary}`);
|
|
177
|
+
if (result.explanations.length > 0) {
|
|
178
|
+
console.log("\n Detailed Findings:");
|
|
179
|
+
for (const expl of result.explanations) {
|
|
180
|
+
const icon = expl.severity === "critical"
|
|
181
|
+
? "🔴"
|
|
182
|
+
: expl.severity === "high"
|
|
183
|
+
? "🟠"
|
|
184
|
+
: expl.severity === "medium"
|
|
185
|
+
? "🟡"
|
|
186
|
+
: "🔵";
|
|
187
|
+
console.log(`\n ${icon} ${expl.pattern} (${expl.severity})`);
|
|
188
|
+
console.log(` What: ${expl.plainExplanation}`);
|
|
189
|
+
console.log(` Impact: ${expl.businessImpact}`);
|
|
190
|
+
console.log(` Action: ${expl.recommendation}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
console.log();
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=review-explain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-explain.js","sourceRoot":"","sources":["../../src/commands/review-explain.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AA0B9C,8EAA8E;AAE9E,MAAM,YAAY,GAA8E;IAC9F,kBAAkB,EAAE;QAClB,KAAK,EAAE,qGAAqG;QAC5G,MAAM,EACJ,4HAA4H;QAC9H,cAAc,EACZ,0GAA0G;KAC7G;IACD,YAAY,EAAE;QACZ,KAAK,EAAE,4EAA4E;QACnF,MAAM,EACJ,kIAAkI;QACpI,cAAc,EAAE,4EAA4E;KAC7F;IACD,eAAe,EAAE;QACf,KAAK,EAAE,iFAAiF;QACxF,MAAM,EACJ,uHAAuH;QACzH,cAAc,EAAE,oFAAoF;KACrG;IACD,YAAY,EAAE;QACZ,KAAK,EAAE,yFAAyF;QAChG,MAAM,EAAE,oFAAoF;QAC5F,cAAc,EAAE,4DAA4D;KAC7E;IACD,UAAU,EAAE;QACV,KAAK,EAAE,wEAAwE;QAC/E,MAAM,EAAE,sGAAsG;QAC9G,cAAc,EAAE,+FAA+F;KAChH;IACD,mBAAmB,EAAE;QACnB,KAAK,EAAE,oEAAoE;QAC3E,MAAM,EAAE,8FAA8F;QACtG,cAAc,EAAE,oFAAoF;KACrG;IACD,aAAa,EAAE;QACb,KAAK,EAAE,yFAAyF;QAChG,MAAM,EACJ,oHAAoH;QACtH,cAAc,EAAE,mEAAmE;KACpF;IACD,UAAU,EAAE;QACV,KAAK,EAAE,mEAAmE;QAC1E,MAAM,EAAE,sFAAsF;QAC9F,cAAc,EAAE,2FAA2F;KAC5G;IACD,aAAa,EAAE;QACb,KAAK,EAAE,0DAA0D;QACjE,MAAM,EAAE,iFAAiF;QACzF,cAAc,EAAE,0DAA0D;KAC3E;IACD,gBAAgB,EAAE;QAChB,KAAK,EAAE,0EAA0E;QACjF,MAAM,EAAE,2GAA2G;QACnH,cAAc,EAAE,8CAA8C;KAC/D;IACD,cAAc,EAAE;QACd,KAAK,EAAE,mFAAmF;QAC1F,MAAM,EAAE,+FAA+F;QACvG,cAAc,EAAE,qEAAqE;KACtF;IACD,YAAY,EAAE;QACZ,KAAK,EAAE,wEAAwE;QAC/E,MAAM,EAAE,6FAA6F;QACrG,cAAc,EAAE,yEAAyE;KAC1F;CACF,CAAC;AAEF,8EAA8E;AAE9E,SAAS,eAAe,CAAC,QAAyB;IAChD,yBAAyB;IACzB,MAAM,UAAU,GAAG,IAAI,GAAG,EAA+C,CAAC;IAC1E,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAC3C,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,CAAC,KAAK,EAAE,CAAC;QACnB,CAAC;aAAM,CAAC;YACN,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;IAED,MAAM,YAAY,GAAuB,EAAE,CAAC;IAC5C,KAAK,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,UAAU,EAAE,CAAC;QACzC,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI;YACpC,KAAK,EAAE,SAAS,IAAI,CAAC,KAAK,oBAAoB,OAAO,YAAY;YACjE,MAAM,EAAE,wEAAwE;YAChF,cAAc,EAAE,kEAAkE;SACnF,CAAC;QAEF,YAAY,CAAC,IAAI,CAAC;YAChB,OAAO;YACP,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,gBAAgB,EAAE,IAAI,CAAC,KAAK;YAC5B,cAAc,EAAE,IAAI,CAAC,MAAM;YAC3B,cAAc,EAAE,IAAI,CAAC,cAAc;SACpC,CAAC,CAAC;IACL,CAAC;IAED,mBAAmB;IACnB,MAAM,OAAO,GAA2B,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;IACpF,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAErF,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,MAAM,CAAC;IAC/E,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;IAEvE,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,IAAI,aAAa,GAAG,CAAC;QAAE,SAAS,GAAG,UAAU,CAAC;SACzC,IAAI,SAAS,GAAG,CAAC;QAAE,SAAS,GAAG,MAAM,CAAC;SACtC,IAAI,QAAQ,CAAC,MAAM,GAAG,EAAE;QAAE,SAAS,GAAG,QAAQ,CAAC;IAEpD,IAAI,gBAAwB,CAAC;IAC7B,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,gBAAgB,GAAG,+FAA+F,CAAC;IACrH,CAAC;SAAM,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;QAC7B,gBAAgB,GAAG,qBAAqB,aAAa,kJAAkJ,CAAC;IAC1M,CAAC;SAAM,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;QACzB,gBAAgB,GAAG,qBAAqB,SAAS,gIAAgI,CAAC;IACpL,CAAC;SAAM,CAAC;QACN,gBAAgB,GAAG,qBAAqB,QAAQ,CAAC,MAAM,sGAAsG,CAAC;IAChK,CAAC;IAED,OAAO,EAAE,aAAa,EAAE,QAAQ,CAAC,MAAM,EAAE,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,CAAC;AACvF,CAAC;AAED,+EAA+E;AAE/E,MAAM,UAAU,gBAAgB,CAAC,IAAc;IAC7C,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;CAef,CAAC,CAAC;QACC,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAU,EAAE,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,UAAU,CAAC,IAAI,MAAM,CAAC;IAC1F,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAU,EAAE,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC;IAEpF,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,sEAAsE,CAAC,CAAC;QACtF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO;IACT,CAAC;IAED,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9B,OAAO,CAAC,KAAK,CAAC,gBAAgB,YAAY,cAAc,CAAC,CAAC;QAC1D,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO;IACT,CAAC;IAED,IAAI,QAAyB,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC;QAC5D,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IACxF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACpD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,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,CAAC,yDAAyD,CAAC,CAAC;IACvE,OAAO,CAAC,GAAG,CAAC,mBAAmB,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;IACnD,OAAO,CAAC,GAAG,CAAC,uBAAuB,MAAM,CAAC,aAAa,IAAI,CAAC,CAAC;IAC7D,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IACtC,OAAO,CAAC,GAAG,CAAC,OAAO,MAAM,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAE9C,IAAI,MAAM,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnC,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;QACxC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;YACvC,MAAM,IAAI,GACR,IAAI,CAAC,QAAQ,KAAK,UAAU;gBAC1B,CAAC,CAAC,IAAI;gBACN,CAAC,CAAC,IAAI,CAAC,QAAQ,KAAK,MAAM;oBACxB,CAAC,CAAC,IAAI;oBACN,CAAC,CAAC,IAAI,CAAC,QAAQ,KAAK,QAAQ;wBAC1B,CAAC,CAAC,IAAI;wBACN,CAAC,CAAC,IAAI,CAAC;YACf,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;YAClE,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC;YACvD,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC;YACvD,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,CAAC;AAChB,CAAC"}
|