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