agents now are not duplicated and isntead cli commmands load from installed agent files

This commit is contained in:
Brian Madison 2025-11-09 20:24:56 -06:00
parent 7eb52520fa
commit f49a4731e7
18 changed files with 388 additions and 292 deletions

View File

@ -1,6 +1,7 @@
const path = require('node:path'); const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk'); const chalk = require('chalk');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
/** /**
* Auggie CLI setup handler * Auggie CLI setup handler
@ -27,8 +28,11 @@ class AuggieSetup extends BaseIdeSetup {
// Clean up old BMAD installation first // Clean up old BMAD installation first
await this.cleanup(projectDir); await this.cleanup(projectDir);
// Get agents, tasks, tools, and workflows (standalone only) // Generate agent launchers
const agents = await this.getAgents(bmadDir); 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 tasks = await this.getTasks(bmadDir, true);
const tools = await this.getTools(bmadDir, true); const tools = await this.getTools(bmadDir, true);
const workflows = await this.getWorkflows(bmadDir, true); const workflows = await this.getWorkflows(bmadDir, true);
@ -44,13 +48,10 @@ class AuggieSetup extends BaseIdeSetup {
await this.ensureDir(toolsDir); await this.ensureDir(toolsDir);
await this.ensureDir(workflowsDir); await this.ensureDir(workflowsDir);
// Install agents // Install agent launchers
for (const agent of agents) { for (const artifact of agentArtifacts) {
const content = await this.readFile(agent.path); const targetPath = path.join(agentsDir, `${artifact.module}-${artifact.name}.md`);
const commandContent = await this.createAgentCommand(agent, content); await this.writeFile(targetPath, artifact.content);
const targetPath = path.join(agentsDir, `${agent.module}-${agent.name}.md`);
await this.writeFile(targetPath, commandContent);
} }
// Install tasks // Install tasks
@ -80,10 +81,10 @@ class AuggieSetup extends BaseIdeSetup {
await this.writeFile(targetPath, commandContent); 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.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(` - ${tasks.length} tasks installed`));
console.log(chalk.dim(` - ${tools.length} tools installed`)); console.log(chalk.dim(` - ${tools.length} tools installed`));
console.log(chalk.dim(` - ${workflows.length} workflows installed`)); console.log(chalk.dim(` - ${workflows.length} workflows installed`));
@ -92,42 +93,13 @@ class AuggieSetup extends BaseIdeSetup {
return { return {
success: true, success: true,
agents: agents.length, agents: agentArtifacts.length,
tasks: tasks.length, tasks: tasks.length,
tools: tools.length, tools: tools.length,
workflows: workflows.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 * Create task command content
*/ */

View File

@ -4,6 +4,7 @@ const chalk = require('chalk');
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root'); const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator'); const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator'); const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { const {
loadModuleInjectionConfig, loadModuleInjectionConfig,
shouldApplyInjection, shouldApplyInjection,
@ -117,33 +118,24 @@ class ClaudeCodeSetup extends BaseIdeSetup {
await this.ensureDir(bmadCommandsDir); await this.ensureDir(bmadCommandsDir);
// Get agents from INSTALLED bmad/ directory // Generate agent launchers using AgentCommandGenerator
// Base installer has already built .md files from .agent.yaml sources // This creates small launcher files that reference the actual agents in .bmad/
const agents = await getAgentsFromBmad(bmadDir, options.selectedModules || []); 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(); 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) { for (const module of modules) {
await this.ensureDir(path.join(bmadCommandsDir, module)); await this.ensureDir(path.join(bmadCommandsDir, module));
await this.ensureDir(path.join(bmadCommandsDir, module, 'agents')); await this.ensureDir(path.join(bmadCommandsDir, module, 'agents'));
} }
// Copy agents from bmad/ to .claude/commands/ // Write agent launcher files
let agentCount = 0; const agentCount = await agentGen.writeAgentLaunchers(bmadCommandsDir, agentArtifacts);
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++;
}
// Process Claude Code specific injections for installed modules // Process Claude Code specific injections for installed modules
// Use pre-collected configuration if available, or skip if already configured // Use pre-collected configuration if available, or skip if already configured

View File

@ -3,6 +3,7 @@ const fs = require('fs-extra');
const chalk = require('chalk'); const chalk = require('chalk');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator'); const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { getAgentsFromBmad, getTasksFromBmad } = require('./shared/bmad-artifacts'); const { getAgentsFromBmad, getTasksFromBmad } = require('./shared/bmad-artifacts');
/** /**
@ -88,23 +89,19 @@ class ClineSetup extends BaseIdeSetup {
const selectedModules = options.selectedModules || []; const selectedModules = options.selectedModules || [];
const artifacts = []; const artifacts = [];
// Get agents // Generate agent launchers
const agents = await getAgentsFromBmad(bmadDir, selectedModules); const agentGen = new AgentCommandGenerator(this.bmadFolderName);
for (const agent of agents) { const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, selectedModules);
const content = await this.readAndProcessWithProject(
agent.path, // Process agent launchers with project-specific paths
{ for (const agentArtifact of agentArtifacts) {
module: agent.module, const content = agentArtifact.content;
name: agent.name,
},
projectDir,
);
artifacts.push({ artifacts.push({
type: 'agent', type: 'agent',
module: agent.module, module: agentArtifact.module,
sourcePath: agent.path, sourcePath: agentArtifact.sourcePath,
relativePath: path.join(agent.module, 'agents', `${agent.name}.md`), relativePath: agentArtifact.relativePath,
content, content,
}); });
} }
@ -138,7 +135,7 @@ class ClineSetup extends BaseIdeSetup {
return { return {
artifacts, artifacts,
counts: { counts: {
agents: agents.length, agents: agentArtifacts.length,
tasks: tasks.length, tasks: tasks.length,
workflows: workflowCounts.commands, workflows: workflowCounts.commands,
workflowLaunchers: workflowCounts.launchers, workflowLaunchers: workflowCounts.launchers,

View File

@ -4,7 +4,8 @@ const os = require('node:os');
const chalk = require('chalk'); const chalk = require('chalk');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator'); 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) * Codex setup handler (CLI mode)
@ -92,23 +93,17 @@ class CodexSetup extends BaseIdeSetup {
const selectedModules = options.selectedModules || []; const selectedModules = options.selectedModules || [];
const artifacts = []; const artifacts = [];
const agents = await getAgentsFromBmad(bmadDir, selectedModules); // Generate agent launchers
for (const agent of agents) { const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const content = await this.readAndProcessWithProject( const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, selectedModules);
agent.path,
{
module: agent.module,
name: agent.name,
},
projectDir,
);
for (const artifact of agentArtifacts) {
artifacts.push({ artifacts.push({
type: 'agent', type: 'agent',
module: agent.module, module: artifact.module,
sourcePath: agent.path, sourcePath: artifact.sourcePath,
relativePath: path.join(agent.module, 'agents', `${agent.name}.md`), relativePath: artifact.relativePath,
content, content: artifact.content,
}); });
} }
@ -139,7 +134,7 @@ class CodexSetup extends BaseIdeSetup {
return { return {
artifacts, artifacts,
counts: { counts: {
agents: agents.length, agents: agentArtifacts.length,
tasks: tasks.length, tasks: tasks.length,
workflows: workflowCounts.commands, workflows: workflowCounts.commands,
workflowLaunchers: workflowCounts.launchers, workflowLaunchers: workflowCounts.launchers,

View File

@ -1,6 +1,7 @@
const path = require('node:path'); const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk'); const chalk = require('chalk');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
/** /**
* Crush IDE setup handler * Crush IDE setup handler
@ -28,14 +29,17 @@ class CrushSetup extends BaseIdeSetup {
await this.ensureDir(commandsDir); await this.ensureDir(commandsDir);
// Get agents, tasks, tools, and workflows (standalone only) // Generate agent launchers
const agents = await this.getAgents(bmadDir); 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 tasks = await this.getTasks(bmadDir, true);
const tools = await this.getTools(bmadDir, true); const tools = await this.getTools(bmadDir, true);
const workflows = await this.getWorkflows(bmadDir, true); const workflows = await this.getWorkflows(bmadDir, true);
// Organize by module // 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.green(`${this.name} configured:`));
console.log(chalk.dim(` - ${agentCount.agents} agent commands created`)); console.log(chalk.dim(` - ${agentCount.agents} agent commands created`));
@ -54,10 +58,10 @@ class CrushSetup extends BaseIdeSetup {
/** /**
* Organize commands by module * Organize commands by module
*/ */
async organizeByModule(commandsDir, agents, tasks, tools, workflows, projectDir) { async organizeByModule(commandsDir, agentArtifacts, tasks, tools, workflows, projectDir) {
// Get unique modules // Get unique modules
const modules = new Set(); 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 task of tasks) modules.add(task.module);
for (const tool of tools) modules.add(tool.module); for (const tool of tools) modules.add(tool.module);
for (const workflow of workflows) modules.add(workflow.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(moduleToolsDir);
await this.ensureDir(moduleWorkflowsDir); await this.ensureDir(moduleWorkflowsDir);
// Copy module-specific agents // Write module-specific agent launchers
const moduleAgents = agents.filter((a) => a.module === module); const moduleAgents = agentArtifacts.filter((a) => a.module === module);
for (const agent of moduleAgents) { for (const artifact of moduleAgents) {
const content = await this.readFile(agent.path); const targetPath = path.join(moduleAgentsDir, `${artifact.name}.md`);
const commandContent = await this.createAgentCommand(agent, content, projectDir); await this.writeFile(targetPath, artifact.content);
const targetPath = path.join(moduleAgentsDir, `${agent.name}.md`);
await this.writeFile(targetPath, commandContent);
agentCount++; 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 * Create task command content
*/ */

View File

@ -1,6 +1,7 @@
const path = require('node:path'); const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk'); const chalk = require('chalk');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
/** /**
* Cursor IDE setup handler * Cursor IDE setup handler
@ -45,8 +46,14 @@ class CursorSetup extends BaseIdeSetup {
await this.ensureDir(bmadRulesDir); await this.ensureDir(bmadRulesDir);
// Get agents, tasks, tools, and workflows (standalone only) // Generate agent launchers first
const agents = await this.getAgents(bmadDir); 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 tasks = await this.getTasks(bmadDir, true);
const tools = await this.getTools(bmadDir, true); const tools = await this.getTools(bmadDir, true);
const workflows = await this.getWorkflows(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')); await this.ensureDir(path.join(bmadRulesDir, module, 'workflows'));
} }
// Process and copy agents // Process and write agent launchers with MDC format
let agentCount = 0; let agentCount = 0;
for (const agent of agents) { for (const artifact of agentArtifacts) {
const content = await this.readAndProcess(agent.path, { // Add MDC metadata header to launcher (but don't call processContent which adds activation headers)
module: agent.module, const content = this.wrapLauncherWithMDC(artifact.content, {
name: agent.name, 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); await this.writeFile(targetPath, content);
agentCount++; agentCount++;
@ -311,6 +319,34 @@ alwaysApply: false
// Add the MDC header to the processed content // Add the MDC header to the processed content
return mdcHeader + processed; 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 }; module.exports = { CursorSetup };

View File

@ -3,6 +3,7 @@ const fs = require('fs-extra');
const yaml = require('js-yaml'); const yaml = require('js-yaml');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk'); const chalk = require('chalk');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
/** /**
* Gemini CLI setup handler * Gemini CLI setup handler
@ -63,22 +64,24 @@ class GeminiSetup extends BaseIdeSetup {
// Clean up any existing BMAD files before reinstalling // Clean up any existing BMAD files before reinstalling
await this.cleanup(projectDir); await this.cleanup(projectDir);
// Get agents and tasks // Generate agent launchers
const agents = await this.getAgents(bmadDir); const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
// Get tasks
const tasks = await this.getTasks(bmadDir); const tasks = await this.getTasks(bmadDir);
// Install agents as TOML files with bmad- prefix (flat structure) // Install agents as TOML files with bmad- prefix (flat structure)
let agentCount = 0; let agentCount = 0;
for (const agent of agents) { for (const artifact of agentArtifacts) {
const content = await this.readFile(agent.path); const tomlContent = await this.createAgentLauncherToml(artifact);
const tomlContent = await this.createAgentToml(agent, content);
// Flat structure: bmad-agent-{module}-{name}.toml // 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); await this.writeFile(tomlPath, tomlContent);
agentCount++; 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) // 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 * Create agent TOML content using template
*/ */

View File

@ -2,6 +2,7 @@ const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk'); const chalk = require('chalk');
const inquirer = require('inquirer'); const inquirer = require('inquirer');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
/** /**
* GitHub Copilot setup handler * GitHub Copilot setup handler
@ -104,21 +105,22 @@ class GitHubCopilotSetup extends BaseIdeSetup {
// Clean up any existing BMAD files before reinstalling // Clean up any existing BMAD files before reinstalling
await this.cleanup(projectDir); await this.cleanup(projectDir);
// Get agents // Generate agent launchers
const agents = await this.getAgents(bmadDir); const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
// Create chat mode files with bmad- prefix // Create chat mode files with bmad- prefix
let modeCount = 0; let modeCount = 0;
for (const agent of agents) { for (const artifact of agentArtifacts) {
const content = await this.readFile(agent.path); const content = artifact.content;
const chatmodeContent = await this.createChatmodeContent(agent, content); const chatmodeContent = await this.createChatmodeContent({ module: artifact.module, name: artifact.name }, content);
// Use bmad- prefix: bmad-agent-{module}-{name}.chatmode.md // 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); await this.writeFile(targetPath, chatmodeContent);
modeCount++; 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:`)); console.log(chalk.green(`${this.name} configured:`));
@ -207,21 +209,17 @@ class GitHubCopilotSetup extends BaseIdeSetup {
* Create chat mode content * Create chat mode content
*/ */
async createChatmodeContent(agent, content) { async createChatmodeContent(agent, content) {
// Extract metadata // Extract metadata from launcher frontmatter if present
const titleMatch = content.match(/title="([^"]+)"/); const descMatch = content.match(/description:\s*"([^"]+)"/);
const title = titleMatch ? titleMatch[1] : this.formatTitle(agent.name); const title = descMatch ? descMatch[1] : this.formatTitle(agent.name);
const whenToUseMatch = content.match(/whenToUse="([^"]+)"/); const description = `Activates the ${title} agent persona.`;
const description = whenToUseMatch ? whenToUseMatch[1] : `Activates the ${title} agent persona.`;
// Get the activation header from central template
const activationHeader = await this.getAgentCommandHeader();
// Strip any existing frontmatter from the content // Strip any existing frontmatter from the content
const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/; const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
let cleanContent = content; let cleanContent = content;
if (frontmatterRegex.test(content)) { if (frontmatterRegex.test(content)) {
cleanContent = content.replace(frontmatterRegex, ''); cleanContent = content.replace(frontmatterRegex, '').trim();
} }
// Available GitHub Copilot tools (November 2025 - Official VS Code Documentation) // Available GitHub Copilot tools (November 2025 - Official VS Code Documentation)
@ -258,8 +256,6 @@ tools: ${JSON.stringify(tools)}
# ${title} Agent # ${title} Agent
${activationHeader}
${cleanContent} ${cleanContent}
`; `;

View File

@ -1,6 +1,7 @@
const path = require('node:path'); const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk'); const chalk = require('chalk');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
/** /**
* iFlow CLI setup handler * iFlow CLI setup handler
@ -31,21 +32,23 @@ class IFlowSetup extends BaseIdeSetup {
await this.ensureDir(agentsDir); await this.ensureDir(agentsDir);
await this.ensureDir(tasksDir); await this.ensureDir(tasksDir);
// Get agents and tasks // Generate agent launchers
const agents = await this.getAgents(bmadDir); const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const tasks = await this.getTasks(bmadDir); const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
// Setup agents as commands // Setup agents as commands
let agentCount = 0; let agentCount = 0;
for (const agent of agents) { for (const artifact of agentArtifacts) {
const content = await this.readFile(agent.path); const commandContent = await this.createAgentCommand(artifact);
const commandContent = await this.createAgentCommand(agent, content);
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); await this.writeFile(targetPath, commandContent);
agentCount++; agentCount++;
} }
// Get tasks
const tasks = await this.getTasks(bmadDir);
// Setup tasks as commands // Setup tasks as commands
let taskCount = 0; let taskCount = 0;
for (const task of tasks) { for (const task of tasks) {
@ -72,29 +75,9 @@ class IFlowSetup extends BaseIdeSetup {
/** /**
* Create agent command content * Create agent command content
*/ */
async createAgentCommand(agent, content) { async createAgentCommand(artifact) {
// Extract metadata // The launcher content is already complete - just return it as-is
const titleMatch = content.match(/title="([^"]+)"/); return artifact.content;
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;
} }
/** /**

View File

@ -1,6 +1,7 @@
const path = require('node:path'); const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk'); const chalk = require('chalk');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
/** /**
* KiloCode IDE setup handler * KiloCode IDE setup handler
@ -36,16 +37,17 @@ class KiloSetup extends BaseIdeSetup {
console.log(chalk.yellow(`Found existing .kilocodemodes file with ${existingModes.length} modes`)); console.log(chalk.yellow(`Found existing .kilocodemodes file with ${existingModes.length} modes`));
} }
// Get agents // Generate agent launchers
const agents = await this.getAgents(bmadDir); const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
// Create modes content // Create modes content
let newModesContent = ''; let newModesContent = '';
let addedCount = 0; let addedCount = 0;
let skippedCount = 0; let skippedCount = 0;
for (const agent of agents) { for (const artifact of agentArtifacts) {
const slug = `bmad-${agent.module}-${agent.name}`; const slug = `bmad-${artifact.module}-${artifact.name}`;
// Skip if already exists // Skip if already exists
if (existingModes.includes(slug)) { if (existingModes.includes(slug)) {
@ -54,8 +56,7 @@ class KiloSetup extends BaseIdeSetup {
continue; continue;
} }
const content = await this.readFile(agent.path); const modeEntry = await this.createModeEntry(artifact, projectDir);
const modeEntry = await this.createModeEntry(agent, content, projectDir);
newModesContent += modeEntry; newModesContent += modeEntry;
addedCount++; addedCount++;
@ -90,30 +91,30 @@ class KiloSetup extends BaseIdeSetup {
/** /**
* Create a mode entry for an agent * Create a mode entry for an agent
*/ */
async createModeEntry(agent, content, projectDir) { async createModeEntry(artifact, projectDir) {
// Extract metadata // Extract metadata from launcher content
const titleMatch = content.match(/title="([^"]+)"/); const titleMatch = artifact.content.match(/title="([^"]+)"/);
const title = titleMatch ? titleMatch[1] : this.formatTitle(agent.name); 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 icon = iconMatch ? iconMatch[1] : '🤖';
const whenToUseMatch = content.match(/whenToUse="([^"]+)"/); const whenToUseMatch = artifact.content.match(/whenToUse="([^"]+)"/);
const whenToUse = whenToUseMatch ? whenToUseMatch[1] : `Use for ${title} tasks`; const whenToUse = whenToUseMatch ? whenToUseMatch[1] : `Use for ${title} tasks`;
// Get the activation header from central template // Get the activation header from central template
const activationHeader = await this.getAgentCommandHeader(); const activationHeader = await this.getAgentCommandHeader();
const roleDefinitionMatch = content.match(/roleDefinition="([^"]+)"/); const roleDefinitionMatch = artifact.content.match(/roleDefinition="([^"]+)"/);
const roleDefinition = roleDefinitionMatch const roleDefinition = roleDefinitionMatch
? roleDefinitionMatch[1] ? roleDefinitionMatch[1]
: `You are a ${title} specializing in ${title.toLowerCase()} tasks.`; : `You are a ${title} specializing in ${title.toLowerCase()} tasks.`;
// Get relative path // 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) // 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`; let modeEntry = ` - slug: ${slug}\n`;
modeEntry += ` name: '${icon} ${title}'\n`; modeEntry += ` name: '${icon} ${title}'\n`;
modeEntry += ` roleDefinition: ${roleDefinition}\n`; modeEntry += ` roleDefinition: ${roleDefinition}\n`;

View File

@ -6,8 +6,7 @@ const yaml = require('js-yaml');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator'); const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator'); const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { getAgentsFromBmad } = require('./shared/bmad-artifacts');
/** /**
* OpenCode IDE setup handler * OpenCode IDE setup handler
@ -33,20 +32,17 @@ class OpenCodeSetup extends BaseIdeSetup {
// Clean up any existing BMAD files before reinstalling // Clean up any existing BMAD files before reinstalling
await this.cleanup(projectDir); 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 // Install primary agents with flat naming: bmad-agent-{module}-{name}.md
// OpenCode agents go in the agent folder (not command folder) // OpenCode agents go in the agent folder (not command folder)
const agents = await getAgentsFromBmad(bmadDir, options.selectedModules || []);
let agentCount = 0; let agentCount = 0;
for (const agent of agents) { for (const artifact of agentArtifacts) {
const processed = await this.readAndProcess(agent.path, { const agentContent = artifact.content;
module: agent.module,
name: agent.name,
});
const agentContent = await this.createAgentContent(processed, agent);
// Flat structure in agent folder: bmad-agent-{module}-{name}.md // 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); await this.writeFile(targetPath, agentContent);
agentCount++; agentCount++;
} }

View File

@ -2,6 +2,7 @@ const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk'); const chalk = require('chalk');
const { getAgentsFromBmad, getTasksFromBmad } = require('./shared/bmad-artifacts'); const { getAgentsFromBmad, getTasksFromBmad } = require('./shared/bmad-artifacts');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
/** /**
* Qwen Code setup handler * Qwen Code setup handler
@ -37,15 +38,18 @@ class QwenSetup extends BaseIdeSetup {
// Clean up old configuration if exists // Clean up old configuration if exists
await this.cleanupOldConfig(qwenDir); await this.cleanupOldConfig(qwenDir);
// Get agents, tasks, tools, and workflows (standalone only for tools/workflows) // Generate agent launchers
const agents = await getAgentsFromBmad(bmadDir, options.selectedModules || []); 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 tasks = await getTasksFromBmad(bmadDir, options.selectedModules || []);
const tools = await this.getTools(bmadDir, true); const tools = await this.getTools(bmadDir, true);
const workflows = await this.getWorkflows(bmadDir, true); const workflows = await this.getWorkflows(bmadDir, true);
// Create directories for each module (including standalone) // Create directories for each module (including standalone)
const modules = new Set(); 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) { for (const module of modules) {
await this.ensureDir(path.join(bmadCommandsDir, module)); await this.ensureDir(path.join(bmadCommandsDir, module));
@ -55,20 +59,21 @@ class QwenSetup extends BaseIdeSetup {
await this.ensureDir(path.join(bmadCommandsDir, module, 'workflows')); await this.ensureDir(path.join(bmadCommandsDir, module, 'workflows'));
} }
// Create TOML files for each agent // Create TOML files for each agent launcher
let agentCount = 0; let agentCount = 0;
for (const agent of agents) { for (const artifact of agentArtifacts) {
const content = await this.readAndProcess(agent.path, { // Convert markdown launcher content to TOML format
module: agent.module, const tomlContent = this.processAgentLauncherContent(artifact.content, {
name: agent.name, 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++; 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 // Create TOML files for each task
@ -204,6 +209,29 @@ class QwenSetup extends BaseIdeSetup {
return this.processContent(content, metadata); 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 * Override processContent to add TOML metadata header for Qwen
* @param {string} content - File content * @param {string} content - File content

View File

@ -1,6 +1,7 @@
const path = require('node:path'); const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk'); const chalk = require('chalk');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
/** /**
* Roo IDE setup handler * Roo IDE setup handler
@ -58,8 +59,9 @@ class RooSetup extends BaseIdeSetup {
console.log(chalk.yellow(`Found existing .roomodes file with ${existingModes.length} modes`)); console.log(chalk.yellow(`Found existing .roomodes file with ${existingModes.length} modes`));
} }
// Get agents // Generate agent launchers (though Roo will reference the actual .bmad agents)
const agents = await this.getAgents(bmadDir); 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 // Always use 'all' permissions - users can customize in .roomodes file
const permissionChoice = 'all'; const permissionChoice = 'all';
@ -69,8 +71,8 @@ class RooSetup extends BaseIdeSetup {
let addedCount = 0; let addedCount = 0;
let skippedCount = 0; let skippedCount = 0;
for (const agent of agents) { for (const artifact of agentArtifacts) {
const slug = `bmad-${agent.module}-${agent.name}`; const slug = `bmad-${artifact.module}-${artifact.name}`;
// Skip if already exists // Skip if already exists
if (existingModes.includes(slug)) { if (existingModes.includes(slug)) {
@ -79,8 +81,17 @@ class RooSetup extends BaseIdeSetup {
continue; continue;
} }
const content = await this.readFile(agent.path); // Read the actual agent file from .bmad for metadata extraction
const modeEntry = await this.createModeEntry(agent, content, permissionChoice, projectDir); 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; newModesContent += modeEntry;
addedCount++; addedCount++;

View File

@ -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 };

View File

@ -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.
<agent-activation CRITICAL="TRUE">
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
</agent-activation>

View File

@ -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: IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded:
<steps CRITICAL="TRUE"> <steps CRITICAL="TRUE">
1. Always LOAD the FULL {project-root}/{bmad_folder}/core/tasks/workflow.xml 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}} 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 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 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 5. Save outputs after EACH section when generating any documents from templates

View File

@ -1,6 +1,7 @@
const path = require('node:path'); const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk'); const chalk = require('chalk');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
/** /**
* Trae IDE setup handler * Trae IDE setup handler
@ -30,20 +31,22 @@ class TraeSetup extends BaseIdeSetup {
// Clean up any existing BMAD files before reinstalling // Clean up any existing BMAD files before reinstalling
await this.cleanup(projectDir); await this.cleanup(projectDir);
// Get agents, tasks, tools, and workflows (standalone only) // Generate agent launchers
const agents = await this.getAgents(bmadDir); 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 tasks = await this.getTasks(bmadDir, true);
const tools = await this.getTools(bmadDir, true); const tools = await this.getTools(bmadDir, true);
const workflows = await this.getWorkflows(bmadDir, true); const workflows = await this.getWorkflows(bmadDir, true);
// Process agents as rules with bmad- prefix // Process agents as rules with bmad- prefix
let agentCount = 0; let agentCount = 0;
for (const agent of agents) { for (const artifact of agentArtifacts) {
const content = await this.readFile(agent.path); const processedContent = await this.createAgentRule(artifact, bmadDir, projectDir);
const processedContent = await this.createAgentRule(agent, content, bmadDir, projectDir);
// Use bmad- prefix: bmad-agent-{module}-{name}.md // 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); await this.writeFile(targetPath, processedContent);
agentCount++; agentCount++;
} }
@ -108,46 +111,29 @@ class TraeSetup extends BaseIdeSetup {
/** /**
* Create rule content for an agent * Create rule content for an agent
*/ */
async createAgentRule(agent, content, bmadDir, projectDir) { async createAgentRule(artifact, bmadDir, projectDir) {
// Extract metadata from agent content // Strip frontmatter from launcher
const titleMatch = content.match(/title="([^"]+)"/); const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
const title = titleMatch ? titleMatch[1] : this.formatTitle(agent.name); const contentWithoutFrontmatter = artifact.content.replace(frontmatterRegex, '').trim();
const iconMatch = content.match(/icon="([^"]+)"/); // Extract metadata from launcher content
const icon = iconMatch ? iconMatch[1] : '🤖'; const titleMatch = artifact.content.match(/description:\s*"([^"]+)"/);
const title = titleMatch ? titleMatch[1] : this.formatTitle(artifact.name);
// 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;
// Calculate relative path for reference // 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 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 ## Agent Activation
${activationHeader} ${contentWithoutFrontmatter}
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}
\`\`\`
## File Reference ## File Reference
The complete agent definition is available in [${relativePath}](${relativePath}). The full agent definition is located at: \`${relativePath}\`
## Usage
When the user types \`@${agent.name}\`, activate this ${title} persona and follow all instructions defined in the YAML configuration above.
`; `;
return ruleContent; return ruleContent;

View File

@ -1,6 +1,7 @@
const path = require('node:path'); const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk'); const chalk = require('chalk');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
/** /**
* Windsurf IDE setup handler * Windsurf IDE setup handler
@ -31,8 +32,14 @@ class WindsurfSetup extends BaseIdeSetup {
// Clean up any existing BMAD workflows before reinstalling // Clean up any existing BMAD workflows before reinstalling
await this.cleanup(projectDir); await this.cleanup(projectDir);
// Get agents, tasks, tools, and workflows (standalone only) // Generate agent launchers
const agents = await this.getAgents(bmadDir); 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 tasks = await this.getTasks(bmadDir, true);
const tools = await this.getTools(bmadDir, true); const tools = await this.getTools(bmadDir, true);
const workflows = await this.getWorkflows(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')); 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; let agentCount = 0;
for (const agent of agents) { for (const artifact of agentArtifacts) {
const content = await this.readFile(agent.path); const processedContent = this.createWorkflowContent({ module: artifact.module, name: artifact.name }, artifact.content);
const processedContent = this.createWorkflowContent(agent, content);
// Organized path: bmad/module/agents/agent-name.md // 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); await this.writeFile(targetPath, processedContent);
agentCount++; agentCount++;
} }
@ -127,13 +133,17 @@ class WindsurfSetup extends BaseIdeSetup {
* Create workflow content for an agent * Create workflow content for an agent
*/ */
createWorkflowContent(agent, content) { 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 // Create simple Windsurf frontmatter matching original format
let workflowContent = `--- let workflowContent = `---
description: ${agent.name} description: ${agent.name}
auto_execution_mode: 3 auto_execution_mode: 3
--- ---
${content}`; ${contentWithoutFrontmatter}`;
return workflowContent; return workflowContent;
} }