284 lines
9.5 KiB
JavaScript
Raw Normal View History

2025-10-02 21:45:59 -05:00
const chalk = require('chalk');
const path = require('node:path');
const fs = require('fs-extra');
const { YamlXmlBuilder } = require('../lib/yaml-xml-builder');
const { getProjectRoot } = require('../lib/project-root');
const builder = new YamlXmlBuilder();
/**
* Find .claude directory by searching up from current directory
*/
async function findClaudeDir(startDir) {
let currentDir = startDir;
const root = path.parse(currentDir).root;
while (currentDir !== root) {
const claudeDir = path.join(currentDir, '.claude');
if (await fs.pathExists(claudeDir)) {
return claudeDir;
}
currentDir = path.dirname(currentDir);
}
return null;
}
module.exports = {
command: 'build [agent]',
description: 'Build agent XML files from YAML sources',
options: [
['-a, --all', 'Build all agents'],
2025-10-02 21:45:59 -05:00
['-d, --directory <path>', 'Project directory', '.'],
],
action: async (agentName, options) => {
try {
let projectDir = path.resolve(options.directory);
// Auto-detect .claude directory (search up from current dir)
const claudeDir = await findClaudeDir(projectDir);
if (!claudeDir) {
console.log(chalk.yellow('\n⚠ No .claude directory found'));
console.log(chalk.dim('Run this command from your project directory or'));
console.log(chalk.dim('use --directory flag to specify location'));
console.log(chalk.dim('\nExample: npx bmad-method build pm --directory /path/to/project'));
process.exit(1);
}
// Use the directory containing .claude
projectDir = path.dirname(claudeDir);
console.log(chalk.dim(`Using project: ${projectDir}\n`));
console.log(chalk.cyan('🔨 Building Agent Files\n'));
if (options.all) {
// Build all agents
await buildAllAgents(projectDir);
2025-10-02 21:45:59 -05:00
} else if (agentName) {
// Build specific agent
await buildAgent(projectDir, agentName);
2025-10-02 21:45:59 -05:00
} else {
// No agent specified, list available agents
console.log(chalk.yellow('No agent specified. Use --all to build all agents or specify an agent name.'));
console.log(chalk.dim('\nAvailable agents:'));
await listAvailableAgents(projectDir);
2025-10-02 21:45:59 -05:00
}
process.exit(0);
} catch (error) {
console.error(chalk.red('\nError:'), error.message);
if (process.env.DEBUG) {
console.error(error.stack);
}
process.exit(1);
}
},
};
/**
* Build a specific agent
*/
async function buildAgent(projectDir, agentName) {
// First check standalone agents in bmad/agents/{agentname}/
const standaloneAgentDir = path.join(projectDir, 'bmad', 'agents', agentName);
let standaloneYamlPath = path.join(standaloneAgentDir, `${agentName}.agent.yaml`);
// If exact match doesn't exist, look for any .agent.yaml file in the directory
if (!(await fs.pathExists(standaloneYamlPath)) && (await fs.pathExists(standaloneAgentDir))) {
const files = await fs.readdir(standaloneAgentDir);
const agentFile = files.find((f) => f.endsWith('.agent.yaml'));
if (agentFile) {
standaloneYamlPath = path.join(standaloneAgentDir, agentFile);
}
}
if (await fs.pathExists(standaloneYamlPath)) {
const yamlFileName = path.basename(standaloneYamlPath, '.agent.yaml');
const outputPath = path.join(standaloneAgentDir, `${yamlFileName}.md`);
// Build the standalone agent
console.log(chalk.cyan(` Building standalone agent ${agentName}...`));
const customizePath = path.join(projectDir, 'bmad', '_cfg', 'agents', `${agentName}.customize.yaml`);
const customizeExists = await fs.pathExists(customizePath);
await builder.buildAgent(standaloneYamlPath, customizeExists ? customizePath : null, outputPath, { includeMetadata: true });
console.log(chalk.green(`${agentName} built successfully (standalone)`));
return;
}
2025-10-02 21:45:59 -05:00
// Find the agent YAML file in .claude/commands/bmad/
const bmadCommandsDir = path.join(projectDir, '.claude', 'commands', 'bmad');
// Search all module directories for the agent
const modules = await fs.readdir(bmadCommandsDir);
let found = false;
for (const module of modules) {
const agentYamlPath = path.join(bmadCommandsDir, module, 'agents', `${agentName}.agent.yaml`);
const outputPath = path.join(bmadCommandsDir, module, 'agents', `${agentName}.md`);
if (await fs.pathExists(agentYamlPath)) {
found = true;
// Build the agent
console.log(chalk.cyan(` Building ${agentName}...`));
const customizePath = path.join(projectDir, '.claude', '_cfg', 'agents', `${agentName}.customize.yaml`);
const customizeExists = await fs.pathExists(customizePath);
await builder.buildAgent(agentYamlPath, customizeExists ? customizePath : null, outputPath, { includeMetadata: true });
console.log(chalk.green(`${agentName} built successfully`));
return;
}
}
if (!found) {
console.log(chalk.yellow(` ⚠️ Agent '${agentName}' not found`));
console.log(chalk.dim(' Available agents:'));
await listAvailableAgents(projectDir);
2025-10-02 21:45:59 -05:00
}
}
/**
* Build all agents
*/
async function buildAllAgents(projectDir) {
2025-10-02 21:45:59 -05:00
let builtCount = 0;
// First, build standalone agents in bmad/agents/
const standaloneAgentsDir = path.join(projectDir, 'bmad', 'agents');
if (await fs.pathExists(standaloneAgentsDir)) {
console.log(chalk.cyan('\nBuilding standalone agents...'));
const agentDirs = await fs.readdir(standaloneAgentsDir);
2025-10-02 21:45:59 -05:00
for (const agentDirName of agentDirs) {
const agentDir = path.join(standaloneAgentsDir, agentDirName);
// Skip if not a directory
const stat = await fs.stat(agentDir);
if (!stat.isDirectory()) {
continue;
}
2025-10-02 21:45:59 -05:00
// Find any .agent.yaml file in the directory
const files = await fs.readdir(agentDir);
const agentFile = files.find((f) => f.endsWith('.agent.yaml'));
2025-10-02 21:45:59 -05:00
if (!agentFile) {
2025-10-02 21:45:59 -05:00
continue;
}
const agentYamlPath = path.join(agentDir, agentFile);
const agentName = path.basename(agentFile, '.agent.yaml');
const outputPath = path.join(agentDir, `${agentName}.md`);
2025-10-02 21:45:59 -05:00
console.log(chalk.cyan(` Building standalone agent ${agentName}...`));
2025-10-02 21:45:59 -05:00
const customizePath = path.join(projectDir, 'bmad', '_cfg', 'agents', `${agentName}.customize.yaml`);
2025-10-02 21:45:59 -05:00
const customizeExists = await fs.pathExists(customizePath);
await builder.buildAgent(agentYamlPath, customizeExists ? customizePath : null, outputPath, { includeMetadata: true });
console.log(chalk.green(`${agentName} (standalone)`));
2025-10-02 21:45:59 -05:00
builtCount++;
}
}
// Then, build module agents in .claude/commands/bmad/
const bmadCommandsDir = path.join(projectDir, '.claude', 'commands', 'bmad');
if (await fs.pathExists(bmadCommandsDir)) {
console.log(chalk.cyan('\nBuilding module agents...'));
const modules = await fs.readdir(bmadCommandsDir);
for (const module of modules) {
const agentsDir = path.join(bmadCommandsDir, module, 'agents');
if (!(await fs.pathExists(agentsDir))) {
continue;
}
const files = await fs.readdir(agentsDir);
for (const file of files) {
if (!file.endsWith('.agent.yaml')) {
continue;
}
const agentName = file.replace('.agent.yaml', '');
const agentYamlPath = path.join(agentsDir, file);
const outputPath = path.join(agentsDir, `${agentName}.md`);
console.log(chalk.cyan(` Building ${agentName}...`));
const customizePath = path.join(projectDir, '.claude', '_cfg', 'agents', `${agentName}.customize.yaml`);
const customizeExists = await fs.pathExists(customizePath);
await builder.buildAgent(agentYamlPath, customizeExists ? customizePath : null, outputPath, { includeMetadata: true });
console.log(chalk.green(`${agentName} (${module})`));
builtCount++;
}
}
}
2025-10-02 21:45:59 -05:00
console.log(chalk.green(`\n✓ Built ${builtCount} agent(s)`));
}
/**
* List available agents
*/
async function listAvailableAgents(projectDir) {
// List standalone agents first
const standaloneAgentsDir = path.join(projectDir, 'bmad', 'agents');
if (await fs.pathExists(standaloneAgentsDir)) {
console.log(chalk.dim(' Standalone agents:'));
const agentDirs = await fs.readdir(standaloneAgentsDir);
for (const agentDirName of agentDirs) {
const agentDir = path.join(standaloneAgentsDir, agentDirName);
// Skip if not a directory
const stat = await fs.stat(agentDir);
if (!stat.isDirectory()) {
continue;
}
2025-10-02 21:45:59 -05:00
// Find any .agent.yaml file in the directory
const files = await fs.readdir(agentDir);
const agentFile = files.find((f) => f.endsWith('.agent.yaml'));
2025-10-02 21:45:59 -05:00
if (agentFile) {
const agentName = path.basename(agentFile, '.agent.yaml');
console.log(chalk.dim(` - ${agentName} (in ${agentDirName}/)`));
}
2025-10-02 21:45:59 -05:00
}
}
2025-10-02 21:45:59 -05:00
// List module agents
const bmadCommandsDir = path.join(projectDir, '.claude', 'commands', 'bmad');
if (await fs.pathExists(bmadCommandsDir)) {
console.log(chalk.dim(' Module agents:'));
const modules = await fs.readdir(bmadCommandsDir);
2025-10-02 21:45:59 -05:00
for (const module of modules) {
const agentsDir = path.join(bmadCommandsDir, module, 'agents');
if (!(await fs.pathExists(agentsDir))) {
continue;
}
const files = await fs.readdir(agentsDir);
for (const file of files) {
if (file.endsWith('.agent.yaml')) {
const agentName = file.replace('.agent.yaml', '');
console.log(chalk.dim(` - ${agentName} (${module})`));
}
2025-10-02 21:45:59 -05:00
}
}
}
}