docs updated and agent standalone builder working now from the main install flow
This commit is contained in:
@@ -78,6 +78,44 @@ module.exports = {
|
||||
* Build a specific agent
|
||||
*/
|
||||
async function buildAgent(projectDir, agentName, force = false) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Find the agent YAML file in .claude/commands/bmad/
|
||||
const bmadCommandsDir = path.join(projectDir, '.claude', 'commands', 'bmad');
|
||||
|
||||
@@ -117,7 +155,7 @@ async function buildAgent(projectDir, agentName, force = false) {
|
||||
if (!found) {
|
||||
console.log(chalk.yellow(` ⚠️ Agent '${agentName}' not found`));
|
||||
console.log(chalk.dim(' Available agents:'));
|
||||
await listAvailableAgents(bmadCommandsDir);
|
||||
await listAvailableAgents(projectDir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,29 +163,35 @@ async function buildAgent(projectDir, agentName, force = false) {
|
||||
* Build all agents
|
||||
*/
|
||||
async function buildAllAgents(projectDir, force = false) {
|
||||
const bmadCommandsDir = path.join(projectDir, '.claude', 'commands', 'bmad');
|
||||
const modules = await fs.readdir(bmadCommandsDir);
|
||||
|
||||
let builtCount = 0;
|
||||
let skippedCount = 0;
|
||||
|
||||
for (const module of modules) {
|
||||
const agentsDir = path.join(bmadCommandsDir, module, 'agents');
|
||||
// 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);
|
||||
|
||||
if (!(await fs.pathExists(agentsDir))) {
|
||||
continue;
|
||||
}
|
||||
for (const agentDirName of agentDirs) {
|
||||
const agentDir = path.join(standaloneAgentsDir, agentDirName);
|
||||
|
||||
const files = await fs.readdir(agentsDir);
|
||||
|
||||
for (const file of files) {
|
||||
if (!file.endsWith('.agent.yaml')) {
|
||||
// Skip if not a directory
|
||||
const stat = await fs.stat(agentDir);
|
||||
if (!stat.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const agentName = file.replace('.agent.yaml', '');
|
||||
const agentYamlPath = path.join(agentsDir, file);
|
||||
const outputPath = path.join(agentsDir, `${agentName}.md`);
|
||||
// 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`);
|
||||
|
||||
// Check if rebuild needed
|
||||
if (!force && (await fs.pathExists(outputPath))) {
|
||||
@@ -159,18 +203,65 @@ async function buildAllAgents(projectDir, force = false) {
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.cyan(` Building ${agentName}...`));
|
||||
console.log(chalk.cyan(` Building standalone agent ${agentName}...`));
|
||||
|
||||
const customizePath = path.join(projectDir, '.claude', '_cfg', 'agents', `${agentName}.customize.yaml`);
|
||||
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}`));
|
||||
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`);
|
||||
|
||||
// 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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.green(`\n✓ Built ${builtCount} agent(s)`));
|
||||
if (skippedCount > 0) {
|
||||
console.log(chalk.dim(` Skipped ${skippedCount} (already up to date)`));
|
||||
@@ -181,36 +272,75 @@ async function buildAllAgents(projectDir, force = false) {
|
||||
* Check what needs rebuilding
|
||||
*/
|
||||
async function checkBuildStatus(projectDir) {
|
||||
const bmadCommandsDir = path.join(projectDir, '.claude', 'commands', 'bmad');
|
||||
const modules = await fs.readdir(bmadCommandsDir);
|
||||
|
||||
const needsRebuild = [];
|
||||
const upToDate = [];
|
||||
|
||||
for (const module of modules) {
|
||||
const agentsDir = path.join(bmadCommandsDir, module, 'agents');
|
||||
// Check standalone agents in bmad/agents/
|
||||
const standaloneAgentsDir = path.join(projectDir, 'bmad', 'agents');
|
||||
if (await fs.pathExists(standaloneAgentsDir)) {
|
||||
const agentDirs = await fs.readdir(standaloneAgentsDir);
|
||||
|
||||
if (!(await fs.pathExists(agentsDir))) {
|
||||
continue;
|
||||
}
|
||||
for (const agentDirName of agentDirs) {
|
||||
const agentDir = path.join(standaloneAgentsDir, agentDirName);
|
||||
|
||||
const files = await fs.readdir(agentsDir);
|
||||
|
||||
for (const file of files) {
|
||||
if (!file.endsWith('.agent.yaml')) {
|
||||
// Skip if not a directory
|
||||
const stat = await fs.stat(agentDir);
|
||||
if (!stat.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const agentName = file.replace('.agent.yaml', '');
|
||||
const agentYamlPath = path.join(agentsDir, file);
|
||||
const outputPath = path.join(agentsDir, `${agentName}.md`);
|
||||
// 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`);
|
||||
|
||||
if (!(await fs.pathExists(outputPath))) {
|
||||
needsRebuild.push(agentName);
|
||||
needsRebuild.push(`${agentName} (standalone)`);
|
||||
} else if (await checkIfNeedsRebuild(agentYamlPath, outputPath, projectDir, agentName)) {
|
||||
needsRebuild.push(agentName);
|
||||
needsRebuild.push(`${agentName} (standalone)`);
|
||||
} else {
|
||||
upToDate.push(agentName);
|
||||
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})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -275,22 +405,53 @@ async function checkIfNeedsRebuild(yamlPath, outputPath, projectDir, agentName)
|
||||
/**
|
||||
* List available agents
|
||||
*/
|
||||
async function listAvailableAgents(bmadCommandsDir) {
|
||||
const modules = await fs.readdir(bmadCommandsDir);
|
||||
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 module of modules) {
|
||||
const agentsDir = path.join(bmadCommandsDir, module, 'agents');
|
||||
for (const agentDirName of agentDirs) {
|
||||
const agentDir = path.join(standaloneAgentsDir, agentDirName);
|
||||
|
||||
if (!(await fs.pathExists(agentsDir))) {
|
||||
continue;
|
||||
// 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}/)`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const files = await fs.readdir(agentsDir);
|
||||
// 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 file of files) {
|
||||
if (file.endsWith('.agent.yaml')) {
|
||||
const agentName = file.replace('.agent.yaml', '');
|
||||
console.log(chalk.dim(` - ${agentName} (${module})`));
|
||||
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})`));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -992,6 +992,93 @@ class Installer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build standalone agents in bmad/agents/ directory
|
||||
* @param {string} bmadDir - Path to bmad directory
|
||||
* @param {string} projectDir - Path to project directory
|
||||
*/
|
||||
async buildStandaloneAgents(bmadDir, projectDir) {
|
||||
const standaloneAgentsPath = path.join(bmadDir, 'agents');
|
||||
const cfgAgentsDir = path.join(bmadDir, '_cfg', 'agents');
|
||||
|
||||
// Check if standalone agents directory exists
|
||||
if (!(await fs.pathExists(standaloneAgentsPath))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all subdirectories in agents/
|
||||
const agentDirs = await fs.readdir(standaloneAgentsPath, { withFileTypes: true });
|
||||
|
||||
for (const agentDir of agentDirs) {
|
||||
if (!agentDir.isDirectory()) continue;
|
||||
|
||||
const agentDirPath = path.join(standaloneAgentsPath, agentDir.name);
|
||||
|
||||
// Find any .agent.yaml file in the directory
|
||||
const files = await fs.readdir(agentDirPath);
|
||||
const yamlFile = files.find((f) => f.endsWith('.agent.yaml'));
|
||||
|
||||
if (!yamlFile) continue;
|
||||
|
||||
const agentName = path.basename(yamlFile, '.agent.yaml');
|
||||
const sourceYamlPath = path.join(agentDirPath, yamlFile);
|
||||
const targetMdPath = path.join(agentDirPath, `${agentName}.md`);
|
||||
const customizePath = path.join(cfgAgentsDir, `${agentName}.customize.yaml`);
|
||||
|
||||
// Check for customizations
|
||||
const customizeExists = await fs.pathExists(customizePath);
|
||||
let customizedFields = [];
|
||||
|
||||
if (customizeExists) {
|
||||
const customizeContent = await fs.readFile(customizePath, 'utf8');
|
||||
const yaml = require('js-yaml');
|
||||
const customizeYaml = yaml.load(customizeContent);
|
||||
|
||||
// Detect what fields are customized (similar to rebuildAgentFiles)
|
||||
if (customizeYaml) {
|
||||
if (customizeYaml.persona) {
|
||||
for (const [key, value] of Object.entries(customizeYaml.persona)) {
|
||||
if (value !== '' && value !== null && !(Array.isArray(value) && value.length === 0)) {
|
||||
customizedFields.push(`persona.${key}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (customizeYaml.agent?.metadata) {
|
||||
for (const [key, value] of Object.entries(customizeYaml.agent.metadata)) {
|
||||
if (value !== '' && value !== null) {
|
||||
customizedFields.push(`metadata.${key}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (customizeYaml.critical_actions && customizeYaml.critical_actions.length > 0) {
|
||||
customizedFields.push('critical_actions');
|
||||
}
|
||||
if (customizeYaml.menu && customizeYaml.menu.length > 0) {
|
||||
customizedFields.push('menu');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build YAML to XML .md
|
||||
const xmlContent = await this.xmlHandler.buildFromYaml(sourceYamlPath, customizeExists ? customizePath : null, {
|
||||
includeMetadata: true,
|
||||
});
|
||||
|
||||
// Replace {project-root} placeholder
|
||||
const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
|
||||
|
||||
// Write the built .md file
|
||||
await fs.writeFile(targetMdPath, processedContent, 'utf8');
|
||||
|
||||
// Display result
|
||||
if (customizedFields.length > 0) {
|
||||
console.log(chalk.dim(` Built standalone agent: ${agentName}.md `) + chalk.yellow(`(customized: ${customizedFields.join(', ')})`));
|
||||
} else {
|
||||
console.log(chalk.dim(` Built standalone agent: ${agentName}.md`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild agent files from installer source (for compile command)
|
||||
* @param {string} modulePath - Path to module in bmad/ installation
|
||||
@@ -1116,19 +1203,36 @@ class Installer {
|
||||
if (entry.isDirectory() && entry.name !== '_cfg' && entry.name !== 'docs') {
|
||||
const modulePath = path.join(bmadDir, entry.name);
|
||||
|
||||
// Rebuild agents from installer source
|
||||
const agentsPath = path.join(modulePath, 'agents');
|
||||
if (await fs.pathExists(agentsPath)) {
|
||||
await this.rebuildAgentFiles(modulePath, entry.name);
|
||||
const agentFiles = await fs.readdir(agentsPath);
|
||||
agentCount += agentFiles.filter((f) => f.endsWith('.md')).length;
|
||||
}
|
||||
// Special handling for standalone agents in bmad/agents/ directory
|
||||
if (entry.name === 'agents') {
|
||||
spinner.text = 'Building standalone agents...';
|
||||
await this.buildStandaloneAgents(bmadDir, projectDir);
|
||||
|
||||
// Count tasks (already built)
|
||||
const tasksPath = path.join(modulePath, 'tasks');
|
||||
if (await fs.pathExists(tasksPath)) {
|
||||
const taskFiles = await fs.readdir(tasksPath);
|
||||
taskCount += taskFiles.filter((f) => f.endsWith('.md')).length;
|
||||
// Count standalone agents
|
||||
const standaloneAgentsPath = path.join(bmadDir, 'agents');
|
||||
const standaloneAgentDirs = await fs.readdir(standaloneAgentsPath, { withFileTypes: true });
|
||||
for (const agentDir of standaloneAgentDirs) {
|
||||
if (agentDir.isDirectory()) {
|
||||
const agentDirPath = path.join(standaloneAgentsPath, agentDir.name);
|
||||
const agentFiles = await fs.readdir(agentDirPath);
|
||||
agentCount += agentFiles.filter((f) => f.endsWith('.md') && !f.endsWith('.agent.yaml')).length;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Rebuild module agents from installer source
|
||||
const agentsPath = path.join(modulePath, 'agents');
|
||||
if (await fs.pathExists(agentsPath)) {
|
||||
await this.rebuildAgentFiles(modulePath, entry.name);
|
||||
const agentFiles = await fs.readdir(agentsPath);
|
||||
agentCount += agentFiles.filter((f) => f.endsWith('.md')).length;
|
||||
}
|
||||
|
||||
// Count tasks (already built)
|
||||
const tasksPath = path.join(modulePath, 'tasks');
|
||||
if (await fs.pathExists(tasksPath)) {
|
||||
const taskFiles = await fs.readdir(tasksPath);
|
||||
taskCount += taskFiles.filter((f) => f.endsWith('.md')).length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user