Files
16gagent/scripts/audit-governance.mjs
2026-06-06 10:40:48 +08:00

348 lines
15 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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[0][\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();
}
}