#!/usr/bin/env node /** * audit-governance.mjs — GOVERNANCE.md Execution Governance Audit * * Checks: * 1. All task states are legal (7-state machine) * 2. State transitions are legal (no skipping) * 3. Stale task detection rules exist * 4. Blocker management (blocker.md template + schema) * 5. Execution audit exists (execution-audit.md template) * 6. Project Health Score formula defined * 7. Auto-Stop Rule defined * 8. Governance Quality Gate defined * * Usage: * node scripts/audit-governance.mjs ← standalone * import { runGovernanceAudit } from './audit-governance.mjs' ← via orchestrator */ 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, '..'); const SELF = process.argv[1].replace(/\\/g, '/'); /** * @typedef {Object} CheckResult * @property {string} id * @property {string} name * @property {'PASS'|'WARN'|'FAIL'} status * @property {string} evidence * @property {string} [suggestion] */ /** @returns {CheckResult[]} */ export function runGovernanceAudit(workspaceDir) { const root = workspaceDir || WORKSPACE; const checks = []; // ---- Helpers ---- function readFile(rel) { const p = path.join(root, rel); if (!fs.existsSync(p)) return null; return fs.readFileSync(p, 'utf8'); } function check(prefix, id, name, ok, passEv, failEv, suggestion) { checks.push({ id: `${prefix}-${id}`, name, status: ok ? 'PASS' : 'FAIL', evidence: ok ? passEv : failEv, ...(ok ? {} : { suggestion: suggestion || `Implement ${id} in GOVERNANCE.md` }), }); } function warn(prefix, id, name, evidence, suggestion) { checks.push({ id: `${prefix}-${id}`, name, status: 'WARN', evidence, suggestion }); } // ---- Read protocol file ---- const gov = readFile('GOVERNANCE.md'); if (!gov) { checks.push({ id: 'GV-001', name: 'GOVERNANCE.md exists', status: 'FAIL', evidence: 'GOVERNANCE.md not found in workspace root', suggestion: 'Create GOVERNANCE.md with the Execution Governance Protocol', }); return checks; } checks.push({ id: 'GV-001', name: 'GOVERNANCE.md exists', status: 'PASS', evidence: `GOVERNANCE.md found (${gov.length} bytes)`, }); // ---- §1 State Machine ---- const hasStateMachine = /§1 State Machine/.test(gov); check('GV', '1', '§1 State Machine defined', hasStateMachine, 'Found "§1 State Machine" section', 'Add §1 State Machine section'); // 7 states const states = ['NOT_STARTED', 'IN_PROGRESS', 'BLOCKED', 'WAITING_DEPENDENCY', 'READY_FOR_REVIEW', 'VERIFIED', 'FAILED', 'ARCHIVED']; const foundStates = states.filter(s => gov.includes(s)); check('GV', '1a', `§1a ${states.length} states defined (found ${foundStates.length})`, foundStates.length >= states.length, `All ${states.length} states present: ${foundStates.join(', ')}`, `Missing states: ${states.filter(s => !gov.includes(s)).join(', ')}`, 'Add missing states to §1.1 Allowed States'); // Mermaid diagram const hasMermaid = /stateDiagram-v2/.test(gov); check('GV', '1b', '§1b State transition diagram (mermaid)', hasMermaid, 'Found mermaid stateDiagram-v2 with all legal transitions', 'Add a mermaid stateDiagram-v2 for the state machine'); // Transition table const hasTransitionTable = /Transition Table/.test(gov) || /§1\.3/.test(gov); check('GV', '1c', '§1c Transition table (From/To/Condition)', hasTransitionTable, 'Found Transition Table with From/To/Condition columns', 'Add Transition Table (From | To | Condition)'); // Prohibited transitions const hasProhibited = /Prohibited Transitions/.test(gov) || /§1\.4/.test(gov); check('GV', '1d', '§1d Prohibited transitions defined', hasProhibited, 'Found "Prohibited Transitions" with ❌ markers for illegal jumps', 'Add "Prohibited Transitions" with explicit ❌ transition violations'); // ---- §2 Heartbeat Rule ---- const hasHeartbeat = /§2 Heartbeat Rule/.test(gov); check('GV', '2', '§2 Heartbeat Rule defined', hasHeartbeat, 'Found "§2 Heartbeat Rule" section', 'Add §2 Heartbeat Rule section'); // Heartbeat format defined const hasHeartbeatFormat = /"ts".*"status".*"completed".*"remaining".*"next_action"/.test(gov); check('GV', '2a', '§2a Heartbeat JSON format defined (ts/status/completed/remaining/next_action)', hasHeartbeatFormat, 'Heartbeat JSON format found with ts, status, completed, remaining, next_action fields', 'Define heartbeat format as JSON with ts/status/completed/remaining/next_action'); // Stale thresholds const hasStaleThresholds = /Small[\s\S]*3[00][\s\S]*Medium[\s\S]*2[\s\S]*Large[\s\S]*8/.test(gov); check('GV', '2b', '§2b Stale thresholds defined (Small=30m, Medium=2h, Large=8h)', hasStaleThresholds, 'Found stale thresholds: Small=30min/30分钟, Medium=2h/2小时, Large=8h/8小时', 'Add Stale thresholds: Small=30min, Medium=2h, Large=8h'); // progress.log template exists const progressTmpl = readFile('scripts/templates/progress.log.template'); check('GV', '2c', 'progress.log.template exists', progressTmpl !== null, 'Found scripts/templates/progress.log.template', 'Create scripts/templates/progress.log.template'); // ---- §3 Blocker Management ---- const hasBlockerMgmt = /§3 Blocker Management/.test(gov); check('GV', '3', '§3 Blocker Management defined', hasBlockerMgmt, 'Found "§3 Blocker Management" section', 'Add §3 Blocker Management section'); // Blocker schema const hasBlockerSchema = /blocker[\s\S]*root_cause[\s\S]*owner[\s\S]*mitigation[\s\S]*dependency[\s\S]*created_at/.test(gov) || /protocol-reference\/templates\.md[\s\S]*blocker/.test(gov); check('GV', '3a', '§3a Blocker schema (blocker/root_cause/owner/mitigation/dependency/created_at)', hasBlockerSchema, 'Blocker schema found in GOVERNANCE.md or protocol-reference/templates.md', 'Define blocker md schema in protocol-reference/templates.md'); // Blocker escalation const hasBlockerEsc = /Blocker Escalation/.test(gov) || /escalated/.test(gov); check('GV', '3b', '§3b Blocker escalation defined', hasBlockerEsc, 'Found Blocker Escalation mechanism with escalated: YES/NO', 'Add Blocker Escalation: if escalated=YES, notify user and stop AP'); // blocker.md template exists const blockerTmpl = readFile('scripts/templates/blocker.md.template'); check('GV', '3c', 'blocker.md.template exists', blockerTmpl !== null, 'Found scripts/templates/blocker.md.template', 'Create scripts/templates/blocker.md.template'); // ---- §4 Replanning Trigger ---- const hasReplan = /§4 Replanning Trigger/.test(gov); check('GV', '4', '§4 Replanning Trigger defined', hasReplan, 'Found "§4 Replanning Trigger" with R1-R8 conditions', 'Add §4 Replanning Trigger with R1-R8 conditions'); // 8 trigger conditions const triggerCount = (gov.match(/R\d /g) || []).length; warn('GV', '4a', `§4a Replan triggers: ${triggerCount} (target: ≥8)`, `Found ${triggerCount} replan trigger conditions (R1-R? )`, 'Ensure all 8 replan triggers (R1-R8) are defined'); // Replan log entry format const hasReplanLog = /Replan.*#N/.test(gov) || /replan.*evidence/.test(gov); check('GV', '4b', '§4b Replan log entry format defined', hasReplanLog, 'Found Replan log entry format with trigger/cause/before/after/impact', 'Define Replan log entry format (trigger, cause, before, after, impact)'); // ---- §5 Scope Explosion ---- const hasScope = /§5 Scope Explosion/.test(gov); check('GV', '5', '§5 Scope Explosion Detector defined', hasScope, 'Found "§5 Scope Explosion Detector" with S1-S4 detection rules', 'Add §5 Scope Explosion Detector with S1-S4 detection rules'); // ---- §6 Execution Audit ---- const hasAudit = /§6 Execution Audit/.test(gov); check('GV', '6', '§6 Execution Audit defined', hasAudit, 'Found "§6 Execution Audit" section', 'Add §6 Execution Audit section'); // execution-audit.md template exists const auditTmpl = readFile('scripts/templates/execution-audit.md.template'); check('GV', '6a', 'execution-audit.md.template exists', auditTmpl !== null, 'Found scripts/templates/execution-audit.md.template', 'Create scripts/templates/execution-audit.md.template'); // ---- §7 Health Score ---- const hasHealth = /§7 Project Health Score/.test(gov); check('GV', '7', '§7 Project Health Score defined', hasHealth, 'Found "§7 Project Health Score" with 5 dimensions (Delivery/Quality/Parallelism/Dependency/Learning)', 'Add §7 Project Health Score with 5 dimensions'); // All 5 sub-scores const hasDelivery = /Delivery Score/.test(gov); const hasQuality = /Quality Score/.test(gov); const hasParallelism = /Parallelism Score/.test(gov); const hasDependency = /Dependency Score/.test(gov); const hasLearning = /Learning Score/.test(gov); const all5 = hasDelivery && hasQuality && hasParallelism && hasDependency && hasLearning; check('GV', '7a', '§7a All 5 Health Score dimensions defined (Delivery/Quality/Parallelism/Dependency/Learning)', all5, 'All 5 dimensions found with formulas', 'Ensure all 5 dimensions: Delivery, Quality, Parallelism, Dependency, Learning'); // Weighted formula const hasWeighted = /Health Score\s?=\s?Delivery\s*[×x]\s*0\.\d+/.test(gov); check('GV', '7b', '§7b Weighted final formula defined', hasWeighted, 'Found weighted formula: Health Score = Delivery×0.30 + Quality×0.25 + Parallelism×0.15 + Dependency×0.15 + Learning×0.15', 'Define the weighted sum formula for final Health Score'); // Rating const hasRating = /EXCELLENT[\s\S]*GOOD[\s\S]*ACCEPTABLE[\s\S]*AT RISK[\s\S]*CRITICAL/.test(gov) || /protocol-reference\/metrics\.md/.test(gov); check('GV', '7c', '§7c Health Score rating defined (EXCELLENT/GOOD/ACCEPTABLE/AT RISK/CRITICAL)', hasRating, 'Found 5-level rating in GOVERNANCE.md or protocol-reference/metrics.md', 'Add 5-level rating to protocol-reference/metrics.md'); // ---- §8 Auto-Stop ---- const hasAutoStop = /§8 Auto-Stop/.test(gov); check('GV', '8', '§8 Auto-Stop Rule defined', hasAutoStop, 'Found "§8 Auto-Stop Rule" with A1-A3 conditions and RECOVERY MODE', 'Add §8 Auto-Stop Rule with A1(>3 failures), A2(Health<60), A3(Critical dep failed)'); // Recovery mode const hasRecoveryMode = /RECOVERY MODE/.test(gov); check('GV', '8a', '§8a RECOVERY MODE defined', hasRecoveryMode, 'Found RECOVERY MODE with recovery steps and user approval requirement', 'Add RECOVERY MODE procedure (stop, analyze, plan, user approval)'); // ---- §9 Quality Gate ---- const hasGovGate = /§9 Governance Quality Gate/.test(gov); check('GV', '9', '§9 Governance Quality Gate defined', hasGovGate, 'Found "§9 Governance Quality Gate" with 8-item checklist', 'Add §9 Governance Quality Gate with 8 pre-output checks'); // ---- §10 State Initialization ---- const hasInit = /§10 Governance State Initialization/.test(gov); check('GV', '10', '§10 Governance State Initialization defined', hasInit, 'Found "§10 Governance State Initialization" with project start/handoff/override', 'Add §10 Governance State Initialization section'); // ---- §11 Integration ---- const hasIntegration = /§11 Integration Summary/.test(gov); check('GV', '11', '§11 Integration Summary defined', hasIntegration, 'Found "§11 Integration Summary" with raw diagram and lifecycle', 'Add §11 Integration Summary'); // ---- §11 Integration Summary (re-numbered — §12 was deleted) ---- const hasInt11 = /§11 Integration Summary/.test(gov); check('GV', '11b', '§11 Integration Summary includes Portfolio ref', hasInt11, 'Found §11 Integration Summary (with governance→portfolio data flow)', 'Portfolio Integration reference is covered by §11 Integration Summary'); // ---- Runtime artifacts scan ---- // Scan for actual progress.log files const progressLogs = findFiles(root, 'progress.log'); const blockerFiles = findFiles(root, 'blocker.md'); const execAudits = findFiles(root, 'execution-audit.md'); warn('GV', 'RA-progress', `Progress logs found: ${progressLogs.length}`, progressLogs.length > 0 ? `Found ${progressLogs.length} progress.log file(s): ${progressLogs.join(', ')}` : 'No progress.log files found (expected during active project execution)', progressLogs.length === 0 ? 'Active projects should have progress.log heartbeat entries' : undefined); warn('GV', 'RA-blocker', `Blocker files found: ${blockerFiles.length}`, blockerFiles.length > 0 ? `Found ${blockerFiles.length} blocker.md file(s): ${blockerFiles.join(', ')}` : 'No blocker.md files found', blockerFiles.length === 0 ? '(no active blockers — good)' : undefined); warn('GV', 'RA-audit', `Execution audits found: ${execAudits.length}`, execAudits.length > 0 ? `Found ${execAudits.length} execution-audit.md file(s): ${execAudits.join(', ')}` : 'No execution-audit.md found (expected after first project execution)', execAudits.length === 0 ? 'Run first project and generate execution-audit.md' : undefined); // ---- Health Score calculation (from available data) ---- // Without actual project data, report N/A checks.push({ id: 'GV-HS', name: 'Project Health Score calculated', status: 'WARN', evidence: 'No active project data available for health score calculation. Formula is defined in GOVERNANCE.md §7.', suggestion: 'Health Score will be calculated during/after project execution', }); // ---- Summary ---- const passCount = checks.filter(c => c.status === 'PASS').length; const warnCount = checks.filter(c => c.status === 'WARN').length; const failCount = checks.filter(c => c.status === 'FAIL').length; checks.push({ id: 'GV-SUMMARY', name: `Summary — ${passCount} PASS / ${warnCount} WARN / ${failCount} FAIL`, status: failCount > 0 ? 'FAIL' : warnCount > 0 ? 'WARN' : 'PASS', evidence: `${passCount} passed, ${warnCount} warnings, ${failCount} failures`, }); return checks; } /** Recursively find files by name (limited depth) */ function findFiles(dir, filename, depth = 3) { const results = []; if (depth <= 0) return results; try { const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { if (entry.name === filename) { results.push(path.relative(WORKSPACE, path.join(dir, entry.name))); } if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') { results.push(...findFiles(path.join(dir, entry.name), filename, depth - 1)); } } } catch { /* skip */ } return results; } // ---- Standalone runner ---- if (SELF.endsWith('/audit-governance.mjs') || SELF.endsWith('\\audit-governance.mjs')) { const results = runGovernanceAudit(WORKSPACE); console.log(`\n=== GOVERNANCE.md Audit ===\n`); for (const r of results) { const icon = r.status === 'PASS' ? '✅' : r.status === 'WARN' ? '⚠️' : '❌'; console.log(` ${icon} [${r.id}] ${r.name}`); console.log(` ${r.evidence}`); if (r.suggestion) console.log(` → ${r.suggestion}`); console.log(); } }