@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
JavaScript
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;