/** * Agent Schema Validator CLI * * Scans all *.agent.yaml files in src/{core,modules/*}/agents/ * and validates them against the Zod schema. * * Usage: node tools/validate-agent-schema.js [project_root] * Exit codes: 0 = success, 1 = validation failures * * Optional argument: * project_root - Directory to scan (defaults to BMAD repo root) */ const { glob } = require('glob'); const yaml = require('js-yaml'); const fs = require('node:fs'); const path = require('node:path'); const { validateAgentFile } = require('./schema/agent.js'); /** * Main validation routine * @param {string} [customProjectRoot] - Optional project root to scan (for testing) */ async function main(customProjectRoot) { console.log('šŸ” Scanning for agent files...\n'); // Determine project root: use custom path if provided, otherwise default to repo root const project_root = customProjectRoot || path.join(__dirname, '..'); // Find all agent files const agentFiles = await glob('src/{core,modules/*}/agents/*.agent.yaml', { cwd: project_root, absolute: true, }); if (agentFiles.length === 0) { console.log('āŒ No agent files found. This likely indicates a configuration error.'); console.log(' Expected to find *.agent.yaml files in src/{core,modules/*}/agents/'); process.exit(1); } console.log(`Found ${agentFiles.length} agent file(s)\n`); const errors = []; // Validate each file for (const filePath of agentFiles) { const relativePath = path.relative(process.cwd(), filePath); try { const fileContent = fs.readFileSync(filePath, 'utf8'); const agentData = yaml.load(fileContent); // Convert absolute path to relative src/ path for module detection const srcRelativePath = relativePath.startsWith('src/') ? relativePath : path.relative(project_root, filePath).replaceAll('\\', '/'); const result = validateAgentFile(srcRelativePath, agentData); if (result.success) { console.log(`āœ… ${relativePath}`); } else { errors.push({ file: relativePath, issues: result.error.issues, }); } } catch (error) { errors.push({ file: relativePath, issues: [ { code: 'parse_error', message: `Failed to parse YAML: ${error.message}`, path: [], }, ], }); } } // Report errors if (errors.length > 0) { console.log('\nāŒ Validation failed for the following files:\n'); for (const { file, issues } of errors) { console.log(`\nšŸ“„ ${file}`); for (const issue of issues) { const pathString = issue.path.length > 0 ? issue.path.join('.') : '(root)'; console.log(` Path: ${pathString}`); console.log(` Error: ${issue.message}`); if (issue.code) { console.log(` Code: ${issue.code}`); } } } console.log(`\n\nšŸ’„ ${errors.length} file(s) failed validation`); process.exit(1); } console.log(`\n✨ All ${agentFiles.length} agent file(s) passed validation!\n`); process.exit(0); } // Run with optional command-line argument for project root const customProjectRoot = process.argv[2]; main(customProjectRoot).catch((error) => { console.error('Fatal error:', error); process.exit(1); });