250 lines
11 KiB
JavaScript
250 lines
11 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* audit-agents.mjs — AGENTS.md Execution Protocol Audit
|
|
*
|
|
* Checks:
|
|
* 1. Task Tree exists (§1)
|
|
* 2. Agent Package isolation requirements
|
|
* 3. No XL Package (context budget forbids it)
|
|
* 4. Injected Risks mechanism (§1e / §3b)
|
|
* 5. Quality Gate completeness (§5)
|
|
*
|
|
* Usage:
|
|
* node scripts/audit-agents.mjs ← standalone
|
|
* import { runAgentAudit } from './audit-agents.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 runAgentAudit(workspaceDir) {
|
|
const root = workspaceDir || WORKSPACE;
|
|
const checks = [];
|
|
|
|
// ---- Helper ----
|
|
function readAgents() {
|
|
const p = path.join(root, 'AGENTS.md');
|
|
if (!fs.existsSync(p)) return null;
|
|
return fs.readFileSync(p, 'utf8');
|
|
}
|
|
|
|
function check(prefix, id, name, condition, evidence, suggestion) {
|
|
checks.push({
|
|
id: `${prefix}-${id}`,
|
|
name,
|
|
status: condition ? 'PASS' : 'FAIL',
|
|
evidence: condition ? evidence : `File found but missing: ${evidence}`,
|
|
...(condition ? {} : { suggestion: suggestion || `Ensure §${id} is properly defined in AGENTS.md` }),
|
|
});
|
|
}
|
|
|
|
function warn(prefix, id, name, evidence, suggestion) {
|
|
checks.push({
|
|
id: `${prefix}-${id}`,
|
|
name,
|
|
status: 'WARN',
|
|
evidence,
|
|
suggestion,
|
|
});
|
|
}
|
|
|
|
// ---- Read AGENTS.md ----
|
|
const agents = readAgents();
|
|
if (!agents) {
|
|
checks.push({
|
|
id: 'AG-001',
|
|
name: 'AGENTS.md exists',
|
|
status: 'FAIL',
|
|
evidence: 'AGENTS.md not found in workspace root',
|
|
suggestion: 'Create AGENTS.md with the Execution Protocol',
|
|
});
|
|
return checks;
|
|
}
|
|
checks.push({
|
|
id: 'AG-001',
|
|
name: 'AGENTS.md exists',
|
|
status: 'PASS',
|
|
evidence: `AGENTS.md found (${agents.length} bytes)`,
|
|
});
|
|
|
|
// ---- §0 Complexity Classification ----
|
|
const hasClassify = /###\s*0\.\s*Complexity Classification/.test(agents);
|
|
check('AG', '0', '§0 Complexity Classification defined', hasClassify,
|
|
'Found "### 0. Complexity Classification" with Simple/Complex criteria',
|
|
'Add "### 0. Complexity Classification" with Simple→Direct / Complex→Task Tree decision rules');
|
|
|
|
// Detect if the "if uncertain → treat as Complex" rule exists
|
|
const hasUncertainRule = /if uncertain → treat as Complex/i.test(agents) || /if uncertain.*complex/i.test(agents);
|
|
check('AG', '0b', '§0 Uncertainty fallback ("if uncertain → treat as Complex")', hasUncertainRule,
|
|
'Uncertainty fallback rule found',
|
|
'Add decision rule: "if uncertain → treat as Complex"');
|
|
|
|
// ---- §1 Task Tree ----
|
|
const hasTaskTree = /###\s*1\.\s*Task Tree/.test(agents);
|
|
check('AG', '1', '§1 Task Tree Construction defined', hasTaskTree,
|
|
'Found "### 1. Task Tree Construction" with Task Tree definition',
|
|
'Add "### 1. Task Tree Construction" defining the execution tree');
|
|
|
|
// Task Tree Quality Gate (7 checks)
|
|
const hasTaskTreeGate = /Task Tree Quality Gate/.test(agents);
|
|
check('AG', '1f', '§1f Task Tree Quality Gate (7 checks)', hasTaskTreeGate,
|
|
'Found Task Tree Quality Gate with 7 verification items',
|
|
'Add §1f Quality Gate checklist (Root Task / No orphan / No circular / No XL / No unjust serial / Risks populated / No human dep)');
|
|
|
|
// ---- §2 PLAN ----
|
|
const hasPlan = /###\s*2\.\s*Planning Protocol/.test(agents);
|
|
check('AG', '2', '§2 Planning Protocol defined', hasPlan,
|
|
'Found "### 2. Planning Protocol" with Goal/Subtasks/Risks/Verify',
|
|
'Add "### 2. Planning Protocol" section');
|
|
|
|
// ---- §3 Pre-flight ----
|
|
const hasPreflight = /###\s*3\.\s*Pre-flight/.test(agents);
|
|
check('AG', '3', '§3 Pre-flight Check defined', hasPreflight,
|
|
'Found "### 3. Pre-flight Check" with Dependency Scan / Learning Context / Domain Checklists',
|
|
'Add "### 3. Pre-flight Check" section');
|
|
|
|
// 3a Dependency Scan
|
|
const hasDepScan = /####\s*3a\.\s*Dependency Scan/.test(agents);
|
|
check('AG', '3a', '§3a Dependency Scan defined', hasDepScan,
|
|
'Found §3a Dependency Scan',
|
|
'Add §3a Dependency Scan (files/tables, dependencies, blast radius)');
|
|
|
|
// 3b Learning Context Injector
|
|
const hasLearningCtx = /####\s*3b\.\s*Learning Context Injector/.test(agents);
|
|
check('AG', '3b', '§3b Learning Context Injector (3 searches)', hasLearningCtx,
|
|
'Found §3b Learning Context Injector with 3 mandatory searches',
|
|
'Add §3b with 3 searches: similar failures, best practices, playbooks');
|
|
|
|
// 3c Domain Checklists
|
|
const hasDomainChecklists = /####\s*3c\.\s*Domain Checklist Injection/.test(agents);
|
|
check('AG', '3c', '§3c Domain Checklists with ⚠️ Hard Rules', hasDomainChecklists,
|
|
'Found §3c Domain Checklist Injection with SQL/JS/Next.js/Backend/Generator/Release checklists',
|
|
'Add §3c with at least SQL, JS/TS, Next.js, Backend, Generator, Release checklists');
|
|
|
|
// Hard Rules exist
|
|
const hardRuleCount = (agents.match(/⚠️\s*HARD RULE/g) || []).length;
|
|
warn('AG', '3c-hard', `§3c Hard Rules count: ${hardRuleCount} (target: ≥5)`,
|
|
`Found ${hardRuleCount} ⚠️ HARD RULE markers`,
|
|
'Promote recurring patterns to Hard Rules. Target ≥5.');
|
|
|
|
// ---- §4 Failure Replanning ----
|
|
const hasReplan = /###\s*4\.\s*Failure Replanning Protocol/.test(agents);
|
|
check('AG', '4', '§4 Failure Replanning Protocol defined', hasReplan,
|
|
'Found "### 4. Failure Replanning Protocol" with local/structural replan logic',
|
|
'Add "### 4. Failure Replanning Protocol"');
|
|
|
|
// ---- §5 Verification Protocol ----
|
|
const hasVerification = /###\s*5\.\s*Verification Protocol/.test(agents);
|
|
check('AG', '5', '§5 Verification Protocol defined', hasVerification,
|
|
'Found "### 5. Verification Protocol" with Build/Tests/Lint/Regression/Manual checks',
|
|
'Add "### 5. Verification Protocol" with the verification block format');
|
|
|
|
// Quality Gate in Verification
|
|
const hasQualityGate = /Pre-flight Compliance Check/.test(agents) || /Quality Gate/.test(agents);
|
|
check('AG', '5b', '§5 Pre-flight Compliance / Quality Gate', hasQualityGate,
|
|
'Found "Pre-flight Compliance Check" with 5 mandatory gates',
|
|
'Add Quality Gate checklist within §5: PLAN has Injected Risks, ≥2 sources searched, domain checklist, reflection, 7-field reflection');
|
|
|
|
// ---- §6 Governance Compliance ----
|
|
const hasGovernance = /###\s*6\.\s*Governance Compliance/.test(agents);
|
|
check('AG', '6', '§6 Governance Compliance defined', hasGovernance,
|
|
'Found "### 6. Governance Compliance" with State Machine / Heartbeat / Quality Gate',
|
|
'Add "### 6. Governance Compliance" referencing GOVERNANCE.md');
|
|
|
|
// ---- §7 Auto-Capture ----
|
|
const hasAutoCapture = /###\s*7\.\s*Auto-Capture Trigger/.test(agents);
|
|
check('AG', '7', '§7 Auto-Capture Trigger defined', hasAutoCapture,
|
|
'Found "### 7. Auto-Capture Trigger" with T1-T7 trigger conditions',
|
|
'Add "### 7. Auto-Capture Trigger" with 7 trigger conditions and skip rules');
|
|
|
|
// ---- Learning Loop ----
|
|
const hasLearningLoop = /## Learning Loop/.test(agents);
|
|
check('AG', 'LL', 'Learning Loop (🔄) defined', hasLearningLoop,
|
|
'Found "## Learning Loop 🔄" with Loop Architecture / Promotion Rules / Reflection Quality Gate / Storage Locations',
|
|
'Add "## Learning Loop 🔄" section');
|
|
|
|
// Promotion Rules (1→2→3→4→5)
|
|
const hasPromotion = /Promotion Rules/.test(agents) && /1st.*Reflection/.test(agents) && /5th.*Hard Rule/.test(agents);
|
|
check('AG', 'LL-promo', 'Promotion Rules (1→2→3→4→5 appearances)', hasPromotion,
|
|
'Found Promotion Rules chart with 1st=Reflection through 5th=Hard Rule',
|
|
'Add Promotion Rules table mapping appearance count to promotion destination');
|
|
|
|
// ---- Context Budget ----
|
|
const hasBudget = /## Agent Package Context Budget/.test(agents);
|
|
check('AG', 'CB', 'Agent Package Context Budget defined', hasBudget,
|
|
'Found "## Agent Package Context Budget ⚠️ HARD RULE" with size→token map',
|
|
'Add "## Agent Package Context Budget" section');
|
|
|
|
// No XL
|
|
const forbidsXL = /XL.*FORBIDDEN/.test(agents) || /XL.*❌/.test(agents);
|
|
check('AG', 'CB-noxl', 'XL Package explicitly forbidden', forbidsXL,
|
|
'Found "XL | ❌ FORBIDDEN" in Context Budget table',
|
|
'Add "XL | ❌ FORBIDDEN" row to the Size→Token Budget Mapping table');
|
|
|
|
// Input Size Pre-check
|
|
const hasPrecheck = /Input Size Pre-check/.test(agents);
|
|
check('AG', 'CB-precheck', 'Input Size Pre-check (Context Budget Declaration)', hasPrecheck,
|
|
'Found "Input Size Pre-check" with Context Budget Declaration template',
|
|
'Add "Input Size Pre-check" section with file inventory / token estimate / budget check');
|
|
|
|
// ---- Cross-reference integrity ----
|
|
const refsGovernance = (agents.match(/GOVERNANCE\.md/g) || []).length;
|
|
warn('AG', 'XR-gov', `Cross-references to GOVERNANCE.md: ${refsGovernance} (target: ≥3)`,
|
|
`Found ${refsGovernance} references to GOVERNANCE.md`,
|
|
'Add cross-references from AGENTS.md to GOVERNANCE.md in §6');
|
|
|
|
const refsPortfolio = (agents.match(/PORTFOLIO\.md/g) || []).length;
|
|
warn('AG', 'XR-port', `Cross-references to PORTFOLIO.md: ${refsPortfolio} (target: ≥1)`,
|
|
`Found ${refsPortfolio} references to PORTFOLIO.md`,
|
|
'Add cross-reference to PORTFOLIO.md in §6e Reference section');
|
|
|
|
// ---- Injected Risks (target check) ----
|
|
const hasInjectedRisks = /Injected Risks/.test(agents);
|
|
check('AG', 'IR', 'Injected Risks mechanism defined', hasInjectedRisks,
|
|
'Found "### Injected Risks" / "### Per-Task Injected Risks"',
|
|
'Add "### Injected Risks (from Learning Context)" format in §1e and §3b');
|
|
|
|
// ---- 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: 'AG-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;
|
|
}
|
|
|
|
// ---- Standalone runner ----
|
|
if (SELF.endsWith('/audit-agents.mjs') || SELF.endsWith('\\audit-agents.mjs')) {
|
|
const results = runAgentAudit(WORKSPACE);
|
|
console.log(`\n=== AGENTS.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();
|
|
}
|
|
}
|