BMAD-METHOD/tools/installer/lib/ide-base-setup.js

228 lines
6.9 KiB
JavaScript

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