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