@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.
- package/CHANGELOG.md +19 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +112 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/adoption-track.d.ts +5 -0
- package/dist/commands/adoption-track.d.ts.map +1 -0
- package/dist/commands/adoption-track.js +247 -0
- package/dist/commands/adoption-track.js.map +1 -0
- package/dist/commands/ai-provenance.d.ts +5 -0
- package/dist/commands/ai-provenance.d.ts.map +1 -0
- package/dist/commands/ai-provenance.js +248 -0
- package/dist/commands/ai-provenance.js.map +1 -0
- package/dist/commands/blame-review.d.ts +5 -0
- package/dist/commands/blame-review.d.ts.map +1 -0
- package/dist/commands/blame-review.js +270 -0
- package/dist/commands/blame-review.js.map +1 -0
- package/dist/commands/context-blind.d.ts +5 -0
- package/dist/commands/context-blind.d.ts.map +1 -0
- package/dist/commands/context-blind.js +273 -0
- package/dist/commands/context-blind.js.map +1 -0
- package/dist/commands/evidence-chain.d.ts +5 -0
- package/dist/commands/evidence-chain.d.ts.map +1 -0
- package/dist/commands/evidence-chain.js +310 -0
- package/dist/commands/evidence-chain.js.map +1 -0
- package/dist/commands/finding-budget.d.ts +5 -0
- package/dist/commands/finding-budget.d.ts.map +1 -0
- package/dist/commands/finding-budget.js +233 -0
- package/dist/commands/finding-budget.js.map +1 -0
- package/dist/commands/hallucination-detect.d.ts +5 -0
- package/dist/commands/hallucination-detect.d.ts.map +1 -0
- package/dist/commands/hallucination-detect.js +351 -0
- package/dist/commands/hallucination-detect.js.map +1 -0
- package/dist/commands/merge-verdict.d.ts +5 -0
- package/dist/commands/merge-verdict.d.ts.map +1 -0
- package/dist/commands/merge-verdict.js +288 -0
- package/dist/commands/merge-verdict.js.map +1 -0
- package/dist/commands/over-abstraction.d.ts +5 -0
- package/dist/commands/over-abstraction.d.ts.map +1 -0
- package/dist/commands/over-abstraction.js +308 -0
- package/dist/commands/over-abstraction.js.map +1 -0
- package/dist/commands/quick-check.d.ts +5 -0
- package/dist/commands/quick-check.d.ts.map +1 -0
- package/dist/commands/quick-check.js +174 -0
- package/dist/commands/quick-check.js.map +1 -0
- package/dist/commands/review-contract.d.ts +5 -0
- package/dist/commands/review-contract.d.ts.map +1 -0
- package/dist/commands/review-contract.js +200 -0
- package/dist/commands/review-contract.js.map +1 -0
- package/dist/commands/review-digest.d.ts +5 -0
- package/dist/commands/review-digest.d.ts.map +1 -0
- package/dist/commands/review-digest.js +266 -0
- package/dist/commands/review-digest.js.map +1 -0
- package/dist/commands/review-handoff.d.ts +5 -0
- package/dist/commands/review-handoff.d.ts.map +1 -0
- package/dist/commands/review-handoff.js +209 -0
- package/dist/commands/review-handoff.js.map +1 -0
- package/dist/commands/review-receipt.d.ts +5 -0
- package/dist/commands/review-receipt.d.ts.map +1 -0
- package/dist/commands/review-receipt.js +221 -0
- package/dist/commands/review-receipt.js.map +1 -0
- package/dist/commands/security-theater.d.ts +5 -0
- package/dist/commands/security-theater.d.ts.map +1 -0
- package/dist/commands/security-theater.js +279 -0
- package/dist/commands/security-theater.js.map +1 -0
- package/dist/commands/stale-pattern.d.ts +5 -0
- package/dist/commands/stale-pattern.d.ts.map +1 -0
- package/dist/commands/stale-pattern.js +294 -0
- package/dist/commands/stale-pattern.js.map +1 -0
- package/package.json +1 -1
- 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 @@
|
|
|
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
|