UNPKG

@cloudkinetix/bmad-enhanced

Version:

Cloud-Kinetix enhanced fork of BMAD-METHOD - Breakthrough Method of Agile AI-driven Development with robust versioning and unified validation.

568 lines (475 loc) 18.8 kB
const fs = require("fs-extra"); const path = require("path"); const { execSync, spawn } = require("child_process"); const BMADBackupManager = require("./bmad-backup-manager"); const BMADConfigMerger = require("./bmad-config-merger"); // Import IDE setup functionality const IdeSetup = require("./ide-setup"); // Import ES module loader const { getChalk } = require("./es-module-loader"); /** * Enterprise Manager - Coordinates upstream BMAD with Cloud Kinetix enterprise features * * Strategic architecture: * 1. Delegates to upstream BMAD for foundation (agents, core functionality) * 2. Adds Cloud Kinetix enterprise features (JIRA, backup, AI dev) * 3. Provides professional monitoring and management */ class EnterpriseManager { constructor(targetDirectory = process.cwd()) { this.targetDirectory = path.resolve(targetDirectory); this.backupManager = new BMADBackupManager(this.targetDirectory); this.configMerger = new BMADConfigMerger(this.targetDirectory); // Enterprise features will be auto-discovered from expansion-packs this.enterpriseFeatures = null; // Lazy loaded } /** * Auto-discover enterprise features from expansion-packs directory */ async getEnterpriseFeatures() { if (this.enterpriseFeatures === null) { await this.loadEnterpriseFeatures(); } return this.enterpriseFeatures; } /** * Load enterprise features by scanning expansion-packs for CK features */ async loadEnterpriseFeatures() { this.enterpriseFeatures = {}; try { // Use configLoader to get the correct expansion packs path const configLoader = require("./config-loader"); const expansionPacksDir = configLoader.getExpansionPacksPath(); if (!(await fs.pathExists(expansionPacksDir))) { console.warn("Expansion packs directory not found, using empty enterprise features"); return; } const entries = await fs.readdir(expansionPacksDir, { withFileTypes: true }); for (const entry of entries) { if (entry.isDirectory() && (entry.name.startsWith("ck-") || entry.name.startsWith("bmad-"))) { const manifestPath = path.join(expansionPacksDir, entry.name, "manifest.yaml"); try { let name = entry.name; let description = "Enterprise feature"; // Try to read manifest for better info if (await fs.pathExists(manifestPath)) { const yaml = require("js-yaml"); const manifestContent = await fs.readFile(manifestPath, "utf8"); const manifest = yaml.load(manifestContent); name = manifest.name || entry.name; description = manifest.description || description; } this.enterpriseFeatures[entry.name] = { name: name, description: description, source: path.join(expansionPacksDir, entry.name), target: `.bmad-${entry.name}`, }; } catch (error) { console.warn(`Failed to process enterprise feature ${entry.name}: ${error.message}`); } } } } catch (error) { console.warn(`Failed to auto-discover enterprise features: ${error.message}`); } } /** * Install upstream BMAD foundation using their installer */ async installUpstreamFoundation(options = {}) { try { // Build upstream install command const args = ["bmad-method", "install"]; // Always use --full for non-interactive installation when we have specific options if (options.full || options.ide || options.expansionPacks) { args.push("--full"); } if (options.directory && options.directory !== ".") { args.push("--directory", options.directory); } if (options.ide) { if (Array.isArray(options.ide)) { // Multiple IDEs for (const ide of options.ide) { args.push("--ide", ide); } } else { // Single IDE args.push("--ide", options.ide); } } if (options.expansionPacks) { if (Array.isArray(options.expansionPacks)) { // Multiple expansion packs args.push("--expansion-packs", options.expansionPacks.join(",")); } else { // Single expansion pack args.push("--expansion-packs", options.expansionPacks); } } // Execute upstream installer const result = execSync(`npx ${args.join(" ")}`, { cwd: this.targetDirectory, stdio: "inherit", encoding: "utf8", }); return { success: true, output: result }; } catch (error) { throw new Error(`Upstream BMAD installation failed: ${error.message}`); } } /** * Install Cloud Kinetix enterprise features */ async installEnterpriseFeatures(featureNames = [], ides = []) { const enterpriseFeatures = await this.getEnterpriseFeatures(); const results = []; for (const featureName of featureNames) { const feature = enterpriseFeatures[featureName]; if (!feature) { throw new Error(`Unknown enterprise feature: ${featureName}`); } try { await this.installSingleFeature(featureName, feature); results.push({ feature: featureName, success: true }); } catch (error) { results.push({ feature: featureName, success: false, error: error.message, }); throw error; // Stop on first failure for now } } // IMPORTANT: We do NOT setup IDEs for CK expansion packs here because // they are already handled by the upstream BMAD installer when we pass // them via --expansion-packs. Setting them up again causes duplication // and creates flat structure files that conflict with the organized structure. // The upstream BMAD installer properly creates: // - .claude/commands/ck/jira/agents/ // - .claude/commands/ck/ai-agent/agents/ // etc. return results; } /** * Install a single enterprise feature */ async installSingleFeature(featureName, feature) { const targetPath = path.join(this.targetDirectory, feature.target); // Ensure source exists if (!(await fs.pathExists(feature.source))) { throw new Error(`Enterprise feature source not found: ${feature.source}`); } // Copy feature to target location await fs.ensureDir(path.dirname(targetPath)); await fs.copy(feature.source, targetPath, { overwrite: true, errorOnExist: false, }); // Note: Expansion pack files are kept in their own directories // IDE setup will search for agents in both .bmad-core and expansion pack directories return { success: true, feature: featureName, path: targetPath }; } /** * Setup IDE rules for CK expansion pack agents using the organized installer approach * NOTE: This method is currently NOT USED because the upstream BMAD installer * already handles CK expansion packs when we pass them via --expansion-packs. * Kept for reference in case we need custom IDE setup in the future. */ async setupExpansionPackIDEs_DEPRECATED(ides, featureNames = []) { const chalk = await getChalk(); // Import the proper installer IDE setup module that creates organized structures const ideSetup = require('../../tools/installer/lib/ide-setup'); let totalSetup = 0; for (const ide of ides) { console.log( chalk.cyan(`\nSetting up expansion pack agents for ${ide.toUpperCase()}...`) ); // Setup only the requested CK expansion packs const featuresToSetup = featureNames.length > 0 ? featureNames : await this.getInstalledEnterpriseFeatures(); for (const featureName of featuresToSetup) { // CK expansion packs are installed as .bmad-ck-{name} const expansionPackDir = `.bmad-ck-${featureName.replace('ck-', '')}`; const packPath = path.join(this.targetDirectory, expansionPackDir); if (await fs.pathExists(packPath)) { // Generate proper slashPrefix for Cloud Kinetix expansion packs // We want to create organized subdirectories like "ck/jira", "ck/ai-agent-dev" let slashPrefix = 'ck'; // Base CK directory // Extract the feature name for the subdirectory const featureSubdir = featureName.replace('ck-', '').replace('-integration', '').replace('-dev', ''); const fullSlashPrefix = path.join(slashPrefix, featureSubdir); // For Cloud Kinetix expansion packs, always use organized structure // We ignore any slashPrefix in config.yaml to ensure consistent organization slashPrefix = fullSlashPrefix; // Get agent and task IDs from the expansion pack const agentsDir = path.join(packPath, 'agents'); const tasksDir = path.join(packPath, 'tasks'); let agentIds = []; let taskIds = []; if (await fs.pathExists(agentsDir)) { const agentFiles = await fs.readdir(agentsDir); agentIds = agentFiles .filter(f => f.endsWith('.md')) .map(f => f.replace('.md', '')); } if (await fs.pathExists(tasksDir)) { const taskFiles = await fs.readdir(tasksDir); taskIds = taskFiles .filter(f => f.endsWith('.md')) .map(f => f.replace('.md', '')); } // Handle IDE-specific setup if (ide === 'claude-code') { // Use the installer's organized approach for Claude Code // This will create the proper ck/{feature}/agents and ck/{feature}/tasks structure const packageName = featureName; // e.g., "ck-jira-integration" const rootPath = expansionPackDir; // e.g., ".bmad-ck-jira-integration" // Use the installer's setupClaudeCodeForPackage method directly await ideSetup.setupClaudeCodeForPackage( this.targetDirectory, packageName, slashPrefix, agentIds, taskIds, rootPath ); console.log(chalk.green(`✓ Created Claude Code commands for ${featureName} in ${slashPrefix}`)); } else { // For other IDEs, we need to setup ONLY the CK expansion pack agents // Not ALL agents (which would duplicate what BMAD already did) // For now, we'll use the CK layer's ide-setup which has individual setup methods const ckIdeSetup = require('./ide-setup'); // Setup just this expansion pack's agents for the IDE for (const agentId of agentIds) { try { await ckIdeSetup.setup(ide, this.targetDirectory, agentId); } catch (error) { // Ignore individual agent setup errors } } console.log(chalk.green(`✓ ${ide.toUpperCase()} expansion pack agents setup complete`)); } totalSetup++; } } } // Clean up old flat structure files for Claude Code if (ides.includes('claude-code')) { await this.cleanupOldFlatStructureFiles(featureNames); } // Summary message if (totalSetup > 0) { console.log(chalk.green(`\n✅ Expansion pack agents installed for ${ides.length} IDE(s)`)); console.log(chalk.cyan(` Your expansion pack agents are now available in organized subdirectories!`)); } return { success: true, setupCount: totalSetup, totalCount: ides.length }; } /** * Clean up old flat structure files that conflict with the new organized structure */ async cleanupOldFlatStructureFiles(featureNames) { const chalk = await getChalk(); const commandsDir = path.join(this.targetDirectory, '.claude', 'commands'); // Use central configuration for agent names const { CK_EXPANSION_PACKS } = require('../config/agent-names'); // Map expansion packs to their flat file names const flatFileMapping = {}; for (const [packName, agents] of Object.entries(CK_EXPANSION_PACKS)) { flatFileMapping[packName] = agents.map(agent => `${agent}.md`); } let cleanedCount = 0; for (const featureName of featureNames) { const flatFiles = flatFileMapping[featureName] || []; for (const flatFile of flatFiles) { const flatFilePath = path.join(commandsDir, flatFile); if (await fs.pathExists(flatFilePath)) { try { await fs.remove(flatFilePath); cleanedCount++; console.log(chalk.dim(` Cleaned up old flat file: ${flatFile}`)); } catch (error) { console.warn(chalk.yellow(` Could not remove ${flatFile}: ${error.message}`)); } } } } if (cleanedCount > 0) { console.log(chalk.green(`✓ Cleaned up ${cleanedCount} old flat structure files`)); } } /** * Update upstream BMAD foundation */ async updateUpstreamFoundation() { try { // Use upstream installer's update mechanism const result = execSync("npx bmad-method install", { cwd: this.targetDirectory, stdio: "inherit", encoding: "utf8", }); return { success: true, output: result }; } catch (error) { throw new Error(`Upstream BMAD update failed: ${error.message}`); } } /** * Update enterprise features */ async updateEnterpriseFeatures() { const installedFeatures = await this.getInstalledEnterpriseFeatures(); if (installedFeatures.length === 0) { return { success: true, message: "No enterprise features to update" }; } // Reinstall all currently installed features (this updates them) return await this.installEnterpriseFeatures(installedFeatures); } /** * Get list of currently installed enterprise features */ async getInstalledEnterpriseFeatures() { const enterpriseFeatures = await this.getEnterpriseFeatures(); const installed = []; for (const [featureName, feature] of Object.entries(enterpriseFeatures)) { const targetPath = path.join(this.targetDirectory, feature.target); if (await fs.pathExists(targetPath)) { installed.push(featureName); } } return installed; } /** * Get list of available enterprise features */ async getAvailableEnterpriseFeatures() { const enterpriseFeatures = await this.getEnterpriseFeatures(); return Object.keys(enterpriseFeatures); } /** * Create comprehensive backup */ async createBackup() { return await this.backupManager.createBackups(); } /** * Merge configurations intelligently */ async mergeConfigurations() { return await this.configMerger.handleConfigMerge(true); } /** * Setup enterprise monitoring */ async setupEnterpriseMonitoring() { const monitoringPath = path.join(this.targetDirectory, ".bmad-enterprise"); const monitoringConfig = { version: "1.0.0", installed: new Date().toISOString(), features: await this.getInstalledEnterpriseFeatures(), monitoring: { enabled: true, healthChecks: true, backupSchedule: "auto", }, }; await fs.ensureDir(monitoringPath); await fs.writeJson(path.join(monitoringPath, "config.json"), monitoringConfig, { spaces: 2 }); return monitoringConfig; } /** * Get comprehensive installation status */ async getComprehensiveStatus() { const status = { upstream: await this.getUpstreamStatus(), features: await this.getFeatureStatus(), backup: await this.getBackupStatus(), recommendations: [], }; // Generate recommendations if (!status.upstream.installed) { status.recommendations.push("Install BMAD foundation first"); } if (status.backup.count === 0) { status.recommendations.push("Create your first backup with `bmad-ck backup --create`"); } if (status.features.jira && !(await this.isJiraConfigured())) { status.recommendations.push("Configure JIRA credentials for full integration"); } return status; } /** * Get upstream BMAD installation status */ async getUpstreamStatus() { const bmadCorePath = path.join(this.targetDirectory, ".bmad-core"); const installed = await fs.pathExists(bmadCorePath); let version = null; let agentCount = 0; if (installed) { // Try to get version from package.json or config const agentsPath = path.join(bmadCorePath, "agents"); if (await fs.pathExists(agentsPath)) { const agents = await fs.readdir(agentsPath); agentCount = agents.filter((f) => f.endsWith(".md")).length; } // Try to detect version (this is a best guess) try { const configPath = path.join(bmadCorePath, "core-config.yaml"); if (await fs.pathExists(configPath)) { const config = await fs.readFile(configPath, "utf8"); const versionMatch = config.match(/version:\s*["']?([^"'\n]+)["']?/); if (versionMatch) { version = versionMatch[1]; } } } catch (error) { // Ignore version detection errors } } return { installed, version, agentCount, path: installed ? bmadCorePath : null, }; } /** * Get enterprise features status */ async getFeatureStatus() { const enterpriseFeatures = await this.getEnterpriseFeatures(); const status = {}; for (const [featureName, feature] of Object.entries(enterpriseFeatures)) { const targetPath = path.join(this.targetDirectory, feature.target); status[featureName.replace("ck-", "").replace("-", "")] = await fs.pathExists(targetPath); } return status; } /** * Get backup system status */ async getBackupStatus() { const backups = await this.backupManager.listBackups(); const lastBackup = backups.length > 0 ? backups[0].created.toLocaleDateString() : null; return { enabled: true, count: backups.length, lastBackup, backups, }; } /** * Check if JIRA is properly configured */ async isJiraConfigured() { // Check for JIRA config files or environment variables const jiraConfigPath = path.join(this.targetDirectory, ".bmad-ck-jira-integration", "config"); return await fs.pathExists(jiraConfigPath); } } module.exports = EnterpriseManager;