@kevinrabun/judges 3.59.0 → 3.61.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 +19 -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/adoption-track.d.ts +5 -0
  6. package/dist/commands/adoption-track.d.ts.map +1 -0
  7. package/dist/commands/adoption-track.js +247 -0
  8. package/dist/commands/adoption-track.js.map +1 -0
  9. package/dist/commands/ai-provenance.d.ts +5 -0
  10. package/dist/commands/ai-provenance.d.ts.map +1 -0
  11. package/dist/commands/ai-provenance.js +248 -0
  12. package/dist/commands/ai-provenance.js.map +1 -0
  13. package/dist/commands/blame-review.d.ts +5 -0
  14. package/dist/commands/blame-review.d.ts.map +1 -0
  15. package/dist/commands/blame-review.js +270 -0
  16. package/dist/commands/blame-review.js.map +1 -0
  17. package/dist/commands/context-blind.d.ts +5 -0
  18. package/dist/commands/context-blind.d.ts.map +1 -0
  19. package/dist/commands/context-blind.js +273 -0
  20. package/dist/commands/context-blind.js.map +1 -0
  21. package/dist/commands/evidence-chain.d.ts +5 -0
  22. package/dist/commands/evidence-chain.d.ts.map +1 -0
  23. package/dist/commands/evidence-chain.js +310 -0
  24. package/dist/commands/evidence-chain.js.map +1 -0
  25. package/dist/commands/finding-budget.d.ts +5 -0
  26. package/dist/commands/finding-budget.d.ts.map +1 -0
  27. package/dist/commands/finding-budget.js +233 -0
  28. package/dist/commands/finding-budget.js.map +1 -0
  29. package/dist/commands/hallucination-detect.d.ts +5 -0
  30. package/dist/commands/hallucination-detect.d.ts.map +1 -0
  31. package/dist/commands/hallucination-detect.js +351 -0
  32. package/dist/commands/hallucination-detect.js.map +1 -0
  33. package/dist/commands/merge-verdict.d.ts +5 -0
  34. package/dist/commands/merge-verdict.d.ts.map +1 -0
  35. package/dist/commands/merge-verdict.js +288 -0
  36. package/dist/commands/merge-verdict.js.map +1 -0
  37. package/dist/commands/over-abstraction.d.ts +5 -0
  38. package/dist/commands/over-abstraction.d.ts.map +1 -0
  39. package/dist/commands/over-abstraction.js +308 -0
  40. package/dist/commands/over-abstraction.js.map +1 -0
  41. package/dist/commands/quick-check.d.ts +5 -0
  42. package/dist/commands/quick-check.d.ts.map +1 -0
  43. package/dist/commands/quick-check.js +174 -0
  44. package/dist/commands/quick-check.js.map +1 -0
  45. package/dist/commands/review-contract.d.ts +5 -0
  46. package/dist/commands/review-contract.d.ts.map +1 -0
  47. package/dist/commands/review-contract.js +200 -0
  48. package/dist/commands/review-contract.js.map +1 -0
  49. package/dist/commands/review-digest.d.ts +5 -0
  50. package/dist/commands/review-digest.d.ts.map +1 -0
  51. package/dist/commands/review-digest.js +266 -0
  52. package/dist/commands/review-digest.js.map +1 -0
  53. package/dist/commands/review-handoff.d.ts +5 -0
  54. package/dist/commands/review-handoff.d.ts.map +1 -0
  55. package/dist/commands/review-handoff.js +209 -0
  56. package/dist/commands/review-handoff.js.map +1 -0
  57. package/dist/commands/review-receipt.d.ts +5 -0
  58. package/dist/commands/review-receipt.d.ts.map +1 -0
  59. package/dist/commands/review-receipt.js +221 -0
  60. package/dist/commands/review-receipt.js.map +1 -0
  61. package/dist/commands/security-theater.d.ts +5 -0
  62. package/dist/commands/security-theater.d.ts.map +1 -0
  63. package/dist/commands/security-theater.js +279 -0
  64. package/dist/commands/security-theater.js.map +1 -0
  65. package/dist/commands/stale-pattern.d.ts +5 -0
  66. package/dist/commands/stale-pattern.d.ts.map +1 -0
  67. package/dist/commands/stale-pattern.js +294 -0
  68. package/dist/commands/stale-pattern.js.map +1 -0
  69. package/package.json +1 -1
  70. package/server.json +2 -2
@@ -0,0 +1,351 @@
1
+ /**
2
+ * Hallucination detect — find fabricated API calls, non-existent methods, and invented config options.
3
+ */
4
+ import { readFileSync, readdirSync, statSync } from "fs";
5
+ import { join, extname, resolve } from "path";
6
+ // ─── File Collection ────────────────────────────────────────────────────────
7
+ const CODE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx"]);
8
+ function collectFiles(dir, max = 300) {
9
+ const files = [];
10
+ function walk(d) {
11
+ if (files.length >= max)
12
+ return;
13
+ let entries;
14
+ try {
15
+ entries = readdirSync(d);
16
+ }
17
+ catch {
18
+ return;
19
+ }
20
+ for (const e of entries) {
21
+ if (files.length >= max)
22
+ return;
23
+ if (e.startsWith(".") || e === "node_modules" || e === "dist" || e === "build")
24
+ continue;
25
+ const full = join(d, e);
26
+ try {
27
+ if (statSync(full).isDirectory())
28
+ walk(full);
29
+ else if (CODE_EXTS.has(extname(full)))
30
+ files.push(full);
31
+ }
32
+ catch {
33
+ /* skip */
34
+ }
35
+ }
36
+ }
37
+ walk(dir);
38
+ return files;
39
+ }
40
+ // ─── Known API Databases ────────────────────────────────────────────────────
41
+ const DEPRECATED_NODE_APIS = {
42
+ "url.parse": "new URL()",
43
+ "new Buffer(": "Buffer.from() or Buffer.alloc()",
44
+ "fs.exists(": "fs.existsSync() or fs.access()",
45
+ "path.existsSync": "fs.existsSync()",
46
+ "crypto.createCipher(": "crypto.createCipheriv()",
47
+ "crypto.createDecipher(": "crypto.createDecipheriv()",
48
+ "os.tmpDir()": "os.tmpdir()",
49
+ "util.puts": "console.log",
50
+ "util.print": "console.log",
51
+ "sys.puts": "console.log",
52
+ };
53
+ const NONEXISTENT_METHODS = [
54
+ { pattern: /Array\.flatten\b/, message: "Array.flatten doesn't exist — use Array.prototype.flat()" },
55
+ { pattern: /Array\.contains\b/, message: "Array.contains doesn't exist — use Array.prototype.includes()" },
56
+ { pattern: /String\.contains\b/, message: "String.contains doesn't exist — use String.prototype.includes()" },
57
+ { pattern: /Object\.length\b/, message: "Object.length doesn't exist — use Object.keys().length" },
58
+ {
59
+ pattern: /\.size\(\)/,
60
+ message: ".size() is not a method — use .size (property) for Map/Set or .length for arrays",
61
+ },
62
+ {
63
+ pattern: /Promise\.delay\b/,
64
+ message: "Promise.delay doesn't exist in native Promise — use setTimeout wrapper or Bluebird",
65
+ },
66
+ { pattern: /JSON\.tryParse\b/, message: "JSON.tryParse doesn't exist — wrap JSON.parse in try/catch" },
67
+ {
68
+ pattern: /Array\.from\(\s*\{length:\s*\d+\}\s*,\s*\(\s*_\s*,\s*i\s*\)\s*=>\s*i\s*\)/,
69
+ message: "Consider Array.from({length: N}, (_, i) => i) — correct but verbose; for simple ranges consider other approaches",
70
+ },
71
+ { pattern: /console\.debug\b/, message: "" }, // console.debug exists, don't flag
72
+ { pattern: /Math\.clamp\b/, message: "Math.clamp doesn't exist — use Math.min(Math.max(value, min), max)" },
73
+ { pattern: /Object\.isEmpty\b/, message: "Object.isEmpty doesn't exist — use Object.keys(obj).length === 0" },
74
+ { pattern: /String\.reverse\b/, message: "String.reverse doesn't exist — use .split('').reverse().join('')" },
75
+ { pattern: /Array\.unique\b/, message: "Array.unique doesn't exist — use [...new Set(array)]" },
76
+ { pattern: /\.replaceAll\b/, message: "" }, // replaceAll exists in ES2021+, don't flag
77
+ ];
78
+ // ─── Analysis ───────────────────────────────────────────────────────────────
79
+ function analyzeFile(filepath, projectDir) {
80
+ const issues = [];
81
+ let content;
82
+ try {
83
+ content = readFileSync(filepath, "utf-8");
84
+ }
85
+ catch {
86
+ return issues;
87
+ }
88
+ const lines = content.split("\n");
89
+ for (let i = 0; i < lines.length; i++) {
90
+ const line = lines[i];
91
+ // Check deprecated Node.js APIs
92
+ for (const [api, replacement] of Object.entries(DEPRECATED_NODE_APIS)) {
93
+ if (line.includes(api)) {
94
+ issues.push({
95
+ file: filepath,
96
+ line: i + 1,
97
+ issue: `Deprecated API: ${api}`,
98
+ severity: "medium",
99
+ detail: `\`${api}\` is deprecated — use \`${replacement}\` instead`,
100
+ });
101
+ }
102
+ }
103
+ // Check non-existent methods
104
+ for (const { pattern, message } of NONEXISTENT_METHODS) {
105
+ if (message && pattern.test(line)) {
106
+ issues.push({ file: filepath, line: i + 1, issue: "Non-existent API call", severity: "high", detail: message });
107
+ }
108
+ }
109
+ // Check for fabricated Node.js module methods
110
+ if (/require\s*\(\s*['"]fs['"]\s*\)\.(\w+)/.test(line) || /from\s+['"]fs['"]/.test(line)) {
111
+ const methodMatch = line.match(/fs\.(\w+)\s*\(/g);
112
+ if (methodMatch) {
113
+ const knownFs = new Set([
114
+ "readFile",
115
+ "readFileSync",
116
+ "writeFile",
117
+ "writeFileSync",
118
+ "readdir",
119
+ "readdirSync",
120
+ "stat",
121
+ "statSync",
122
+ "lstat",
123
+ "lstatSync",
124
+ "mkdir",
125
+ "mkdirSync",
126
+ "rmdir",
127
+ "rmdirSync",
128
+ "unlink",
129
+ "unlinkSync",
130
+ "rename",
131
+ "renameSync",
132
+ "copyFile",
133
+ "copyFileSync",
134
+ "access",
135
+ "accessSync",
136
+ "existsSync",
137
+ "createReadStream",
138
+ "createWriteStream",
139
+ "watch",
140
+ "watchFile",
141
+ "unwatchFile",
142
+ "open",
143
+ "openSync",
144
+ "close",
145
+ "closeSync",
146
+ "read",
147
+ "readSync",
148
+ "write",
149
+ "writeSync",
150
+ "appendFile",
151
+ "appendFileSync",
152
+ "chmod",
153
+ "chmodSync",
154
+ "chown",
155
+ "chownSync",
156
+ "realpath",
157
+ "realpathSync",
158
+ "link",
159
+ "linkSync",
160
+ "symlink",
161
+ "symlinkSync",
162
+ "truncate",
163
+ "truncateSync",
164
+ "rm",
165
+ "rmSync",
166
+ "cp",
167
+ "cpSync",
168
+ "promises",
169
+ ]);
170
+ for (const call of methodMatch) {
171
+ const method = call.match(/fs\.(\w+)/)?.[1];
172
+ if (method && !knownFs.has(method)) {
173
+ issues.push({
174
+ file: filepath,
175
+ line: i + 1,
176
+ issue: "Non-existent fs method",
177
+ severity: "high",
178
+ detail: `\`fs.${method}\` does not exist in Node.js fs module — AI may have hallucinated this method`,
179
+ });
180
+ }
181
+ }
182
+ }
183
+ }
184
+ // Check for invented environment variables referenced without fallback
185
+ const envMatch = line.match(/process\.env\.([A-Z_]+)/g);
186
+ if (envMatch) {
187
+ for (const envRef of envMatch) {
188
+ const varName = envRef.replace("process.env.", "");
189
+ // Check if it's defined in any .env file
190
+ const envFiles = [".env", ".env.example", ".env.local", ".env.development"];
191
+ let defined = false;
192
+ for (const envFile of envFiles) {
193
+ try {
194
+ const envContent = readFileSync(join(projectDir, envFile), "utf-8");
195
+ if (envContent.includes(varName)) {
196
+ defined = true;
197
+ break;
198
+ }
199
+ }
200
+ catch {
201
+ /* skip */
202
+ }
203
+ }
204
+ if (!defined && !/\|\||[?][?]|:\s*['"]|default/i.test(line)) {
205
+ issues.push({
206
+ file: filepath,
207
+ line: i + 1,
208
+ issue: "Undeclared env variable without fallback",
209
+ severity: "medium",
210
+ detail: `\`${varName}\` not found in .env files and has no fallback — AI may have invented this variable`,
211
+ });
212
+ }
213
+ }
214
+ }
215
+ // Check for plausible but non-existent Express/Koa/Fastify methods
216
+ if (/(?:app|router|server)\.(\w+)\s*\(/.test(line)) {
217
+ const method = line.match(/(?:app|router|server)\.(\w+)\s*\(/)?.[1];
218
+ const fabricated = new Set([
219
+ "mount",
220
+ "register",
221
+ "addRoute",
222
+ "define",
223
+ "handle",
224
+ "before",
225
+ "after",
226
+ "onRequest",
227
+ "onResponse",
228
+ "preHandler",
229
+ "postHandler",
230
+ ]);
231
+ // Only flag if we see express-like imports
232
+ if (method && fabricated.has(method) && /express|koa|hapi/.test(content)) {
233
+ issues.push({
234
+ file: filepath,
235
+ line: i + 1,
236
+ issue: "Possibly fabricated framework method",
237
+ severity: "medium",
238
+ detail: `\`.${method}()\` may not exist on this framework — verify against official docs`,
239
+ });
240
+ }
241
+ }
242
+ // Type assertions to non-imported types
243
+ if (/\bas\s+([A-Z]\w+)/.test(line)) {
244
+ const typeName = line.match(/\bas\s+([A-Z]\w+)/)?.[1];
245
+ if (typeName &&
246
+ ![
247
+ "HTMLElement",
248
+ "Element",
249
+ "Event",
250
+ "Error",
251
+ "Date",
252
+ "RegExp",
253
+ "Promise",
254
+ "Array",
255
+ "Map",
256
+ "Set",
257
+ "Record",
258
+ "Partial",
259
+ "Required",
260
+ "Pick",
261
+ "Omit",
262
+ "Readonly",
263
+ "Response",
264
+ "Request",
265
+ "Buffer",
266
+ "NodeJS",
267
+ "Window",
268
+ "Document",
269
+ "Function",
270
+ "Object",
271
+ "String",
272
+ "Number",
273
+ "Boolean",
274
+ "Symbol",
275
+ ].includes(typeName)) {
276
+ // Check if it's imported or defined in this file
277
+ if (!content.includes(`interface ${typeName}`) &&
278
+ !content.includes(`type ${typeName}`) &&
279
+ !content.includes(`class ${typeName}`) &&
280
+ !content.includes(`enum ${typeName}`) &&
281
+ !new RegExp(`import.*\\b${typeName}\\b`).test(content)) {
282
+ issues.push({
283
+ file: filepath,
284
+ line: i + 1,
285
+ issue: "Type assertion to undefined type",
286
+ severity: "medium",
287
+ detail: `\`as ${typeName}\` — type is not imported or defined in this file`,
288
+ });
289
+ }
290
+ }
291
+ }
292
+ }
293
+ return issues;
294
+ }
295
+ // ─── CLI ────────────────────────────────────────────────────────────────────
296
+ export function runHallucinationDetect(argv) {
297
+ if (argv.includes("--help") || argv.includes("-h")) {
298
+ console.log(`
299
+ judges hallucination-detect — Find fabricated API calls and non-existent methods
300
+
301
+ Usage:
302
+ judges hallucination-detect [dir]
303
+ judges hallucination-detect src/ --format json
304
+
305
+ Options:
306
+ [dir] Directory to scan (default: .)
307
+ --format json JSON output
308
+ --help, -h Show this help
309
+
310
+ Checks: deprecated Node APIs, non-existent JS methods, fabricated fs methods,
311
+ undeclared env variables, fabricated framework methods, undefined type assertions.
312
+ `);
313
+ return;
314
+ }
315
+ const format = argv.find((_a, i) => argv[i - 1] === "--format") || "text";
316
+ const dir = argv.find((a) => !a.startsWith("-") && argv.indexOf(a) > 0) || ".";
317
+ const projectDir = resolve(dir);
318
+ const files = collectFiles(dir);
319
+ const allIssues = [];
320
+ for (const f of files)
321
+ allIssues.push(...analyzeFile(f, projectDir));
322
+ const highCount = allIssues.filter((i) => i.severity === "high").length;
323
+ const medCount = allIssues.filter((i) => i.severity === "medium").length;
324
+ const score = Math.max(0, 100 - highCount * 15 - medCount * 5);
325
+ if (format === "json") {
326
+ console.log(JSON.stringify({
327
+ issues: allIssues,
328
+ score,
329
+ summary: { high: highCount, medium: medCount, total: allIssues.length },
330
+ timestamp: new Date().toISOString(),
331
+ }, null, 2));
332
+ }
333
+ else {
334
+ const badge = score >= 80 ? "✅ GROUNDED" : score >= 50 ? "⚠️ SUSPECT" : "❌ HALLUCINATING";
335
+ console.log(`\n Hallucination Detect: ${badge} (${score}/100)\n ─────────────────────────────`);
336
+ if (allIssues.length === 0) {
337
+ console.log(" No hallucinated APIs detected.\n");
338
+ return;
339
+ }
340
+ for (const issue of allIssues.slice(0, 25)) {
341
+ const icon = issue.severity === "high" ? "🔴" : issue.severity === "medium" ? "🟡" : "🔵";
342
+ console.log(` ${icon} ${issue.issue}`);
343
+ console.log(` ${issue.file}:${issue.line}`);
344
+ console.log(` ${issue.detail}`);
345
+ }
346
+ if (allIssues.length > 25)
347
+ console.log(` ... and ${allIssues.length - 25} more`);
348
+ console.log(`\n Total: ${allIssues.length} | High: ${highCount} | Medium: ${medCount} | Score: ${score}/100\n`);
349
+ }
350
+ }
351
+ //# sourceMappingURL=hallucination-detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hallucination-detect.js","sourceRoot":"","sources":["../../src/commands/hallucination-detect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Merge-verdict — single authoritative MERGE/HOLD decision with structured rationale.
3
+ */
4
+ export declare function runMergeVerdict(argv: string[]): void;
5
+ //# sourceMappingURL=merge-verdict.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-verdict.d.ts","sourceRoot":"","sources":["../../src/commands/merge-verdict.ts"],"names":[],"mappings":"AAAA;;GAEG;AAyRH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAoEpD"}
@@ -0,0 +1,288 @@
1
+ /**
2
+ * Merge-verdict — single authoritative MERGE/HOLD decision with structured rationale.
3
+ */
4
+ import { readFileSync, readdirSync, statSync } from "fs";
5
+ import { join, extname, relative } from "path";
6
+ // ─── File Collection ────────────────────────────────────────────────────────
7
+ const CODE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".py", ".java", ".go", ".cs", ".rs"]);
8
+ function collectFiles(dir, max = 300) {
9
+ const files = [];
10
+ function walk(d) {
11
+ if (files.length >= max)
12
+ return;
13
+ let entries;
14
+ try {
15
+ entries = readdirSync(d);
16
+ }
17
+ catch {
18
+ return;
19
+ }
20
+ for (const e of entries) {
21
+ if (files.length >= max)
22
+ return;
23
+ if (e.startsWith(".") || e === "node_modules" || e === "dist" || e === "build")
24
+ continue;
25
+ const full = join(d, e);
26
+ try {
27
+ if (statSync(full).isDirectory())
28
+ walk(full);
29
+ else if (CODE_EXTS.has(extname(full)))
30
+ files.push(full);
31
+ }
32
+ catch {
33
+ /* skip */
34
+ }
35
+ }
36
+ }
37
+ walk(dir);
38
+ return files;
39
+ }
40
+ const DIMENSION_PATTERNS = [
41
+ // Security (blocking)
42
+ {
43
+ regex: /\beval\s*\(/,
44
+ category: "Security",
45
+ title: "eval() injection",
46
+ severity: "critical",
47
+ dimension: "security",
48
+ blocking: true,
49
+ },
50
+ {
51
+ regex: /(?:password|secret|api[_-]?key)\s*[:=]\s*['"][^'"]{4,}['"]/,
52
+ category: "Security",
53
+ title: "Hardcoded credential",
54
+ severity: "critical",
55
+ dimension: "security",
56
+ blocking: true,
57
+ },
58
+ {
59
+ regex: /\.innerHTML\s*=/,
60
+ category: "Security",
61
+ title: "XSS via innerHTML",
62
+ severity: "high",
63
+ dimension: "security",
64
+ blocking: true,
65
+ },
66
+ {
67
+ regex: /(?:exec|spawn)\s*\([^)]*\+/,
68
+ category: "Security",
69
+ title: "Command injection",
70
+ severity: "critical",
71
+ dimension: "security",
72
+ blocking: true,
73
+ },
74
+ {
75
+ regex: /SELECT.*FROM.*\+\s*(?:req|input|user|param)/,
76
+ category: "Security",
77
+ title: "SQL injection",
78
+ severity: "critical",
79
+ dimension: "security",
80
+ blocking: true,
81
+ },
82
+ // Quality (non-blocking)
83
+ {
84
+ regex: /catch\s*\(\s*\w*\s*\)\s*\{\s*\}/,
85
+ category: "Quality",
86
+ title: "Empty catch block",
87
+ severity: "medium",
88
+ dimension: "quality",
89
+ blocking: false,
90
+ },
91
+ {
92
+ regex: /console\.log\s*\(/,
93
+ category: "Quality",
94
+ title: "Console statement",
95
+ severity: "low",
96
+ dimension: "quality",
97
+ blocking: false,
98
+ },
99
+ {
100
+ regex: /debugger\b/,
101
+ category: "Quality",
102
+ title: "Debugger statement",
103
+ severity: "medium",
104
+ dimension: "quality",
105
+ blocking: false,
106
+ },
107
+ {
108
+ regex: /TODO|FIXME|HACK|XXX/,
109
+ category: "Quality",
110
+ title: "Open TODO",
111
+ severity: "low",
112
+ dimension: "quality",
113
+ blocking: false,
114
+ },
115
+ // Correctness (blocking for critical)
116
+ {
117
+ regex: /new\s+Buffer\s*\(/,
118
+ category: "Correctness",
119
+ title: "Deprecated Buffer()",
120
+ severity: "high",
121
+ dimension: "correctness",
122
+ blocking: false,
123
+ },
124
+ {
125
+ regex: /process\.exit\s*\(\s*\)/,
126
+ category: "Correctness",
127
+ title: "Ungraceful exit",
128
+ severity: "medium",
129
+ dimension: "correctness",
130
+ blocking: false,
131
+ },
132
+ // Compliance
133
+ {
134
+ regex: /\/\/\s*(?:eslint|tslint|prettier)-disable/,
135
+ category: "Compliance",
136
+ title: "Linter suppression",
137
+ severity: "low",
138
+ dimension: "compliance",
139
+ blocking: false,
140
+ },
141
+ ];
142
+ // ─── Analysis ───────────────────────────────────────────────────────────────
143
+ function analyzeFile(filepath, baseDir) {
144
+ const findings = [];
145
+ let content;
146
+ try {
147
+ content = readFileSync(filepath, "utf-8");
148
+ }
149
+ catch {
150
+ return findings;
151
+ }
152
+ const lines = content.split("\n");
153
+ const rel = relative(baseDir, filepath);
154
+ for (let i = 0; i < lines.length; i++) {
155
+ const line = lines[i];
156
+ const trimmed = line.trim();
157
+ if (trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*"))
158
+ continue;
159
+ for (const pattern of DIMENSION_PATTERNS) {
160
+ if (pattern.regex.test(line)) {
161
+ findings.push({
162
+ file: rel,
163
+ line: i + 1,
164
+ severity: pattern.severity,
165
+ category: pattern.category,
166
+ title: pattern.title,
167
+ blocking: pattern.blocking,
168
+ });
169
+ }
170
+ }
171
+ }
172
+ return findings;
173
+ }
174
+ function renderDecision(allFindings, threshold) {
175
+ const blocking = allFindings.filter((f) => f.blocking);
176
+ const accepted = allFindings.filter((f) => !f.blocking);
177
+ // Dimension scores
178
+ const dimFindings = (dim) => allFindings.filter((f) => {
179
+ const p = DIMENSION_PATTERNS.find((dp) => dp.title === f.title);
180
+ return p && p.dimension === dim;
181
+ });
182
+ const dimScore = (dim) => {
183
+ const df = dimFindings(dim);
184
+ const crits = df.filter((f) => f.severity === "critical").length;
185
+ const highs = df.filter((f) => f.severity === "high").length;
186
+ return Math.max(0, 100 -
187
+ crits * 25 -
188
+ highs * 12 -
189
+ df.filter((f) => f.severity === "medium").length * 5 -
190
+ df.filter((f) => f.severity === "low").length);
191
+ };
192
+ const dimensions = {
193
+ security: dimScore("security"),
194
+ quality: dimScore("quality"),
195
+ correctness: dimScore("correctness"),
196
+ compliance: dimScore("compliance"),
197
+ };
198
+ const riskScore = Math.round(dimensions.security * 0.4 + dimensions.quality * 0.2 + dimensions.correctness * 0.25 + dimensions.compliance * 0.15);
199
+ const decision = blocking.length > 0 || riskScore < threshold ? "HOLD" : "MERGE";
200
+ const confidence = blocking.length === 0 ? Math.min(95, riskScore) : Math.max(60, 100 - blocking.length * 10);
201
+ const rationale = [];
202
+ if (blocking.length > 0)
203
+ rationale.push(`${blocking.length} blocking finding(s) require resolution before merge`);
204
+ if (dimensions.security < 70)
205
+ rationale.push(`Security score (${dimensions.security}) is below acceptable threshold`);
206
+ if (dimensions.correctness < 70)
207
+ rationale.push(`Correctness score (${dimensions.correctness}) indicates potential bugs`);
208
+ if (accepted.length > 0)
209
+ rationale.push(`${accepted.length} non-blocking finding(s) accepted as known risks`);
210
+ if (decision === "MERGE")
211
+ rationale.push(`Risk score (${riskScore}) meets or exceeds threshold (${threshold})`);
212
+ const summary = decision === "MERGE"
213
+ ? `MERGE — Code passes review with ${accepted.length} accepted risk(s). Risk score: ${riskScore}/100.`
214
+ : `HOLD — ${blocking.length} blocking finding(s) and risk score ${riskScore}/100 (threshold: ${threshold}).`;
215
+ return {
216
+ decision,
217
+ confidence,
218
+ riskScore,
219
+ blockingFindings: blocking,
220
+ acceptedRisks: accepted,
221
+ dimensions,
222
+ rationale,
223
+ summary,
224
+ };
225
+ }
226
+ // ─── CLI ────────────────────────────────────────────────────────────────────
227
+ export function runMergeVerdict(argv) {
228
+ if (argv.includes("--help") || argv.includes("-h")) {
229
+ console.log(`
230
+ judges merge-verdict — Single authoritative MERGE/HOLD decision
231
+
232
+ Usage:
233
+ judges merge-verdict [dir]
234
+ judges merge-verdict src/ --threshold 75 --format json
235
+
236
+ Options:
237
+ [dir] Directory to scan (default: .)
238
+ --threshold <n> Minimum risk score for MERGE (default: 70)
239
+ --format json JSON output (for CI/CD integration)
240
+ --help, -h Show this help
241
+
242
+ Synthesizes security, quality, correctness, and compliance dimensions
243
+ into one MERGE or HOLD decision with structured rationale.
244
+ `);
245
+ return;
246
+ }
247
+ const format = argv.find((_a, i) => argv[i - 1] === "--format") || "text";
248
+ const threshStr = argv.find((_a, i) => argv[i - 1] === "--threshold");
249
+ const threshold = threshStr ? parseInt(threshStr, 10) : 70;
250
+ const dir = argv.find((a) => !a.startsWith("-") &&
251
+ argv.indexOf(a) > 0 &&
252
+ argv[argv.indexOf(a) - 1] !== "--format" &&
253
+ argv[argv.indexOf(a) - 1] !== "--threshold") || ".";
254
+ const files = collectFiles(dir);
255
+ const allFindings = [];
256
+ for (const f of files)
257
+ allFindings.push(...analyzeFile(f, dir));
258
+ const result = renderDecision(allFindings, threshold);
259
+ if (format === "json") {
260
+ console.log(JSON.stringify({ ...result, timestamp: new Date().toISOString() }, null, 2));
261
+ }
262
+ else {
263
+ const icon = result.decision === "MERGE" ? "✅" : "❌";
264
+ console.log(`\n ${icon} ${result.decision} (confidence: ${result.confidence}%)\n ─────────────────────────────`);
265
+ console.log(` Risk Score: ${result.riskScore}/100 (threshold: ${threshold})`);
266
+ console.log(` Security: ${result.dimensions.security}/100`);
267
+ console.log(` Quality: ${result.dimensions.quality}/100`);
268
+ console.log(` Correctness: ${result.dimensions.correctness}/100`);
269
+ console.log(` Compliance: ${result.dimensions.compliance}/100\n`);
270
+ if (result.blockingFindings.length > 0) {
271
+ console.log(` Blocking (${result.blockingFindings.length}):`);
272
+ for (const f of result.blockingFindings.slice(0, 10)) {
273
+ console.log(` 🔴 [${f.category}] ${f.title} — ${f.file}:${f.line}`);
274
+ }
275
+ console.log();
276
+ }
277
+ if (result.rationale.length > 0) {
278
+ console.log(` Rationale:`);
279
+ for (const r of result.rationale)
280
+ console.log(` → ${r}`);
281
+ console.log();
282
+ }
283
+ console.log(` ${result.summary}\n`);
284
+ if (result.decision === "HOLD")
285
+ process.exitCode = 1;
286
+ }
287
+ }
288
+ //# sourceMappingURL=merge-verdict.js.map