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

View File

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

View File

@ -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,

View File

@ -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,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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:
<steps CRITICAL="TRUE">
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

View File

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

View File

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