2025-12-10 20:50:24 +09:00

284 lines
9.5 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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'],
['-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);
} else if (agentName) {
// Build specific agent
await buildAgent(projectDir, agentName);
} 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);
}
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;
}
// 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);
}
}
/**
* Build all agents
*/
async function buildAllAgents(projectDir) {
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);
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;
}
// Find any .agent.yaml file in the directory
const files = await fs.readdir(agentDir);
const agentFile = files.find((f) => f.endsWith('.agent.yaml'));
if (!agentFile) {
continue;
}
const agentYamlPath = path.join(agentDir, agentFile);
const agentName = path.basename(agentFile, '.agent.yaml');
const outputPath = path.join(agentDir, `${agentName}.md`);
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(agentYamlPath, customizeExists ? customizePath : null, outputPath, { includeMetadata: true });
console.log(chalk.green(`${agentName} (standalone)`));
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++;
}
}
}
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;
}
// Find any .agent.yaml file in the directory
const files = await fs.readdir(agentDir);
const agentFile = files.find((f) => f.endsWith('.agent.yaml'));
if (agentFile) {
const agentName = path.basename(agentFile, '.agent.yaml');
console.log(chalk.dim(` - ${agentName} (in ${agentDirName}/)`));
}
}
}
// 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);
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})`));
}
}
}
}
}