111 lines
3.2 KiB
JavaScript
111 lines
3.2 KiB
JavaScript
/**
|
||
* 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('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,bmm}/agents/**/*.agent.yaml', {
|
||
cwd: project_root,
|
||
absolute: true,
|
||
});
|
||
|
||
if (agentFiles.length === 0) {
|
||
console.log('ℹ️ No *.agent.yaml files found — agents may use the new SKILL.md format.');
|
||
console.log(' Skipping legacy agent schema validation.\n');
|
||
process.exit(0);
|
||
}
|
||
|
||
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.parse(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);
|
||
});
|