tribunal-kit 4.4.0 → 4.4.2

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