Files
2026-06-06 10:40:48 +08:00

198 lines
7.9 KiB
JavaScript

#!/usr/bin/env node
/**
* audit-all.mjs — Three-Layer Protocol Audit Orchestrator
*
* Runs all three protocol audits sequentially:
* 1. AGENTS.md — Execution Protocol
* 2. GOVERNANCE.md — Execution Governance
* 3. PORTFOLIO.md — Portfolio Management
*
* Outputs:
* - Console: colorized summary with PASS / WARN / FAIL
* - File: audit-report.md (generated in workspace root)
*
* Usage:
* node scripts/audit-all.mjs
*
* npm script (add to package.json):
* "audit": "node scripts/audit-all.mjs"
*/
import { fileURLToPath } from 'url';
import path from 'path';
import fs from 'fs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const WORKSPACE = path.resolve(__dirname, '..');
// Dynamic imports for the three audit modules
const [agentsMod, govMod, portMod] = await Promise.all([
import(path.join(__dirname, 'audit-agents.mjs')),
import(path.join(__dirname, 'audit-governance.mjs')),
import(path.join(__dirname, 'audit-portfolio.mjs')),
]);
/** @returns {{ layer: string, checks: import('./audit-agents.mjs').CheckResult[] }[]} */
function runAll() {
const layers = [
{ layer: 'Execution Protocol (AGENTS.md)', fn: () => agentsMod.runAgentAudit(WORKSPACE) },
{ layer: 'Execution Governance (GOVERNANCE.md)', fn: () => govMod.runGovernanceAudit(WORKSPACE) },
{ layer: 'Portfolio Management (PORTFOLIO.md)', fn: () => portMod.runPortfolioAudit(WORKSPACE) },
];
return layers.map(({ layer, fn }) => ({ layer, checks: fn() }));
}
/** Colorized console output */
function printConsole(results) {
let totalPass = 0, totalWarn = 0, totalFail = 0, totalChecks = 0;
for (const { layer, checks } of results) {
// Skip summary rows for individual counts
const realChecks = checks.filter(c => !c.id.endsWith('-SUMMARY'));
const pass = realChecks.filter(c => c.status === 'PASS').length;
const warn = realChecks.filter(c => c.status === 'WARN').length;
const fail = realChecks.filter(c => c.status === 'FAIL').length;
totalPass += pass;
totalWarn += warn;
totalFail += fail;
totalChecks += realChecks.length;
const overall = fail > 0 ? '\x1b[31mFAIL\x1b[0m' : warn > 0 ? '\x1b[33mWARN\x1b[0m' : '\x1b[32mPASS\x1b[0m';
console.log(`\n\x1b[1m${'='.repeat(60)}\x1b[0m`);
console.log(`\x1b[1m ${layer}\x1b[0m ${overall}`);
console.log(`\x1b[1m${'='.repeat(60)}\x1b[0m`);
for (const r of checks) {
if (r.id.endsWith('-SUMMARY')) continue;
const icon = r.status === 'PASS' ? '\x1b[32m✅\x1b[0m' : r.status === 'WARN' ? '\x1b[33m⚠️\x1b[0m' : '\x1b[31m❌\x1b[0m';
const statusTag = r.status === 'PASS' ? '\x1b[32mPASS\x1b[0m' : r.status === 'WARN' ? '\x1b[33mWARN\x1b[0m' : '\x1b[31mFAIL\x1b[0m';
console.log(` ${icon} [${statusTag}] [${r.id}] ${r.name}`);
console.log(` ${r.evidence}`);
if (r.suggestion) console.log(` \x1b[90m→ ${r.suggestion}\x1b[0m`);
console.log();
}
}
// Final summary
console.log(`\x1b[1m${'='.repeat(60)}\x1b[0m`);
console.log(`\x1b[1m THREE-LAYER AUDIT COMPLETE\x1b[0m`);
console.log(`\x1b[1m${'='.repeat(60)}\x1b[0m`);
console.log(` Total checks: ${totalChecks}`);
console.log(` \x1b[32mPASS: ${totalPass}\x1b[0m`);
console.log(` \x1b[33mWARN: ${totalWarn}\x1b[0m`);
console.log(` \x1b[31mFAIL: ${totalFail}\x1b[0m`);
const verdict = totalFail > 0 ? '\x1b[31mFAIL' : totalWarn > 0 ? '\x1b[33mWARN' : '\x1b[32mPASS';
console.log(`\n VERDICT: ${verdict}\x1b[0m`);
console.log();
}
/** Generate audit-report.md */
function writeReport(results) {
const reportPath = path.join(WORKSPACE, 'audit-report.md');
const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 19) + ' +08:00';
let md = `# Audit Report — ${timestamp}\n\n`;
md += `> Generated by \`scripts/audit-all.mjs\`\n\n`;
md += `## Global Summary\n\n`;
let totalPass = 0, totalWarn = 0, totalFail = 0, totalChecks = 0;
// First pass: aggregate all checks across layers
const allChecksFlat = [];
for (const { layer, checks } of results) {
for (const r of checks) {
if (r.id.endsWith('-SUMMARY')) continue;
allChecksFlat.push({ ...r, layer });
if (r.status === 'PASS') totalPass++;
else if (r.status === 'WARN') totalWarn++;
else if (r.status === 'FAIL') totalFail++;
totalChecks++;
}
}
md += `| Metric | Value |\n|--------|-------|\n`;
md += `| Total Checks | ${totalChecks} |\n`;
md += `| ✅ PASS | ${totalPass} |\n`;
md += `| ⚠️ WARN | ${totalWarn} |\n`;
md += `| ❌ FAIL | ${totalFail} |\n`;
md += `| **Verdict** | **${totalFail > 0 ? '❌ FAIL' : totalWarn > 0 ? '⚠️ WARN' : '✅ PASS'}** |\n\n`;
const verdictOverall = totalFail > 0 ? 'FAIL' : totalWarn > 0 ? 'WARN' : 'PASS';
md += `**Verdict: ${verdictOverall}**\n\n`;
// Layer-by-layer tables
for (const { layer, checks } of results) {
const real = checks.filter(c => !c.id.endsWith('-SUMMARY'));
const lp = real.filter(c => c.status === 'PASS').length;
const lw = real.filter(c => c.status === 'WARN').length;
const lf = real.filter(c => c.status === 'FAIL').length;
const lv = lf > 0 ? '❌ FAIL' : lw > 0 ? '⚠️ WARN' : '✅ PASS';
md += `## ${layer}${lv}\n\n`;
md += `| ID | Name | Status | Evidence | Suggestion |\n`;
md += `|----|------|--------|----------|------------|\n`;
for (const r of real) {
const icon = r.status === 'PASS' ? '✅' : r.status === 'WARN' ? '⚠️' : '❌';
const sug = r.suggestion ? r.suggestion.replace(/\|/g, '\\|') : '';
const ev = r.evidence.replace(/\|/g, '\\|').replace(/\n/g, ' ').slice(0, 120);
md += `| ${r.id} | ${r.name} | ${icon} ${r.status} | ${ev} | ${sug} |\n`;
}
md += `| **Layer Summary** | **${lp} PASS / ${lw} WARN / ${lf} FAIL** | **${lv}** | | |\n\n`;
}
// Fails list
const fails = allChecksFlat.filter(c => c.status === 'FAIL');
if (fails.length > 0) {
md += `## ❌ Failures (repair required)\n\n`;
for (const f of fails) {
md += `- **[${f.id}]** ${f.name}${f.evidence}\n`;
if (f.suggestion) md += ` - *Repair:* ${f.suggestion}\n`;
}
md += '\n';
}
// Warnings list
const warns = allChecksFlat.filter(c => c.status === 'WARN');
if (warns.length > 0) {
md += `## ⚠️ Warnings (improvement recommended)\n\n`;
for (const w of warns) {
md += `- **[${w.id}]** ${w.name}${w.evidence}\n`;
if (w.suggestion) md += ` - *Suggestion:* ${w.suggestion}\n`;
}
md += '\n';
}
// Coverage summary
md += `## Coverage\n\n`;
md += `| Layer | Sections | Checks |\n|-------|----------|--------|\n`;
const agentsReal = results[0].checks.filter(c => !c.id.endsWith('-SUMMARY'));
const govReal = results[1].checks.filter(c => !c.id.endsWith('-SUMMARY'));
const portReal = results[2].checks.filter(c => !c.id.endsWith('-SUMMARY'));
md += `| AGENTS.md | §0-§7, Learning Loop, Context Budget | ${agentsReal.length} |\n`;
md += `| GOVERNANCE.md | §1-§12 | ${govReal.length} |\n`;
md += `| PORTFOLIO.md | §1-§14 | ${portReal.length} |\n`;
md += `| **Total** | **3 layers** | **${totalChecks}** |\n\n`;
md += `---\n*Audit complete. ${timestamp}*\n`;
fs.writeFileSync(reportPath, md, 'utf8');
console.log(`\n 📄 Report written to: audit-report.md`);
return reportPath;
}
// ---- Main ----
console.log(`\x1b[1m\x1b[36m`);
console.log(` ╔══════════════════════════════════════════════════╗`);
console.log(` ║ THREE-LAYER PROTOCOL AUDIT ║`);
console.log(` ║ AGENTS.md · GOVERNANCE.md · PORTFOLIO.md ║`);
console.log(` ╚══════════════════════════════════════════════════╝`);
console.log(`\x1b[0m`);
const results = runAll();
printConsole(results);
writeReport(results);