@kevinrabun/judges 3.110.0 → 3.112.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +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-auto-priority.d.ts +2 -0
- package/dist/commands/finding-auto-priority.d.ts.map +1 -0
- package/dist/commands/finding-auto-priority.js +101 -0
- package/dist/commands/finding-auto-priority.js.map +1 -0
- package/dist/commands/finding-cluster-summary.d.ts +2 -0
- package/dist/commands/finding-cluster-summary.d.ts.map +1 -0
- package/dist/commands/finding-cluster-summary.js +86 -0
- package/dist/commands/finding-cluster-summary.js.map +1 -0
- package/dist/commands/finding-context-link.d.ts +2 -0
- package/dist/commands/finding-context-link.d.ts.map +1 -0
- package/dist/commands/finding-context-link.js +95 -0
- package/dist/commands/finding-context-link.js.map +1 -0
- package/dist/commands/finding-dependency-impact.d.ts +2 -0
- package/dist/commands/finding-dependency-impact.d.ts.map +1 -0
- package/dist/commands/finding-dependency-impact.js +98 -0
- package/dist/commands/finding-dependency-impact.js.map +1 -0
- package/dist/commands/finding-fix-estimate.d.ts +2 -0
- package/dist/commands/finding-fix-estimate.d.ts.map +1 -0
- package/dist/commands/finding-fix-estimate.js +96 -0
- package/dist/commands/finding-fix-estimate.js.map +1 -0
- package/dist/commands/finding-noise-score.d.ts +2 -0
- package/dist/commands/finding-noise-score.d.ts.map +1 -0
- package/dist/commands/finding-noise-score.js +94 -0
- package/dist/commands/finding-noise-score.js.map +1 -0
- package/dist/commands/finding-repeat-detect.d.ts +2 -0
- package/dist/commands/finding-repeat-detect.d.ts.map +1 -0
- package/dist/commands/finding-repeat-detect.js +93 -0
- package/dist/commands/finding-repeat-detect.js.map +1 -0
- package/dist/commands/finding-resolution-workflow.d.ts +2 -0
- package/dist/commands/finding-resolution-workflow.d.ts.map +1 -0
- package/dist/commands/finding-resolution-workflow.js +92 -0
- package/dist/commands/finding-resolution-workflow.js.map +1 -0
- package/dist/commands/finding-scope-impact.d.ts +2 -0
- package/dist/commands/finding-scope-impact.d.ts.map +1 -0
- package/dist/commands/finding-scope-impact.js +84 -0
- package/dist/commands/finding-scope-impact.js.map +1 -0
- package/dist/commands/finding-top-offender.d.ts +2 -0
- package/dist/commands/finding-top-offender.d.ts.map +1 -0
- package/dist/commands/finding-top-offender.js +76 -0
- package/dist/commands/finding-top-offender.js.map +1 -0
- package/dist/commands/review-health-trend.d.ts +2 -0
- package/dist/commands/review-health-trend.d.ts.map +1 -0
- package/dist/commands/review-health-trend.js +108 -0
- package/dist/commands/review-health-trend.js.map +1 -0
- package/dist/commands/review-mentor-suggest.d.ts +2 -0
- package/dist/commands/review-mentor-suggest.d.ts.map +1 -0
- package/dist/commands/review-mentor-suggest.js +113 -0
- package/dist/commands/review-mentor-suggest.js.map +1 -0
- package/dist/commands/review-quality-baseline.d.ts +2 -0
- package/dist/commands/review-quality-baseline.d.ts.map +1 -0
- package/dist/commands/review-quality-baseline.js +135 -0
- package/dist/commands/review-quality-baseline.js.map +1 -0
- package/dist/commands/review-readiness-check.d.ts +2 -0
- package/dist/commands/review-readiness-check.d.ts.map +1 -0
- package/dist/commands/review-readiness-check.js +99 -0
- package/dist/commands/review-readiness-check.js.map +1 -0
- package/dist/commands/review-retrospective.d.ts +2 -0
- package/dist/commands/review-retrospective.d.ts.map +1 -0
- package/dist/commands/review-retrospective.js +119 -0
- package/dist/commands/review-retrospective.js.map +1 -0
- package/dist/commands/review-team-skill-map.d.ts +2 -0
- package/dist/commands/review-team-skill-map.d.ts.map +1 -0
- package/dist/commands/review-team-skill-map.js +103 -0
- package/dist/commands/review-team-skill-map.js.map +1 -0
- package/dist/commands/review-team-velocity.d.ts +2 -0
- package/dist/commands/review-team-velocity.d.ts.map +1 -0
- package/dist/commands/review-team-velocity.js +104 -0
- package/dist/commands/review-team-velocity.js.map +1 -0
- package/dist/commands/review-workflow-suggest.d.ts +2 -0
- package/dist/commands/review-workflow-suggest.d.ts.map +1 -0
- package/dist/commands/review-workflow-suggest.js +130 -0
- package/dist/commands/review-workflow-suggest.js.map +1 -0
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-mentor-suggest.d.ts","sourceRoot":"","sources":["../../src/commands/review-mentor-suggest.ts"],"names":[],"mappings":"AA2GA,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAgD3D"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { readFileSync, existsSync, readdirSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
function extractDomain(ruleId) {
|
|
4
|
+
const parts = ruleId.split("/");
|
|
5
|
+
return parts.length > 1 ? parts[0] : "general";
|
|
6
|
+
}
|
|
7
|
+
function buildProfiles(historyDir) {
|
|
8
|
+
if (!existsSync(historyDir))
|
|
9
|
+
return [];
|
|
10
|
+
const files = readdirSync(historyDir);
|
|
11
|
+
const jsonFiles = files.filter((f) => String(f).endsWith(".json"));
|
|
12
|
+
const domainScores = {};
|
|
13
|
+
for (const file of jsonFiles) {
|
|
14
|
+
const raw = readFileSync(join(historyDir, String(file)), "utf-8");
|
|
15
|
+
let verdict;
|
|
16
|
+
try {
|
|
17
|
+
verdict = JSON.parse(raw);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const reviewer = String(file)
|
|
23
|
+
.replace(/\.json$/, "")
|
|
24
|
+
.replace(/[-_]\d+$/, "");
|
|
25
|
+
if (!domainScores[reviewer])
|
|
26
|
+
domainScores[reviewer] = {};
|
|
27
|
+
const domainCounts = {};
|
|
28
|
+
for (const f of verdict.findings ?? []) {
|
|
29
|
+
const domain = extractDomain(f.ruleId);
|
|
30
|
+
domainCounts[domain] = (domainCounts[domain] ?? 0) + 1;
|
|
31
|
+
}
|
|
32
|
+
for (const domain of Object.keys(domainCounts)) {
|
|
33
|
+
if (!domainScores[reviewer][domain]) {
|
|
34
|
+
domainScores[reviewer][domain] = { total: 0, findings: 0 };
|
|
35
|
+
}
|
|
36
|
+
domainScores[reviewer][domain].total += 1;
|
|
37
|
+
domainScores[reviewer][domain].findings += domainCounts[domain];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const profiles = [];
|
|
41
|
+
for (const [reviewer, domains] of Object.entries(domainScores)) {
|
|
42
|
+
const strengths = [];
|
|
43
|
+
const weaknesses = [];
|
|
44
|
+
for (const [domain, stats] of Object.entries(domains)) {
|
|
45
|
+
const avgFindings = stats.findings / stats.total;
|
|
46
|
+
if (avgFindings <= 1) {
|
|
47
|
+
strengths.push(domain);
|
|
48
|
+
}
|
|
49
|
+
else if (avgFindings >= 3) {
|
|
50
|
+
weaknesses.push(domain);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
profiles.push({ reviewer, strengths, weaknesses });
|
|
54
|
+
}
|
|
55
|
+
return profiles;
|
|
56
|
+
}
|
|
57
|
+
function suggestPairings(profiles) {
|
|
58
|
+
const pairings = [];
|
|
59
|
+
for (const mentee of profiles) {
|
|
60
|
+
for (const weakness of mentee.weaknesses) {
|
|
61
|
+
const mentor = profiles.find((p) => p.reviewer !== mentee.reviewer && p.strengths.includes(weakness));
|
|
62
|
+
if (mentor) {
|
|
63
|
+
pairings.push({
|
|
64
|
+
mentee: mentee.reviewer,
|
|
65
|
+
mentor: mentor.reviewer,
|
|
66
|
+
domain: weakness,
|
|
67
|
+
reason: `${mentor.reviewer} shows strength in "${weakness}" where ${mentee.reviewer} has frequent findings`,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return pairings;
|
|
73
|
+
}
|
|
74
|
+
export function runReviewMentorSuggest(argv) {
|
|
75
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
76
|
+
console.log(`Usage: judges review-mentor-suggest [options]
|
|
77
|
+
|
|
78
|
+
Suggest mentor pairings based on expertise gaps in review history.
|
|
79
|
+
|
|
80
|
+
Options:
|
|
81
|
+
--history <dir> Directory with verdict JSON files (default: .judges/history)
|
|
82
|
+
--format <fmt> Output format: table (default) or json
|
|
83
|
+
-h, --help Show this help message`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const formatIdx = argv.indexOf("--format");
|
|
87
|
+
const format = formatIdx !== -1 && argv[formatIdx + 1] ? argv[formatIdx + 1] : "table";
|
|
88
|
+
const histIdx = argv.indexOf("--history");
|
|
89
|
+
const historyDir = histIdx !== -1 && argv[histIdx + 1]
|
|
90
|
+
? join(process.cwd(), argv[histIdx + 1])
|
|
91
|
+
: join(process.cwd(), ".judges", "history");
|
|
92
|
+
const profiles = buildProfiles(historyDir);
|
|
93
|
+
if (profiles.length === 0) {
|
|
94
|
+
console.log("No reviewer profiles found. Run some reviews first.");
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const pairings = suggestPairings(profiles);
|
|
98
|
+
if (format === "json") {
|
|
99
|
+
console.log(JSON.stringify({ profiles, pairings }, null, 2));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
console.log(`\n=== Mentor Suggestions (${pairings.length} pairings) ===\n`);
|
|
103
|
+
if (pairings.length === 0) {
|
|
104
|
+
console.log("No clear mentor pairings identified — team expertise is well-distributed.");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
for (const p of pairings) {
|
|
108
|
+
console.log(` ${p.mentee} <-- ${p.mentor} (domain: ${p.domain})`);
|
|
109
|
+
console.log(` ${p.reason}`);
|
|
110
|
+
console.log();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=review-mentor-suggest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-mentor-suggest.js","sourceRoot":"","sources":["../../src/commands/review-mentor-suggest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-quality-baseline.d.ts","sourceRoot":"","sources":["../../src/commands/review-quality-baseline.ts"],"names":[],"mappings":"AAoHA,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAkE7D"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { readFileSync, existsSync, readdirSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
function computeBaseline(historyDir) {
|
|
4
|
+
if (!existsSync(historyDir))
|
|
5
|
+
return undefined;
|
|
6
|
+
const files = readdirSync(historyDir)
|
|
7
|
+
.filter((f) => typeof f === "string" && f.endsWith(".json"))
|
|
8
|
+
.sort();
|
|
9
|
+
if (files.length === 0)
|
|
10
|
+
return undefined;
|
|
11
|
+
const scores = [];
|
|
12
|
+
const findingCounts = [];
|
|
13
|
+
const criticalCounts = [];
|
|
14
|
+
const highCounts = [];
|
|
15
|
+
let passes = 0;
|
|
16
|
+
for (const file of files) {
|
|
17
|
+
try {
|
|
18
|
+
const data = JSON.parse(readFileSync(join(historyDir, file), "utf-8"));
|
|
19
|
+
scores.push(data.overallScore ?? 0);
|
|
20
|
+
findingCounts.push((data.findings ?? []).length);
|
|
21
|
+
criticalCounts.push(data.criticalCount ?? 0);
|
|
22
|
+
highCounts.push(data.highCount ?? 0);
|
|
23
|
+
if (data.overallVerdict === "pass")
|
|
24
|
+
passes++;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Skip
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (scores.length === 0)
|
|
31
|
+
return undefined;
|
|
32
|
+
const avg = (arr) => Math.round(arr.reduce((a, b) => a + b, 0) / arr.length);
|
|
33
|
+
return {
|
|
34
|
+
avgScore: avg(scores),
|
|
35
|
+
avgFindings: avg(findingCounts),
|
|
36
|
+
avgCritical: avg(criticalCounts),
|
|
37
|
+
avgHigh: avg(highCounts),
|
|
38
|
+
passRate: Math.round((passes / scores.length) * 100),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function compareToBaseline(baseline, current) {
|
|
42
|
+
const comparisons = [];
|
|
43
|
+
const curScore = current.overallScore ?? 0;
|
|
44
|
+
const curFindings = (current.findings ?? []).length;
|
|
45
|
+
const curCritical = current.criticalCount ?? 0;
|
|
46
|
+
const curHigh = current.highCount ?? 0;
|
|
47
|
+
function status(delta, higherIsBetter) {
|
|
48
|
+
if (delta === 0)
|
|
49
|
+
return "On baseline";
|
|
50
|
+
if (higherIsBetter)
|
|
51
|
+
return delta > 0 ? "Above baseline" : "Below baseline";
|
|
52
|
+
return delta < 0 ? "Better than baseline" : "Worse than baseline";
|
|
53
|
+
}
|
|
54
|
+
comparisons.push({
|
|
55
|
+
metric: "Score",
|
|
56
|
+
baseline: baseline.avgScore,
|
|
57
|
+
current: curScore,
|
|
58
|
+
delta: curScore - baseline.avgScore,
|
|
59
|
+
status: status(curScore - baseline.avgScore, true),
|
|
60
|
+
});
|
|
61
|
+
comparisons.push({
|
|
62
|
+
metric: "Findings",
|
|
63
|
+
baseline: baseline.avgFindings,
|
|
64
|
+
current: curFindings,
|
|
65
|
+
delta: curFindings - baseline.avgFindings,
|
|
66
|
+
status: status(curFindings - baseline.avgFindings, false),
|
|
67
|
+
});
|
|
68
|
+
comparisons.push({
|
|
69
|
+
metric: "Critical",
|
|
70
|
+
baseline: baseline.avgCritical,
|
|
71
|
+
current: curCritical,
|
|
72
|
+
delta: curCritical - baseline.avgCritical,
|
|
73
|
+
status: status(curCritical - baseline.avgCritical, false),
|
|
74
|
+
});
|
|
75
|
+
comparisons.push({
|
|
76
|
+
metric: "High",
|
|
77
|
+
baseline: baseline.avgHigh,
|
|
78
|
+
current: curHigh,
|
|
79
|
+
delta: curHigh - baseline.avgHigh,
|
|
80
|
+
status: status(curHigh - baseline.avgHigh, false),
|
|
81
|
+
});
|
|
82
|
+
return comparisons;
|
|
83
|
+
}
|
|
84
|
+
export function runReviewQualityBaseline(argv) {
|
|
85
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
86
|
+
console.log(`Usage: judges review-quality-baseline [options]
|
|
87
|
+
|
|
88
|
+
Compare current review against quality baseline.
|
|
89
|
+
|
|
90
|
+
Options:
|
|
91
|
+
--report <path> Path to current verdict JSON
|
|
92
|
+
--history <path> Path to review history directory
|
|
93
|
+
--format <fmt> Output format: table (default) or json
|
|
94
|
+
-h, --help Show this help message`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const formatIdx = argv.indexOf("--format");
|
|
98
|
+
const format = formatIdx !== -1 && argv[formatIdx + 1] ? argv[formatIdx + 1] : "table";
|
|
99
|
+
const reportIdx = argv.indexOf("--report");
|
|
100
|
+
const reportPath = reportIdx !== -1 && argv[reportIdx + 1]
|
|
101
|
+
? join(process.cwd(), argv[reportIdx + 1])
|
|
102
|
+
: join(process.cwd(), ".judges", "last-verdict.json");
|
|
103
|
+
const histIdx = argv.indexOf("--history");
|
|
104
|
+
const historyDir = histIdx !== -1 && argv[histIdx + 1]
|
|
105
|
+
? join(process.cwd(), argv[histIdx + 1])
|
|
106
|
+
: join(process.cwd(), ".judges", "history");
|
|
107
|
+
const baseline = computeBaseline(historyDir);
|
|
108
|
+
if (baseline === undefined) {
|
|
109
|
+
console.log("No review history to compute baseline. Run more reviews first.");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (!existsSync(reportPath)) {
|
|
113
|
+
console.log(`No report found at: ${reportPath}`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const current = JSON.parse(readFileSync(reportPath, "utf-8"));
|
|
117
|
+
const comparisons = compareToBaseline(baseline, current);
|
|
118
|
+
if (format === "json") {
|
|
119
|
+
console.log(JSON.stringify({ baseline, comparisons }, null, 2));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
console.log(`\n=== Quality Baseline Comparison ===\n`);
|
|
123
|
+
console.log(" " + "Metric".padEnd(12) + "Baseline".padEnd(12) + "Current".padEnd(12) + "Delta".padEnd(10) + "Status");
|
|
124
|
+
console.log(" " + "-".repeat(55));
|
|
125
|
+
for (const c of comparisons) {
|
|
126
|
+
const deltaStr = c.delta >= 0 ? `+${c.delta}` : String(c.delta);
|
|
127
|
+
console.log(" " +
|
|
128
|
+
c.metric.padEnd(12) +
|
|
129
|
+
String(c.baseline).padEnd(12) +
|
|
130
|
+
String(c.current).padEnd(12) +
|
|
131
|
+
deltaStr.padEnd(10) +
|
|
132
|
+
c.status);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=review-quality-baseline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-quality-baseline.js","sourceRoot":"","sources":["../../src/commands/review-quality-baseline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-readiness-check.d.ts","sourceRoot":"","sources":["../../src/commands/review-readiness-check.ts"],"names":[],"mappings":"AA4FA,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAuC5D"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { readFileSync, existsSync, readdirSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
function checkReadiness(baseDir) {
|
|
4
|
+
const items = [];
|
|
5
|
+
const configPath = join(baseDir, ".judgesrc.json");
|
|
6
|
+
const configExists = existsSync(configPath);
|
|
7
|
+
items.push({
|
|
8
|
+
criterion: "Configuration file",
|
|
9
|
+
passed: configExists,
|
|
10
|
+
detail: configExists ? ".judgesrc.json found" : ".judgesrc.json not found — run judges init",
|
|
11
|
+
});
|
|
12
|
+
const baselinePath = join(baseDir, ".judges", "baseline.json");
|
|
13
|
+
const baselineExists = existsSync(baselinePath);
|
|
14
|
+
items.push({
|
|
15
|
+
criterion: "Baseline available",
|
|
16
|
+
passed: baselineExists,
|
|
17
|
+
detail: baselineExists ? "Baseline found — suppressions will work" : "No baseline — run judges baseline first",
|
|
18
|
+
});
|
|
19
|
+
const historyDir = join(baseDir, ".judges", "history");
|
|
20
|
+
let historyCount = 0;
|
|
21
|
+
if (existsSync(historyDir)) {
|
|
22
|
+
const files = readdirSync(historyDir);
|
|
23
|
+
historyCount = files.filter((f) => String(f).endsWith(".json")).length;
|
|
24
|
+
}
|
|
25
|
+
items.push({
|
|
26
|
+
criterion: "Review history",
|
|
27
|
+
passed: historyCount >= 1,
|
|
28
|
+
detail: historyCount >= 1 ? `${historyCount} prior reviews found` : "No review history — first-time setup",
|
|
29
|
+
});
|
|
30
|
+
const lastVerdictPath = join(baseDir, ".judges", "last-verdict.json");
|
|
31
|
+
let lastVerdictFresh = false;
|
|
32
|
+
if (existsSync(lastVerdictPath)) {
|
|
33
|
+
try {
|
|
34
|
+
const raw = readFileSync(lastVerdictPath, "utf-8");
|
|
35
|
+
const verdict = JSON.parse(raw);
|
|
36
|
+
if (verdict.timestamp) {
|
|
37
|
+
const age = Date.now() - new Date(verdict.timestamp).getTime();
|
|
38
|
+
lastVerdictFresh = age < 7 * 24 * 60 * 60 * 1000;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
/* ignore parse errors */
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
items.push({
|
|
46
|
+
criterion: "Recent verdict",
|
|
47
|
+
passed: lastVerdictFresh,
|
|
48
|
+
detail: lastVerdictFresh ? "Last verdict is less than 7 days old" : "No recent verdict — run judges review",
|
|
49
|
+
});
|
|
50
|
+
const gitDir = join(baseDir, ".git");
|
|
51
|
+
const gitExists = existsSync(gitDir);
|
|
52
|
+
items.push({
|
|
53
|
+
criterion: "Git repository",
|
|
54
|
+
passed: gitExists,
|
|
55
|
+
detail: gitExists ? "Git repo detected" : "Not a git repository — diff features limited",
|
|
56
|
+
});
|
|
57
|
+
const passCount = items.filter((i) => i.passed).length;
|
|
58
|
+
return {
|
|
59
|
+
ready: passCount === items.length,
|
|
60
|
+
passCount,
|
|
61
|
+
totalChecks: items.length,
|
|
62
|
+
items,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function runReviewReadinessCheck(argv) {
|
|
66
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
67
|
+
console.log(`Usage: judges review-readiness-check [options]
|
|
68
|
+
|
|
69
|
+
Assess whether a codebase is ready for review.
|
|
70
|
+
|
|
71
|
+
Options:
|
|
72
|
+
--dir <path> Project directory (default: cwd)
|
|
73
|
+
--format <fmt> Output format: table (default) or json
|
|
74
|
+
-h, --help Show this help message`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const formatIdx = argv.indexOf("--format");
|
|
78
|
+
const format = formatIdx !== -1 && argv[formatIdx + 1] ? argv[formatIdx + 1] : "table";
|
|
79
|
+
const dirIdx = argv.indexOf("--dir");
|
|
80
|
+
const baseDir = dirIdx !== -1 && argv[dirIdx + 1] ? join(process.cwd(), argv[dirIdx + 1]) : process.cwd();
|
|
81
|
+
const report = checkReadiness(baseDir);
|
|
82
|
+
if (format === "json") {
|
|
83
|
+
console.log(JSON.stringify(report, null, 2));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
console.log(`\n=== Review Readiness Check (${report.passCount}/${report.totalChecks}) ===\n`);
|
|
87
|
+
for (const item of report.items) {
|
|
88
|
+
const icon = item.passed ? "✓" : "✗";
|
|
89
|
+
console.log(` ${icon} ${item.criterion.padEnd(24)} ${item.detail}`);
|
|
90
|
+
}
|
|
91
|
+
console.log();
|
|
92
|
+
if (report.ready) {
|
|
93
|
+
console.log(" All checks passed — ready for review.");
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
console.log(" Some checks failed — address issues above before review.");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=review-readiness-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-readiness-check.js","sourceRoot":"","sources":["../../src/commands/review-readiness-check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,IAAI,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAuB5B,SAAS,cAAc,CAAC,OAAe;IACrC,MAAM,KAAK,GAAoB,EAAE,CAAC;IAElC,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;IACnD,MAAM,YAAY,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IAC5C,KAAK,CAAC,IAAI,CAAC;QACT,SAAS,EAAE,oBAAoB;QAC/B,MAAM,EAAE,YAAY;QACpB,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,4CAA4C;KAC7F,CAAC,CAAC;IAEH,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;IAC/D,MAAM,cAAc,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC;IAChD,KAAK,CAAC,IAAI,CAAC;QACT,SAAS,EAAE,oBAAoB;QAC/B,MAAM,EAAE,cAAc;QACtB,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,yCAAyC,CAAC,CAAC,CAAC,yCAAyC;KAC/G,CAAC,CAAC;IAEH,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;IACvD,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,WAAW,CAAC,UAAU,CAAwB,CAAC;QAC7D,YAAY,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;IACzE,CAAC;IACD,KAAK,CAAC,IAAI,CAAC;QACT,SAAS,EAAE,gBAAgB;QAC3B,MAAM,EAAE,YAAY,IAAI,CAAC;QACzB,MAAM,EAAE,YAAY,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,YAAY,sBAAsB,CAAC,CAAC,CAAC,sCAAsC;KAC3G,CAAC,CAAC;IAEH,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,mBAAmB,CAAC,CAAC;IACtE,IAAI,gBAAgB,GAAG,KAAK,CAAC;IAC7B,IAAI,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;QAChC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;YACnD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAoB,CAAC;YACnD,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;gBACtB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC;gBAC/D,gBAAgB,GAAG,GAAG,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;YACnD,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,yBAAyB;QAC3B,CAAC;IACH,CAAC;IACD,KAAK,CAAC,IAAI,CAAC;QACT,SAAS,EAAE,gBAAgB;QAC3B,MAAM,EAAE,gBAAgB;QACxB,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,sCAAsC,CAAC,CAAC,CAAC,uCAAuC;KAC5G,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACrC,MAAM,SAAS,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IACrC,KAAK,CAAC,IAAI,CAAC;QACT,SAAS,EAAE,gBAAgB;QAC3B,MAAM,EAAE,SAAS;QACjB,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,8CAA8C;KACzF,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;IACvD,OAAO;QACL,KAAK,EAAE,SAAS,KAAK,KAAK,CAAC,MAAM;QACjC,SAAS;QACT,WAAW,EAAE,KAAK,CAAC,MAAM;QACzB,KAAK;KACN,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,IAAc;IACpD,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,OAAO,CAAC,GAAG,CAAC;;;;;;;8CAO8B,CAAC,CAAC;QAC5C,OAAO;IACT,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,SAAS,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAEvF,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACrC,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IAE1G,MAAM,MAAM,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IAEvC,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,iCAAiC,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,WAAW,SAAS,CAAC,CAAC;IAE9F,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;QACrC,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACvE,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAC;IACzD,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,4DAA4D,CAAC,CAAC;IAC5E,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-retrospective.d.ts","sourceRoot":"","sources":["../../src/commands/review-retrospective.ts"],"names":[],"mappings":"AA0GA,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CA2C3D"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { readFileSync, existsSync, readdirSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
function generateRetro(historyDir) {
|
|
4
|
+
const empty = {
|
|
5
|
+
period: "No data",
|
|
6
|
+
reviewCount: 0,
|
|
7
|
+
sections: [],
|
|
8
|
+
};
|
|
9
|
+
if (!existsSync(historyDir))
|
|
10
|
+
return empty;
|
|
11
|
+
const files = readdirSync(historyDir)
|
|
12
|
+
.filter((f) => typeof f === "string" && f.endsWith(".json"))
|
|
13
|
+
.sort();
|
|
14
|
+
if (files.length === 0)
|
|
15
|
+
return empty;
|
|
16
|
+
const verdicts = [];
|
|
17
|
+
for (const file of files) {
|
|
18
|
+
try {
|
|
19
|
+
verdicts.push(JSON.parse(readFileSync(join(historyDir, file), "utf-8")));
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Skip
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (verdicts.length === 0)
|
|
26
|
+
return empty;
|
|
27
|
+
const scores = verdicts.map((v) => v.overallScore ?? 0);
|
|
28
|
+
const avgScore = Math.round(scores.reduce((a, b) => a + b, 0) / scores.length);
|
|
29
|
+
const passes = verdicts.filter((v) => v.overallVerdict === "pass").length;
|
|
30
|
+
const passRate = Math.round((passes / verdicts.length) * 100);
|
|
31
|
+
// Score trend
|
|
32
|
+
const mid = Math.floor(scores.length / 2) || 1;
|
|
33
|
+
const firstAvg = scores.slice(0, mid).reduce((a, b) => a + b, 0) / mid;
|
|
34
|
+
const secondAvg = scores.slice(mid).reduce((a, b) => a + b, 0) / (scores.length - mid);
|
|
35
|
+
const improving = secondAvg >= firstAvg;
|
|
36
|
+
// Most common rules
|
|
37
|
+
const ruleFreq = new Map();
|
|
38
|
+
for (const v of verdicts) {
|
|
39
|
+
for (const f of v.findings ?? []) {
|
|
40
|
+
ruleFreq.set(f.ruleId, (ruleFreq.get(f.ruleId) ?? 0) + 1);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const topRules = [...ruleFreq.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
44
|
+
// Build sections
|
|
45
|
+
const wentWell = [];
|
|
46
|
+
const needsImprovement = [];
|
|
47
|
+
const actionItems = [];
|
|
48
|
+
if (passRate >= 80)
|
|
49
|
+
wentWell.push(`High pass rate: ${passRate}%`);
|
|
50
|
+
else
|
|
51
|
+
needsImprovement.push(`Pass rate below threshold: ${passRate}%`);
|
|
52
|
+
if (improving)
|
|
53
|
+
wentWell.push(`Scores trending upward (${Math.round(firstAvg)} → ${Math.round(secondAvg)})`);
|
|
54
|
+
else
|
|
55
|
+
needsImprovement.push(`Scores trending downward (${Math.round(firstAvg)} → ${Math.round(secondAvg)})`);
|
|
56
|
+
if (avgScore >= 70)
|
|
57
|
+
wentWell.push(`Good average score: ${avgScore}/100`);
|
|
58
|
+
else
|
|
59
|
+
needsImprovement.push(`Average score needs work: ${avgScore}/100`);
|
|
60
|
+
if (topRules.length > 0) {
|
|
61
|
+
needsImprovement.push(`Most recurring rules: ${topRules.map(([r, c]) => `${r} (${c}x)`).join(", ")}`);
|
|
62
|
+
actionItems.push(`Address top recurring rule: ${topRules[0][0]}`);
|
|
63
|
+
}
|
|
64
|
+
if (!improving)
|
|
65
|
+
actionItems.push("Investigate declining score trend");
|
|
66
|
+
if (passRate < 80)
|
|
67
|
+
actionItems.push("Focus on reducing critical/high findings to improve pass rate");
|
|
68
|
+
actionItems.push("Review and update severity thresholds if too noisy");
|
|
69
|
+
const sections = [
|
|
70
|
+
{ category: "What Went Well", items: wentWell },
|
|
71
|
+
{ category: "Needs Improvement", items: needsImprovement },
|
|
72
|
+
{ category: "Action Items", items: actionItems },
|
|
73
|
+
];
|
|
74
|
+
const firstDate = verdicts[0].timestamp ?? "unknown";
|
|
75
|
+
const lastDate = verdicts[verdicts.length - 1].timestamp ?? "unknown";
|
|
76
|
+
return {
|
|
77
|
+
period: `${firstDate.slice(0, 10)} to ${lastDate.slice(0, 10)}`,
|
|
78
|
+
reviewCount: verdicts.length,
|
|
79
|
+
sections,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export function runReviewRetrospective(argv) {
|
|
83
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
84
|
+
console.log(`Usage: judges review-retrospective [options]
|
|
85
|
+
|
|
86
|
+
Generate review retrospective summary.
|
|
87
|
+
|
|
88
|
+
Options:
|
|
89
|
+
--history <path> Path to review history directory
|
|
90
|
+
--format <fmt> Output format: table (default) or json
|
|
91
|
+
-h, --help Show this help message`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const formatIdx = argv.indexOf("--format");
|
|
95
|
+
const format = formatIdx !== -1 && argv[formatIdx + 1] ? argv[formatIdx + 1] : "table";
|
|
96
|
+
const histIdx = argv.indexOf("--history");
|
|
97
|
+
const historyDir = histIdx !== -1 && argv[histIdx + 1]
|
|
98
|
+
? join(process.cwd(), argv[histIdx + 1])
|
|
99
|
+
: join(process.cwd(), ".judges", "history");
|
|
100
|
+
const retro = generateRetro(historyDir);
|
|
101
|
+
if (format === "json") {
|
|
102
|
+
console.log(JSON.stringify(retro, null, 2));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
console.log(`\n=== Review Retrospective ===`);
|
|
106
|
+
console.log(` Period: ${retro.period}`);
|
|
107
|
+
console.log(` Reviews: ${retro.reviewCount}\n`);
|
|
108
|
+
for (const section of retro.sections) {
|
|
109
|
+
console.log(` ${section.category}:`);
|
|
110
|
+
if (section.items.length === 0) {
|
|
111
|
+
console.log(" (none)");
|
|
112
|
+
}
|
|
113
|
+
for (const item of section.items) {
|
|
114
|
+
console.log(` • ${item}`);
|
|
115
|
+
}
|
|
116
|
+
console.log();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=review-retrospective.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-retrospective.js","sourceRoot":"","sources":["../../src/commands/review-retrospective.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-team-skill-map.d.ts","sourceRoot":"","sources":["../../src/commands/review-team-skill-map.ts"],"names":[],"mappings":"AAmGA,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CA8C1D"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { readFileSync, existsSync, readdirSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
function extractDomain(ruleId) {
|
|
4
|
+
const parts = ruleId.split("/");
|
|
5
|
+
return parts.length > 1 ? parts[0] : "general";
|
|
6
|
+
}
|
|
7
|
+
function buildSkillMaps(historyDir) {
|
|
8
|
+
if (!existsSync(historyDir))
|
|
9
|
+
return [];
|
|
10
|
+
const files = readdirSync(historyDir);
|
|
11
|
+
const jsonFiles = files.filter((f) => String(f).endsWith(".json"));
|
|
12
|
+
const memberData = {};
|
|
13
|
+
for (const file of jsonFiles) {
|
|
14
|
+
const member = String(file)
|
|
15
|
+
.replace(/\.json$/, "")
|
|
16
|
+
.replace(/[-_]\d+$/, "");
|
|
17
|
+
const raw = readFileSync(join(historyDir, String(file)), "utf-8");
|
|
18
|
+
let verdict;
|
|
19
|
+
try {
|
|
20
|
+
verdict = JSON.parse(raw);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (!memberData[member])
|
|
26
|
+
memberData[member] = {};
|
|
27
|
+
const domainCounts = {};
|
|
28
|
+
for (const f of verdict.findings ?? []) {
|
|
29
|
+
const domain = extractDomain(f.ruleId);
|
|
30
|
+
domainCounts[domain] = (domainCounts[domain] ?? 0) + 1;
|
|
31
|
+
}
|
|
32
|
+
const allDomains = new Set(Object.keys(domainCounts));
|
|
33
|
+
if (allDomains.size === 0)
|
|
34
|
+
allDomains.add("general");
|
|
35
|
+
for (const domain of allDomains) {
|
|
36
|
+
if (!memberData[member][domain]) {
|
|
37
|
+
memberData[member][domain] = { reviews: 0, findings: 0 };
|
|
38
|
+
}
|
|
39
|
+
memberData[member][domain].reviews += 1;
|
|
40
|
+
memberData[member][domain].findings += domainCounts[domain] ?? 0;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const maps = [];
|
|
44
|
+
for (const [member, domains] of Object.entries(memberData)) {
|
|
45
|
+
const skills = [];
|
|
46
|
+
for (const [domain, stats] of Object.entries(domains)) {
|
|
47
|
+
const rate = stats.reviews > 0 ? stats.findings / stats.reviews : 0;
|
|
48
|
+
let level;
|
|
49
|
+
if (rate <= 0.5)
|
|
50
|
+
level = "expert";
|
|
51
|
+
else if (rate <= 1.5)
|
|
52
|
+
level = "proficient";
|
|
53
|
+
else if (rate <= 3)
|
|
54
|
+
level = "developing";
|
|
55
|
+
else
|
|
56
|
+
level = "beginner";
|
|
57
|
+
skills.push({ domain, level, findingRate: Math.round(rate * 10) / 10, reviewCount: stats.reviews });
|
|
58
|
+
}
|
|
59
|
+
skills.sort((a, b) => a.findingRate - b.findingRate);
|
|
60
|
+
const strongest = skills.length > 0 ? skills[0].domain : "none";
|
|
61
|
+
const weakest = skills.length > 0 ? skills[skills.length - 1].domain : "none";
|
|
62
|
+
maps.push({ member, skills, strongestDomain: strongest, weakestDomain: weakest });
|
|
63
|
+
}
|
|
64
|
+
return maps;
|
|
65
|
+
}
|
|
66
|
+
export function runReviewTeamSkillMap(argv) {
|
|
67
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
68
|
+
console.log(`Usage: judges review-team-skill-map [options]
|
|
69
|
+
|
|
70
|
+
Build a team skill map from review history.
|
|
71
|
+
|
|
72
|
+
Options:
|
|
73
|
+
--history <dir> Directory with verdict JSON files (default: .judges/history)
|
|
74
|
+
--format <fmt> Output format: table (default) or json
|
|
75
|
+
-h, --help Show this help message`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const formatIdx = argv.indexOf("--format");
|
|
79
|
+
const format = formatIdx !== -1 && argv[formatIdx + 1] ? argv[formatIdx + 1] : "table";
|
|
80
|
+
const histIdx = argv.indexOf("--history");
|
|
81
|
+
const historyDir = histIdx !== -1 && argv[histIdx + 1]
|
|
82
|
+
? join(process.cwd(), argv[histIdx + 1])
|
|
83
|
+
: join(process.cwd(), ".judges", "history");
|
|
84
|
+
const maps = buildSkillMaps(historyDir);
|
|
85
|
+
if (format === "json") {
|
|
86
|
+
console.log(JSON.stringify(maps, null, 2));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
console.log(`\n=== Team Skill Map (${maps.length} members) ===\n`);
|
|
90
|
+
if (maps.length === 0) {
|
|
91
|
+
console.log("No review history found. Run some reviews first.");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
for (const m of maps) {
|
|
95
|
+
console.log(` ${m.member}`);
|
|
96
|
+
console.log(` Strongest: ${m.strongestDomain} | Weakest: ${m.weakestDomain}`);
|
|
97
|
+
for (const s of m.skills) {
|
|
98
|
+
console.log(` ${s.domain.padEnd(20)} ${s.level.padEnd(12)} (rate: ${s.findingRate}, reviews: ${s.reviewCount})`);
|
|
99
|
+
}
|
|
100
|
+
console.log();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=review-team-skill-map.js.map
|