tribunal-kit 4.3.1 → 4.4.1
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/.agent/agents/api-architect.md +66 -66
- package/.agent/agents/db-latency-auditor.md +216 -216
- package/.agent/agents/precedence-reviewer.md +250 -250
- package/.agent/agents/resilience-reviewer.md +88 -88
- package/.agent/agents/schema-reviewer.md +67 -67
- package/.agent/agents/throughput-optimizer.md +299 -299
- package/.agent/agents/ui-ux-auditor.md +292 -292
- package/.agent/agents/vitals-reviewer.md +223 -223
- package/.agent/scripts/_colors.js +18 -18
- package/.agent/scripts/_utils.js +42 -42
- package/.agent/scripts/append_flow.js +72 -72
- package/.agent/scripts/auto_preview.js +197 -197
- package/.agent/scripts/bundle_analyzer.js +290 -290
- package/.agent/scripts/case_law_manager.js +17 -6
- package/.agent/scripts/checklist.js +266 -266
- package/.agent/scripts/colors.js +17 -17
- package/.agent/scripts/compress_skills.js +141 -141
- package/.agent/scripts/consolidate_skills.js +149 -149
- package/.agent/scripts/context_broker.js +611 -609
- package/.agent/scripts/deep_compress.js +150 -150
- package/.agent/scripts/dependency_analyzer.js +272 -272
- package/.agent/scripts/graph_builder.js +151 -37
- package/.agent/scripts/graph_visualizer.js +384 -0
- package/.agent/scripts/inner_loop_validator.js +451 -465
- package/.agent/scripts/lint_runner.js +187 -187
- package/.agent/scripts/minify_context.js +100 -100
- package/.agent/scripts/mutation_runner.js +280 -0
- package/.agent/scripts/patch_skills_meta.js +156 -156
- package/.agent/scripts/patch_skills_output.js +244 -244
- package/.agent/scripts/schema_validator.js +297 -297
- package/.agent/scripts/security_scan.js +303 -303
- package/.agent/scripts/session_manager.js +276 -276
- package/.agent/scripts/skill_evolution.js +644 -644
- package/.agent/scripts/skill_integrator.js +313 -313
- package/.agent/scripts/strengthen_skills.js +193 -193
- package/.agent/scripts/strip_tribunal.js +47 -47
- package/.agent/scripts/swarm_dispatcher.js +360 -360
- package/.agent/scripts/test_runner.js +193 -193
- package/.agent/scripts/utils.js +32 -32
- package/.agent/scripts/verify_all.js +257 -256
- package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +1 -1
- package/.agent/skills/doc.md +1 -1
- package/.agent/skills/knowledge-graph/SKILL.md +32 -16
- package/.agent/skills/testing-patterns/SKILL.md +19 -2
- package/.agent/skills/ui-ux-pro-max/SKILL.md +480 -43
- package/.agent/workflows/generate.md +183 -183
- package/.agent/workflows/tribunal-speed.md +183 -183
- package/README.md +1 -1
- package/bin/tribunal-kit.js +134 -17
- package/package.json +6 -3
- package/scripts/changelog.js +167 -167
- package/scripts/sync-version.js +81 -81
- package/.agent/scripts/__pycache__/_colors.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/_utils.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/case_law_manager.cpython-311.pyc +0 -0
|
@@ -1,465 +1,451 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* inner_loop_validator.js — Tribunal Kit Inner-Loop Self-Healing CI
|
|
4
|
-
* ==================================================================
|
|
5
|
-
* Orchestrates security_scan.js and lint heuristics on code snippets
|
|
6
|
-
* IN MEMORY before they are presented to the Human Gate.
|
|
7
|
-
*
|
|
8
|
-
* This is the "Phase 6 Auto-Correction Engine": it feeds structured
|
|
9
|
-
* JSON findings back to the Maker Agent for autonomous self-healing
|
|
10
|
-
* without requiring user involvement.
|
|
11
|
-
*
|
|
12
|
-
* Architecture:
|
|
13
|
-
* 1. Receive a code snippet (via stdin or --snippet flag)
|
|
14
|
-
* 2. Write to a temp file within the OS temp directory
|
|
15
|
-
* 3. Run OWASP security pattern scan (using PATTERNS from security_scan.js)
|
|
16
|
-
* 4. Run lightweight syntax heuristics (no external tools required)
|
|
17
|
-
* 5. Emit structured JSON verdict for the Maker Agent to consume
|
|
18
|
-
* 6. Clean up temp file
|
|
19
|
-
*
|
|
20
|
-
* Usage:
|
|
21
|
-
* node .agent/scripts/inner_loop_validator.js --snippet "const x = eval(input)"
|
|
22
|
-
* node .agent/scripts/inner_loop_validator.js --file ./output.js
|
|
23
|
-
* node .agent/scripts/inner_loop_validator.js --file ./output.js --lang ts
|
|
24
|
-
* node .agent/scripts/inner_loop_validator.js test-case
|
|
25
|
-
*
|
|
26
|
-
* Output (JSON to stdout):
|
|
27
|
-
* {
|
|
28
|
-
* "verdict": "APPROVED" | "WARNING" | "REJECTED",
|
|
29
|
-
* "passed": boolean,
|
|
30
|
-
* "issues": [{ "severity": "critical|high|medium|low", "category": string, "line": number, "message": string, "fix": string }],
|
|
31
|
-
* "summary": string,
|
|
32
|
-
* "self_healing_instructions": string | null ← fed back to Maker Agent
|
|
33
|
-
* }
|
|
34
|
-
*/
|
|
35
|
-
|
|
36
|
-
'use strict';
|
|
37
|
-
|
|
38
|
-
const fs = require('fs');
|
|
39
|
-
const path = require('path');
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
let
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
[
|
|
56
|
-
[
|
|
57
|
-
[
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* @param {string}
|
|
140
|
-
* @
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
*
|
|
190
|
-
* @
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
'
|
|
196
|
-
'
|
|
197
|
-
'
|
|
198
|
-
'
|
|
199
|
-
'
|
|
200
|
-
'Weak
|
|
201
|
-
'
|
|
202
|
-
'
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
* @
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (maxSeverityRank
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
* @
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
lines.push(`
|
|
249
|
-
lines.push(`
|
|
250
|
-
lines.push(
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
lines.
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
if (argv[
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
const result = validate(code, lang);
|
|
454
|
-
|
|
455
|
-
// Always emit JSON to stdout (for machine consumption)
|
|
456
|
-
console.log(JSON.stringify(result, null, 2));
|
|
457
|
-
|
|
458
|
-
// Emit human report to stderr (safe to suppress with 2>/dev/null)
|
|
459
|
-
if (!jsonOnly) {
|
|
460
|
-
printHumanReport(result);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// Exit code: 0 = passed (APPROVED or WARNING), 1 = REJECTED
|
|
464
|
-
process.exit(result.passed ? 0 : 1);
|
|
465
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* inner_loop_validator.js — Tribunal Kit Inner-Loop Self-Healing CI
|
|
4
|
+
* ==================================================================
|
|
5
|
+
* Orchestrates security_scan.js and lint heuristics on code snippets
|
|
6
|
+
* IN MEMORY before they are presented to the Human Gate.
|
|
7
|
+
*
|
|
8
|
+
* This is the "Phase 6 Auto-Correction Engine": it feeds structured
|
|
9
|
+
* JSON findings back to the Maker Agent for autonomous self-healing
|
|
10
|
+
* without requiring user involvement.
|
|
11
|
+
*
|
|
12
|
+
* Architecture:
|
|
13
|
+
* 1. Receive a code snippet (via stdin or --snippet flag)
|
|
14
|
+
* 2. Write to a temp file within the OS temp directory
|
|
15
|
+
* 3. Run OWASP security pattern scan (using PATTERNS from security_scan.js)
|
|
16
|
+
* 4. Run lightweight syntax heuristics (no external tools required)
|
|
17
|
+
* 5. Emit structured JSON verdict for the Maker Agent to consume
|
|
18
|
+
* 6. Clean up temp file
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* node .agent/scripts/inner_loop_validator.js --snippet "const x = eval(input)"
|
|
22
|
+
* node .agent/scripts/inner_loop_validator.js --file ./output.js
|
|
23
|
+
* node .agent/scripts/inner_loop_validator.js --file ./output.js --lang ts
|
|
24
|
+
* node .agent/scripts/inner_loop_validator.js test-case
|
|
25
|
+
*
|
|
26
|
+
* Output (JSON to stdout):
|
|
27
|
+
* {
|
|
28
|
+
* "verdict": "APPROVED" | "WARNING" | "REJECTED",
|
|
29
|
+
* "passed": boolean,
|
|
30
|
+
* "issues": [{ "severity": "critical|high|medium|low", "category": string, "line": number, "message": string, "fix": string }],
|
|
31
|
+
* "summary": string,
|
|
32
|
+
* "self_healing_instructions": string | null ← fed back to Maker Agent
|
|
33
|
+
* }
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
'use strict';
|
|
37
|
+
|
|
38
|
+
const fs = require('fs');
|
|
39
|
+
const path = require('path');
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
// ── Resolve security_scan patterns (reuse — do not duplicate) ─────────────
|
|
43
|
+
const SCRIPT_DIR = __dirname;
|
|
44
|
+
let SECURITY_PATTERNS = [];
|
|
45
|
+
let SEVERITY_RANK = {};
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const secScan = require(path.join(SCRIPT_DIR, 'security_scan.js'));
|
|
49
|
+
SECURITY_PATTERNS = secScan.PATTERNS || [];
|
|
50
|
+
SEVERITY_RANK = secScan.SEVERITY_RANK || { critical: 0, high: 1, medium: 2, low: 3 };
|
|
51
|
+
} catch {
|
|
52
|
+
// Fallback: minimal critical patterns only (never fail silently on missing module)
|
|
53
|
+
SECURITY_PATTERNS = [
|
|
54
|
+
[/(?:password|passwd)\s*=\s*["'][^"']+["']/i, 'critical', 'Hardcoded Secret', 'Hardcoded password'],
|
|
55
|
+
[/\beval\s*\(/, 'high', 'Code Injection', 'eval() is a code injection vector'],
|
|
56
|
+
[/\.innerHTML\s*=/, 'high', 'XSS', 'Direct innerHTML assignment'],
|
|
57
|
+
[/algorithms\s*:\s*\[\s*["']none["']/, 'critical', 'Auth Bypass', "JWT 'none' algorithm"],
|
|
58
|
+
];
|
|
59
|
+
SEVERITY_RANK = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── Syntax heuristics (no external deps) ─────────────────────────────────
|
|
63
|
+
// These catch structural issues in generated code before a linter runs.
|
|
64
|
+
const SYNTAX_HEURISTICS = [
|
|
65
|
+
{
|
|
66
|
+
pattern: /\bconst\s+\w+\s*=\s*require\s*\(\s*["'](?!\.\/|\.\.\/|[a-zA-Z])/,
|
|
67
|
+
severity: 'medium',
|
|
68
|
+
category: 'Hallucination Risk',
|
|
69
|
+
message: 'Suspicious require() path — verify module exists in package.json',
|
|
70
|
+
fix: 'Check that this package is listed in package.json dependencies',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
pattern: /\/\/\s*VERIFY:/,
|
|
74
|
+
severity: 'low',
|
|
75
|
+
category: 'Verification Flag',
|
|
76
|
+
message: 'Maker Agent flagged this line as uncertain — human review required',
|
|
77
|
+
fix: 'The Maker Agent marked this with // VERIFY: — confirm before approving',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
pattern: /:\s*any\b(?!\s*=)/,
|
|
81
|
+
severity: 'low',
|
|
82
|
+
category: 'Type Safety',
|
|
83
|
+
message: 'TypeScript `any` type used without explanation comment',
|
|
84
|
+
fix: 'Replace :any with a specific type, or add // any: [reason] comment',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
pattern: /process\.env\.\w+(?!\s*\?\?|\s*\|\|)/,
|
|
88
|
+
severity: 'low',
|
|
89
|
+
category: 'Config Safety',
|
|
90
|
+
message: 'process.env access without nullish fallback — may throw at runtime',
|
|
91
|
+
fix: 'Use: process.env.VAR ?? "default" — always guard env var access',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
pattern: /throw\s+["'`]/,
|
|
95
|
+
severity: 'low',
|
|
96
|
+
category: 'Error Quality',
|
|
97
|
+
message: 'Throwing a string instead of an Error object — stack traces will be lost',
|
|
98
|
+
fix: 'Use: throw new Error("message") instead of throw "message"',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
pattern: /catch\s*\(\s*\w+\s*\)\s*\{?\s*\}/,
|
|
102
|
+
severity: 'medium',
|
|
103
|
+
category: 'Error Handling',
|
|
104
|
+
message: 'Empty catch block swallows errors silently',
|
|
105
|
+
fix: 'Add at minimum: catch (err) { console.error(err); throw err; }',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
pattern: /\.then\(\s*\)\s*\.catch\s*\(|\.catch\s*\(\s*\)/,
|
|
109
|
+
severity: 'medium',
|
|
110
|
+
category: 'Error Handling',
|
|
111
|
+
message: 'Empty .then() or .catch() handler — Promise errors may be silenced',
|
|
112
|
+
fix: 'Implement proper resolution and rejection handlers',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
pattern: /window\.|document\.|navigator\./,
|
|
116
|
+
severity: 'low',
|
|
117
|
+
category: 'Environment Check',
|
|
118
|
+
message: 'Browser global access — may fail in SSR/Node environments',
|
|
119
|
+
fix: 'Guard with: typeof window !== "undefined" before accessing browser globals',
|
|
120
|
+
},
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
// ── ANSI colors (inline to avoid deps on colors.js path) ─────────────────
|
|
124
|
+
const GREEN = '\x1b[92m';
|
|
125
|
+
const YELLOW = '\x1b[93m';
|
|
126
|
+
const RED = '\x1b[91m';
|
|
127
|
+
const CYAN = '\x1b[96m';
|
|
128
|
+
const BOLD = '\x1b[1m';
|
|
129
|
+
const DIM = '\x1b[2m';
|
|
130
|
+
const RESET = '\x1b[0m';
|
|
131
|
+
|
|
132
|
+
// ── Core scanning ─────────────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Scan a code string for security and heuristic issues.
|
|
136
|
+
* Returns an array of structured finding objects.
|
|
137
|
+
*
|
|
138
|
+
* @param {string} code - Raw source code string
|
|
139
|
+
* @param {string} [lang] - Language hint ('js' | 'ts' | 'py' | 'jsx' | 'tsx')
|
|
140
|
+
* @returns {Array<{severity, category, line, message, fix, source}>}
|
|
141
|
+
*/
|
|
142
|
+
function scanCode(code, _lang = 'js') {
|
|
143
|
+
const findings = [];
|
|
144
|
+
const lines = code.split('\n');
|
|
145
|
+
|
|
146
|
+
for (let i = 0; i < lines.length; i++) {
|
|
147
|
+
const stripped = lines[i].trim();
|
|
148
|
+
const lineNum = i + 1;
|
|
149
|
+
|
|
150
|
+
// Skip pure comments
|
|
151
|
+
if (stripped.startsWith('//') || stripped.startsWith('#') || stripped.startsWith('*')) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Run OWASP security patterns
|
|
156
|
+
for (const [pattern, severity, category, message] of SECURITY_PATTERNS) {
|
|
157
|
+
if (pattern.test(stripped)) {
|
|
158
|
+
findings.push({
|
|
159
|
+
severity,
|
|
160
|
+
category,
|
|
161
|
+
line: lineNum,
|
|
162
|
+
message,
|
|
163
|
+
fix: buildSecurityFix(category),
|
|
164
|
+
source: 'security_scan',
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Run structural heuristics
|
|
170
|
+
for (const h of SYNTAX_HEURISTICS) {
|
|
171
|
+
if (h.pattern.test(stripped)) {
|
|
172
|
+
findings.push({
|
|
173
|
+
severity: h.severity,
|
|
174
|
+
category: h.category,
|
|
175
|
+
line: lineNum,
|
|
176
|
+
message: h.message,
|
|
177
|
+
fix: h.fix,
|
|
178
|
+
source: 'heuristic',
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return findings;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Build a fix suggestion for known security categories.
|
|
189
|
+
* @param {string} category
|
|
190
|
+
* @returns {string}
|
|
191
|
+
*/
|
|
192
|
+
function buildSecurityFix(category) {
|
|
193
|
+
const fixes = {
|
|
194
|
+
'Hardcoded Secret': 'Move to environment variable: process.env.SECRET_NAME',
|
|
195
|
+
'SQL Injection': 'Use parameterized queries. Never interpolate user input into SQL.',
|
|
196
|
+
'XSS': 'Use textContent instead of innerHTML. Sanitize with DOMPurify if HTML is needed.',
|
|
197
|
+
'Code Injection': 'Remove eval()/new Function(). Use a safe alternative or a JSON parser.',
|
|
198
|
+
'Command Injection': 'Use execFile() with an args array instead of exec() with a shell string.',
|
|
199
|
+
'Weak Crypto': 'Use crypto.createHash("sha256") or bcrypt for password hashing.',
|
|
200
|
+
'Weak Randomness': 'Use crypto.randomBytes(n) or crypto.randomUUID() for security-sensitive values.',
|
|
201
|
+
'Auth Bypass': 'Enforce JWT algorithm explicitly: { algorithms: ["HS256"] }',
|
|
202
|
+
'Info Disclosure': 'Remove logging of sensitive values. Use structured logging with redaction.',
|
|
203
|
+
};
|
|
204
|
+
return fixes[category] || 'Review and remediate according to OWASP guidelines.';
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Determine the overall verdict from a list of findings.
|
|
209
|
+
* REJECTED if any critical/high. WARNING if medium. APPROVED if low/clean.
|
|
210
|
+
*
|
|
211
|
+
* @param {Array} findings
|
|
212
|
+
* @returns {{ verdict: string, passed: boolean }}
|
|
213
|
+
*/
|
|
214
|
+
function computeVerdict(findings) {
|
|
215
|
+
// Filter out VERIFY flags from blocking logic — they are informational
|
|
216
|
+
const blocking = findings.filter(f => f.category !== 'Verification Flag');
|
|
217
|
+
const maxSeverityRank = blocking.reduce((min, f) => {
|
|
218
|
+
const rank = SEVERITY_RANK[f.severity] ?? 3;
|
|
219
|
+
return rank < min ? rank : min;
|
|
220
|
+
}, 4); // 4 = no findings
|
|
221
|
+
|
|
222
|
+
if (maxSeverityRank <= 1) return { verdict: 'REJECTED', passed: false }; // critical or high
|
|
223
|
+
if (maxSeverityRank === 2) return { verdict: 'WARNING', passed: true }; // medium
|
|
224
|
+
return { verdict: 'APPROVED', passed: true };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Build a self-healing instruction string for the Maker Agent.
|
|
229
|
+
* This is what you paste back into the AI to trigger auto-correction.
|
|
230
|
+
*
|
|
231
|
+
* @param {Array} findings
|
|
232
|
+
* @returns {string|null}
|
|
233
|
+
*/
|
|
234
|
+
function buildSelfHealingInstructions(findings) {
|
|
235
|
+
const blocking = findings.filter(f => {
|
|
236
|
+
const rank = SEVERITY_RANK[f.severity] ?? 3;
|
|
237
|
+
return rank <= 1; // critical + high only
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
if (!blocking.length) return null;
|
|
241
|
+
|
|
242
|
+
const lines = [
|
|
243
|
+
'⚠️ Inner-Loop Validator found blocking issues. Auto-correct the following before writing to disk:\n',
|
|
244
|
+
];
|
|
245
|
+
|
|
246
|
+
for (const f of blocking) {
|
|
247
|
+
lines.push(`[${f.severity.toUpperCase()}] Line ${f.line} — ${f.category}`);
|
|
248
|
+
lines.push(` Issue: ${f.message}`);
|
|
249
|
+
lines.push(` Fix: ${f.fix}`);
|
|
250
|
+
lines.push('');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
lines.push('Re-generate the affected lines only. Do not change unaffected code.');
|
|
254
|
+
return lines.join('\n');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
// ── Output ────────────────────────────────────────────────────────────────
|
|
260
|
+
|
|
261
|
+
function printHumanReport(result) {
|
|
262
|
+
const { verdict, issues, summary, self_healing_instructions } = result;
|
|
263
|
+
|
|
264
|
+
const verdictColor = verdict === 'APPROVED' ? GREEN : verdict === 'WARNING' ? YELLOW : RED;
|
|
265
|
+
const verdictIcon = verdict === 'APPROVED' ? '✅' : verdict === 'WARNING' ? '⚠️' : '❌';
|
|
266
|
+
|
|
267
|
+
console.error(`\n${BOLD}${CYAN}━━━ Inner-Loop Validator ━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
|
|
268
|
+
console.error(` Verdict: ${verdictColor}${BOLD}${verdictIcon} ${verdict}${RESET}`);
|
|
269
|
+
console.error(` Summary: ${summary}`);
|
|
270
|
+
|
|
271
|
+
if (issues.length) {
|
|
272
|
+
console.error(`\n ${BOLD}Issues found:${RESET}`);
|
|
273
|
+
for (const iss of issues) {
|
|
274
|
+
const color = iss.severity === 'critical' || iss.severity === 'high' ? RED :
|
|
275
|
+
iss.severity === 'medium' ? YELLOW : DIM;
|
|
276
|
+
console.error(` ${color}[${iss.severity.toUpperCase()}]${RESET} Line ${iss.line} — ${iss.category}`);
|
|
277
|
+
console.error(` ${iss.message}`);
|
|
278
|
+
console.error(` ${DIM}Fix: ${iss.fix}${RESET}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (self_healing_instructions) {
|
|
283
|
+
console.error(`\n ${YELLOW}${BOLD}Self-Healing Instructions (for Maker Agent):${RESET}`);
|
|
284
|
+
console.error(self_healing_instructions.split('\n').map(l => ` ${l}`).join('\n'));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
console.error(`${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}\n`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ── Built-in test case ────────────────────────────────────────────────────
|
|
291
|
+
|
|
292
|
+
function runTestCase() {
|
|
293
|
+
console.error(`\n${BOLD}${CYAN}━━━ Inner-Loop Validator — Self-Test ━━━━━━━━━━━━━━${RESET}`);
|
|
294
|
+
|
|
295
|
+
const badCode = `
|
|
296
|
+
const password = "supersecret123";
|
|
297
|
+
const result = db.query("SELECT * FROM users WHERE id = " + req.params.id);
|
|
298
|
+
document.getElementById('output').innerHTML = userInput;
|
|
299
|
+
const token = eval(req.body.expr);
|
|
300
|
+
const rand = Math.random() * 1000;
|
|
301
|
+
`;
|
|
302
|
+
|
|
303
|
+
const result = validate(badCode, 'js');
|
|
304
|
+
printHumanReport(result);
|
|
305
|
+
|
|
306
|
+
const hasCritical = result.issues.some(i => i.severity === 'critical' || i.severity === 'high');
|
|
307
|
+
if (hasCritical && result.verdict === 'REJECTED') {
|
|
308
|
+
console.error(`${GREEN}✅ Self-test PASSED — validator correctly identified and blocked critical issues${RESET}\n`);
|
|
309
|
+
process.exit(0);
|
|
310
|
+
} else {
|
|
311
|
+
console.error(`${RED}❌ Self-test FAILED — expected REJECTED verdict for bad code${RESET}\n`);
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// ── Public API ────────────────────────────────────────────────────────────
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Validate a code string. Returns a structured result object.
|
|
320
|
+
* This is the primary programmatic API — call this from other scripts.
|
|
321
|
+
*
|
|
322
|
+
* @param {string} code - Source code to validate
|
|
323
|
+
* @param {string} [lang] - Language hint
|
|
324
|
+
* @param {object} [opts] - Options: { timeout: number }
|
|
325
|
+
* @returns {{ verdict, passed, issues, summary, self_healing_instructions }}
|
|
326
|
+
*/
|
|
327
|
+
function validate(code, lang = 'js', _opts = {}) {
|
|
328
|
+
if (!code || typeof code !== 'string') {
|
|
329
|
+
return {
|
|
330
|
+
verdict: 'APPROVED',
|
|
331
|
+
passed: true,
|
|
332
|
+
issues: [],
|
|
333
|
+
summary: 'No code provided — skipped.',
|
|
334
|
+
self_healing_instructions: null,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const issues = scanCode(code, lang);
|
|
339
|
+
|
|
340
|
+
// Sort by severity rank
|
|
341
|
+
issues.sort((a, b) => (SEVERITY_RANK[a.severity] ?? 3) - (SEVERITY_RANK[b.severity] ?? 3));
|
|
342
|
+
|
|
343
|
+
const { verdict, passed } = computeVerdict(issues);
|
|
344
|
+
const healingInstructions = buildSelfHealingInstructions(issues);
|
|
345
|
+
|
|
346
|
+
const critCount = issues.filter(i => i.severity === 'critical').length;
|
|
347
|
+
const highCount = issues.filter(i => i.severity === 'high').length;
|
|
348
|
+
const medCount = issues.filter(i => i.severity === 'medium').length;
|
|
349
|
+
const lowCount = issues.filter(i => i.severity === 'low').length;
|
|
350
|
+
const verifyCount = issues.filter(i => i.category === 'Verification Flag').length;
|
|
351
|
+
|
|
352
|
+
let summary = `${issues.length} issue(s) found`;
|
|
353
|
+
if (!issues.length) {
|
|
354
|
+
summary = 'No issues detected — code is clean';
|
|
355
|
+
} else {
|
|
356
|
+
const parts = [];
|
|
357
|
+
if (critCount) parts.push(`${critCount} critical`);
|
|
358
|
+
if (highCount) parts.push(`${highCount} high`);
|
|
359
|
+
if (medCount) parts.push(`${medCount} medium`);
|
|
360
|
+
if (lowCount) parts.push(`${lowCount} low`);
|
|
361
|
+
if (verifyCount) parts.push(`${verifyCount} VERIFY flag(s) need human review`);
|
|
362
|
+
summary = parts.join(', ');
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
verdict,
|
|
367
|
+
passed,
|
|
368
|
+
issues,
|
|
369
|
+
summary,
|
|
370
|
+
self_healing_instructions: healingInstructions,
|
|
371
|
+
meta: {
|
|
372
|
+
lines_scanned: code.split('\n').length,
|
|
373
|
+
lang,
|
|
374
|
+
timestamp: new Date().toISOString(),
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
module.exports = { validate, scanCode, computeVerdict, buildSelfHealingInstructions };
|
|
380
|
+
|
|
381
|
+
// ── CLI Entry ─────────────────────────────────────────────────────────────
|
|
382
|
+
|
|
383
|
+
if (require.main === module) {
|
|
384
|
+
const argv = process.argv.slice(2);
|
|
385
|
+
|
|
386
|
+
if (!argv.length || argv.includes('--help') || argv.includes('-h')) {
|
|
387
|
+
console.log(`
|
|
388
|
+
${BOLD}inner_loop_validator.js${RESET} — Tribunal Self-Healing CI
|
|
389
|
+
|
|
390
|
+
${BOLD}Usage:${RESET}
|
|
391
|
+
node .agent/scripts/inner_loop_validator.js --snippet "<code>"
|
|
392
|
+
node .agent/scripts/inner_loop_validator.js --file ./output.js [--lang ts]
|
|
393
|
+
node .agent/scripts/inner_loop_validator.js test-case
|
|
394
|
+
|
|
395
|
+
${BOLD}Output:${RESET}
|
|
396
|
+
JSON to stdout. Human-readable summary to stderr.
|
|
397
|
+
Use --json-only to suppress the human report.
|
|
398
|
+
|
|
399
|
+
${BOLD}Verdict:${RESET}
|
|
400
|
+
APPROVED → No critical/high issues. Safe to proceed.
|
|
401
|
+
WARNING → Medium issues found. Human should review.
|
|
402
|
+
REJECTED → Critical/high issues. Maker Agent must self-correct.
|
|
403
|
+
`);
|
|
404
|
+
process.exit(0);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Built-in self-test
|
|
408
|
+
if (argv[0] === 'test-case') {
|
|
409
|
+
runTestCase();
|
|
410
|
+
process.exit(0);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const jsonOnly = argv.includes('--json-only');
|
|
414
|
+
const fileFlagIdx = argv.indexOf('--file');
|
|
415
|
+
const snippetIdx = argv.indexOf('--snippet');
|
|
416
|
+
const langIdx = argv.indexOf('--lang');
|
|
417
|
+
|
|
418
|
+
const lang = langIdx !== -1 && argv[langIdx + 1] ? argv[langIdx + 1] : 'js';
|
|
419
|
+
|
|
420
|
+
let code = '';
|
|
421
|
+
|
|
422
|
+
if (fileFlagIdx !== -1 && argv[fileFlagIdx + 1]) {
|
|
423
|
+
const filePath = path.resolve(argv[fileFlagIdx + 1]);
|
|
424
|
+
if (!fs.existsSync(filePath)) {
|
|
425
|
+
console.error(`${RED}✖ File not found: ${filePath}${RESET}`);
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
|
428
|
+
code = fs.readFileSync(filePath, 'utf8');
|
|
429
|
+
} else if (snippetIdx !== -1 && argv[snippetIdx + 1]) {
|
|
430
|
+
code = argv[snippetIdx + 1];
|
|
431
|
+
} else if (!process.stdin.isTTY) {
|
|
432
|
+
// Read from stdin if piped (cross-platform, works on Windows)
|
|
433
|
+
code = fs.readFileSync(0, 'utf8');
|
|
434
|
+
} else {
|
|
435
|
+
console.error(`${RED}✖ Provide --snippet "<code>" or --file <path>${RESET}`);
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const result = validate(code, lang);
|
|
440
|
+
|
|
441
|
+
// Always emit JSON to stdout (for machine consumption)
|
|
442
|
+
console.log(JSON.stringify(result, null, 2));
|
|
443
|
+
|
|
444
|
+
// Emit human report to stderr (safe to suppress with 2>/dev/null)
|
|
445
|
+
if (!jsonOnly) {
|
|
446
|
+
printHumanReport(result);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Exit code: 0 = passed (APPROVED or WARNING), 1 = REJECTED
|
|
450
|
+
process.exit(result.passed ? 0 : 1);
|
|
451
|
+
}
|