build tools temporarily removed, replacement incoming

This commit is contained in:
Brian Madison
2025-06-10 17:04:57 -05:00
parent cd5fc44de1
commit e3a8f0315c
7 changed files with 0 additions and 1997 deletions

View File

@@ -1,546 +0,0 @@
/**
* BMAD v4 Web Builder
* Builds optimized web bundles and standalone agents
*/
const fs = require('fs');
const path = require('path');
const yaml = require('js-yaml');
const DependencyResolver = require('../lib/dependency-resolver');
const BundleOptimizer = require('../lib/bundle-optimizer');
class WebBuilder {
constructor(rootPath = process.cwd()) {
this.rootPath = rootPath;
this.agentsPath = path.join(rootPath, 'bmad-core', 'agents');
this.teamsPath = path.join(rootPath, 'bmad-core', 'agent-teams');
this.outputPath = path.join(rootPath, 'dist');
this.sampleUpdatePath = path.join(rootPath, 'web-bundles');
this.resolver = new DependencyResolver(rootPath);
this.optimizer = new BundleOptimizer(rootPath);
this.sampleUpdateEnabled = false;
}
/**
* Enable sample update mode to output to web-bundles directory as well
*/
enableSampleUpdate() {
this.sampleUpdateEnabled = true;
}
/**
* Build all web bundles
*/
async buildAll() {
console.log('🚀 Building all bundles...');
const results = {
teams: [],
bundles: [],
agents: [],
errors: []
};
try {
// Ensure output directories exist
this.ensureOutputDirectory();
if (this.sampleUpdateEnabled) {
this.ensureSampleUpdateDirectory();
}
// Build orchestrator bundles
const bundleConfigs = this.loadBundleConfigs();
for (const config of bundleConfigs) {
try {
const result = await this.buildBundle(config);
const isTeamBundle = config.name.toLowerCase().includes('team');
if (isTeamBundle) {
results.teams.push(result);
} else {
results.bundles.push(result);
}
const bundleType = isTeamBundle ? 'team bundle' : 'bundle';
console.log(`✅ Built ${bundleType}: ${config.name}`);
} catch (error) {
const isTeamBundle = config.name.toLowerCase().includes('team');
const bundleType = isTeamBundle ? 'team bundle' : 'bundle';
const errorType = isTeamBundle ? 'team' : 'bundle';
console.error(`❌ Failed to build ${bundleType} ${config.name}:`, error.message);
results.errors.push({ type: errorType, name: config.name, error: error.message });
}
}
// Build all individual agents as standalone bundles
const availableAgents = this.resolver.getAvailableAgents();
// Filter out team bundles and include all non-team agents
const individualAgents = availableAgents.filter(agentId => {
// Skip team bundles
if (agentId.startsWith('team-')) {
return false;
}
try {
const config = this.resolver.loadAgentConfig(agentId);
// Build all agents that don't explicitly disable web
return config.environments?.web?.available !== false;
} catch {
return false;
}
});
for (const agentId of individualAgents) {
try {
const result = await this.buildStandaloneAgent(agentId);
results.agents.push(result);
console.log(`✅ Built agent bundle: ${agentId}`);
} catch (error) {
console.error(`❌ Failed to build agent ${agentId}:`, error.message);
results.errors.push({ type: 'agent', name: agentId, error: error.message });
}
}
console.log(`\n📊 Build Summary:`);
console.log(` Teams: ${results.teams.length} built, ${results.errors.filter(e => e.type === 'team').length} failed`);
if (results.bundles.length > 0 || results.errors.filter(e => e.type === 'bundle').length > 0) {
console.log(` Bundles: ${results.bundles.length} built, ${results.errors.filter(e => e.type === 'bundle').length} failed`);
}
console.log(` Agents: ${results.agents.length} built, ${results.errors.filter(e => e.type === 'agent').length} failed`);
return results;
} catch (error) {
console.error('❌ Build failed:', error);
throw error;
}
}
/**
* Expand agent wildcards in the list
* If the list contains '*', it will be replaced with all available agents
*/
expandAgentWildcards(agentIds) {
// Check if wildcard is present
const wildcardIndex = agentIds.indexOf('*');
if (wildcardIndex === -1) {
return agentIds;
}
// Get all available agents
const allAgents = this.resolver.getAvailableAgents()
.filter(agentId => {
// Exclude team bundles
if (agentId.startsWith('team-')) {
return false;
}
try {
const config = this.resolver.loadAgentConfig(agentId);
// Include all agents that don't explicitly disable web
return config.environments?.web?.available !== false;
} catch {
return false;
}
});
// Create expanded list
const expandedList = [...agentIds];
// Replace wildcard with all agents not already in the list
const existingAgents = new Set(agentIds.filter(id => id !== '*'));
const agentsToAdd = allAgents.filter(agent => !existingAgents.has(agent));
// Replace the wildcard with the missing agents
expandedList.splice(wildcardIndex, 1, ...agentsToAdd);
console.log(` Expanded wildcard to include: ${agentsToAdd.join(', ')}`);
return expandedList;
}
/**
* Build a specific bundle
*/
async buildBundle(bundleConfig) {
const isTeamBundle = bundleConfig.name.toLowerCase().includes('team');
const emoji = isTeamBundle ? '👥' : '📦';
const bundleType = isTeamBundle ? 'team bundle' : 'bundle';
console.log(`${emoji} Building ${bundleType}: ${bundleConfig.name}`);
// Ensure agents is an array of strings
let agentIds = Array.isArray(bundleConfig.agents) ? bundleConfig.agents : [];
// Expand wildcards
agentIds = this.expandAgentWildcards(agentIds);
// Resolve dependencies
const agentDependencies = this.resolver.resolveBundleDependencies(
agentIds,
'web',
bundleConfig.optimize !== false
);
// Optimize bundle
const optimizedBundle = this.optimizer.optimizeBundle(bundleConfig, agentDependencies);
// Validate bundle
const validation = this.optimizer.validateBundle(optimizedBundle, {
maxBundleSize: bundleConfig.max_bundle_size
});
if (!validation.valid) {
throw new Error(`Bundle validation failed: ${validation.issues.map(i => i.message).join(', ')}`);
}
// Write output files
const outputDir = path.join(this.outputPath, 'teams');
this.ensureDirectory(outputDir);
const outputs = [];
// Default to single_file format if not specified
const outputFormat = bundleConfig.output?.format || 'single_file';
const outputFilename = bundleConfig.output?.filename || bundleConfig.filename || `${bundleConfig.name.toLowerCase().replace(/\s+/g, '-')}.txt`;
if (outputFormat === 'single_file') {
// Create single bundle file
const bundleContent = this.createBundleContent(optimizedBundle, bundleConfig);
const bundleFile = path.join(outputDir, outputFilename);
fs.writeFileSync(bundleFile, bundleContent);
outputs.push(bundleFile);
// Also write to web-bundles if sample update is enabled
if (this.sampleUpdateEnabled) {
const sampleOutputDir = path.join(this.sampleUpdatePath, 'teams');
this.ensureDirectory(sampleOutputDir);
const sampleBundleFile = path.join(sampleOutputDir, outputFilename);
fs.writeFileSync(sampleBundleFile, bundleContent);
}
// For single_file format, everything is in the bundle file
// No need for separate orchestrator files
}
return {
name: bundleConfig.name,
type: 'bundle',
outputs: outputs,
statistics: optimizedBundle.statistics,
validation: validation
};
}
/**
* Build a standalone agent
*/
async buildStandaloneAgent(agentId) {
console.log(`👤 Building standalone agent: ${agentId}`);
const optimizedBundle = this.optimizer.createStandaloneAgent(agentId, 'web');
// Get agent config to extract name
const agentConfig = this.resolver.loadAgentConfig(agentId);
const agentName = agentConfig.name || agentId;
// Create lowercase-dashcase filename with format: {id}-{name}.txt
const filename = `${agentId}-${agentName.toLowerCase().replace(/\s+/g, '-')}.txt`;
// Write standalone agent file
const outputDir = path.join(this.outputPath, 'agents');
this.ensureDirectory(outputDir);
const agentFile = path.join(outputDir, filename);
fs.writeFileSync(agentFile, optimizedBundle.standaloneContent);
// Also write to web-bundles if sample update is enabled
if (this.sampleUpdateEnabled) {
const sampleOutputDir = path.join(this.sampleUpdatePath, 'agents');
this.ensureDirectory(sampleOutputDir);
const sampleAgentFile = path.join(sampleOutputDir, filename);
fs.writeFileSync(sampleAgentFile, optimizedBundle.standaloneContent);
}
return {
name: agentId,
type: 'standalone_agent',
outputs: [agentFile],
statistics: optimizedBundle.statistics
};
}
/**
* Create bundle content for single file output
*/
createBundleContent(bundle, config) {
// For a truly self-contained bundle, start with the orchestrator prompt
let content = this.createOrchestratorPrompt(bundle, config);
content += '\n\n';
// Add agent configurations section
content += `==================== START: agent-config ====================\n`;
const configData = {
name: bundle.metadata.name,
version: bundle.metadata.version || '1.0.0',
agents: bundle.agents,
commands: config.output?.orchestrator_commands || []
};
// Include workflows if defined
if (config.workflows) {
configData.workflows = config.workflows;
}
content += yaml.dump(configData);
content += `==================== END: agent-config ====================\n\n`;
// Add resource sections
bundle.sections.forEach(section => {
content += section.content + '\n\n';
});
return content;
}
/**
* Create orchestrator files (agent-prompt.txt and agent-config.txt)
*/
createOrchestratorFiles(bundle, config) {
const files = [];
const outputDir = path.join(this.outputPath, 'teams');
// Create agent-config.txt
const agentConfigContent = yaml.dump({
name: bundle.metadata.name,
version: bundle.metadata.version || '1.0.0',
environment: 'web',
agents: bundle.agents,
commands: config.output?.orchestrator_commands || [],
metadata: {
generatedAt: bundle.metadata.generatedAt,
totalResources: bundle.statistics.totalResources,
optimization: bundle.metadata.optimization
}
});
files.push({
path: path.join(outputDir, 'agent-config.txt'),
content: agentConfigContent
});
// Create agent-prompt.txt (orchestrator instructions)
const promptContent = this.createOrchestratorPrompt(bundle, config);
files.push({
path: path.join(outputDir, 'agent-prompt.txt'),
content: promptContent
});
// Create individual section files
bundle.sections.forEach(section => {
files.push({
path: path.join(outputDir, section.filename),
content: section.content
});
});
return files;
}
/**
* Create orchestrator prompt content
*/
createOrchestratorPrompt(bundle, config) {
// Try to use the bmad persona as the orchestrator base
const bmadPersonaPath = path.join(this.rootPath, 'bmad-core', 'personas', 'bmad.md');
if (fs.existsSync(bmadPersonaPath)) {
const bmadContent = fs.readFileSync(bmadPersonaPath, 'utf8');
// Append bundle-specific agent information
let prompt = bmadContent + '\n\n';
prompt += `## Available Agents in ${bundle.metadata.name}\n\n`;
Object.entries(bundle.agents).forEach(([id, agent]) => {
const command = config.output?.orchestrator_commands?.find(cmd => cmd.includes(id)) || `/${id}`;
prompt += `### ${agent.name} (${command})\n`;
prompt += `- **Role:** ${agent.title}\n`;
prompt += `- **Description:** ${agent.description}\n`;
if (agent.customize) {
prompt += `- **Customization:** ${agent.customize}\n`;
}
prompt += '\n';
});
return prompt;
}
// Fallback to basic prompt if bmad persona not found
let prompt = `# BMAD ${bundle.metadata.name} Orchestrator\n\n`;
prompt += `You are the BMAD orchestrator for the ${bundle.metadata.name}. `;
prompt += `You can transform into any of the following specialized agents:\n\n`;
// List available agents
Object.entries(bundle.agents).forEach(([id, agent]) => {
prompt += `## ${agent.name} (${config.output?.orchestrator_commands?.find(cmd => cmd.includes(id)) || `/${id}`})\n`;
prompt += `**Role:** ${agent.title}\n`;
prompt += `${agent.description}\n`;
if (agent.customize) {
prompt += `**Customization:** ${agent.customize}\n`;
}
prompt += '\n';
if (agent.capabilities && agent.capabilities.length > 0) {
prompt += `**Capabilities:**\n`;
agent.capabilities.forEach(cap => prompt += `- ${cap}\n`);
prompt += '\n';
}
});
prompt += `## Usage\n\n`;
prompt += `To transform into a specific agent, use the corresponding command:\n`;
(config.output?.orchestrator_commands || []).forEach(cmd => {
prompt += `- \`${cmd}\` - Transform into the corresponding agent\n`;
});
prompt += `\n## Resources Available\n\n`;
prompt += `This bundle includes ${bundle.statistics.totalResources} resources:\n`;
Object.entries(bundle.statistics.resourcesByType).forEach(([type, count]) => {
prompt += `- ${count} ${type}\n`;
});
return prompt;
}
/**
* Load all bundle configurations
*/
loadBundleConfigs() {
const configs = [];
// Load team configurations from agent-teams directory
const teamFiles = this.findAgentFiles(this.teamsPath);
teamFiles.forEach(file => {
try {
const content = fs.readFileSync(file, 'utf8');
const config = yaml.load(content);
// Check if this has bundle config
if (config.bundle) {
// Merge agents list from root level into bundle config
const bundleConfig = { ...config.bundle };
if (config.agents && !bundleConfig.agents) {
bundleConfig.agents = config.agents;
}
// Include workflows if defined
if (config.workflows) {
bundleConfig.workflows = config.workflows;
}
configs.push(bundleConfig);
}
} catch (error) {
console.warn(`Warning: Failed to load config ${file}:`, error.message);
}
});
// For backward compatibility, also check agents directory for team-*.yml files
const agentFiles = this.findAgentFiles(this.agentsPath);
agentFiles.forEach(file => {
try {
const content = fs.readFileSync(file, 'utf8');
const config = yaml.load(content);
const filename = path.basename(file);
// Check if this is a team bundle (team-*.yml) with bundle config
if (filename.startsWith('team-') && config.bundle) {
// Merge agents list from root level into bundle config
const bundleConfig = { ...config.bundle };
if (config.agents && !bundleConfig.agents) {
bundleConfig.agents = config.agents;
}
configs.push(bundleConfig);
}
} catch (error) {
console.warn(`Warning: Failed to load config ${file}:`, error.message);
}
});
return configs;
}
/**
* Find all agent configuration files
*/
findAgentFiles(dir) {
const files = [];
if (!fs.existsSync(dir)) {
return files;
}
const items = fs.readdirSync(dir);
items.forEach(item => {
const itemPath = path.join(dir, item);
const stat = fs.statSync(itemPath);
if (stat.isDirectory()) {
files.push(...this.findAgentFiles(itemPath));
} else if (item.endsWith('.yml') || item.endsWith('.yaml')) {
files.push(itemPath);
}
});
return files;
}
/**
* Ensure directory exists
*/
ensureDirectory(dir) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
}
/**
* Ensure output directory exists
*/
ensureOutputDirectory() {
this.ensureDirectory(this.outputPath);
this.ensureDirectory(path.join(this.outputPath, 'teams'));
this.ensureDirectory(path.join(this.outputPath, 'agents'));
}
/**
* Ensure sample update directory exists
*/
ensureSampleUpdateDirectory() {
// Clean existing files in web-bundles subdirectories
const teamsDir = path.join(this.sampleUpdatePath, 'teams');
const agentsDir = path.join(this.sampleUpdatePath, 'agents');
// Remove existing .txt files in teams directory
if (fs.existsSync(teamsDir)) {
const teamFiles = fs.readdirSync(teamsDir).filter(f => f.endsWith('.txt'));
teamFiles.forEach(file => {
fs.unlinkSync(path.join(teamsDir, file));
});
console.log(`🧹 Cleaned ${teamFiles.length} files from web-bundles/teams`);
}
// Remove existing .txt files in agents directory
if (fs.existsSync(agentsDir)) {
const agentFiles = fs.readdirSync(agentsDir).filter(f => f.endsWith('.txt'));
agentFiles.forEach(file => {
fs.unlinkSync(path.join(agentsDir, file));
});
console.log(`🧹 Cleaned ${agentFiles.length} files from web-bundles/agents`);
}
// Ensure directories exist
this.ensureDirectory(this.sampleUpdatePath);
this.ensureDirectory(teamsDir);
this.ensureDirectory(agentsDir);
}
}
module.exports = WebBuilder;

View File

@@ -1,215 +0,0 @@
#!/usr/bin/env node
/**
* BMAD v4 Build CLI
* Command line interface for building agents and bundles
*/
const { program } = require('commander');
const WebBuilder = require('./builders/web-builder');
const DependencyResolver = require('./lib/dependency-resolver');
// Initialize resolver
const resolver = new DependencyResolver();
program
.name('bmad-build')
.description('BMAD v4 Build System')
.version('4.0.0');
// Build all web bundles and agents
program
.command('build')
.alias('build:web')
.description('Build all web bundles and standalone agents')
.option('--sample-update', 'Also output to web-bundles directory')
.action(async (options) => {
try {
const builder = new WebBuilder();
if (options.sampleUpdate) {
builder.enableSampleUpdate();
}
const results = await builder.buildAll();
if (results.errors.length > 0) {
console.log('\n⚠ Build completed with errors:');
results.errors.forEach(error => {
console.log(` ${error.type}: ${error.name} - ${error.error}`);
});
process.exit(1);
} else {
console.log('\n🎉 All builds completed successfully!');
if (options.sampleUpdate) {
console.log(' 📁 Also updated web-bundles directory');
}
}
} catch (error) {
console.error('❌ Build failed:', error.message);
process.exit(1);
}
});
// Build specific bundle
program
.command('build:bundle')
.description('Build a specific bundle')
.requiredOption('-n, --name <name>', 'Bundle name')
.action(async (options) => {
try {
const builder = new WebBuilder();
const configs = builder.loadBundleConfigs();
const config = configs.find(c => c.name.toLowerCase().includes(options.name.toLowerCase()));
if (!config) {
console.error(`❌ Bundle not found: ${options.name}`);
process.exit(1);
}
const result = await builder.buildBundle(config);
console.log(`✅ Built bundle: ${result.name}`);
console.log(` Files: ${result.outputs.length}`);
console.log(` Size: ${result.statistics.totalSize} characters`);
} catch (error) {
console.error('❌ Build failed:', error.message);
process.exit(1);
}
});
// Build standalone agent
program
.command('build:agent')
.description('Build a standalone agent')
.requiredOption('-a, --agent <id>', 'Agent ID')
.action(async (options) => {
try {
const builder = new WebBuilder();
const result = await builder.buildStandaloneAgent(options.agent);
console.log(`✅ Built agent: ${result.name}`);
console.log(` File: ${result.outputs[0]}`);
console.log(` Size: ${result.statistics.totalSize} characters`);
} catch (error) {
console.error('❌ Build failed:', error.message);
process.exit(1);
}
});
// List available agents
program
.command('list:agents')
.description('List all available agents')
.action(() => {
try {
const agents = resolver.getAvailableAgents();
console.log('📋 Available agents:');
agents.forEach(agentId => {
try {
const config = resolver.loadAgentConfig(agentId);
const webCompatible = config.environments?.web?.available !== false;
const ideOnly = config.environments?.ide?.ide_only === true;
console.log(` ${agentId}: ${config.name}`);
console.log(` ${config.description}`);
console.log(` Environments: ${webCompatible ? 'web' : ''}${webCompatible && !ideOnly ? ', ' : ''}${!ideOnly ? 'ide' : 'ide-only'}`);
console.log('');
} catch (error) {
console.log(` ${agentId}: Error loading config`);
}
});
} catch (error) {
console.error('❌ Failed to list agents:', error.message);
process.exit(1);
}
});
// Analyze dependencies
program
.command('analyze:deps')
.description('Analyze agent dependencies')
.option('-a, --agent <id>', 'Specific agent ID')
.option('-g, --graph', 'Generate dependency graph')
.action((options) => {
try {
if (options.agent) {
const deps = resolver.resolveAgentDependencies(options.agent, 'web');
console.log(`📊 Dependencies for ${deps.config.name}:`);
Object.entries(deps.resources).forEach(([type, resources]) => {
if (resources.length > 0) {
console.log(` ${type}: ${resources.join(', ')}`);
}
});
} else if (options.graph) {
const graph = resolver.generateDependencyGraph();
console.log('📈 Dependency Graph:');
console.log(` Nodes: ${graph.nodes.length}`);
console.log(` Edges: ${graph.edges.length}`);
console.log('\n Agents:');
graph.nodes.filter(n => n.type === 'agent').forEach(n => {
console.log(` ${n.id}: ${n.label}`);
});
} else {
const agents = resolver.getAvailableAgents();
console.log('📊 All Agent Dependencies:');
agents.forEach(agentId => {
try {
const deps = resolver.resolveAgentDependencies(agentId, 'web');
const totalDeps = Object.values(deps.resources).reduce((sum, arr) => sum + arr.length, 0);
console.log(` ${agentId}: ${totalDeps} total dependencies`);
} catch (error) {
console.log(` ${agentId}: Error resolving dependencies`);
}
});
}
} catch (error) {
console.error('❌ Analysis failed:', error.message);
process.exit(1);
}
});
// Validate configuration
program
.command('validate')
.description('Validate all configurations')
.action(() => {
try {
const agents = resolver.getAvailableAgents();
let errors = 0;
console.log('🔍 Validating configurations...');
agents.forEach(agentId => {
try {
const deps = resolver.resolveAgentDependencies(agentId, 'web');
console.log(`${agentId}: Valid`);
} catch (error) {
console.log(`${agentId}: ${error.message}`);
errors++;
}
});
if (errors > 0) {
console.log(`\n❌ Validation failed: ${errors} errors found`);
process.exit(1);
} else {
console.log('\n✅ All configurations valid!');
}
} catch (error) {
console.error('❌ Validation failed:', error.message);
process.exit(1);
}
});
// Handle unknown commands
program.on('command:*', () => {
console.error('Invalid command: %s\nSee --help for a list of available commands.', program.args.join(' '));
process.exit(1);
});
// Parse command line arguments
program.parse();
// Show help if no command provided
if (!process.argv.slice(2).length) {
program.outputHelp();
}

View File

@@ -1,206 +0,0 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const yaml = require('js-yaml');
// Colors for console output
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
green: '\x1b[32m',
red: '\x1b[31m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
cyan: '\x1b[36m'
};
function log(message, color = 'reset') {
console.log(`${colors[color]}${message}${colors.reset}`);
}
function error(message) {
console.error(`${colors.red}❌ Error: ${message}${colors.reset}`);
}
function success(message) {
console.log(`${colors.green}${message}${colors.reset}`);
}
function info(message) {
console.log(`${colors.blue} ${message}${colors.reset}`);
}
async function installExpansionPack(packName) {
const expansionPackPath = path.join(__dirname, '..', 'expansion-packs', packName);
const manifestPath = path.join(expansionPackPath, 'manifest.yml');
// Check if expansion pack exists
if (!fs.existsSync(expansionPackPath)) {
error(`Expansion pack '${packName}' not found`);
log('\nAvailable expansion packs:', 'cyan');
const packsDir = path.join(__dirname, '..', 'expansion-packs');
const packs = fs.readdirSync(packsDir)
.filter(f => fs.statSync(path.join(packsDir, f)).isDirectory())
.filter(f => f !== 'README.md');
packs.forEach(pack => log(` - ${pack}`, 'cyan'));
process.exit(1);
}
// Load manifest
if (!fs.existsSync(manifestPath)) {
error(`Manifest file not found for expansion pack '${packName}'`);
process.exit(1);
}
let manifest;
try {
const manifestContent = fs.readFileSync(manifestPath, 'utf8');
manifest = yaml.load(manifestContent);
} catch (e) {
error(`Failed to parse manifest: ${e.message}`);
process.exit(1);
}
log(`\n${colors.bright}Installing ${manifest.name} v${manifest.version}${colors.reset}`, 'bright');
log(`${manifest.description}\n`, 'cyan');
// Create directories if needed
const projectRoot = path.join(__dirname, '..');
const bmadCore = path.join(projectRoot, 'bmad-core');
// Install files
let installedCount = 0;
let skippedCount = 0;
for (const fileMapping of manifest.files) {
const sourcePath = path.join(expansionPackPath, fileMapping.source);
const destPath = path.join(projectRoot, fileMapping.destination);
const destDir = path.dirname(destPath);
// Create destination directory if it doesn't exist
if (!fs.existsSync(destDir)) {
fs.mkdirSync(destDir, { recursive: true });
info(`Created directory: ${path.relative(projectRoot, destDir)}`);
}
// Check if file already exists
if (fs.existsSync(destPath)) {
const response = await promptUser(`File ${path.relative(projectRoot, destPath)} already exists. Overwrite? (y/N): `);
if (response.toLowerCase() !== 'y') {
info(`Skipped: ${fileMapping.source}`);
skippedCount++;
continue;
}
}
// Copy file
try {
fs.copyFileSync(sourcePath, destPath);
success(`Installed: ${fileMapping.source}${path.relative(projectRoot, destPath)}`);
installedCount++;
} catch (e) {
error(`Failed to install ${fileMapping.source}: ${e.message}`);
}
}
// Update team configurations
if (manifest.team_updates && manifest.team_updates.length > 0) {
log('\nUpdating team configurations...', 'yellow');
for (const update of manifest.team_updates) {
// Try new location first (agent-teams), then fallback to old location (agents)
let teamPath = path.join(projectRoot, 'agent-teams', update.team);
if (!fs.existsSync(teamPath)) {
teamPath = path.join(projectRoot, 'agents', update.team);
}
if (fs.existsSync(teamPath)) {
try {
let teamConfig = yaml.load(fs.readFileSync(teamPath, 'utf8'));
if (!teamConfig.agents) {
teamConfig.agents = [];
}
if (!teamConfig.agents.includes(update.add_agent)) {
teamConfig.agents.push(update.add_agent);
fs.writeFileSync(teamPath, yaml.dump(teamConfig));
success(`Updated ${update.team} with ${update.add_agent} agent`);
} else {
info(`${update.team} already includes ${update.add_agent} agent`);
}
} catch (e) {
error(`Failed to update ${update.team}: ${e.message}`);
}
}
}
}
// Show summary
log(`\n${colors.bright}Installation Summary${colors.reset}`, 'bright');
log(`Files installed: ${installedCount}`, 'green');
if (skippedCount > 0) {
log(`Files skipped: ${skippedCount}`, 'yellow');
}
// Show post-install message
if (manifest.post_install_message) {
log(`\n${colors.bright}Next Steps:${colors.reset}`, 'bright');
log(manifest.post_install_message, 'cyan');
}
// Remind to rebuild
log('\nRemember to rebuild bundles:', 'yellow');
log(' npm run build', 'yellow');
}
function promptUser(question) {
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise(resolve => {
rl.question(question, answer => {
rl.close();
resolve(answer);
});
});
}
// Main execution
const packName = process.argv[2];
if (!packName) {
log(`${colors.bright}BMAD Method Expansion Pack Installer${colors.reset}\n`, 'bright');
log('Usage: node install-expansion-pack.js <pack-name>', 'yellow');
log('\nExample:', 'cyan');
log(' node install-expansion-pack.js infrastructure', 'cyan');
log('\nAvailable expansion packs:', 'cyan');
const packsDir = path.join(__dirname, '..', 'expansion-packs');
if (fs.existsSync(packsDir)) {
const packs = fs.readdirSync(packsDir)
.filter(f => fs.statSync(path.join(packsDir, f)).isDirectory())
.filter(f => f !== 'README.md');
packs.forEach(pack => {
const manifestPath = path.join(packsDir, pack, 'manifest.yml');
if (fs.existsSync(manifestPath)) {
try {
const manifest = yaml.load(fs.readFileSync(manifestPath, 'utf8'));
log(` - ${pack}: ${manifest.description}`, 'cyan');
} catch (e) {
log(` - ${pack}`, 'cyan');
}
}
});
}
process.exit(0);
}
installExpansionPack(packName).catch(err => {
error(`Installation failed: ${err.message}`);
process.exit(1);
});

View File

@@ -1,306 +0,0 @@
/**
* BMAD v4 Bundle Optimizer
* Optimizes bundles by deduplicating resources and minimizing size
*/
const fs = require('fs');
const path = require('path');
class BundleOptimizer {
constructor(rootPath = process.cwd()) {
this.rootPath = rootPath;
this.corePath = path.join(rootPath, 'bmad-core');
}
/**
* Optimize a bundle by loading and processing resources
*/
optimizeBundle(bundleConfig, agentDependencies) {
const optimizedBundle = {
metadata: {
name: bundleConfig.name,
version: bundleConfig.version,
environment: 'web',
generatedAt: new Date().toISOString(),
optimization: bundleConfig.optimize || false
},
agents: {},
resources: {
personas: {},
tasks: {},
templates: {},
checklists: {},
data: {},
utils: {}
},
sections: [],
statistics: {}
};
// Process each agent
agentDependencies.agents.forEach(agentDep => {
optimizedBundle.agents[agentDep.agent] = {
name: agentDep.config.name,
id: agentDep.config.id,
title: agentDep.config.title,
description: agentDep.config.description,
persona: agentDep.config.persona,
customize: agentDep.config.customize || '',
capabilities: agentDep.config.capabilities || [],
workflow: agentDep.config.workflow || []
};
});
// Load and process resources
this.loadResources(optimizedBundle, agentDependencies.bundleResources, agentDependencies.agents);
// Create optimized sections for web output
this.createWebSections(optimizedBundle, bundleConfig);
// Calculate statistics
optimizedBundle.statistics = this.calculateBundleStats(optimizedBundle, agentDependencies);
return optimizedBundle;
}
/**
* Load resources from core directory
*/
loadResources(bundle, resourceLists, agentDeps = []) {
const resourceTypes = ['tasks', 'templates', 'checklists', 'data', 'utils'];
resourceTypes.forEach(type => {
const resourceDir = path.join(this.corePath, type);
(resourceLists[type] || []).forEach(resourceName => {
const content = this.loadResourceFile(resourceDir, resourceName);
if (content) {
bundle.resources[type][resourceName] = {
name: resourceName,
content: content,
size: content.length
};
}
});
});
// Load personas for agents
const personaDir = path.join(this.corePath, 'personas');
agentDeps.forEach(agentDep => {
const agentId = agentDep.agent;
const personaName = agentDep.config.persona || agentId;
const personaContent = this.loadResourceFile(personaDir, personaName);
if (personaContent) {
bundle.resources.personas[agentId] = {
name: personaName,
content: personaContent,
size: personaContent.length
};
}
});
}
/**
* Load a resource file from disk
*/
loadResourceFile(dir, name) {
const extensions = ['.md', '.yml', '.yaml'];
for (const ext of extensions) {
const filePath = path.join(dir, `${name}${ext}`);
if (fs.existsSync(filePath)) {
return fs.readFileSync(filePath, 'utf8');
}
}
console.warn(`Resource file not found: ${name} in ${dir}`);
return null;
}
/**
* Create web-formatted sections with markers
*/
createWebSections(bundle, bundleConfig) {
const sections = [];
// Create personas section
// For team bundles, exclude BMAD from personas since it's already the orchestrator
if (Object.keys(bundle.resources.personas).length > 0) {
const personasContent = Object.entries(bundle.resources.personas)
.filter(([id, persona]) => id !== 'bmad') // Exclude BMAD from personas section
.map(([id, persona]) =>
`==================== START: personas#${id} ====================\n` +
persona.content +
`\n==================== END: personas#${id} ====================`
).join('\n\n');
if (personasContent) { // Only add section if there's content after filtering
sections.push({
name: 'personas',
filename: 'personas.txt',
content: personasContent,
size: personasContent.length
});
}
}
// Create other resource sections
['tasks', 'templates', 'checklists', 'data', 'utils'].forEach(type => {
const resources = bundle.resources[type];
if (Object.keys(resources).length > 0) {
const sectionContent = Object.entries(resources)
.map(([name, resource]) =>
`==================== START: ${type}#${name} ====================\n` +
resource.content +
`\n==================== END: ${type}#${name} ====================`
).join('\n\n');
sections.push({
name: type,
filename: `${type}.txt`,
content: sectionContent,
size: sectionContent.length
});
}
});
bundle.sections = sections;
}
/**
* Create standalone agent bundle
*/
createStandaloneAgent(agentId, environment = 'web') {
const DependencyResolver = require('./dependency-resolver');
const resolver = new DependencyResolver(this.rootPath);
const agentDep = resolver.resolveAgentDependencies(agentId, environment);
const bundleConfig = {
name: `${agentDep.config.name} Standalone`,
version: agentDep.config.version || '1.0.0',
// Environment is always 'web' for standalone agents
optimize: true
};
// Create bundle with just this agent
const agentDependencies = {
agents: [agentDep],
bundleResources: agentDep.resources
};
const optimizedBundle = this.optimizeBundle(bundleConfig, agentDependencies);
// For standalone agents, create a single combined content
if (environment === 'web') {
optimizedBundle.standaloneContent = this.createStandaloneContent(optimizedBundle, agentId);
}
return optimizedBundle;
}
/**
* Create single-file content for standalone agent
*/
createStandaloneContent(bundle, agentId) {
const agent = bundle.agents[agentId];
const persona = bundle.resources.personas[agentId];
let content = `# ${agent.name}\n\n`;
content += `${agent.description}\n\n`;
if (persona) {
content += `==================== START: personas#${agentId} ====================\n`;
content += `${persona.content}\n`;
content += `==================== END: personas#${agentId} ====================\n\n`;
}
// Add required resources inline
const resourceTypes = ['tasks', 'templates', 'checklists', 'data', 'utils'];
resourceTypes.forEach(type => {
const resources = bundle.resources[type];
if (Object.keys(resources).length > 0) {
Object.entries(resources).forEach(([name, resource]) => {
content += `==================== START: ${type}#${name} ====================\n`;
content += `${resource.content}\n`;
content += `==================== END: ${type}#${name} ====================\n\n`;
});
}
});
return content;
}
/**
* Calculate bundle statistics
*/
calculateBundleStats(bundle, agentDependencies) {
const stats = {
agents: Object.keys(bundle.agents).length,
totalResources: 0,
resourcesByType: {},
totalSize: 0,
sizeByType: {},
averageResourceSize: 0
};
// Count resources and calculate sizes
Object.entries(bundle.resources).forEach(([type, resources]) => {
const count = Object.keys(resources).length;
stats.resourcesByType[type] = count;
stats.totalResources += count;
const typeSize = Object.values(resources).reduce((sum, r) => sum + (r.size || 0), 0);
stats.sizeByType[type] = typeSize;
stats.totalSize += typeSize;
});
if (stats.totalResources > 0) {
stats.averageResourceSize = Math.round(stats.totalSize / stats.totalResources);
}
// Add web-specific stats
if (bundle.sections) {
stats.webSections = bundle.sections.length;
stats.webTotalSize = bundle.sections.reduce((sum, s) => sum + s.size, 0);
}
return stats;
}
/**
* Validate bundle against size constraints
*/
validateBundle(bundle, constraints = {}) {
const issues = [];
const stats = bundle.statistics;
// Check max bundle size
if (constraints.maxBundleSize && stats.totalSize > constraints.maxBundleSize) {
issues.push({
type: 'size_exceeded',
message: `Bundle size ${stats.totalSize} exceeds limit ${constraints.maxBundleSize}`,
severity: 'error'
});
}
// Check web section sizes
if (bundle.sections) {
bundle.sections.forEach(section => {
if (constraints.maxSectionSize && section.size > constraints.maxSectionSize) {
issues.push({
type: 'section_size_exceeded',
message: `Section ${section.name} size ${section.size} exceeds limit ${constraints.maxSectionSize}`,
severity: 'warning'
});
}
});
}
return {
valid: issues.filter(i => i.severity === 'error').length === 0,
issues: issues
};
}
}
module.exports = BundleOptimizer;

View File

@@ -1,262 +0,0 @@
/**
* BMAD v4 Dependency Resolver
* Analyzes agent configurations and resolves resource dependencies
*/
const fs = require('fs');
const path = require('path');
const yaml = require('js-yaml');
class DependencyResolver {
constructor(rootPath = process.cwd()) {
this.rootPath = rootPath;
this.agentsPath = path.join(rootPath, 'bmad-core', 'agents');
this.corePath = path.join(rootPath, 'bmad-core');
this.cache = new Map();
}
/**
* Load and parse an agent configuration
*/
loadAgentConfig(agentId) {
if (this.cache.has(agentId)) {
return this.cache.get(agentId);
}
const configPath = path.join(this.agentsPath, `${agentId}.yml`);
if (!fs.existsSync(configPath)) {
throw new Error(`Agent configuration not found: ${configPath}`);
}
const configContent = fs.readFileSync(configPath, 'utf8');
const rawConfig = yaml.load(configContent);
// Extract agent config from nested structure if present
const config = rawConfig.agent || rawConfig;
// Merge other root-level fields that might be needed
if (rawConfig.dependencies) {
config.dependencies = rawConfig.dependencies;
}
// Validate required fields
this.validateAgentConfig(config, agentId);
this.cache.set(agentId, config);
return config;
}
/**
* Validate agent configuration structure
*/
validateAgentConfig(config, agentId) {
const required = ['name', 'id'];
for (const field of required) {
if (!config[field]) {
throw new Error(`Missing required field '${field}' in agent ${agentId}`);
}
}
if (config.id !== agentId) {
throw new Error(`Agent ID mismatch: expected '${agentId}', got '${config.id}'`);
}
// Ensure persona exists
if (!config.persona) {
// Default to agent id if no persona specified
config.persona = config.id;
}
// Ensure dependencies exist with defaults
if (!config.dependencies) {
config.dependencies = {
tasks: [],
templates: [],
checklists: [],
data: []
};
}
}
/**
* Resolve dependencies for a single agent
*/
resolveAgentDependencies(agentId, environment = 'web') {
const config = this.loadAgentConfig(agentId);
const dependencies = {
agent: agentId,
config: config,
resources: {
tasks: config.dependencies?.tasks || [],
templates: config.dependencies?.templates || [],
checklists: config.dependencies?.checklists || [],
data: config.dependencies?.data || [],
utils: config.dependencies?.utils || []
}
};
// Validate that all required resources exist
this.validateResourceExistence(dependencies.resources);
return dependencies;
}
/**
* Resolve dependencies for multiple agents (bundle)
*/
resolveBundleDependencies(agentIds, environment = 'web', optimize = true) {
const agentDependencies = [];
const allResources = {
tasks: new Set(),
templates: new Set(),
checklists: new Set(),
data: new Set(),
utils: new Set()
};
// Collect dependencies for each agent
for (const agentId of agentIds) {
const deps = this.resolveAgentDependencies(agentId, environment);
agentDependencies.push(deps);
// Aggregate all resources
Object.keys(allResources).forEach(type => {
deps.resources[type].forEach(resource => {
allResources[type].add(resource);
});
});
}
const result = {
agents: agentDependencies,
bundleResources: {
tasks: Array.from(allResources.tasks),
templates: Array.from(allResources.templates),
checklists: Array.from(allResources.checklists),
data: Array.from(allResources.data),
utils: Array.from(allResources.utils)
},
optimized: optimize
};
if (optimize) {
result.statistics = this.calculateOptimizationStats(agentDependencies, result.bundleResources);
}
return result;
}
/**
* Calculate optimization statistics
*/
calculateOptimizationStats(agentDeps, bundleResources) {
const totalAgents = agentDeps.length;
const totalResources = Object.values(bundleResources).reduce((sum, arr) => sum + arr.length, 0);
// Calculate how many resources would be needed without optimization
const unoptimizedTotal = agentDeps.reduce((sum, agent) => {
return sum + Object.values(agent.resources).reduce((agentSum, arr) => agentSum + arr.length, 0);
}, 0);
const savings = unoptimizedTotal - totalResources;
const savingsPercentage = unoptimizedTotal > 0 ? (savings / unoptimizedTotal * 100).toFixed(1) : 0;
return {
totalAgents,
totalUniqueResources: totalResources,
unoptimizedResourceCount: unoptimizedTotal,
resourcesSaved: savings,
optimizationPercentage: savingsPercentage
};
}
/**
* Validate that all required resources exist in core
*/
validateResourceExistence(resources) {
const resourceTypes = ['tasks', 'templates', 'checklists', 'data', 'utils'];
for (const type of resourceTypes) {
const resourceDir = path.join(this.corePath, type);
for (const resource of resources[type] || []) {
const resourcePath = path.join(resourceDir, `${resource}.md`);
const altPath = path.join(resourceDir, `${resource}.yml`);
if (!fs.existsSync(resourcePath) && !fs.existsSync(altPath)) {
throw new Error(`Resource not found: ${type}/${resource} (checked .md and .yml)`);
}
}
}
}
/**
* Get all available agents
*/
getAvailableAgents() {
if (!fs.existsSync(this.agentsPath)) {
return [];
}
return fs.readdirSync(this.agentsPath)
.filter(file => {
return (file.endsWith('.yml') || file.endsWith('.yaml')) &&
fs.statSync(path.join(this.agentsPath, file)).isFile();
})
.map(file => path.basename(file, path.extname(file)));
}
/**
* Generate dependency graph for visualization
*/
generateDependencyGraph(agentIds = null) {
const agents = agentIds || this.getAvailableAgents();
const graph = {
nodes: [],
edges: []
};
for (const agentId of agents) {
const config = this.loadAgentConfig(agentId);
// Add agent node
graph.nodes.push({
id: agentId,
type: 'agent',
label: config.name,
description: config.description
});
// Add resource nodes and edges
Object.entries(config.requires).forEach(([type, resources]) => {
resources.forEach(resource => {
const resourceId = `${type}:${resource}`;
// Add resource node if not exists
if (!graph.nodes.find(n => n.id === resourceId)) {
graph.nodes.push({
id: resourceId,
type: type,
label: resource,
category: type
});
}
// Add edge
graph.edges.push({
from: agentId,
to: resourceId,
type: 'requires'
});
});
});
}
return graph;
}
}
module.exports = DependencyResolver;

View File

@@ -1,262 +0,0 @@
/**
* BMAD v5 Unified Dependency Resolver
* Works with unified agent configurations that can generate both IDE and web outputs
*/
const fs = require('fs');
const path = require('path');
const yaml = require('js-yaml');
class UnifiedDependencyResolver {
constructor(rootPath = process.cwd()) {
this.rootPath = rootPath;
this.agentsPath = path.join(rootPath, 'agents');
this.corePath = path.join(rootPath, 'bmad-core');
this.cache = new Map();
}
/**
* Load and parse a unified agent configuration
*/
loadUnifiedAgentConfig(agentId) {
if (this.cache.has(agentId)) {
return this.cache.get(agentId);
}
const configPath = path.join(this.agentsPath, `${agentId}.yml`);
if (!fs.existsSync(configPath)) {
throw new Error(`Unified agent configuration not found: ${configPath}`);
}
try {
const content = fs.readFileSync(configPath, 'utf8');
const config = yaml.load(content);
// Validate unified config structure
this.validateUnifiedConfig(config, agentId);
this.cache.set(agentId, config);
return config;
} catch (error) {
throw new Error(`Failed to load unified agent config ${agentId}: ${error.message}`);
}
}
/**
* Validate unified configuration structure
*/
validateUnifiedConfig(config, agentId) {
if (!config.agent || !config.agent.id || !config.agent.name) {
throw new Error(`Invalid unified config for ${agentId}: missing agent identity`);
}
if (!config.dependencies) {
throw new Error(`Invalid unified config for ${agentId}: missing dependencies`);
}
if (!config.environments || !config.environments.web || !config.environments.ide) {
throw new Error(`Invalid unified config for ${agentId}: missing environment configurations`);
}
// CRITICAL: Ensure ONLY BMAD has bmad-kb access
const hasBmadKb = (
config.dependencies.data?.includes('bmad-kb') ||
config.environments.ide.dependencies?.data?.includes('bmad-kb') ||
config.environments.web.dependencies?.data?.includes('bmad-kb')
);
if (hasBmadKb && agentId !== 'bmad') {
throw new Error(`SECURITY VIOLATION: Agent ${agentId} has bmad-kb access but only BMAD should have it!`);
}
if (!hasBmadKb && agentId === 'bmad') {
throw new Error(`Configuration error: BMAD agent missing required bmad-kb access`);
}
}
/**
* Resolve dependencies for an agent in a specific environment
*/
resolveAgentDependencies(agentId, environment = 'web', bundleContext = null) {
const config = this.loadUnifiedAgentConfig(agentId);
// Start with base dependencies
const baseDeps = {
tasks: [...(config.dependencies.tasks || [])],
templates: [...(config.dependencies.templates || [])],
checklists: [...(config.dependencies.checklists || [])],
data: [...(config.dependencies.data || [])]
};
// Apply environment-specific overrides
const envConfig = config.environments[environment];
if (envConfig && envConfig.dependencies) {
Object.keys(envConfig.dependencies).forEach(type => {
if (envConfig.dependencies[type]) {
baseDeps[type] = [...new Set([...baseDeps[type], ...envConfig.dependencies[type]])];
}
});
}
// Special handling for team bundles containing BMAD
if (bundleContext && bundleContext.agents && bundleContext.agents.includes('bmad')) {
// Only add bmad-kb if this IS the BMAD agent
if (agentId === 'bmad') {
if (!baseDeps.data.includes('bmad-kb')) {
baseDeps.data.push('bmad-kb');
}
}
}
// Resolve file paths and validate existence
const resolvedDeps = {};
Object.keys(baseDeps).forEach(type => {
resolvedDeps[type] = baseDeps[type].map(dep => {
const filePath = this.resolveResourcePath(type, dep);
if (!fs.existsSync(filePath)) {
throw new Error(`Resource not found: ${filePath} (required by ${agentId})`);
}
return {
id: dep,
path: filePath,
content: fs.readFileSync(filePath, 'utf8')
};
});
});
return {
config: config,
agentId: agentId,
environment: environment,
resources: resolvedDeps
};
}
/**
* Resolve path for a resource
*/
resolveResourcePath(type, resourceId) {
const resourceMap = {
'tasks': 'tasks',
'templates': 'templates',
'checklists': 'checklists',
'data': 'data',
'personas': 'personas',
'utils': 'utils'
};
const subdir = resourceMap[type];
if (!subdir) {
throw new Error(`Unknown resource type: ${type}`);
}
return path.join(this.corePath, subdir, `${resourceId}.md`);
}
/**
* Get all available agents
*/
getAvailableAgents() {
if (!fs.existsSync(this.agentsPath)) {
return [];
}
return fs.readdirSync(this.agentsPath)
.filter(file => file.endsWith('.yml'))
.map(file => path.basename(file, '.yml'))
.sort();
}
/**
* Generate dependency graph for all agents
*/
generateDependencyGraph() {
const agents = this.getAvailableAgents();
const nodes = [];
const edges = [];
// Add agent nodes
agents.forEach(agentId => {
try {
const config = this.loadUnifiedAgentConfig(agentId);
nodes.push({
id: agentId,
type: 'agent',
label: config.agent.name,
title: config.agent.title,
hasBmadKb: config.dependencies.data?.includes('bmad-kb') || false
});
} catch (error) {
console.warn(`Skipping ${agentId}: ${error.message}`);
}
});
// Add resource nodes and edges
agents.forEach(agentId => {
try {
const deps = this.resolveAgentDependencies(agentId, 'web');
Object.entries(deps.resources).forEach(([type, resources]) => {
resources.forEach(resource => {
const resourceNodeId = `${type}:${resource.id}`;
// Add resource node if not exists
if (!nodes.find(n => n.id === resourceNodeId)) {
nodes.push({
id: resourceNodeId,
type: 'resource',
resourceType: type,
label: resource.id
});
}
// Add edge from agent to resource
edges.push({
from: agentId,
to: resourceNodeId,
type: 'requires'
});
});
});
} catch (error) {
console.warn(`Failed to analyze dependencies for ${agentId}: ${error.message}`);
}
});
return { nodes, edges };
}
/**
* Validate all configurations
*/
validateAllConfigurations() {
const agents = this.getAvailableAgents();
const results = {
valid: [],
invalid: [],
bmadKbViolations: []
};
agents.forEach(agentId => {
try {
const deps = this.resolveAgentDependencies(agentId, 'web');
results.valid.push(agentId);
// Check for bmad-kb violations
const hasBmadKb = deps.resources.data?.some(d => d.id === 'bmad-kb');
if (hasBmadKb && agentId !== 'bmad') {
results.bmadKbViolations.push(agentId);
}
} catch (error) {
results.invalid.push({
agentId: agentId,
error: error.message
});
}
});
return results;
}
}
module.exports = UnifiedDependencyResolver;

View File

@@ -1,200 +0,0 @@
#!/usr/bin/env node
/**
* BMAD v5 Unified Build CLI
* Works with unified agent configurations
*/
const { program } = require('commander');
const UnifiedWebBuilder = require('./builders/unified-web-builder');
const UnifiedDependencyResolver = require('./lib/unified-dependency-resolver');
// Initialize resolver
const resolver = new UnifiedDependencyResolver();
program
.name('bmad-unified-build')
.description('BMAD v5 Unified Build System')
.version('5.0.0');
// Build all web outputs
program
.command('build:web')
.description('Build all web outputs from unified configurations')
.action(async () => {
try {
const builder = new UnifiedWebBuilder();
const results = await builder.buildAll();
if (results.errors.length > 0) {
console.log('\\n⚠ Build completed with errors:');
results.errors.forEach(error => {
console.log(` ${error.type}: ${error.name} - ${error.error}`);
});
process.exit(1);
} else {
console.log('\\n🎉 All builds completed successfully!');
}
} catch (error) {
console.error('❌ Build failed:', error.message);
process.exit(1);
}
});
// Build specific agent
program
.command('build:agent')
.description('Build a specific standalone agent')
.requiredOption('-a, --agent <id>', 'Agent ID')
.action(async (options) => {
try {
const builder = new UnifiedWebBuilder();
const result = await builder.buildStandaloneAgent(options.agent);
console.log(`✅ Built agent: ${result.name}`);
console.log(` File: ${result.outputs[0]}`);
console.log(` Size: ${result.size} characters`);
} catch (error) {
console.error('❌ Build failed:', error.message);
process.exit(1);
}
});
// List available agents
program
.command('list:agents')
.description('List all available agents from unified configs')
.action(() => {
try {
const agents = resolver.getAvailableAgents();
console.log('📋 Available agents:');
agents.forEach(agentId => {
try {
const config = resolver.loadUnifiedAgentConfig(agentId);
const webCompatible = config.environments.web?.available !== false;
const ideOnly = config.environments.ide?.ide_only === true;
const hasBmadKb = config.dependencies.data?.includes('bmad-kb');
console.log(` ${agentId}: ${config.agent.name} (${config.agent.title})`);
console.log(` ${config.agent.description}`);
console.log(` Environments: ${webCompatible ? 'web' : ''}${webCompatible && !ideOnly ? ', ' : ''}${!ideOnly ? 'ide' : 'ide-only'}`);
if (hasBmadKb) {
console.log(` 🔑 Has BMAD-KB access`);
}
console.log('');
} catch (error) {
console.log(` ${agentId}: Error loading config - ${error.message}`);
}
});
} catch (error) {
console.error('❌ Failed to list agents:', error.message);
process.exit(1);
}
});
// Validate configurations
program
.command('validate')
.description('Validate all unified configurations')
.action(() => {
try {
console.log('🔍 Validating unified configurations...');
const results = resolver.validateAllConfigurations();
console.log(`\\n📊 Validation Results:`);
console.log(` Valid: ${results.valid.length}`);
console.log(` Invalid: ${results.invalid.length}`);
console.log(` BMAD-KB Violations: ${results.bmadKbViolations.length}`);
if (results.invalid.length > 0) {
console.log('\\n❌ Invalid Configurations:');
results.invalid.forEach(item => {
console.log(` ${item.agentId}: ${item.error}`);
});
}
if (results.bmadKbViolations.length > 0) {
console.log('\\n🚨 CRITICAL: BMAD-KB Security Violations:');
results.bmadKbViolations.forEach(agentId => {
console.log(` ${agentId}: Has bmad-kb access but only BMAD should have it!`);
});
}
if (results.invalid.length > 0 || results.bmadKbViolations.length > 0) {
process.exit(1);
} else {
console.log('\\n✅ All configurations valid and secure!');
}
} catch (error) {
console.error('❌ Validation failed:', error.message);
process.exit(1);
}
});
// Analyze dependencies
program
.command('analyze:deps')
.description('Analyze agent dependencies')
.option('-a, --agent <id>', 'Specific agent ID')
.option('-g, --graph', 'Generate dependency graph')
.action((options) => {
try {
if (options.agent) {
const deps = resolver.resolveAgentDependencies(options.agent, 'web');
console.log(`📊 Dependencies for ${deps.config.agent.name} (${deps.agentId}):`);
Object.entries(deps.resources).forEach(([type, resources]) => {
if (resources.length > 0) {
console.log(` ${type}: ${resources.map(r => r.id).join(', ')}`);
}
});
// Check for BMAD-KB access
const hasBmadKb = deps.resources.data?.some(r => r.id === 'bmad-kb');
if (hasBmadKb) {
console.log(` 🔑 Has BMAD-KB access: ${deps.agentId === 'bmad' ? 'AUTHORIZED' : '⚠️ VIOLATION!'}`);
}
} else if (options.graph) {
const graph = resolver.generateDependencyGraph();
console.log('📈 Dependency Graph:');
console.log(` Nodes: ${graph.nodes.length}`);
console.log(` Edges: ${graph.edges.length}`);
console.log('\\n Agents:');
graph.nodes.filter(n => n.type === 'agent').forEach(n => {
const bmadKbIndicator = n.hasBmadKb ? ' 🔑' : '';
console.log(` ${n.id}: ${n.label} (${n.title})${bmadKbIndicator}`);
});
} else {
const agents = resolver.getAvailableAgents();
console.log('📊 All Agent Dependencies:');
agents.forEach(agentId => {
try {
const deps = resolver.resolveAgentDependencies(agentId, 'web');
const totalDeps = Object.values(deps.resources).reduce((sum, arr) => sum + arr.length, 0);
const hasBmadKb = deps.resources.data?.some(r => r.id === 'bmad-kb') ? ' 🔑' : '';
console.log(` ${agentId}: ${totalDeps} total dependencies${hasBmadKb}`);
} catch (error) {
console.log(` ${agentId}: Error resolving dependencies - ${error.message}`);
}
});
}
} catch (error) {
console.error('❌ Analysis failed:', error.message);
process.exit(1);
}
});
// Handle unknown commands
program.on('command:*', () => {
console.error('Invalid command: %s\\nSee --help for a list of available commands.', program.args.join(' '));
process.exit(1);
});
// Parse command line arguments
program.parse();
// Show help if no command provided
if (!process.argv.slice(2).length) {
program.outputHelp();
}