/** * 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') 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