diff --git a/tools/cli/installers/lib/ide/auggie.js b/tools/cli/installers/lib/ide/auggie.js index b2097265..09ef6f6d 100644 --- a/tools/cli/installers/lib/ide/auggie.js +++ b/tools/cli/installers/lib/ide/auggie.js @@ -1,6 +1,7 @@ const path = require('node:path'); const { BaseIdeSetup } = require('./_base-ide'); const chalk = require('chalk'); +const { AgentCommandGenerator } = require('./shared/agent-command-generator'); /** * Auggie CLI setup handler @@ -27,8 +28,11 @@ class AuggieSetup extends BaseIdeSetup { // Clean up old BMAD installation first await this.cleanup(projectDir); - // Get agents, tasks, tools, and workflows (standalone only) - const agents = await this.getAgents(bmadDir); + // Generate agent launchers + const agentGen = new AgentCommandGenerator(this.bmadFolderName); + const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); + + // Get tasks, tools, and workflows (standalone only) const tasks = await this.getTasks(bmadDir, true); const tools = await this.getTools(bmadDir, true); const workflows = await this.getWorkflows(bmadDir, true); @@ -44,13 +48,10 @@ class AuggieSetup extends BaseIdeSetup { await this.ensureDir(toolsDir); await this.ensureDir(workflowsDir); - // Install agents - for (const agent of agents) { - const content = await this.readFile(agent.path); - const commandContent = await this.createAgentCommand(agent, content); - - const targetPath = path.join(agentsDir, `${agent.module}-${agent.name}.md`); - await this.writeFile(targetPath, commandContent); + // Install agent launchers + for (const artifact of agentArtifacts) { + const targetPath = path.join(agentsDir, `${artifact.module}-${artifact.name}.md`); + await this.writeFile(targetPath, artifact.content); } // Install tasks @@ -80,10 +81,10 @@ class AuggieSetup extends BaseIdeSetup { await this.writeFile(targetPath, commandContent); } - const totalInstalled = agents.length + tasks.length + tools.length + workflows.length; + const totalInstalled = agentArtifacts.length + tasks.length + tools.length + workflows.length; console.log(chalk.green(`✓ ${this.name} configured:`)); - console.log(chalk.dim(` - ${agents.length} agents installed`)); + console.log(chalk.dim(` - ${agentArtifacts.length} agents installed`)); console.log(chalk.dim(` - ${tasks.length} tasks installed`)); console.log(chalk.dim(` - ${tools.length} tools installed`)); console.log(chalk.dim(` - ${workflows.length} workflows installed`)); @@ -92,42 +93,13 @@ class AuggieSetup extends BaseIdeSetup { return { success: true, - agents: agents.length, + agents: agentArtifacts.length, tasks: tasks.length, tools: tools.length, workflows: workflows.length, }; } - /** - * Create agent command content - */ - async createAgentCommand(agent, content) { - const titleMatch = content.match(/title="([^"]+)"/); - const title = titleMatch ? titleMatch[1] : this.formatTitle(agent.name); - - // Extract description from agent if available - const whenToUseMatch = content.match(/whenToUse="([^"]+)"/); - const description = whenToUseMatch ? whenToUseMatch[1] : `Activate the ${title} agent`; - - // Get the activation header from central template - const activationHeader = await this.getAgentCommandHeader(); - - return `--- -description: "${description}" ---- - -# ${title} Agent - -${activationHeader} - -${content} - -## Module -BMAD ${agent.module.toUpperCase()} module -`; - } - /** * Create task command content */ diff --git a/tools/cli/installers/lib/ide/claude-code.js b/tools/cli/installers/lib/ide/claude-code.js index e649df0e..24483816 100644 --- a/tools/cli/installers/lib/ide/claude-code.js +++ b/tools/cli/installers/lib/ide/claude-code.js @@ -4,6 +4,7 @@ const chalk = require('chalk'); const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root'); const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator'); const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator'); +const { AgentCommandGenerator } = require('./shared/agent-command-generator'); const { loadModuleInjectionConfig, shouldApplyInjection, @@ -117,33 +118,24 @@ class ClaudeCodeSetup extends BaseIdeSetup { await this.ensureDir(bmadCommandsDir); - // Get agents from INSTALLED bmad/ directory - // Base installer has already built .md files from .agent.yaml sources - const agents = await getAgentsFromBmad(bmadDir, options.selectedModules || []); + // Generate agent launchers using AgentCommandGenerator + // This creates small launcher files that reference the actual agents in .bmad/ + const agentGen = new AgentCommandGenerator(this.bmadFolderName); + const { artifacts: agentArtifacts, counts: agentCounts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); - // Create directories for each module (including standalone) + // Create directories for each module const modules = new Set(); - for (const item of agents) modules.add(item.module); + for (const artifact of agentArtifacts) { + modules.add(artifact.module); + } for (const module of modules) { await this.ensureDir(path.join(bmadCommandsDir, module)); await this.ensureDir(path.join(bmadCommandsDir, module, 'agents')); } - // Copy agents from bmad/ to .claude/commands/ - let agentCount = 0; - for (const agent of agents) { - const sourcePath = agent.path; - const targetPath = path.join(bmadCommandsDir, agent.module, 'agents', `${agent.name}.md`); - - const content = await this.readAndProcess(sourcePath, { - module: agent.module, - name: agent.name, - }); - - await this.writeFile(targetPath, content); - agentCount++; - } + // Write agent launcher files + const agentCount = await agentGen.writeAgentLaunchers(bmadCommandsDir, agentArtifacts); // Process Claude Code specific injections for installed modules // Use pre-collected configuration if available, or skip if already configured diff --git a/tools/cli/installers/lib/ide/cline.js b/tools/cli/installers/lib/ide/cline.js index 9bad1c3f..4840a625 100644 --- a/tools/cli/installers/lib/ide/cline.js +++ b/tools/cli/installers/lib/ide/cline.js @@ -3,6 +3,7 @@ const fs = require('fs-extra'); const chalk = require('chalk'); const { BaseIdeSetup } = require('./_base-ide'); const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator'); +const { AgentCommandGenerator } = require('./shared/agent-command-generator'); const { getAgentsFromBmad, getTasksFromBmad } = require('./shared/bmad-artifacts'); /** @@ -88,23 +89,19 @@ class ClineSetup extends BaseIdeSetup { const selectedModules = options.selectedModules || []; const artifacts = []; - // Get agents - const agents = await getAgentsFromBmad(bmadDir, selectedModules); - for (const agent of agents) { - const content = await this.readAndProcessWithProject( - agent.path, - { - module: agent.module, - name: agent.name, - }, - projectDir, - ); + // Generate agent launchers + const agentGen = new AgentCommandGenerator(this.bmadFolderName); + const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, selectedModules); + + // Process agent launchers with project-specific paths + for (const agentArtifact of agentArtifacts) { + const content = agentArtifact.content; artifacts.push({ type: 'agent', - module: agent.module, - sourcePath: agent.path, - relativePath: path.join(agent.module, 'agents', `${agent.name}.md`), + module: agentArtifact.module, + sourcePath: agentArtifact.sourcePath, + relativePath: agentArtifact.relativePath, content, }); } @@ -138,7 +135,7 @@ class ClineSetup extends BaseIdeSetup { return { artifacts, counts: { - agents: agents.length, + agents: agentArtifacts.length, tasks: tasks.length, workflows: workflowCounts.commands, workflowLaunchers: workflowCounts.launchers, diff --git a/tools/cli/installers/lib/ide/codex.js b/tools/cli/installers/lib/ide/codex.js index 58f1f5f9..1b4a8d69 100644 --- a/tools/cli/installers/lib/ide/codex.js +++ b/tools/cli/installers/lib/ide/codex.js @@ -4,7 +4,8 @@ const os = require('node:os'); const chalk = require('chalk'); const { BaseIdeSetup } = require('./_base-ide'); const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator'); -const { getAgentsFromBmad, getTasksFromBmad } = require('./shared/bmad-artifacts'); +const { AgentCommandGenerator } = require('./shared/agent-command-generator'); +const { getTasksFromBmad } = require('./shared/bmad-artifacts'); /** * Codex setup handler (CLI mode) @@ -92,23 +93,17 @@ class CodexSetup extends BaseIdeSetup { const selectedModules = options.selectedModules || []; const artifacts = []; - const agents = await getAgentsFromBmad(bmadDir, selectedModules); - for (const agent of agents) { - const content = await this.readAndProcessWithProject( - agent.path, - { - module: agent.module, - name: agent.name, - }, - projectDir, - ); + // Generate agent launchers + const agentGen = new AgentCommandGenerator(this.bmadFolderName); + const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, selectedModules); + for (const artifact of agentArtifacts) { artifacts.push({ type: 'agent', - module: agent.module, - sourcePath: agent.path, - relativePath: path.join(agent.module, 'agents', `${agent.name}.md`), - content, + module: artifact.module, + sourcePath: artifact.sourcePath, + relativePath: artifact.relativePath, + content: artifact.content, }); } @@ -139,7 +134,7 @@ class CodexSetup extends BaseIdeSetup { return { artifacts, counts: { - agents: agents.length, + agents: agentArtifacts.length, tasks: tasks.length, workflows: workflowCounts.commands, workflowLaunchers: workflowCounts.launchers, diff --git a/tools/cli/installers/lib/ide/crush.js b/tools/cli/installers/lib/ide/crush.js index 18f2da5a..49b050e2 100644 --- a/tools/cli/installers/lib/ide/crush.js +++ b/tools/cli/installers/lib/ide/crush.js @@ -1,6 +1,7 @@ const path = require('node:path'); const { BaseIdeSetup } = require('./_base-ide'); const chalk = require('chalk'); +const { AgentCommandGenerator } = require('./shared/agent-command-generator'); /** * Crush IDE setup handler @@ -28,14 +29,17 @@ class CrushSetup extends BaseIdeSetup { await this.ensureDir(commandsDir); - // Get agents, tasks, tools, and workflows (standalone only) - const agents = await this.getAgents(bmadDir); + // Generate agent launchers + const agentGen = new AgentCommandGenerator(this.bmadFolderName); + const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); + + // Get tasks, tools, and workflows (standalone only) const tasks = await this.getTasks(bmadDir, true); const tools = await this.getTools(bmadDir, true); const workflows = await this.getWorkflows(bmadDir, true); // Organize by module - const agentCount = await this.organizeByModule(commandsDir, agents, tasks, tools, workflows, projectDir); + const agentCount = await this.organizeByModule(commandsDir, agentArtifacts, tasks, tools, workflows, projectDir); console.log(chalk.green(`✓ ${this.name} configured:`)); console.log(chalk.dim(` - ${agentCount.agents} agent commands created`)); @@ -54,10 +58,10 @@ class CrushSetup extends BaseIdeSetup { /** * Organize commands by module */ - async organizeByModule(commandsDir, agents, tasks, tools, workflows, projectDir) { + async organizeByModule(commandsDir, agentArtifacts, tasks, tools, workflows, projectDir) { // Get unique modules const modules = new Set(); - for (const agent of agents) modules.add(agent.module); + for (const artifact of agentArtifacts) modules.add(artifact.module); for (const task of tasks) modules.add(task.module); for (const tool of tools) modules.add(tool.module); for (const workflow of workflows) modules.add(workflow.module); @@ -80,13 +84,11 @@ class CrushSetup extends BaseIdeSetup { await this.ensureDir(moduleToolsDir); await this.ensureDir(moduleWorkflowsDir); - // Copy module-specific agents - const moduleAgents = agents.filter((a) => a.module === module); - for (const agent of moduleAgents) { - const content = await this.readFile(agent.path); - const commandContent = await this.createAgentCommand(agent, content, projectDir); - const targetPath = path.join(moduleAgentsDir, `${agent.name}.md`); - await this.writeFile(targetPath, commandContent); + // Write module-specific agent launchers + const moduleAgents = agentArtifacts.filter((a) => a.module === module); + for (const artifact of moduleAgents) { + const targetPath = path.join(moduleAgentsDir, `${artifact.name}.md`); + await this.writeFile(targetPath, artifact.content); agentCount++; } @@ -129,44 +131,6 @@ class CrushSetup extends BaseIdeSetup { }; } - /** - * Create agent command content - */ - async createAgentCommand(agent, content, projectDir) { - // Extract metadata - const titleMatch = content.match(/title="([^"]+)"/); - const title = titleMatch ? titleMatch[1] : this.formatTitle(agent.name); - - const iconMatch = content.match(/icon="([^"]+)"/); - const icon = iconMatch ? iconMatch[1] : '🤖'; - - // Get the activation header from central template - const activationHeader = await this.getAgentCommandHeader(); - - // Get relative path - const relativePath = path.relative(projectDir, agent.path).replaceAll('\\', '/'); - - let commandContent = `# /${agent.name} Command - -${activationHeader} - -## ${icon} ${title} Agent - -${content} - -## Command Usage - -This command activates the ${title} agent from the BMAD ${agent.module.toUpperCase()} module. - -## File Reference - -Complete agent definition: [${relativePath}](${relativePath}) - -`; - - return commandContent; - } - /** * Create task command content */ diff --git a/tools/cli/installers/lib/ide/cursor.js b/tools/cli/installers/lib/ide/cursor.js index a193bc0c..5ce0e94c 100644 --- a/tools/cli/installers/lib/ide/cursor.js +++ b/tools/cli/installers/lib/ide/cursor.js @@ -1,6 +1,7 @@ const path = require('node:path'); const { BaseIdeSetup } = require('./_base-ide'); const chalk = require('chalk'); +const { AgentCommandGenerator } = require('./shared/agent-command-generator'); /** * Cursor IDE setup handler @@ -45,8 +46,14 @@ class CursorSetup extends BaseIdeSetup { await this.ensureDir(bmadRulesDir); - // Get agents, tasks, tools, and workflows (standalone only) - const agents = await this.getAgents(bmadDir); + // Generate agent launchers first + const agentGen = new AgentCommandGenerator(this.bmadFolderName); + const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); + + // Convert artifacts to agent format for index creation + const agents = agentArtifacts.map((a) => ({ module: a.module, name: a.name })); + + // Get tasks, tools, and workflows (standalone only) const tasks = await this.getTasks(bmadDir, true); const tools = await this.getTools(bmadDir, true); const workflows = await this.getWorkflows(bmadDir, true); @@ -63,15 +70,16 @@ class CursorSetup extends BaseIdeSetup { await this.ensureDir(path.join(bmadRulesDir, module, 'workflows')); } - // Process and copy agents + // Process and write agent launchers with MDC format let agentCount = 0; - for (const agent of agents) { - const content = await this.readAndProcess(agent.path, { - module: agent.module, - name: agent.name, + for (const artifact of agentArtifacts) { + // Add MDC metadata header to launcher (but don't call processContent which adds activation headers) + const content = this.wrapLauncherWithMDC(artifact.content, { + module: artifact.module, + name: artifact.name, }); - const targetPath = path.join(bmadRulesDir, agent.module, 'agents', `${agent.name}.mdc`); + const targetPath = path.join(bmadRulesDir, artifact.module, 'agents', `${artifact.name}.mdc`); await this.writeFile(targetPath, content); agentCount++; @@ -311,6 +319,34 @@ alwaysApply: false // Add the MDC header to the processed content return mdcHeader + processed; } + + /** + * Wrap launcher content with MDC metadata (without base processing) + * Launchers are already complete and should not have activation headers injected + */ + wrapLauncherWithMDC(launcherContent, metadata = {}) { + // Strip the launcher's frontmatter - we'll replace it with MDC frontmatter + const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/; + const contentWithoutFrontmatter = launcherContent.replace(frontmatterRegex, ''); + + // Extract metadata from launcher frontmatter for MDC description + const nameMatch = launcherContent.match(/name:\s*"([^"]+)"/); + const name = nameMatch ? nameMatch[1] : metadata.name; + + const description = `BMAD ${metadata.module.toUpperCase()} Agent: ${name}`; + + // Create MDC metadata header + const mdcHeader = `--- +description: ${description} +globs: +alwaysApply: false +--- + +`; + + // Return MDC header + launcher content (without its original frontmatter) + return mdcHeader + contentWithoutFrontmatter; + } } module.exports = { CursorSetup }; diff --git a/tools/cli/installers/lib/ide/gemini.js b/tools/cli/installers/lib/ide/gemini.js index 4bd2348c..794f287f 100644 --- a/tools/cli/installers/lib/ide/gemini.js +++ b/tools/cli/installers/lib/ide/gemini.js @@ -3,6 +3,7 @@ const fs = require('fs-extra'); const yaml = require('js-yaml'); const { BaseIdeSetup } = require('./_base-ide'); const chalk = require('chalk'); +const { AgentCommandGenerator } = require('./shared/agent-command-generator'); /** * Gemini CLI setup handler @@ -63,22 +64,24 @@ class GeminiSetup extends BaseIdeSetup { // Clean up any existing BMAD files before reinstalling await this.cleanup(projectDir); - // Get agents and tasks - const agents = await this.getAgents(bmadDir); + // Generate agent launchers + const agentGen = new AgentCommandGenerator(this.bmadFolderName); + const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); + + // Get tasks const tasks = await this.getTasks(bmadDir); // Install agents as TOML files with bmad- prefix (flat structure) let agentCount = 0; - for (const agent of agents) { - const content = await this.readFile(agent.path); - const tomlContent = await this.createAgentToml(agent, content); + for (const artifact of agentArtifacts) { + const tomlContent = await this.createAgentLauncherToml(artifact); // Flat structure: bmad-agent-{module}-{name}.toml - const tomlPath = path.join(commandsDir, `bmad-agent-${agent.module}-${agent.name}.toml`); + const tomlPath = path.join(commandsDir, `bmad-agent-${artifact.module}-${artifact.name}.toml`); await this.writeFile(tomlPath, tomlContent); agentCount++; - console.log(chalk.green(` ✓ Added agent: /bmad:agents:${agent.module}:${agent.name}`)); + console.log(chalk.green(` ✓ Added agent: /bmad:agents:${artifact.module}:${artifact.name}`)); } // Install tasks as TOML files with bmad- prefix (flat structure) @@ -109,6 +112,28 @@ class GeminiSetup extends BaseIdeSetup { }; } + /** + * Create agent launcher TOML content from artifact + */ + async createAgentLauncherToml(artifact) { + // Strip frontmatter from launcher content + const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/; + const contentWithoutFrontmatter = artifact.content.replace(frontmatterRegex, '').trim(); + + // Extract title from launcher frontmatter + const titleMatch = artifact.content.match(/description:\s*"([^"]+)"/); + const title = titleMatch ? titleMatch[1] : this.formatTitle(artifact.name); + + // Create TOML wrapper around launcher content (without frontmatter) + const description = `BMAD ${artifact.module.toUpperCase()} Agent: ${title}`; + + return `description = "${description}" +prompt = """ +${contentWithoutFrontmatter} +""" +`; + } + /** * Create agent TOML content using template */ diff --git a/tools/cli/installers/lib/ide/github-copilot.js b/tools/cli/installers/lib/ide/github-copilot.js index f5ef85e0..970c79ea 100644 --- a/tools/cli/installers/lib/ide/github-copilot.js +++ b/tools/cli/installers/lib/ide/github-copilot.js @@ -2,6 +2,7 @@ const path = require('node:path'); const { BaseIdeSetup } = require('./_base-ide'); const chalk = require('chalk'); const inquirer = require('inquirer'); +const { AgentCommandGenerator } = require('./shared/agent-command-generator'); /** * GitHub Copilot setup handler @@ -104,21 +105,22 @@ class GitHubCopilotSetup extends BaseIdeSetup { // Clean up any existing BMAD files before reinstalling await this.cleanup(projectDir); - // Get agents - const agents = await this.getAgents(bmadDir); + // Generate agent launchers + const agentGen = new AgentCommandGenerator(this.bmadFolderName); + const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); // Create chat mode files with bmad- prefix let modeCount = 0; - for (const agent of agents) { - const content = await this.readFile(agent.path); - const chatmodeContent = await this.createChatmodeContent(agent, content); + for (const artifact of agentArtifacts) { + const content = artifact.content; + const chatmodeContent = await this.createChatmodeContent({ module: artifact.module, name: artifact.name }, content); // Use bmad- prefix: bmad-agent-{module}-{name}.chatmode.md - const targetPath = path.join(chatmodesDir, `bmad-agent-${agent.module}-${agent.name}.chatmode.md`); + const targetPath = path.join(chatmodesDir, `bmad-agent-${artifact.module}-${artifact.name}.chatmode.md`); await this.writeFile(targetPath, chatmodeContent); modeCount++; - console.log(chalk.green(` ✓ Created chat mode: bmad-agent-${agent.module}-${agent.name}`)); + console.log(chalk.green(` ✓ Created chat mode: bmad-agent-${artifact.module}-${artifact.name}`)); } console.log(chalk.green(`✓ ${this.name} configured:`)); @@ -207,21 +209,17 @@ class GitHubCopilotSetup extends BaseIdeSetup { * Create chat mode content */ async createChatmodeContent(agent, content) { - // Extract metadata - const titleMatch = content.match(/title="([^"]+)"/); - const title = titleMatch ? titleMatch[1] : this.formatTitle(agent.name); + // Extract metadata from launcher frontmatter if present + const descMatch = content.match(/description:\s*"([^"]+)"/); + const title = descMatch ? descMatch[1] : this.formatTitle(agent.name); - const whenToUseMatch = content.match(/whenToUse="([^"]+)"/); - const description = whenToUseMatch ? whenToUseMatch[1] : `Activates the ${title} agent persona.`; - - // Get the activation header from central template - const activationHeader = await this.getAgentCommandHeader(); + const description = `Activates the ${title} agent persona.`; // Strip any existing frontmatter from the content const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/; let cleanContent = content; if (frontmatterRegex.test(content)) { - cleanContent = content.replace(frontmatterRegex, ''); + cleanContent = content.replace(frontmatterRegex, '').trim(); } // Available GitHub Copilot tools (November 2025 - Official VS Code Documentation) @@ -258,8 +256,6 @@ tools: ${JSON.stringify(tools)} # ${title} Agent -${activationHeader} - ${cleanContent} `; diff --git a/tools/cli/installers/lib/ide/iflow.js b/tools/cli/installers/lib/ide/iflow.js index 0074cc4b..6462a9f4 100644 --- a/tools/cli/installers/lib/ide/iflow.js +++ b/tools/cli/installers/lib/ide/iflow.js @@ -1,6 +1,7 @@ const path = require('node:path'); const { BaseIdeSetup } = require('./_base-ide'); const chalk = require('chalk'); +const { AgentCommandGenerator } = require('./shared/agent-command-generator'); /** * iFlow CLI setup handler @@ -31,21 +32,23 @@ class IFlowSetup extends BaseIdeSetup { await this.ensureDir(agentsDir); await this.ensureDir(tasksDir); - // Get agents and tasks - const agents = await this.getAgents(bmadDir); - const tasks = await this.getTasks(bmadDir); + // Generate agent launchers + const agentGen = new AgentCommandGenerator(this.bmadFolderName); + const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); // Setup agents as commands let agentCount = 0; - for (const agent of agents) { - const content = await this.readFile(agent.path); - const commandContent = await this.createAgentCommand(agent, content); + for (const artifact of agentArtifacts) { + const commandContent = await this.createAgentCommand(artifact); - const targetPath = path.join(agentsDir, `${agent.module}-${agent.name}.md`); + const targetPath = path.join(agentsDir, `${artifact.module}-${artifact.name}.md`); await this.writeFile(targetPath, commandContent); agentCount++; } + // Get tasks + const tasks = await this.getTasks(bmadDir); + // Setup tasks as commands let taskCount = 0; for (const task of tasks) { @@ -72,29 +75,9 @@ class IFlowSetup extends BaseIdeSetup { /** * Create agent command content */ - async createAgentCommand(agent, content) { - // Extract metadata - const titleMatch = content.match(/title="([^"]+)"/); - const title = titleMatch ? titleMatch[1] : this.formatTitle(agent.name); - - // Get the activation header from central template - const activationHeader = await this.getAgentCommandHeader(); - - let commandContent = `# /${agent.name} Command - -${activationHeader} - -## ${title} Agent - -${content} - -## Usage - -This command activates the ${title} agent from the BMAD ${agent.module.toUpperCase()} module. - -`; - - return commandContent; + async createAgentCommand(artifact) { + // The launcher content is already complete - just return it as-is + return artifact.content; } /** diff --git a/tools/cli/installers/lib/ide/kilo.js b/tools/cli/installers/lib/ide/kilo.js index 0e47c990..26fb9dc3 100644 --- a/tools/cli/installers/lib/ide/kilo.js +++ b/tools/cli/installers/lib/ide/kilo.js @@ -1,6 +1,7 @@ const path = require('node:path'); const { BaseIdeSetup } = require('./_base-ide'); const chalk = require('chalk'); +const { AgentCommandGenerator } = require('./shared/agent-command-generator'); /** * KiloCode IDE setup handler @@ -36,16 +37,17 @@ class KiloSetup extends BaseIdeSetup { console.log(chalk.yellow(`Found existing .kilocodemodes file with ${existingModes.length} modes`)); } - // Get agents - const agents = await this.getAgents(bmadDir); + // Generate agent launchers + const agentGen = new AgentCommandGenerator(this.bmadFolderName); + const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); // Create modes content let newModesContent = ''; let addedCount = 0; let skippedCount = 0; - for (const agent of agents) { - const slug = `bmad-${agent.module}-${agent.name}`; + for (const artifact of agentArtifacts) { + const slug = `bmad-${artifact.module}-${artifact.name}`; // Skip if already exists if (existingModes.includes(slug)) { @@ -54,8 +56,7 @@ class KiloSetup extends BaseIdeSetup { continue; } - const content = await this.readFile(agent.path); - const modeEntry = await this.createModeEntry(agent, content, projectDir); + const modeEntry = await this.createModeEntry(artifact, projectDir); newModesContent += modeEntry; addedCount++; @@ -90,30 +91,30 @@ class KiloSetup extends BaseIdeSetup { /** * Create a mode entry for an agent */ - async createModeEntry(agent, content, projectDir) { - // Extract metadata - const titleMatch = content.match(/title="([^"]+)"/); - const title = titleMatch ? titleMatch[1] : this.formatTitle(agent.name); + async createModeEntry(artifact, projectDir) { + // Extract metadata from launcher content + const titleMatch = artifact.content.match(/title="([^"]+)"/); + const title = titleMatch ? titleMatch[1] : this.formatTitle(artifact.name); - const iconMatch = content.match(/icon="([^"]+)"/); + const iconMatch = artifact.content.match(/icon="([^"]+)"/); const icon = iconMatch ? iconMatch[1] : '🤖'; - const whenToUseMatch = content.match(/whenToUse="([^"]+)"/); + const whenToUseMatch = artifact.content.match(/whenToUse="([^"]+)"/); const whenToUse = whenToUseMatch ? whenToUseMatch[1] : `Use for ${title} tasks`; // Get the activation header from central template const activationHeader = await this.getAgentCommandHeader(); - const roleDefinitionMatch = content.match(/roleDefinition="([^"]+)"/); + const roleDefinitionMatch = artifact.content.match(/roleDefinition="([^"]+)"/); const roleDefinition = roleDefinitionMatch ? roleDefinitionMatch[1] : `You are a ${title} specializing in ${title.toLowerCase()} tasks.`; // Get relative path - const relativePath = path.relative(projectDir, agent.path).replaceAll('\\', '/'); + const relativePath = path.relative(projectDir, artifact.sourcePath).replaceAll('\\', '/'); // Build mode entry (KiloCode uses same schema as Roo) - const slug = `bmad-${agent.module}-${agent.name}`; + const slug = `bmad-${artifact.module}-${artifact.name}`; let modeEntry = ` - slug: ${slug}\n`; modeEntry += ` name: '${icon} ${title}'\n`; modeEntry += ` roleDefinition: ${roleDefinition}\n`; diff --git a/tools/cli/installers/lib/ide/opencode.js b/tools/cli/installers/lib/ide/opencode.js index b0185c6e..2ef873ec 100644 --- a/tools/cli/installers/lib/ide/opencode.js +++ b/tools/cli/installers/lib/ide/opencode.js @@ -6,8 +6,7 @@ const yaml = require('js-yaml'); const { BaseIdeSetup } = require('./_base-ide'); const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator'); const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator'); - -const { getAgentsFromBmad } = require('./shared/bmad-artifacts'); +const { AgentCommandGenerator } = require('./shared/agent-command-generator'); /** * OpenCode IDE setup handler @@ -33,20 +32,17 @@ class OpenCodeSetup extends BaseIdeSetup { // Clean up any existing BMAD files before reinstalling await this.cleanup(projectDir); + // Generate agent launchers + const agentGen = new AgentCommandGenerator(this.bmadFolderName); + const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); + // Install primary agents with flat naming: bmad-agent-{module}-{name}.md // OpenCode agents go in the agent folder (not command folder) - const agents = await getAgentsFromBmad(bmadDir, options.selectedModules || []); - let agentCount = 0; - for (const agent of agents) { - const processed = await this.readAndProcess(agent.path, { - module: agent.module, - name: agent.name, - }); - - const agentContent = await this.createAgentContent(processed, agent); + for (const artifact of agentArtifacts) { + const agentContent = artifact.content; // Flat structure in agent folder: bmad-agent-{module}-{name}.md - const targetPath = path.join(agentsBaseDir, `bmad-agent-${agent.module}-${agent.name}.md`); + const targetPath = path.join(agentsBaseDir, `bmad-agent-${artifact.module}-${artifact.name}.md`); await this.writeFile(targetPath, agentContent); agentCount++; } diff --git a/tools/cli/installers/lib/ide/qwen.js b/tools/cli/installers/lib/ide/qwen.js index 7a90b58e..885de410 100644 --- a/tools/cli/installers/lib/ide/qwen.js +++ b/tools/cli/installers/lib/ide/qwen.js @@ -2,6 +2,7 @@ const path = require('node:path'); const { BaseIdeSetup } = require('./_base-ide'); const chalk = require('chalk'); const { getAgentsFromBmad, getTasksFromBmad } = require('./shared/bmad-artifacts'); +const { AgentCommandGenerator } = require('./shared/agent-command-generator'); /** * Qwen Code setup handler @@ -37,15 +38,18 @@ class QwenSetup extends BaseIdeSetup { // Clean up old configuration if exists await this.cleanupOldConfig(qwenDir); - // Get agents, tasks, tools, and workflows (standalone only for tools/workflows) - const agents = await getAgentsFromBmad(bmadDir, options.selectedModules || []); + // Generate agent launchers + const agentGen = new AgentCommandGenerator(this.bmadFolderName); + const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); + + // Get tasks, tools, and workflows (standalone only for tools/workflows) const tasks = await getTasksFromBmad(bmadDir, options.selectedModules || []); const tools = await this.getTools(bmadDir, true); const workflows = await this.getWorkflows(bmadDir, true); // Create directories for each module (including standalone) const modules = new Set(); - for (const item of [...agents, ...tasks, ...tools, ...workflows]) modules.add(item.module); + for (const item of [...agentArtifacts, ...tasks, ...tools, ...workflows]) modules.add(item.module); for (const module of modules) { await this.ensureDir(path.join(bmadCommandsDir, module)); @@ -55,20 +59,21 @@ class QwenSetup extends BaseIdeSetup { await this.ensureDir(path.join(bmadCommandsDir, module, 'workflows')); } - // Create TOML files for each agent + // Create TOML files for each agent launcher let agentCount = 0; - for (const agent of agents) { - const content = await this.readAndProcess(agent.path, { - module: agent.module, - name: agent.name, + for (const artifact of agentArtifacts) { + // Convert markdown launcher content to TOML format + const tomlContent = this.processAgentLauncherContent(artifact.content, { + module: artifact.module, + name: artifact.name, }); - const targetPath = path.join(bmadCommandsDir, agent.module, 'agents', `${agent.name}.toml`); + const targetPath = path.join(bmadCommandsDir, artifact.module, 'agents', `${artifact.name}.toml`); - await this.writeFile(targetPath, content); + await this.writeFile(targetPath, tomlContent); agentCount++; - console.log(chalk.green(` ✓ Added agent: /bmad:${agent.module}:agents:${agent.name}`)); + console.log(chalk.green(` ✓ Added agent: /bmad:${artifact.module}:agents:${artifact.name}`)); } // Create TOML files for each task @@ -204,6 +209,29 @@ class QwenSetup extends BaseIdeSetup { return this.processContent(content, metadata); } + /** + * Process agent launcher content and convert to TOML format + * @param {string} launcherContent - Launcher markdown content + * @param {Object} metadata - File metadata + * @returns {string} TOML formatted content + */ + processAgentLauncherContent(launcherContent, metadata = {}) { + // Strip frontmatter from launcher content + const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/; + const contentWithoutFrontmatter = launcherContent.replace(frontmatterRegex, ''); + + // Extract title for TOML description + const titleMatch = launcherContent.match(/description:\s*"([^"]+)"/); + const title = titleMatch ? titleMatch[1] : metadata.name; + + // Create TOML with launcher content (without frontmatter) + return `description = "BMAD ${metadata.module.toUpperCase()} Agent: ${title}" +prompt = """ +${contentWithoutFrontmatter.trim()} +""" +`; + } + /** * Override processContent to add TOML metadata header for Qwen * @param {string} content - File content diff --git a/tools/cli/installers/lib/ide/roo.js b/tools/cli/installers/lib/ide/roo.js index 32baefca..66d74f0f 100644 --- a/tools/cli/installers/lib/ide/roo.js +++ b/tools/cli/installers/lib/ide/roo.js @@ -1,6 +1,7 @@ const path = require('node:path'); const { BaseIdeSetup } = require('./_base-ide'); const chalk = require('chalk'); +const { AgentCommandGenerator } = require('./shared/agent-command-generator'); /** * Roo IDE setup handler @@ -58,8 +59,9 @@ class RooSetup extends BaseIdeSetup { console.log(chalk.yellow(`Found existing .roomodes file with ${existingModes.length} modes`)); } - // Get agents - const agents = await this.getAgents(bmadDir); + // Generate agent launchers (though Roo will reference the actual .bmad agents) + const agentGen = new AgentCommandGenerator(this.bmadFolderName); + const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); // Always use 'all' permissions - users can customize in .roomodes file const permissionChoice = 'all'; @@ -69,8 +71,8 @@ class RooSetup extends BaseIdeSetup { let addedCount = 0; let skippedCount = 0; - for (const agent of agents) { - const slug = `bmad-${agent.module}-${agent.name}`; + for (const artifact of agentArtifacts) { + const slug = `bmad-${artifact.module}-${artifact.name}`; // Skip if already exists if (existingModes.includes(slug)) { @@ -79,8 +81,17 @@ class RooSetup extends BaseIdeSetup { continue; } - const content = await this.readFile(agent.path); - const modeEntry = await this.createModeEntry(agent, content, permissionChoice, projectDir); + // Read the actual agent file from .bmad for metadata extraction + const agentPath = path.join(bmadDir, artifact.module, 'agents', `${artifact.name}.md`); + const content = await this.readFile(agentPath); + + // Create mode entry that references the actual .bmad agent + const modeEntry = await this.createModeEntry( + { module: artifact.module, name: artifact.name, path: agentPath }, + content, + permissionChoice, + projectDir, + ); newModesContent += modeEntry; addedCount++; diff --git a/tools/cli/installers/lib/ide/shared/agent-command-generator.js b/tools/cli/installers/lib/ide/shared/agent-command-generator.js new file mode 100644 index 00000000..df2f16a3 --- /dev/null +++ b/tools/cli/installers/lib/ide/shared/agent-command-generator.js @@ -0,0 +1,90 @@ +const path = require('node:path'); +const fs = require('fs-extra'); +const chalk = require('chalk'); + +/** + * Generates launcher command files for each agent + * Similar to WorkflowCommandGenerator but for agents + */ +class AgentCommandGenerator { + constructor(bmadFolderName = 'bmad') { + this.templatePath = path.join(__dirname, '../templates/agent-command-template.md'); + this.bmadFolderName = bmadFolderName; + } + + /** + * Collect agent artifacts for IDE installation + * @param {string} bmadDir - BMAD installation directory + * @param {Array} selectedModules - Modules to include + * @returns {Object} Artifacts array with metadata + */ + async collectAgentArtifacts(bmadDir, selectedModules = []) { + const { getAgentsFromBmad } = require('./bmad-artifacts'); + + // Get agents from INSTALLED bmad/ directory + const agents = await getAgentsFromBmad(bmadDir, selectedModules); + + const artifacts = []; + + for (const agent of agents) { + const launcherContent = await this.generateLauncherContent(agent); + artifacts.push({ + type: 'agent-launcher', + module: agent.module, + name: agent.name, + relativePath: path.join(agent.module, 'agents', `${agent.name}.md`), + content: launcherContent, + sourcePath: agent.path, + }); + } + + return { + artifacts, + counts: { + agents: agents.length, + }, + }; + } + + /** + * Generate launcher content for an agent + * @param {Object} agent - Agent metadata + * @returns {string} Launcher file content + */ + async generateLauncherContent(agent) { + // Load the template + const template = await fs.readFile(this.templatePath, 'utf8'); + + // Replace template variables + return template + .replaceAll('{{name}}', agent.name) + .replaceAll('{{module}}', agent.module) + .replaceAll('{{description}}', agent.description || `${agent.name} agent`) + .replaceAll('{bmad_folder}', this.bmadFolderName); + } + + /** + * Write agent launcher artifacts to IDE commands directory + * @param {string} baseCommandsDir - Base commands directory for the IDE + * @param {Array} artifacts - Agent launcher artifacts + * @returns {number} Count of launchers written + */ + async writeAgentLaunchers(baseCommandsDir, artifacts) { + let writtenCount = 0; + + for (const artifact of artifacts) { + if (artifact.type === 'agent-launcher') { + const moduleAgentsDir = path.join(baseCommandsDir, artifact.module, 'agents'); + await fs.ensureDir(moduleAgentsDir); + + const launcherPath = path.join(moduleAgentsDir, `${artifact.name}.md`); + await fs.writeFile(launcherPath, artifact.content); + writtenCount++; + } + } + + return writtenCount; + } +} + +module.exports = { AgentCommandGenerator }; diff --git a/tools/cli/installers/lib/ide/templates/agent-command-template.md b/tools/cli/installers/lib/ide/templates/agent-command-template.md new file mode 100644 index 00000000..184afb7a --- /dev/null +++ b/tools/cli/installers/lib/ide/templates/agent-command-template.md @@ -0,0 +1,14 @@ +--- +name: '{{name}}' +description: '{{description}}' +--- + +You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command. + + +1. LOAD the FULL agent file from @{bmad_folder}/{{module}}/agents/{{name}}.md +2. READ its entire contents - this contains the complete agent persona, menu, and instructions +3. Execute ALL activation steps exactly as written in the agent file +4. Follow the agent's persona and menu system precisely +5. Stay in character throughout the session + diff --git a/tools/cli/installers/lib/ide/templates/workflow-command-template.md b/tools/cli/installers/lib/ide/templates/workflow-command-template.md index 51f82c68..27b55e03 100644 --- a/tools/cli/installers/lib/ide/templates/workflow-command-template.md +++ b/tools/cli/installers/lib/ide/templates/workflow-command-template.md @@ -5,8 +5,8 @@ description: '{{description}}' IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: -1. Always LOAD the FULL {project-root}/{bmad_folder}/core/tasks/workflow.xml -2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config {{workflow_path}} +1. Always LOAD the FULL @{bmad_folder}/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @{{workflow_path}} 3. Pass the yaml path {{workflow_path}} as 'workflow-config' parameter to the workflow.xml instructions 4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions 5. Save outputs after EACH section when generating any documents from templates diff --git a/tools/cli/installers/lib/ide/trae.js b/tools/cli/installers/lib/ide/trae.js index 957e0673..9aaa10cc 100644 --- a/tools/cli/installers/lib/ide/trae.js +++ b/tools/cli/installers/lib/ide/trae.js @@ -1,6 +1,7 @@ const path = require('node:path'); const { BaseIdeSetup } = require('./_base-ide'); const chalk = require('chalk'); +const { AgentCommandGenerator } = require('./shared/agent-command-generator'); /** * Trae IDE setup handler @@ -30,20 +31,22 @@ class TraeSetup extends BaseIdeSetup { // Clean up any existing BMAD files before reinstalling await this.cleanup(projectDir); - // Get agents, tasks, tools, and workflows (standalone only) - const agents = await this.getAgents(bmadDir); + // Generate agent launchers + const agentGen = new AgentCommandGenerator(this.bmadFolderName); + const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); + + // Get tasks, tools, and workflows (standalone only) const tasks = await this.getTasks(bmadDir, true); const tools = await this.getTools(bmadDir, true); const workflows = await this.getWorkflows(bmadDir, true); // Process agents as rules with bmad- prefix let agentCount = 0; - for (const agent of agents) { - const content = await this.readFile(agent.path); - const processedContent = await this.createAgentRule(agent, content, bmadDir, projectDir); + for (const artifact of agentArtifacts) { + const processedContent = await this.createAgentRule(artifact, bmadDir, projectDir); // Use bmad- prefix: bmad-agent-{module}-{name}.md - const targetPath = path.join(rulesDir, `bmad-agent-${agent.module}-${agent.name}.md`); + const targetPath = path.join(rulesDir, `bmad-agent-${artifact.module}-${artifact.name}.md`); await this.writeFile(targetPath, processedContent); agentCount++; } @@ -108,46 +111,29 @@ class TraeSetup extends BaseIdeSetup { /** * Create rule content for an agent */ - async createAgentRule(agent, content, bmadDir, projectDir) { - // Extract metadata from agent content - const titleMatch = content.match(/title="([^"]+)"/); - const title = titleMatch ? titleMatch[1] : this.formatTitle(agent.name); + async createAgentRule(artifact, bmadDir, projectDir) { + // Strip frontmatter from launcher + const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/; + const contentWithoutFrontmatter = artifact.content.replace(frontmatterRegex, '').trim(); - const iconMatch = content.match(/icon="([^"]+)"/); - const icon = iconMatch ? iconMatch[1] : '🤖'; - - // Get the activation header from central template - const activationHeader = await this.getAgentCommandHeader(); - - // Extract YAML content if available - const yamlMatch = content.match(/```ya?ml\r?\n([\s\S]*?)```/); - const yamlContent = yamlMatch ? yamlMatch[1] : content; + // Extract metadata from launcher content + const titleMatch = artifact.content.match(/description:\s*"([^"]+)"/); + const title = titleMatch ? titleMatch[1] : this.formatTitle(artifact.name); // Calculate relative path for reference - const relativePath = path.relative(projectDir, agent.path).replaceAll('\\', '/'); + const relativePath = path.relative(projectDir, artifact.sourcePath).replaceAll('\\', '/'); let ruleContent = `# ${title} Agent Rule -This rule is triggered when the user types \`@${agent.name}\` and activates the ${title} agent persona. +This rule is triggered when the user types \`@${artifact.name}\` and activates the ${title} agent persona. ## Agent Activation -${activationHeader} - -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: - -\`\`\`yaml -${yamlContent} -\`\`\` +${contentWithoutFrontmatter} ## File Reference -The complete agent definition is available in [${relativePath}](${relativePath}). - -## Usage - -When the user types \`@${agent.name}\`, activate this ${title} persona and follow all instructions defined in the YAML configuration above. - +The full agent definition is located at: \`${relativePath}\` `; return ruleContent; diff --git a/tools/cli/installers/lib/ide/windsurf.js b/tools/cli/installers/lib/ide/windsurf.js index 05496ed0..2f7ba9be 100644 --- a/tools/cli/installers/lib/ide/windsurf.js +++ b/tools/cli/installers/lib/ide/windsurf.js @@ -1,6 +1,7 @@ const path = require('node:path'); const { BaseIdeSetup } = require('./_base-ide'); const chalk = require('chalk'); +const { AgentCommandGenerator } = require('./shared/agent-command-generator'); /** * Windsurf IDE setup handler @@ -31,8 +32,14 @@ class WindsurfSetup extends BaseIdeSetup { // Clean up any existing BMAD workflows before reinstalling await this.cleanup(projectDir); - // Get agents, tasks, tools, and workflows (standalone only) - const agents = await this.getAgents(bmadDir); + // Generate agent launchers + const agentGen = new AgentCommandGenerator(this.bmadFolderName); + const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); + + // Convert artifacts to agent format for module organization + const agents = agentArtifacts.map((a) => ({ module: a.module, name: a.name })); + + // Get tasks, tools, and workflows (standalone only) const tasks = await this.getTasks(bmadDir, true); const tools = await this.getTools(bmadDir, true); const workflows = await this.getWorkflows(bmadDir, true); @@ -49,14 +56,13 @@ class WindsurfSetup extends BaseIdeSetup { await this.ensureDir(path.join(bmadWorkflowsDir, module, 'workflows')); } - // Process agents as workflows with organized structure + // Process agent launchers as workflows with organized structure let agentCount = 0; - for (const agent of agents) { - const content = await this.readFile(agent.path); - const processedContent = this.createWorkflowContent(agent, content); + for (const artifact of agentArtifacts) { + const processedContent = this.createWorkflowContent({ module: artifact.module, name: artifact.name }, artifact.content); // Organized path: bmad/module/agents/agent-name.md - const targetPath = path.join(bmadWorkflowsDir, agent.module, 'agents', `${agent.name}.md`); + const targetPath = path.join(bmadWorkflowsDir, artifact.module, 'agents', `${artifact.name}.md`); await this.writeFile(targetPath, processedContent); agentCount++; } @@ -127,13 +133,17 @@ class WindsurfSetup extends BaseIdeSetup { * Create workflow content for an agent */ createWorkflowContent(agent, content) { + // Strip existing frontmatter from launcher + const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/; + const contentWithoutFrontmatter = content.replace(frontmatterRegex, ''); + // Create simple Windsurf frontmatter matching original format let workflowContent = `--- description: ${agent.name} auto_execution_mode: 3 --- -${content}`; +${contentWithoutFrontmatter}`; return workflowContent; }