@kevinrabun/judges 3.66.0 → 3.68.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.
Files changed (70) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +112 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/config-lint.d.ts +5 -0
  6. package/dist/commands/config-lint.d.ts.map +1 -0
  7. package/dist/commands/config-lint.js +188 -0
  8. package/dist/commands/config-lint.js.map +1 -0
  9. package/dist/commands/finding-age.d.ts +5 -0
  10. package/dist/commands/finding-age.d.ts.map +1 -0
  11. package/dist/commands/finding-age.js +146 -0
  12. package/dist/commands/finding-age.js.map +1 -0
  13. package/dist/commands/finding-cluster.d.ts +5 -0
  14. package/dist/commands/finding-cluster.d.ts.map +1 -0
  15. package/dist/commands/finding-cluster.js +158 -0
  16. package/dist/commands/finding-cluster.js.map +1 -0
  17. package/dist/commands/finding-hotspot.d.ts +5 -0
  18. package/dist/commands/finding-hotspot.d.ts.map +1 -0
  19. package/dist/commands/finding-hotspot.js +116 -0
  20. package/dist/commands/finding-hotspot.js.map +1 -0
  21. package/dist/commands/finding-rank.d.ts +5 -0
  22. package/dist/commands/finding-rank.d.ts.map +1 -0
  23. package/dist/commands/finding-rank.js +139 -0
  24. package/dist/commands/finding-rank.js.map +1 -0
  25. package/dist/commands/review-ab-test.d.ts +5 -0
  26. package/dist/commands/review-ab-test.d.ts.map +1 -0
  27. package/dist/commands/review-ab-test.js +225 -0
  28. package/dist/commands/review-ab-test.js.map +1 -0
  29. package/dist/commands/review-audit-log.d.ts +5 -0
  30. package/dist/commands/review-audit-log.d.ts.map +1 -0
  31. package/dist/commands/review-audit-log.js +140 -0
  32. package/dist/commands/review-audit-log.js.map +1 -0
  33. package/dist/commands/review-badge.d.ts +5 -0
  34. package/dist/commands/review-badge.d.ts.map +1 -0
  35. package/dist/commands/review-badge.js +153 -0
  36. package/dist/commands/review-badge.js.map +1 -0
  37. package/dist/commands/review-dashboard.d.ts +5 -0
  38. package/dist/commands/review-dashboard.d.ts.map +1 -0
  39. package/dist/commands/review-dashboard.js +141 -0
  40. package/dist/commands/review-dashboard.js.map +1 -0
  41. package/dist/commands/review-diff-summary.d.ts +5 -0
  42. package/dist/commands/review-diff-summary.d.ts.map +1 -0
  43. package/dist/commands/review-diff-summary.js +155 -0
  44. package/dist/commands/review-diff-summary.js.map +1 -0
  45. package/dist/commands/review-integration.d.ts +5 -0
  46. package/dist/commands/review-integration.d.ts.map +1 -0
  47. package/dist/commands/review-integration.js +237 -0
  48. package/dist/commands/review-integration.js.map +1 -0
  49. package/dist/commands/review-notify.d.ts +5 -0
  50. package/dist/commands/review-notify.d.ts.map +1 -0
  51. package/dist/commands/review-notify.js +144 -0
  52. package/dist/commands/review-notify.js.map +1 -0
  53. package/dist/commands/review-offline.d.ts +5 -0
  54. package/dist/commands/review-offline.d.ts.map +1 -0
  55. package/dist/commands/review-offline.js +126 -0
  56. package/dist/commands/review-offline.js.map +1 -0
  57. package/dist/commands/review-quota.d.ts +5 -0
  58. package/dist/commands/review-quota.d.ts.map +1 -0
  59. package/dist/commands/review-quota.js +127 -0
  60. package/dist/commands/review-quota.js.map +1 -0
  61. package/dist/commands/review-sandbox.d.ts +5 -0
  62. package/dist/commands/review-sandbox.d.ts.map +1 -0
  63. package/dist/commands/review-sandbox.js +192 -0
  64. package/dist/commands/review-sandbox.js.map +1 -0
  65. package/dist/commands/review-streak.d.ts +5 -0
  66. package/dist/commands/review-streak.d.ts.map +1 -0
  67. package/dist/commands/review-streak.js +151 -0
  68. package/dist/commands/review-streak.js.map +1 -0
  69. package/package.json +1 -1
  70. package/server.json +2 -2
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Config-lint — Lint and validate .judgesrc configuration files.
3
+ */
4
+ export declare function runConfigLint(argv: string[]): void;
5
+ //# sourceMappingURL=config-lint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-lint.d.ts","sourceRoot":"","sources":["../../src/commands/config-lint.ts"],"names":[],"mappings":"AAAA;;GAEG;AAgIH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CA8FlD"}
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Config-lint — Lint and validate .judgesrc configuration files.
3
+ */
4
+ import { readFileSync, existsSync } from "fs";
5
+ // ─── Validation ─────────────────────────────────────────────────────────────
6
+ const VALID_PRESETS = [
7
+ "strict",
8
+ "lenient",
9
+ "security-only",
10
+ "startup",
11
+ "compliance",
12
+ "performance",
13
+ "react",
14
+ "express",
15
+ "fastapi",
16
+ "django",
17
+ "spring-boot",
18
+ "rails",
19
+ "nextjs",
20
+ "terraform",
21
+ "kubernetes",
22
+ ];
23
+ const VALID_SEVERITIES = ["critical", "high", "medium", "low", "info"];
24
+ function lintConfig(config, issues) {
25
+ // Check preset
26
+ if (config["preset"] !== undefined) {
27
+ const preset = String(config["preset"]);
28
+ const presets = preset.split(",").map((p) => p.trim());
29
+ for (const p of presets) {
30
+ if (!VALID_PRESETS.includes(p)) {
31
+ issues.push({
32
+ level: "warning",
33
+ field: "preset",
34
+ message: `Unknown preset '${p}'. Valid: ${VALID_PRESETS.join(", ")}`,
35
+ });
36
+ }
37
+ }
38
+ }
39
+ // Check minSeverity
40
+ if (config["minSeverity"] !== undefined) {
41
+ const sev = String(config["minSeverity"]).toLowerCase();
42
+ if (!VALID_SEVERITIES.includes(sev)) {
43
+ issues.push({
44
+ level: "error",
45
+ field: "minSeverity",
46
+ message: `Invalid severity '${sev}'. Valid: ${VALID_SEVERITIES.join(", ")}`,
47
+ });
48
+ }
49
+ }
50
+ // Check disabledJudges
51
+ if (config["disabledJudges"] !== undefined) {
52
+ if (!Array.isArray(config["disabledJudges"])) {
53
+ issues.push({ level: "error", field: "disabledJudges", message: "Must be an array of strings" });
54
+ }
55
+ }
56
+ // Check disabledRules
57
+ if (config["disabledRules"] !== undefined) {
58
+ if (!Array.isArray(config["disabledRules"])) {
59
+ issues.push({ level: "error", field: "disabledRules", message: "Must be an array of strings" });
60
+ }
61
+ }
62
+ // Check ruleOverrides
63
+ if (config["ruleOverrides"] !== undefined) {
64
+ if (typeof config["ruleOverrides"] !== "object" || config["ruleOverrides"] === null) {
65
+ issues.push({ level: "error", field: "ruleOverrides", message: "Must be an object" });
66
+ }
67
+ }
68
+ // Warn about unknown fields
69
+ const knownFields = new Set([
70
+ "preset",
71
+ "minSeverity",
72
+ "disabledJudges",
73
+ "disabledRules",
74
+ "ruleOverrides",
75
+ "format",
76
+ "failOnFindings",
77
+ "minScore",
78
+ "exclude",
79
+ "include",
80
+ "maxFiles",
81
+ "language",
82
+ "baseline",
83
+ ]);
84
+ for (const key of Object.keys(config)) {
85
+ if (!knownFields.has(key)) {
86
+ issues.push({ level: "info", field: key, message: `Unknown field '${key}' — may be ignored` });
87
+ }
88
+ }
89
+ // Check format
90
+ if (config["format"] !== undefined) {
91
+ const validFormats = ["text", "json", "sarif", "markdown", "html", "pdf", "junit", "codeclimate", "github-actions"];
92
+ if (!validFormats.includes(String(config["format"]))) {
93
+ issues.push({
94
+ level: "warning",
95
+ field: "format",
96
+ message: `Unknown format '${config["format"]}'. Valid: ${validFormats.join(", ")}`,
97
+ });
98
+ }
99
+ }
100
+ // Check minScore
101
+ if (config["minScore"] !== undefined) {
102
+ const score = Number(config["minScore"]);
103
+ if (isNaN(score) || score < 0 || score > 100) {
104
+ issues.push({ level: "error", field: "minScore", message: "Must be a number between 0 and 100" });
105
+ }
106
+ }
107
+ }
108
+ // ─── CLI ────────────────────────────────────────────────────────────────────
109
+ export function runConfigLint(argv) {
110
+ if (argv.includes("--help") || argv.includes("-h")) {
111
+ console.log(`
112
+ judges config-lint — Lint and validate .judgesrc configuration
113
+
114
+ Usage:
115
+ judges config-lint Lint .judgesrc in current dir
116
+ judges config-lint --file custom.judgesrc Lint specific file
117
+ judges config-lint --strict Treat warnings as errors
118
+ judges config-lint --format json JSON output
119
+
120
+ Options:
121
+ --file <path> Configuration file to lint (default: .judgesrc)
122
+ --strict Treat warnings as errors
123
+ --format json JSON output
124
+ --help, -h Show this help
125
+
126
+ Validates configuration for correctness: preset names,
127
+ severity levels, field types, and unknown properties.
128
+ `);
129
+ return;
130
+ }
131
+ const format = argv.find((_a, i) => argv[i - 1] === "--format") || "text";
132
+ const configFile = argv.find((_a, i) => argv[i - 1] === "--file") || ".judgesrc";
133
+ const strict = argv.includes("--strict");
134
+ if (!existsSync(configFile)) {
135
+ console.error(`Error: Configuration file not found: ${configFile}`);
136
+ process.exitCode = 1;
137
+ return;
138
+ }
139
+ let config;
140
+ try {
141
+ config = JSON.parse(readFileSync(configFile, "utf-8"));
142
+ }
143
+ catch (e) {
144
+ console.error(`Error: Invalid JSON in ${configFile}: ${e instanceof Error ? e.message : "parse error"}`);
145
+ process.exitCode = 1;
146
+ return;
147
+ }
148
+ const issues = [];
149
+ lintConfig(config, issues);
150
+ const errors = issues.filter((i) => i.level === "error");
151
+ const warnings = issues.filter((i) => i.level === "warning");
152
+ const infos = issues.filter((i) => i.level === "info");
153
+ const hasErrors = errors.length > 0 || (strict && warnings.length > 0);
154
+ if (format === "json") {
155
+ console.log(JSON.stringify({
156
+ file: configFile,
157
+ valid: !hasErrors,
158
+ errors: errors.length,
159
+ warnings: warnings.length,
160
+ infos: infos.length,
161
+ issues,
162
+ }, null, 2));
163
+ if (hasErrors)
164
+ process.exitCode = 1;
165
+ return;
166
+ }
167
+ console.log(`\n Config Lint: ${configFile}\n ─────────────────────────────`);
168
+ if (issues.length === 0) {
169
+ console.log(" ✅ Configuration is valid. No issues found.");
170
+ console.log();
171
+ return;
172
+ }
173
+ for (const issue of issues) {
174
+ const icon = issue.level === "error" ? "❌" : issue.level === "warning" ? "⚠️" : "ℹ️";
175
+ console.log(` ${icon} [${issue.level.toUpperCase()}] ${issue.field}: ${issue.message}`);
176
+ }
177
+ console.log();
178
+ console.log(` Errors: ${errors.length}, Warnings: ${warnings.length}, Info: ${infos.length}`);
179
+ if (hasErrors) {
180
+ console.log(" ❌ Configuration has errors.");
181
+ process.exitCode = 1;
182
+ }
183
+ else {
184
+ console.log(" ✅ Configuration is valid (with warnings).");
185
+ }
186
+ console.log();
187
+ }
188
+ //# sourceMappingURL=config-lint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-lint.js","sourceRoot":"","sources":["../../src/commands/config-lint.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Finding-age — Track how long findings have been unresolved.
3
+ */
4
+ export declare function runFindingAge(argv: string[]): void;
5
+ //# sourceMappingURL=finding-age.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding-age.d.ts","sourceRoot":"","sources":["../../src/commands/finding-age.ts"],"names":[],"mappings":"AAAA;;GAEG;AAsDH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAqIlD"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Finding-age — Track how long findings have been unresolved.
3
+ */
4
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
5
+ import { join, dirname } from "path";
6
+ // ─── Storage ────────────────────────────────────────────────────────────────
7
+ const AGE_FILE = join(".judges", "finding-age.json");
8
+ function loadAgeStore() {
9
+ if (!existsSync(AGE_FILE))
10
+ return { version: "1.0.0", records: [] };
11
+ try {
12
+ return JSON.parse(readFileSync(AGE_FILE, "utf-8"));
13
+ }
14
+ catch {
15
+ return { version: "1.0.0", records: [] };
16
+ }
17
+ }
18
+ function saveAgeStore(store) {
19
+ mkdirSync(dirname(AGE_FILE), { recursive: true });
20
+ writeFileSync(AGE_FILE, JSON.stringify(store, null, 2), "utf-8");
21
+ }
22
+ function findingFingerprint(f) {
23
+ return [f.ruleId || "", f.title || "", String(f.severity || "")].join("|").toLowerCase();
24
+ }
25
+ function daysBetween(a, b) {
26
+ const msPerDay = 86400000;
27
+ return Math.floor((new Date(b).getTime() - new Date(a).getTime()) / msPerDay);
28
+ }
29
+ // ─── CLI ────────────────────────────────────────────────────────────────────
30
+ export function runFindingAge(argv) {
31
+ if (argv.includes("--help") || argv.includes("-h")) {
32
+ console.log(`
33
+ judges finding-age — Track how long findings have been unresolved
34
+
35
+ Usage:
36
+ judges finding-age update --file verdict.json Update age records
37
+ judges finding-age show Show finding ages
38
+ judges finding-age show --stale 30 Show findings older than 30 days
39
+ judges finding-age clear Clear all records
40
+
41
+ Subcommands:
42
+ update Update records from a verdict file
43
+ show Show age report
44
+ clear Clear all tracking data
45
+
46
+ Options:
47
+ --file <path> Verdict JSON (for update)
48
+ --stale <days> Show only findings older than N days
49
+ --format json JSON output
50
+ --help, -h Show this help
51
+
52
+ Tracks first-seen dates and age of findings across reviews.
53
+ Data stored locally in .judges/finding-age.json.
54
+ `);
55
+ return;
56
+ }
57
+ const format = argv.find((_a, i) => argv[i - 1] === "--format") || "text";
58
+ const subcommand = argv.find((a) => ["update", "show", "clear"].includes(a)) || "show";
59
+ const store = loadAgeStore();
60
+ if (subcommand === "update") {
61
+ const file = argv.find((_a, i) => argv[i - 1] === "--file");
62
+ if (!file || !existsSync(file)) {
63
+ console.error("Error: --file with valid verdict JSON is required.");
64
+ process.exitCode = 1;
65
+ return;
66
+ }
67
+ let verdict;
68
+ try {
69
+ verdict = JSON.parse(readFileSync(file, "utf-8"));
70
+ }
71
+ catch {
72
+ console.error(`Error: Could not parse ${file}`);
73
+ process.exitCode = 1;
74
+ return;
75
+ }
76
+ const now = new Date().toISOString();
77
+ const currentFingerprints = new Set();
78
+ for (const f of verdict.findings || []) {
79
+ const fp = findingFingerprint(f);
80
+ currentFingerprints.add(fp);
81
+ const existing = store.records.find((r) => r.fingerprint === fp);
82
+ if (existing) {
83
+ existing.lastSeen = now;
84
+ existing.ageInDays = daysBetween(existing.firstSeen, now);
85
+ existing.resolved = false;
86
+ existing.resolvedAt = "";
87
+ }
88
+ else {
89
+ store.records.push({
90
+ fingerprint: fp,
91
+ ruleId: f.ruleId || "",
92
+ title: f.title || "",
93
+ severity: f.severity || "unknown",
94
+ firstSeen: now,
95
+ lastSeen: now,
96
+ resolved: false,
97
+ resolvedAt: "",
98
+ ageInDays: 0,
99
+ });
100
+ }
101
+ }
102
+ // Mark resolved findings
103
+ for (const r of store.records) {
104
+ if (!currentFingerprints.has(r.fingerprint) && !r.resolved) {
105
+ r.resolved = true;
106
+ r.resolvedAt = now;
107
+ }
108
+ }
109
+ saveAgeStore(store);
110
+ console.log(`Updated ${store.records.length} finding age records.`);
111
+ return;
112
+ }
113
+ if (subcommand === "clear") {
114
+ saveAgeStore({ version: "1.0.0", records: [] });
115
+ console.log("Finding age records cleared.");
116
+ return;
117
+ }
118
+ // Show
119
+ const staleStr = argv.find((_a, i) => argv[i - 1] === "--stale");
120
+ const staleDays = staleStr ? parseInt(staleStr, 10) : 0;
121
+ let records = store.records.filter((r) => !r.resolved);
122
+ if (staleDays > 0) {
123
+ records = records.filter((r) => r.ageInDays >= staleDays);
124
+ }
125
+ records.sort((a, b) => b.ageInDays - a.ageInDays);
126
+ if (format === "json") {
127
+ console.log(JSON.stringify({ total: store.records.length, unresolved: records.length, staleDays, records }, null, 2));
128
+ return;
129
+ }
130
+ console.log(`\n Finding Age Report\n ═════════════════════════════`);
131
+ console.log(` Total tracked: ${store.records.length}`);
132
+ console.log(` Unresolved: ${records.length}`);
133
+ console.log(` Resolved: ${store.records.filter((r) => r.resolved).length}`);
134
+ if (staleDays > 0)
135
+ console.log(` Showing: older than ${staleDays} days`);
136
+ console.log();
137
+ if (records.length === 0) {
138
+ console.log(" No unresolved findings matching criteria.");
139
+ }
140
+ for (const r of records) {
141
+ const icon = r.ageInDays > 30 ? "🔴" : r.ageInDays > 7 ? "🟡" : "🟢";
142
+ console.log(` ${icon} ${r.ageInDays}d — [${r.severity.toUpperCase()}] ${r.title || r.ruleId} (since ${r.firstSeen.slice(0, 10)})`);
143
+ }
144
+ console.log();
145
+ }
146
+ //# sourceMappingURL=finding-age.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding-age.js","sourceRoot":"","sources":["../../src/commands/finding-age.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Finding-cluster — Cluster related findings by similarity to reveal systemic AI patterns.
3
+ */
4
+ export declare function runFindingCluster(argv: string[]): void;
5
+ //# sourceMappingURL=finding-cluster.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding-cluster.d.ts","sourceRoot":"","sources":["../../src/commands/finding-cluster.ts"],"names":[],"mappings":"AAAA;;GAEG;AAuGH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAuFtD"}
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Finding-cluster — Cluster related findings by similarity to reveal systemic AI patterns.
3
+ */
4
+ import { readFileSync, existsSync } from "fs";
5
+ // ─── Clustering ─────────────────────────────────────────────────────────────
6
+ function clusterFindings(findings) {
7
+ const groups = new Map();
8
+ for (const f of findings) {
9
+ const key = (f.ruleId || "UNKNOWN").split("-")[0];
10
+ const list = groups.get(key) || [];
11
+ list.push(f);
12
+ groups.set(key, list);
13
+ }
14
+ const clusters = [];
15
+ let id = 1;
16
+ for (const [prefix, members] of groups) {
17
+ // Sub-cluster by severity
18
+ const sevGroups = new Map();
19
+ for (const m of members) {
20
+ const sev = String(m.severity || "medium");
21
+ const list = sevGroups.get(sev) || [];
22
+ list.push(m);
23
+ sevGroups.set(sev, list);
24
+ }
25
+ for (const [sev, sevMembers] of sevGroups) {
26
+ clusters.push({
27
+ id: id++,
28
+ label: `${prefix} — ${sev} findings`,
29
+ ruleId: prefix,
30
+ severity: sev,
31
+ count: sevMembers.length,
32
+ findings: sevMembers.map((f) => ({
33
+ title: f.title || "",
34
+ ruleId: f.ruleId || "",
35
+ severity: String(f.severity || "medium"),
36
+ })),
37
+ });
38
+ }
39
+ }
40
+ clusters.sort((a, b) => b.count - a.count);
41
+ return clusters;
42
+ }
43
+ // ─── Similarity ─────────────────────────────────────────────────────────────
44
+ function tokenize(text) {
45
+ return new Set(text
46
+ .toLowerCase()
47
+ .split(/[\s\-_.,;:!?()[\]{}]+/)
48
+ .filter((t) => t.length > 2));
49
+ }
50
+ function jaccardSimilarity(a, b) {
51
+ let intersection = 0;
52
+ for (const token of a) {
53
+ if (b.has(token))
54
+ intersection++;
55
+ }
56
+ const union = a.size + b.size - intersection;
57
+ return union === 0 ? 0 : intersection / union;
58
+ }
59
+ function findSimilarPairs(findings, threshold) {
60
+ const pairs = [];
61
+ for (let i = 0; i < findings.length; i++) {
62
+ const tokensA = tokenize([findings[i].title || "", findings[i].description || ""].join(" "));
63
+ for (let j = i + 1; j < findings.length; j++) {
64
+ const tokensB = tokenize([findings[j].title || "", findings[j].description || ""].join(" "));
65
+ const sim = jaccardSimilarity(tokensA, tokensB);
66
+ if (sim >= threshold) {
67
+ pairs.push({ a: findings[i].title || `Finding ${i}`, b: findings[j].title || `Finding ${j}`, similarity: sim });
68
+ }
69
+ }
70
+ }
71
+ pairs.sort((a, b) => b.similarity - a.similarity);
72
+ return pairs;
73
+ }
74
+ // ─── CLI ────────────────────────────────────────────────────────────────────
75
+ export function runFindingCluster(argv) {
76
+ if (argv.includes("--help") || argv.includes("-h")) {
77
+ console.log(`
78
+ judges finding-cluster — Cluster related findings to reveal systemic patterns
79
+
80
+ Usage:
81
+ judges finding-cluster --file verdict.json Cluster findings from verdict
82
+ judges finding-cluster --file v.json --similar Show similar finding pairs
83
+ judges finding-cluster --file v.json --top 5 Show top N clusters
84
+
85
+ Options:
86
+ --file <path> Verdict JSON file
87
+ --similar Show similar finding pairs
88
+ --threshold <n> Similarity threshold 0-1 (default: 0.3)
89
+ --top <n> Show top N clusters (default: all)
90
+ --format json JSON output
91
+ --help, -h Show this help
92
+
93
+ Identifies recurring patterns in AI-generated code findings.
94
+ `);
95
+ return;
96
+ }
97
+ const file = argv.find((_a, i) => argv[i - 1] === "--file");
98
+ if (!file || !existsSync(file)) {
99
+ console.error("Error: --file with valid verdict JSON is required.");
100
+ process.exitCode = 1;
101
+ return;
102
+ }
103
+ let verdict;
104
+ try {
105
+ verdict = JSON.parse(readFileSync(file, "utf-8"));
106
+ }
107
+ catch {
108
+ console.error("Error: Failed to parse verdict file.");
109
+ process.exitCode = 1;
110
+ return;
111
+ }
112
+ const format = argv.find((_a, i) => argv[i - 1] === "--format") || "text";
113
+ const topN = parseInt(argv.find((_a, i) => argv[i - 1] === "--top") || "0", 10);
114
+ if (argv.includes("--similar")) {
115
+ const threshold = parseFloat(argv.find((_a, i) => argv[i - 1] === "--threshold") || "0.3");
116
+ const pairs = findSimilarPairs(verdict.findings || [], threshold);
117
+ if (format === "json") {
118
+ console.log(JSON.stringify(pairs, null, 2));
119
+ return;
120
+ }
121
+ if (pairs.length === 0) {
122
+ console.log("No similar finding pairs found above threshold.");
123
+ return;
124
+ }
125
+ console.log("\nSimilar Finding Pairs:");
126
+ console.log("─".repeat(70));
127
+ for (const p of pairs.slice(0, 20)) {
128
+ console.log(` ${(p.similarity * 100).toFixed(0)}% "${p.a}" ↔ "${p.b}"`);
129
+ }
130
+ console.log("─".repeat(70));
131
+ return;
132
+ }
133
+ let clusters = clusterFindings(verdict.findings || []);
134
+ if (topN > 0)
135
+ clusters = clusters.slice(0, topN);
136
+ if (format === "json") {
137
+ console.log(JSON.stringify(clusters, null, 2));
138
+ return;
139
+ }
140
+ if (clusters.length === 0) {
141
+ console.log("No findings to cluster.");
142
+ return;
143
+ }
144
+ console.log("\nFinding Clusters:");
145
+ console.log("─".repeat(60));
146
+ for (const c of clusters) {
147
+ console.log(` Cluster #${c.id}: ${c.label} (${c.count} findings)`);
148
+ for (const m of c.findings.slice(0, 5)) {
149
+ console.log(` - [${m.ruleId}] ${m.title}`);
150
+ }
151
+ if (c.findings.length > 5)
152
+ console.log(` ... and ${c.findings.length - 5} more`);
153
+ console.log();
154
+ }
155
+ console.log("─".repeat(60));
156
+ console.log(`Total: ${clusters.length} cluster(s), ${(verdict.findings || []).length} finding(s)`);
157
+ }
158
+ //# sourceMappingURL=finding-cluster.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding-cluster.js","sourceRoot":"","sources":["../../src/commands/finding-cluster.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Finding-hotspot — Identify files and directories with highest finding density.
3
+ */
4
+ export declare function runFindingHotspot(argv: string[]): void;
5
+ //# sourceMappingURL=finding-hotspot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding-hotspot.d.ts","sourceRoot":"","sources":["../../src/commands/finding-hotspot.ts"],"names":[],"mappings":"AAAA;;GAEG;AA+DH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAyEtD"}