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', 'Rebuild all agents'],
|
|
|
|
|
|
['-d, --directory <path>', 'Project directory', '.'],
|
|
|
|
|
|
['--force', 'Force rebuild even if up to date'],
|
|
|
|
|
|
],
|
|
|
|
|
|
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, options.force);
|
|
|
|
|
|
} else if (agentName) {
|
|
|
|
|
|
// Build specific agent
|
|
|
|
|
|
await buildAgent(projectDir, agentName, options.force);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// No agent specified, check what needs rebuilding
|
|
|
|
|
|
await checkBuildStatus(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, force = false) {
|
2025-10-04 01:26:38 -05:00
|
|
|
|
// 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`);
|
|
|
|
|
|
|
|
|
|
|
|
// Check if rebuild needed
|
|
|
|
|
|
if (!force && (await fs.pathExists(outputPath))) {
|
|
|
|
|
|
const needsRebuild = await checkIfNeedsRebuild(standaloneYamlPath, outputPath, projectDir, agentName);
|
|
|
|
|
|
if (!needsRebuild) {
|
|
|
|
|
|
console.log(chalk.dim(` ${agentName}: already up to date`));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
|
|
|
|
// 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}: already up to date`));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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:'));
|
2025-10-04 01:26:38 -05:00
|
|
|
|
await listAvailableAgents(projectDir);
|
2025-10-02 21:45:59 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Build all agents
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function buildAllAgents(projectDir, force = false) {
|
|
|
|
|
|
let builtCount = 0;
|
|
|
|
|
|
let skippedCount = 0;
|
|
|
|
|
|
|
2025-10-04 01:26:38 -05:00
|
|
|
|
// 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
|
|
|
|
|
2025-10-04 01:26:38 -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
|
|
|
|
|
2025-10-04 01:26:38 -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
|
|
|
|
|
2025-10-04 01:26:38 -05:00
|
|
|
|
if (!agentFile) {
|
2025-10-02 21:45:59 -05:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-04 01:26:38 -05:00
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-04 01:26:38 -05:00
|
|
|
|
console.log(chalk.cyan(` Building standalone agent ${agentName}...`));
|
2025-10-02 21:45:59 -05:00
|
|
|
|
|
2025-10-04 01:26:38 -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 });
|
|
|
|
|
|
|
2025-10-04 01:26:38 -05:00
|
|
|
|
console.log(chalk.green(` ✓ ${agentName} (standalone)`));
|
2025-10-02 21:45:59 -05:00
|
|
|
|
builtCount++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-04 01:26:38 -05:00
|
|
|
|
// 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`);
|
|
|
|
|
|
|
|
|
|
|
|
// 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(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)`));
|
|
|
|
|
|
if (skippedCount > 0) {
|
|
|
|
|
|
console.log(chalk.dim(` Skipped ${skippedCount} (already up to date)`));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Check what needs rebuilding
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function checkBuildStatus(projectDir) {
|
|
|
|
|
|
const needsRebuild = [];
|
|
|
|
|
|
const upToDate = [];
|
|
|
|
|
|
|
2025-10-04 01:26:38 -05:00
|
|
|
|
// Check standalone agents in bmad/agents/
|
|
|
|
|
|
const standaloneAgentsDir = path.join(projectDir, 'bmad', 'agents');
|
|
|
|
|
|
if (await fs.pathExists(standaloneAgentsDir)) {
|
|
|
|
|
|
const agentDirs = await fs.readdir(standaloneAgentsDir);
|
2025-10-02 21:45:59 -05:00
|
|
|
|
|
2025-10-04 01:26:38 -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
|
|
|
|
|
2025-10-04 01:26:38 -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
|
|
|
|
|
2025-10-04 01:26:38 -05:00
|
|
|
|
if (!agentFile) {
|
2025-10-02 21:45:59 -05:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-04 01:26:38 -05:00
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
if (!(await fs.pathExists(outputPath))) {
|
2025-10-04 01:26:38 -05:00
|
|
|
|
needsRebuild.push(`${agentName} (standalone)`);
|
2025-10-02 21:45:59 -05:00
|
|
|
|
} else if (await checkIfNeedsRebuild(agentYamlPath, outputPath, projectDir, agentName)) {
|
2025-10-04 01:26:38 -05:00
|
|
|
|
needsRebuild.push(`${agentName} (standalone)`);
|
2025-10-02 21:45:59 -05:00
|
|
|
|
} else {
|
2025-10-04 01:26:38 -05:00
|
|
|
|
upToDate.push(`${agentName} (standalone)`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check module agents in .claude/commands/bmad/
|
|
|
|
|
|
const bmadCommandsDir = path.join(projectDir, '.claude', 'commands', 'bmad');
|
|
|
|
|
|
if (await fs.pathExists(bmadCommandsDir)) {
|
|
|
|
|
|
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`);
|
|
|
|
|
|
|
|
|
|
|
|
if (!(await fs.pathExists(outputPath))) {
|
|
|
|
|
|
needsRebuild.push(`${agentName} (${module})`);
|
|
|
|
|
|
} else if (await checkIfNeedsRebuild(agentYamlPath, outputPath, projectDir, agentName)) {
|
|
|
|
|
|
needsRebuild.push(`${agentName} (${module})`);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
upToDate.push(`${agentName} (${module})`);
|
|
|
|
|
|
}
|
2025-10-02 21:45:59 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (needsRebuild.length === 0) {
|
|
|
|
|
|
console.log(chalk.green('✓ All agents are up to date'));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.log(chalk.yellow(`${needsRebuild.length} agent(s) need rebuilding:`));
|
|
|
|
|
|
for (const agent of needsRebuild) {
|
|
|
|
|
|
console.log(chalk.dim(` - ${agent}`));
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log(chalk.dim('\nRun "bmad build --all" to rebuild all agents'));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (upToDate.length > 0) {
|
|
|
|
|
|
console.log(chalk.dim(`\n${upToDate.length} agent(s) up to date`));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Check if an agent needs rebuilding by comparing hashes
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function checkIfNeedsRebuild(yamlPath, outputPath, projectDir, agentName) {
|
|
|
|
|
|
// Read the output file to check its metadata
|
|
|
|
|
|
const outputContent = await fs.readFile(outputPath, 'utf8');
|
|
|
|
|
|
|
|
|
|
|
|
// Extract hash from BUILD-META comment
|
|
|
|
|
|
const metaMatch = outputContent.match(/source:.*\(hash: ([a-f0-9]+)\)/);
|
|
|
|
|
|
if (!metaMatch) {
|
|
|
|
|
|
// No metadata, needs rebuild
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const storedHash = metaMatch[1];
|
|
|
|
|
|
|
|
|
|
|
|
// Calculate current hash
|
|
|
|
|
|
const currentHash = await builder.calculateFileHash(yamlPath);
|
|
|
|
|
|
|
|
|
|
|
|
if (storedHash !== currentHash) {
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check customize file if it exists
|
|
|
|
|
|
const customizePath = path.join(projectDir, '.claude', '_cfg', 'agents', `${agentName}.customize.yaml`);
|
|
|
|
|
|
if (await fs.pathExists(customizePath)) {
|
|
|
|
|
|
const customizeMetaMatch = outputContent.match(/customize:.*\(hash: ([a-f0-9]+)\)/);
|
|
|
|
|
|
if (!customizeMetaMatch) {
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const storedCustomizeHash = customizeMetaMatch[1];
|
|
|
|
|
|
const currentCustomizeHash = await builder.calculateFileHash(customizePath);
|
|
|
|
|
|
|
|
|
|
|
|
if (storedCustomizeHash !== currentCustomizeHash) {
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* List available agents
|
|
|
|
|
|
*/
|
2025-10-04 01:26:38 -05:00
|
|
|
|
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
|
|
|
|
|
2025-10-04 01:26:38 -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
|
|
|
|
|
2025-10-04 01:26:38 -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-04 01:26:38 -05:00
|
|
|
|
}
|
2025-10-02 21:45:59 -05:00
|
|
|
|
|
2025-10-04 01:26:38 -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
|
|
|
|
|
2025-10-04 01:26:38 -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
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|