/** * Base IDE Setup - Common functionality for all IDE setups * Reduces duplication and provides shared methods */ const path = require("path"); const fs = require("fs-extra"); const yaml = require("js-yaml"); const chalk = require("chalk").default || require("chalk"); const fileManager = require("./file-manager"); const resourceLocator = require("./resource-locator"); const { extractYamlFromAgent } = require("../../lib/yaml-utils"); class BaseIdeSetup { constructor() { this._agentCache = new Map(); this._pathCache = new Map(); } /** * Get all agent IDs with caching */ async getAllAgentIds(installDir) { const cacheKey = `all-agents:${installDir}`; if (this._agentCache.has(cacheKey)) { return this._agentCache.get(cacheKey); } const allAgents = new Set(); // Get core agents const coreAgents = await this.getCoreAgentIds(installDir); coreAgents.forEach(id => allAgents.add(id)); // Get expansion pack agents const expansionPacks = await this.getInstalledExpansionPacks(installDir); for (const pack of expansionPacks) { const packAgents = await this.getExpansionPackAgents(pack.path); packAgents.forEach(id => allAgents.add(id)); } const result = Array.from(allAgents); this._agentCache.set(cacheKey, result); return result; } /** * Get core agent IDs */ async getCoreAgentIds(installDir) { const coreAgents = []; const corePaths = [ path.join(installDir, ".bmad-core", "agents"), path.join(installDir, "bmad-core", "agents") ]; for (const agentsDir of corePaths) { if (await fileManager.pathExists(agentsDir)) { const files = await resourceLocator.findFiles("*.md", { cwd: agentsDir }); coreAgents.push(...files.map(file => path.basename(file, ".md"))); break; // Use first found } } return coreAgents; } /** * Find agent path with caching */ async findAgentPath(agentId, installDir) { const cacheKey = `agent-path:${agentId}:${installDir}`; if (this._pathCache.has(cacheKey)) { return this._pathCache.get(cacheKey); } // Use resource locator for efficient path finding let agentPath = await resourceLocator.getAgentPath(agentId); if (!agentPath) { // Check installation-specific paths const possiblePaths = [ path.join(installDir, ".bmad-core", "agents", `${agentId}.md`), path.join(installDir, "bmad-core", "agents", `${agentId}.md`), path.join(installDir, "common", "agents", `${agentId}.md`) ]; for (const testPath of possiblePaths) { if (await fileManager.pathExists(testPath)) { agentPath = testPath; break; } } } if (agentPath) { this._pathCache.set(cacheKey, agentPath); } return agentPath; } /** * Get agent title from metadata */ async getAgentTitle(agentId, installDir) { const agentPath = await this.findAgentPath(agentId, installDir); if (!agentPath) return agentId; try { const content = await fileManager.readFile(agentPath); const yamlContent = extractYamlFromAgent(content); if (yamlContent) { const metadata = yaml.load(yamlContent); return metadata.agent_name || agentId; } } catch (error) { // Fallback to agent ID } return agentId; } /** * Get installed expansion packs */ async getInstalledExpansionPacks(installDir) { const cacheKey = `expansion-packs:${installDir}`; if (this._pathCache.has(cacheKey)) { return this._pathCache.get(cacheKey); } const expansionPacks = []; // Check for dot-prefixed expansion packs const dotExpansions = await resourceLocator.findFiles(".bmad-*", { cwd: installDir }); for (const dotExpansion of dotExpansions) { if (dotExpansion !== ".bmad-core") { const packPath = path.join(installDir, dotExpansion); const packName = dotExpansion.substring(1); // remove the dot expansionPacks.push({ name: packName, path: packPath }); } } // Check other dot folders that have config.yaml const allDotFolders = await resourceLocator.findFiles(".*", { cwd: installDir }); for (const folder of allDotFolders) { if (!folder.startsWith(".bmad-") && folder !== ".bmad-core") { const packPath = path.join(installDir, folder); const configPath = path.join(packPath, "config.yaml"); if (await fileManager.pathExists(configPath)) { expansionPacks.push({ name: folder.substring(1), // remove the dot path: packPath }); } } } this._pathCache.set(cacheKey, expansionPacks); return expansionPacks; } /** * Get expansion pack agents */ async getExpansionPackAgents(packPath) { const agentsDir = path.join(packPath, "agents"); if (!(await fileManager.pathExists(agentsDir))) { return []; } const agentFiles = await resourceLocator.findFiles("*.md", { cwd: agentsDir }); return agentFiles.map(file => path.basename(file, ".md")); } /** * Create agent rule content (shared logic) */ async createAgentRuleContent(agentId, agentPath, installDir, format = 'mdc') { const agentContent = await fileManager.readFile(agentPath); const agentTitle = await this.getAgentTitle(agentId, installDir); const yamlContent = extractYamlFromAgent(agentContent); let content = ""; if (format === 'mdc') { // MDC format for Cursor content = "---\n"; content += "description: \n"; content += "globs: []\n"; content += "alwaysApply: false\n"; content += "---\n\n"; content += `# ${agentId.toUpperCase()} Agent Rule\n\n`; content += `This rule is triggered when the user types \`@${agentId}\` and activates the ${agentTitle} agent persona.\n\n`; content += "## Agent Activation\n\n"; content += "CRITICAL: Read the full YAML, start activation to alter your state of being, follow startup section instructions, stay in this being until told to exit this mode:\n\n"; content += "```yaml\n"; content += yamlContent || agentContent.replace(/^#.*$/m, "").trim(); content += "\n```\n\n"; content += "## File Reference\n\n"; const relativePath = path.relative(installDir, agentPath).replace(/\\/g, '/'); content += `The complete agent definition is available in [${relativePath}](mdc:${relativePath}).\n\n`; content += "## Usage\n\n"; content += `When the user types \`@${agentId}\`, activate this ${agentTitle} persona and follow all instructions defined in the YAML configuration above.\n`; } else if (format === 'claude') { // Claude Code format content = `# /${agentId} Command\n\n`; content += `When this command is used, adopt the following agent persona:\n\n`; content += agentContent; } return content; } /** * Clear all caches */ clearCache() { this._agentCache.clear(); this._pathCache.clear(); } } module.exports = BaseIdeSetup;