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.

382 lines (311 loc) 13 kB
/** * IDEEnhancer - Generic IDE enhancement system for CK expansion packs * * This class enhances any IDE configuration by adding CK expansion pack agents * using the same patterns and conventions that upstream used. */ const fs = require('fs-extra'); const path = require('path'); class IDEEnhancer { constructor(targetDir, verbose = false) { this.targetDir = targetDir; this.verbose = verbose; } /** * Enhance an IDE configuration with CK agents */ async enhanceIDE(ideInfo, ckAgents, packName) { if (!ideInfo.configured) { if (this.verbose) { console.log(` ⚠️ Skipping ${ideInfo.name} - not configured`); } return false; } try { const enhancementType = this.determineEnhancementType(ideInfo); const success = await this.applyEnhancement(enhancementType, ideInfo, ckAgents, packName); if (success && this.verbose) { console.log(` ✅ Enhanced ${ideInfo.name} with ${ckAgents.length} CK agents`); } return success; } catch (error) { if (this.verbose) { console.error(` ❌ Failed to enhance ${ideInfo.name}: ${error.message}`); } return false; } } /** * Determine enhancement type based on IDE characteristics */ determineEnhancementType(ideInfo) { // Use the enhancement type from the analyzer, or fallback to detection if (ideInfo.enhancementType) { return ideInfo.enhancementType; } // Detect based on IDE characteristics if (ideInfo.id === 'roo') return 'yaml-append'; if (ideInfo.id === 'github-copilot') return 'chatmode-creation'; if (ideInfo.id === 'cline' && ideInfo.structure?.hasNumberedFiles) return 'numbered-files'; if (ideInfo.structure?.filePattern === '.mdc') return 'mdc-files'; if (ideInfo.type === 'single-file') return 'single-file-append'; return 'standard-files'; } /** * Apply the appropriate enhancement strategy */ async applyEnhancement(enhancementType, ideInfo, ckAgents, packName) { switch (enhancementType) { case 'yaml-append': return await this.enhanceYamlFile(ideInfo, ckAgents, packName); case 'chatmode-creation': return await this.enhanceChatmodes(ideInfo, ckAgents, packName); case 'numbered-files': return await this.enhanceNumberedFiles(ideInfo, ckAgents, packName); case 'mdc-files': return await this.enhanceMdcFiles(ideInfo, ckAgents, packName); case 'single-file-append': return await this.enhanceSingleFile(ideInfo, ckAgents, packName); case 'standard-files': return await this.enhanceStandardFiles(ideInfo, ckAgents, packName); case 'directory-creation': return await this.enhanceWithDirectories(ideInfo, ckAgents, packName); default: if (this.verbose) { console.warn(` ⚠️ Unknown enhancement type: ${enhancementType}`); } return await this.enhanceStandardFiles(ideInfo, ckAgents, packName); } } /** * Enhance YAML file (Roo) */ async enhanceYamlFile(ideInfo, ckAgents, packName) { const yamlFile = ideInfo.path || path.join(this.targetDir, '.roomodes'); let content = ''; if (await fs.pathExists(yamlFile)) { content = await fs.readFile(yamlFile, 'utf8'); } // Add CK agents if not already present for (const agent of ckAgents) { const agentSlug = `bmad-${agent}`; if (!content.includes(`slug: ${agentSlug}`)) { // Add a newline if content exists if (content && !content.endsWith('\n')) { content += '\n'; } // Add the agent entry content += `- name: ${packName} ${agent.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')}\n`; content += ` slug: ${agentSlug}\n`; content += ` description: ${packName} ${agent} agent for specialized operations\n`; } } await fs.writeFile(yamlFile, content); return true; } /** * Enhance GitHub Copilot chatmodes */ async enhanceChatmodes(ideInfo, ckAgents, packName) { const chatmodesDir = ideInfo.paths?.chatmodes || path.join(this.targetDir, '.github', 'chatmodes'); await fs.ensureDir(chatmodesDir); for (const agent of ckAgents) { const chatmodePath = path.join(chatmodesDir, `${agent}.chatmode.md`); // Read agent content from the expansion pack const sourceAgentPath = path.join(this.targetDir, `.${packName}`, 'agents', `${agent}.md`); if (await fs.pathExists(sourceAgentPath)) { const agentContent = await fs.readFile(sourceAgentPath, 'utf8'); // Extract description from YAML front matter let description = `Activates the ${agent} agent persona.`; const yamlMatch = agentContent.match(/```ya?ml\r?\n([\s\S]*?)```/); if (yamlMatch) { const whenToUseMatch = yamlMatch[1].match(/whenToUse:\s*"(.*?)"/); if (whenToUseMatch && whenToUseMatch[1]) { description = whenToUseMatch[1]; } } // Create chatmode content let chatmodeContent = `---\ndescription: "${description.replace(/"/g, '\\"')}"\ntools: ['changes', 'codebase', 'fetch', 'findTestFiles', 'githubRepo', 'problems', 'usages', 'editFiles', 'runCommands', 'runTasks', 'runTests', 'search', 'searchResults', 'terminalLastCommand', 'terminalSelection', 'testFailure']\n---\n`; chatmodeContent += agentContent; await fs.writeFile(chatmodePath, chatmodeContent); } } return true; } /** * Enhance with numbered files (Cline) */ async enhanceNumberedFiles(ideInfo, ckAgents, packName) { const targetDir = ideInfo.path; await fs.ensureDir(targetDir); // Get existing files to determine next number const existingFiles = await fs.readdir(targetDir); const numbers = existingFiles .filter(f => f.match(/^\d+-/)) .map(f => parseInt(f.split('-')[0])) .filter(n => !isNaN(n)); let nextNumber = numbers.length > 0 ? Math.max(...numbers) + 1 : 20; // Add CK agent rules with appropriate numbering for (const agent of ckAgents) { const sourceAgentPath = path.join(this.targetDir, `.${packName}`, 'agents', `${agent}.md`); if (await fs.pathExists(sourceAgentPath)) { const agentContent = await fs.readFile(sourceAgentPath, 'utf8'); const ruleFile = path.join(targetDir, `${String(nextNumber).padStart(2, '0')}-${agent}.md`); await fs.writeFile(ruleFile, agentContent); nextNumber++; } } return true; } /** * Enhance with .mdc files (Cursor) */ async enhanceMdcFiles(ideInfo, ckAgents, packName) { const targetDir = ideInfo.path; await fs.ensureDir(targetDir); // Add CK agent rules for (const agent of ckAgents) { const ruleFile = path.join(targetDir, `${agent}.mdc`); if (!await fs.pathExists(ruleFile)) { await fs.writeFile(ruleFile, `@${agent}`); } } return true; } /** * Enhance single file by appending content */ async enhanceSingleFile(ideInfo, ckAgents, packName) { const targetFile = ideInfo.path; let content = ''; if (await fs.pathExists(targetFile)) { content = await fs.readFile(targetFile, 'utf8'); } // Add CK agents section if not already present if (!content.includes(`## Cloud-Kinetix ${packName} Agents`)) { content += `\n\n## Cloud-Kinetix ${packName} Agents\n\n`; for (const agent of ckAgents) { const sourceAgentPath = path.join(this.targetDir, `.${packName}`, 'agents', `${agent}.md`); if (await fs.pathExists(sourceAgentPath)) { const agentContent = await fs.readFile(sourceAgentPath, 'utf8'); content += `### @${agent}\n\n`; content += agentContent + '\n\n---\n\n'; } } await fs.writeFile(targetFile, content); } return true; } /** * Enhance with standard files (most IDEs) */ async enhanceStandardFiles(ideInfo, ckAgents, packName) { const targetDir = ideInfo.path; await fs.ensureDir(targetDir); // Determine file extension from IDE structure const fileExt = ideInfo.format || ideInfo.structure?.filePattern || '.md'; // Add CK agent files for (const agent of ckAgents) { const targetFile = path.join(targetDir, `${agent}${fileExt}`); // For Claude Code, create in a subdirectory structure if (ideInfo.id === 'claude-code') { return await this.enhanceClaudeCodeStructure(ideInfo, ckAgents, packName); } // For Windsurf, create enhanced content if (ideInfo.id === 'windsurf') { return await this.enhanceWindsurfFiles(ideInfo, ckAgents, packName); } // For Trae, create individual files in rules directory if (ideInfo.id === 'trae') { return await this.enhanceTraeFiles(ideInfo, ckAgents, packName); } // For most other IDEs, create simple agent content if (!await fs.pathExists(targetFile)) { const sourceAgentPath = path.join(this.targetDir, `.${packName}`, 'agents', `${agent}.md`); if (await fs.pathExists(sourceAgentPath)) { const agentContent = await fs.readFile(sourceAgentPath, 'utf8'); await fs.writeFile(targetFile, agentContent); } } } return true; } /** * Enhance Claude Code structure */ async enhanceClaudeCodeStructure(ideInfo, ckAgents, packName) { // Claude Code uses commands structure // ideInfo.path points to something like .claude/commands/BMad/, so we need to go up to .claude/commands/ const claudeCommandsDir = path.join(ideInfo.path, '..'); // Use "ck" as the unified folder name for all Cloud-Kinetix expansion packs const ckCommandsDir = path.join(claudeCommandsDir, 'ck'); await fs.ensureDir(ckCommandsDir); const agentsDir = path.join(ckCommandsDir, 'agents'); await fs.ensureDir(agentsDir); // Create command files for each agent in the consolidated ck/agents folder for (const agent of ckAgents) { const commandContent = `/${agent}`; const commandFile = path.join(agentsDir, `${agent}.mdc`); await fs.writeFile(commandFile, commandContent); } return true; } /** * Enhance Windsurf files with enhanced content */ async enhanceWindsurfFiles(ideInfo, ckAgents, packName) { const targetDir = ideInfo.path; await fs.ensureDir(targetDir); // Read the agent content from the expansion pack for (const agent of ckAgents) { const sourceAgentPath = path.join(this.targetDir, `.${packName}`, 'agents', `${agent}.md`); if (await fs.pathExists(sourceAgentPath)) { const agentContent = await fs.readFile(sourceAgentPath, 'utf8'); // Create a simplified version for Windsurf const windsurfContent = `# ${agent.toUpperCase().replace(/-/g, ' ')} Agent Rule\n\nThis rule is triggered when the user types \`@${agent}\` and activates the ${agent} agent persona.\n\n## Agent Activation\n\n${agentContent}`; const ruleFile = path.join(targetDir, `${agent}.md`); await fs.writeFile(ruleFile, windsurfContent); } } return true; } /** * Enhance Trae files - create individual .md files in rules directory */ async enhanceTraeFiles(ideInfo, ckAgents, packName) { const rulesDir = ideInfo.path || path.join(this.targetDir, '.trae', 'rules'); await fs.ensureDir(rulesDir); // Create individual agent files in Trae rules format for (const agent of ckAgents) { const targetFile = path.join(rulesDir, `${agent}.md`); // Only create if it doesn't exist if (!await fs.pathExists(targetFile)) { const sourceAgentPath = path.join(this.targetDir, `.${packName}`, 'agents', `${agent}.md`); if (await fs.pathExists(sourceAgentPath)) { const agentContent = await fs.readFile(sourceAgentPath, 'utf8'); // Trae expects the agent content directly await fs.writeFile(targetFile, agentContent); } } } return true; } /** * Enhance with directory creation (for unknown IDEs) */ async enhanceWithDirectories(ideInfo, ckAgents, packName) { // Create a subdirectory for CK agents const ckDir = path.join(ideInfo.path, packName); await fs.ensureDir(ckDir); // Copy agent files for (const agent of ckAgents) { const sourceAgentPath = path.join(this.targetDir, `.${packName}`, 'agents', `${agent}.md`); const targetAgentPath = path.join(ckDir, `${agent}.md`); if (await fs.pathExists(sourceAgentPath)) { await fs.copy(sourceAgentPath, targetAgentPath); } } return true; } } module.exports = IDEEnhancer;