diff --git a/tools/builders/web-builder.js b/tools/builders/web-builder.js deleted file mode 100644 index 58d60acf..00000000 --- a/tools/builders/web-builder.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/tools/cli.js b/tools/cli.js deleted file mode 100755 index de1ad39a..00000000 --- a/tools/cli.js +++ /dev/null @@ -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 ', '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 ', '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 ', '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(); -} \ No newline at end of file diff --git a/tools/install-expansion-pack.js b/tools/install-expansion-pack.js deleted file mode 100644 index a8eb1a54..00000000 --- a/tools/install-expansion-pack.js +++ /dev/null @@ -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 ', '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); -}); \ No newline at end of file diff --git a/tools/lib/bundle-optimizer.js b/tools/lib/bundle-optimizer.js deleted file mode 100644 index 992c511f..00000000 --- a/tools/lib/bundle-optimizer.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/tools/lib/dependency-resolver.js b/tools/lib/dependency-resolver.js deleted file mode 100644 index cc946195..00000000 --- a/tools/lib/dependency-resolver.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/tools/lib/unified-dependency-resolver.js b/tools/lib/unified-dependency-resolver.js deleted file mode 100644 index 715beec8..00000000 --- a/tools/lib/unified-dependency-resolver.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/tools/unified-cli.js b/tools/unified-cli.js deleted file mode 100644 index 1f701e74..00000000 --- a/tools/unified-cli.js +++ /dev/null @@ -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 ', '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 ', '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(); -} \ No newline at end of file