Enhance build command to scan all .bmad/ modules

- Update buildAllAgents() to scan all directories in .bmad/
- Update checkBuildStatus() to check all modules
- Update listAvailableAgents() to list all modules
- Auto-detect .bmad or bmad folder name
- Skip special directories (_cfg, docs)

This makes 'bmad build --all' discover custom modules (custom/, hde/, etc.)
and builds their agents automatically, matching the behavior of the fixed
install/compile workflow.

Related to #990
This commit is contained in:
Ziyu Huang 2025-11-28 07:06:09 +08:00
parent 1af87338ae
commit e9b75bd3cb
1 changed files with 143 additions and 7 deletions

View File

@ -166,8 +166,66 @@ async function buildAllAgents(projectDir, force = false) {
let builtCount = 0;
let skippedCount = 0;
// First, build standalone agents in bmad/agents/
const standaloneAgentsDir = path.join(projectDir, 'bmad', 'agents');
// Detect .bmad folder name (could be .bmad or bmad)
const bmadFolder = (await fs.pathExists(path.join(projectDir, '.bmad'))) ? '.bmad' : 'bmad';
const bmadDir = path.join(projectDir, bmadFolder);
// Build agents from ALL module directories in .bmad/ (including custom, hde, etc.)
if (await fs.pathExists(bmadDir)) {
console.log(chalk.cyan('\nScanning all modules in .bmad/...'));
const moduleEntries = await fs.readdir(bmadDir, { withFileTypes: true });
for (const moduleEntry of moduleEntries) {
// Skip special directories
if (!moduleEntry.isDirectory() || moduleEntry.name === '_cfg' || moduleEntry.name === 'docs') {
continue;
}
const modulePath = path.join(bmadDir, moduleEntry.name);
const agentsPath = path.join(modulePath, 'agents');
// Check if this module has an agents/ directory
if (!(await fs.pathExists(agentsPath))) {
continue;
}
console.log(chalk.cyan(`\nBuilding agents in ${moduleEntry.name} module...`));
const agentFiles = await fs.readdir(agentsPath);
for (const file of agentFiles) {
if (!file.endsWith('.agent.yaml')) {
continue;
}
const agentName = file.replace('.agent.yaml', '');
const agentYamlPath = path.join(agentsPath, file);
const outputPath = path.join(agentsPath, `${agentName}.md`);
// Check if rebuild needed
if (!force && (await fs.pathExists(outputPath))) {
const needsRebuild = await checkIfNeedsRebuild(agentYamlPath, outputPath, projectDir, agentName);
if (!needsRebuild) {
console.log(chalk.dim(` ${agentName}: up to date`));
skippedCount++;
continue;
}
}
console.log(chalk.cyan(` Building ${agentName}...`));
const customizePath = path.join(bmadDir, '_cfg', 'agents', `${moduleEntry.name}-${agentName}.customize.yaml`);
const customizeExists = await fs.pathExists(customizePath);
await builder.buildAgent(agentYamlPath, customizeExists ? customizePath : null, outputPath, { includeMetadata: true });
console.log(chalk.green(`${agentName} (${moduleEntry.name})`));
builtCount++;
}
}
}
// Also build standalone agents in bmad/agents/ (top-level, for backward compatibility)
const standaloneAgentsDir = path.join(projectDir, bmadFolder, 'agents');
if (await fs.pathExists(standaloneAgentsDir)) {
console.log(chalk.cyan('\nBuilding standalone agents...'));
const agentDirs = await fs.readdir(standaloneAgentsDir);
@ -205,7 +263,7 @@ async function buildAllAgents(projectDir, force = false) {
console.log(chalk.cyan(` Building standalone agent ${agentName}...`));
const customizePath = path.join(projectDir, 'bmad', '_cfg', 'agents', `${agentName}.customize.yaml`);
const customizePath = path.join(projectDir, bmadFolder, '_cfg', 'agents', `${agentName}.customize.yaml`);
const customizeExists = await fs.pathExists(customizePath);
await builder.buildAgent(agentYamlPath, customizeExists ? customizePath : null, outputPath, { includeMetadata: true });
@ -275,8 +333,52 @@ async function checkBuildStatus(projectDir) {
const needsRebuild = [];
const upToDate = [];
// Check standalone agents in bmad/agents/
const standaloneAgentsDir = path.join(projectDir, 'bmad', 'agents');
// Detect .bmad folder name (could be .bmad or bmad)
const bmadFolder = (await fs.pathExists(path.join(projectDir, '.bmad'))) ? '.bmad' : 'bmad';
const bmadDir = path.join(projectDir, bmadFolder);
// Check agents in ALL module directories in .bmad/
if (await fs.pathExists(bmadDir)) {
const moduleEntries = await fs.readdir(bmadDir, { withFileTypes: true });
for (const moduleEntry of moduleEntries) {
// Skip special directories
if (!moduleEntry.isDirectory() || moduleEntry.name === '_cfg' || moduleEntry.name === 'docs') {
continue;
}
const modulePath = path.join(bmadDir, moduleEntry.name);
const agentsPath = path.join(modulePath, 'agents');
// Check if this module has an agents/ directory
if (!(await fs.pathExists(agentsPath))) {
continue;
}
const agentFiles = await fs.readdir(agentsPath);
for (const file of agentFiles) {
if (!file.endsWith('.agent.yaml')) {
continue;
}
const agentName = file.replace('.agent.yaml', '');
const agentYamlPath = path.join(agentsPath, file);
const outputPath = path.join(agentsPath, `${agentName}.md`);
if (!(await fs.pathExists(outputPath))) {
needsRebuild.push(`${agentName} (${moduleEntry.name})`);
} else if (await checkIfNeedsRebuild(agentYamlPath, outputPath, projectDir, agentName)) {
needsRebuild.push(`${agentName} (${moduleEntry.name})`);
} else {
upToDate.push(`${agentName} (${moduleEntry.name})`);
}
}
}
}
// Check standalone agents in bmad/agents/ (top-level)
const standaloneAgentsDir = path.join(projectDir, bmadFolder, 'agents');
if (await fs.pathExists(standaloneAgentsDir)) {
const agentDirs = await fs.readdir(standaloneAgentsDir);
@ -406,8 +508,42 @@ async function checkIfNeedsRebuild(yamlPath, outputPath, projectDir, agentName)
* List available agents
*/
async function listAvailableAgents(projectDir) {
// List standalone agents first
const standaloneAgentsDir = path.join(projectDir, 'bmad', 'agents');
// Detect .bmad folder name (could be .bmad or bmad)
const bmadFolder = (await fs.pathExists(path.join(projectDir, '.bmad'))) ? '.bmad' : 'bmad';
const bmadDir = path.join(projectDir, bmadFolder);
// List agents from ALL module directories in .bmad/
if (await fs.pathExists(bmadDir)) {
console.log(chalk.dim(' Module agents:'));
const moduleEntries = await fs.readdir(bmadDir, { withFileTypes: true });
for (const moduleEntry of moduleEntries) {
// Skip special directories
if (!moduleEntry.isDirectory() || moduleEntry.name === '_cfg' || moduleEntry.name === 'docs') {
continue;
}
const modulePath = path.join(bmadDir, moduleEntry.name);
const agentsPath = path.join(modulePath, 'agents');
// Check if this module has an agents/ directory
if (!(await fs.pathExists(agentsPath))) {
continue;
}
const agentFiles = await fs.readdir(agentsPath);
for (const file of agentFiles) {
if (file.endsWith('.agent.yaml')) {
const agentName = file.replace('.agent.yaml', '');
console.log(chalk.dim(` - ${agentName} (${moduleEntry.name})`));
}
}
}
}
// List standalone agents
const standaloneAgentsDir = path.join(projectDir, bmadFolder, 'agents');
if (await fs.pathExists(standaloneAgentsDir)) {
console.log(chalk.dim(' Standalone agents:'));
const agentDirs = await fs.readdir(standaloneAgentsDir);