mirror of
https://github.com/bmad-code-org/BMAD-METHOD.git
synced 2026-01-30 04:32:02 +00:00
installer standardization
This commit is contained in:
@@ -1,208 +0,0 @@
|
||||
# IDE Installer Standardization Plan
|
||||
|
||||
## Overview
|
||||
|
||||
Standardize IDE installers to use **flat file naming** with **underscores** (Windows-compatible) and centralize duplicated code in shared utilities.
|
||||
|
||||
**Key Rule: All IDEs use underscore format for Windows compatibility (colons don't work on Windows).**
|
||||
|
||||
## Current State Analysis
|
||||
|
||||
### File Structure Patterns
|
||||
|
||||
| IDE | Current Pattern | Path Format |
|
||||
|-----|-----------------|-------------|
|
||||
| **claude-code** | Hierarchical | `.claude/commands/bmad/{module}/agents/{name}.md` |
|
||||
| **cursor** | Hierarchical | `.cursor/commands/bmad/{module}/agents/{name}.md` |
|
||||
| **crush** | Hierarchical | `.crush/commands/bmad/{module}/agents/{name}.md` |
|
||||
| **antigravity** | Flattened (underscores) | `.agent/workflows/bmad_module_agents_name.md` |
|
||||
| **codex** | Flattened (underscores) | `~/.codex/prompts/bmad_module_agents_name.md` |
|
||||
| **cline** | Flattened (underscores) | `.clinerules/workflows/bmad_module_type_name.md` |
|
||||
| **roo** | Flattened (underscores) | `.roo/commands/bmad_module_agent_name.md` |
|
||||
| **auggie** | Hybrid | `.augment/commands/bmad/agents/{module}-{name}.md` |
|
||||
| **iflow** | Hybrid | `.iflow/commands/bmad/agents/{module}-{name}.md` |
|
||||
| **trae** | Different (rules) | `.trae/rules/bmad-agent-{module}-{name}.md` |
|
||||
| **github-copilot** | Different (agents) | `.github/agents/bmd-custom-{module}-{name}.agent.md` |
|
||||
|
||||
### Shared Generators (in `/shared`)
|
||||
|
||||
1. `agent-command-generator.js` - generates agent launchers
|
||||
2. `task-tool-command-generator.js` - generates task/tool commands
|
||||
3. `workflow-command-generator.js` - generates workflow commands
|
||||
|
||||
All currently create artifacts with **nested relative paths** like `{module}/agents/{name}.md`
|
||||
|
||||
### Code Duplication Issues
|
||||
|
||||
1. **Flattening logic** duplicated in multiple IDEs
|
||||
2. **Agent launcher content creation** duplicated
|
||||
3. **Path transformation** duplicated
|
||||
|
||||
## Target Standardization
|
||||
|
||||
### For All IDEs (underscore format - Windows-compatible)
|
||||
|
||||
**IDEs affected:** claude-code, cursor, crush, antigravity, codex, cline, roo
|
||||
|
||||
```
|
||||
Format: bmad_{module}_{type}_{name}.md
|
||||
|
||||
Examples:
|
||||
- Agent: bmad_bmm_agents_pm.md
|
||||
- Agent: bmad_core_agents_dev.md
|
||||
- Workflow: bmad_bmm_workflows_correct-course.md
|
||||
- Task: bmad_bmm_tasks_bmad-help.md
|
||||
- Tool: bmad_core_tools_code-review.md
|
||||
- Custom: bmad_custom_agents_fred-commit-poet.md
|
||||
```
|
||||
|
||||
**Note:** Type segments (agents, workflows, tasks, tools) are filtered out from names:
|
||||
- `bmm/agents/pm.md` → `bmad_bmm_pm.md` (not `bmad_bmm_agents_pm.md`)
|
||||
|
||||
### For Hybrid IDEs (keep as-is)
|
||||
|
||||
**IDEs affected:** auggie, iflow
|
||||
|
||||
These use `{module}-{name}.md` format within subdirectories - keep as-is.
|
||||
|
||||
### Skip (drastically different)
|
||||
|
||||
**IDEs affected:** trae, github-copilot
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Create Shared Utility
|
||||
|
||||
**File:** `shared/path-utils.js`
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Convert hierarchical path to flat underscore-separated name (Windows-compatible)
|
||||
* @param {string} module - Module name (e.g., 'bmm', 'core')
|
||||
* @param {string} type - Artifact type ('agents', 'workflows', 'tasks', 'tools') - filtered out
|
||||
* @param {string} name - Artifact name (e.g., 'pm', 'correct-course')
|
||||
* @returns {string} Flat filename like 'bmad_bmm_pm.md'
|
||||
*/
|
||||
function toUnderscoreName(module, type, name) {
|
||||
return `bmad_${module}_${name}.md`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert relative path to flat underscore-separated name (Windows-compatible)
|
||||
* @param {string} relativePath - Path like 'bmm/agents/pm.md'
|
||||
* @returns {string} Flat filename like 'bmad_bmm_pm.md'
|
||||
*/
|
||||
function toUnderscorePath(relativePath) {
|
||||
const withoutExt = relativePath.replace('.md', '');
|
||||
const parts = withoutExt.split(/[\/\\]/);
|
||||
// Filter out type segments (agents, workflows, tasks, tools)
|
||||
const filtered = parts.filter((p) => !TYPE_SEGMENTS.includes(p));
|
||||
return `bmad_${filtered.join('_')}.md`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create custom agent underscore name
|
||||
* @param {string} agentName - Custom agent name
|
||||
* @returns {string} Flat filename like 'bmad_custom_fred-commit-poet.md'
|
||||
*/
|
||||
function customAgentUnderscoreName(agentName) {
|
||||
return `bmad_custom_${agentName}.md`;
|
||||
}
|
||||
|
||||
// Backward compatibility aliases
|
||||
const toColonName = toUnderscoreName;
|
||||
const toColonPath = toUnderscorePath;
|
||||
const toDashPath = toUnderscorePath;
|
||||
const customAgentColonName = customAgentUnderscoreName;
|
||||
const customAgentDashName = customAgentUnderscoreName;
|
||||
|
||||
module.exports = {
|
||||
toUnderscoreName,
|
||||
toUnderscorePath,
|
||||
customAgentUnderscoreName,
|
||||
// Backward compatibility
|
||||
toColonName,
|
||||
toColonPath,
|
||||
toDashPath,
|
||||
customAgentColonName,
|
||||
customAgentDashName,
|
||||
};
|
||||
```
|
||||
|
||||
### Phase 2: Update Shared Generators
|
||||
|
||||
**Files to modify:**
|
||||
- `shared/agent-command-generator.js`
|
||||
- `shared/task-tool-command-generator.js`
|
||||
- `shared/workflow-command-generator.js`
|
||||
|
||||
**Changes:**
|
||||
1. Import path utilities
|
||||
2. Change `relativePath` to use flat format
|
||||
3. Add method `writeColonArtifacts()` for folder-based IDEs (uses underscore)
|
||||
4. Add method `writeDashArtifacts()` for flat IDEs (uses underscore)
|
||||
|
||||
### Phase 3: Update All IDEs
|
||||
|
||||
**Files to modify:**
|
||||
- `claude-code.js`
|
||||
- `cursor.js`
|
||||
- `crush.js`
|
||||
- `antigravity.js`
|
||||
- `codex.js`
|
||||
- `cline.js`
|
||||
- `roo.js`
|
||||
|
||||
**Changes:**
|
||||
1. Import utilities from path-utils
|
||||
2. Change from hierarchical to flat underscore naming
|
||||
3. Update cleanup to handle flat structure (`startsWith('bmad')`)
|
||||
|
||||
### Phase 4: Update Base Class
|
||||
|
||||
**File:** `_base-ide.js`
|
||||
|
||||
**Changes:**
|
||||
1. Mark `flattenFilename()` as `@deprecated`
|
||||
2. Add comment pointing to new path-utils
|
||||
|
||||
## Migration Checklist
|
||||
|
||||
### New Files
|
||||
- [x] Create `shared/path-utils.js`
|
||||
|
||||
### All IDEs (convert to underscore format)
|
||||
- [x] Update `shared/agent-command-generator.js` - update for underscore
|
||||
- [x] Update `shared/task-tool-command-generator.js` - update for underscore
|
||||
- [x] Update `shared/workflow-command-generator.js` - update for underscore
|
||||
- [x] Update `claude-code.js` - convert to underscore format
|
||||
- [x] Update `cursor.js` - convert to underscore format
|
||||
- [x] Update `crush.js` - convert to underscore format
|
||||
- [ ] Update `antigravity.js` - use underscore format
|
||||
- [ ] Update `codex.js` - use underscore format
|
||||
- [ ] Update `cline.js` - use underscore format
|
||||
- [ ] Update `roo.js` - use underscore format
|
||||
|
||||
### CSV Command Files
|
||||
- [x] Update `src/core/module-help.csv` - change colons to underscores
|
||||
- [x] Update `src/bmm/module-help.csv` - change colons to underscores
|
||||
|
||||
### Base Class
|
||||
- [ ] Update `_base-ide.js` - add deprecation notice
|
||||
|
||||
### Testing
|
||||
- [ ] Test claude-code installation
|
||||
- [ ] Test cursor installation
|
||||
- [ ] Test crush installation
|
||||
- [ ] Test antigravity installation
|
||||
- [ ] Test codex installation
|
||||
- [ ] Test cline installation
|
||||
- [ ] Test roo installation
|
||||
|
||||
## Notes
|
||||
|
||||
1. **Filter type segments**: agents, workflows, tasks, tools are filtered out from flat names
|
||||
2. **Underscore format**: Universal underscore format for Windows compatibility
|
||||
3. **Custom agents**: Follow the same pattern as regular agents
|
||||
4. **Backward compatibility**: Old function names kept as aliases
|
||||
5. **Cleanup**: Will remove old `bmad:` format files on next install
|
||||
@@ -91,10 +91,13 @@ class AntigravitySetup extends BaseIdeSetup {
|
||||
* @param {string} projectDir - Project directory
|
||||
*/
|
||||
async cleanup(projectDir) {
|
||||
const bmadWorkflowsDir = path.join(projectDir, this.configDir, this.workflowsDir, 'bmad');
|
||||
const workflowsDir = path.join(projectDir, this.configDir, this.workflowsDir);
|
||||
|
||||
if (await fs.pathExists(bmadWorkflowsDir)) {
|
||||
await fs.remove(bmadWorkflowsDir);
|
||||
if (await fs.pathExists(workflowsDir)) {
|
||||
const bmadFiles = (await fs.readdir(workflowsDir)).filter((f) => f.startsWith('bmad'));
|
||||
for (const f of bmadFiles) {
|
||||
await fs.remove(path.join(workflowsDir, f));
|
||||
}
|
||||
console.log(chalk.dim(` Removed old BMAD workflows from ${this.name}`));
|
||||
}
|
||||
}
|
||||
@@ -115,11 +118,9 @@ class AntigravitySetup extends BaseIdeSetup {
|
||||
await this.cleanup(projectDir);
|
||||
|
||||
// Create .agent/workflows directory structure
|
||||
const agentDir = path.join(projectDir, this.configDir);
|
||||
const workflowsDir = path.join(agentDir, this.workflowsDir);
|
||||
const bmadWorkflowsDir = path.join(workflowsDir, 'bmad');
|
||||
const workflowsDir = path.join(projectDir, this.configDir, this.workflowsDir);
|
||||
|
||||
await this.ensureDir(bmadWorkflowsDir);
|
||||
await this.ensureDir(workflowsDir);
|
||||
|
||||
// Generate agent launchers using AgentCommandGenerator
|
||||
// This creates small launcher files that reference the actual agents in _bmad/
|
||||
@@ -129,7 +130,7 @@ class AntigravitySetup extends BaseIdeSetup {
|
||||
// Write agent launcher files with FLATTENED naming using shared utility
|
||||
// Antigravity ignores directory structure, so we flatten to: bmad_module_name.md
|
||||
// This creates slash commands like /bmad_bmm_dev instead of /dev
|
||||
const agentCount = await agentGen.writeDashArtifacts(bmadWorkflowsDir, agentArtifacts);
|
||||
const agentCount = await agentGen.writeDashArtifacts(workflowsDir, agentArtifacts);
|
||||
|
||||
// Process Antigravity specific injections for installed modules
|
||||
// Use pre-collected configuration if available, or skip if already configured
|
||||
@@ -148,12 +149,12 @@ class AntigravitySetup extends BaseIdeSetup {
|
||||
const { artifacts: workflowArtifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
|
||||
|
||||
// Write workflow-command artifacts with FLATTENED naming using shared utility
|
||||
const workflowCommandCount = await workflowGen.writeDashArtifacts(bmadWorkflowsDir, workflowArtifacts);
|
||||
const workflowCommandCount = await workflowGen.writeDashArtifacts(workflowsDir, workflowArtifacts);
|
||||
|
||||
// Generate task and tool commands using FLAT naming (not nested!)
|
||||
// Use the new generateDashTaskToolCommands method with explicit target directory
|
||||
const taskToolGen = new TaskToolCommandGenerator();
|
||||
const taskToolResult = await taskToolGen.generateDashTaskToolCommands(projectDir, bmadDir, bmadWorkflowsDir);
|
||||
const taskToolResult = await taskToolGen.generateDashTaskToolCommands(projectDir, bmadDir, workflowsDir);
|
||||
|
||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||
console.log(chalk.dim(` - ${agentCount} agents installed`));
|
||||
@@ -167,7 +168,7 @@ class AntigravitySetup extends BaseIdeSetup {
|
||||
),
|
||||
);
|
||||
}
|
||||
console.log(chalk.dim(` - Workflows directory: ${path.relative(projectDir, bmadWorkflowsDir)}`));
|
||||
console.log(chalk.dim(` - Workflows directory: ${path.relative(projectDir, workflowsDir)}`));
|
||||
console.log(chalk.yellow(`\n Note: Antigravity uses flattened slash commands (e.g., /bmad_module_agents_name)`));
|
||||
|
||||
return {
|
||||
@@ -430,12 +431,10 @@ class AntigravitySetup extends BaseIdeSetup {
|
||||
* @returns {Object} Installation result
|
||||
*/
|
||||
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
|
||||
// Create .agent/workflows/bmad directory structure (same as regular agents)
|
||||
const agentDir = path.join(projectDir, this.configDir);
|
||||
const workflowsDir = path.join(agentDir, this.workflowsDir);
|
||||
const bmadWorkflowsDir = path.join(workflowsDir, 'bmad');
|
||||
// Create .agent/workflows directory structure
|
||||
const workflowsDir = path.join(projectDir, this.configDir, this.workflowsDir);
|
||||
|
||||
await fs.ensureDir(bmadWorkflowsDir);
|
||||
await fs.ensureDir(workflowsDir);
|
||||
|
||||
// Create custom agent launcher with same pattern as regular agents
|
||||
const launcherContent = `name: '${agentName}'
|
||||
@@ -458,7 +457,7 @@ usage: |
|
||||
|
||||
// Use underscore format: bmad_custom_fred-commit-poet.md
|
||||
const fileName = customAgentDashName(agentName);
|
||||
const launcherPath = path.join(bmadWorkflowsDir, fileName);
|
||||
const launcherPath = path.join(workflowsDir, fileName);
|
||||
|
||||
// Write the launcher file
|
||||
await fs.writeFile(launcherPath, launcherContent, 'utf8');
|
||||
|
||||
@@ -25,30 +25,21 @@ class IFlowSetup extends BaseIdeSetup {
|
||||
async setup(projectDir, bmadDir, options = {}) {
|
||||
console.log(chalk.cyan(`Setting up ${this.name}...`));
|
||||
|
||||
// Create .iflow/commands/bmad directory structure
|
||||
const iflowDir = path.join(projectDir, this.configDir);
|
||||
const commandsDir = path.join(iflowDir, this.commandsDir, 'bmad');
|
||||
const agentsDir = path.join(commandsDir, 'agents');
|
||||
const tasksDir = path.join(commandsDir, 'tasks');
|
||||
const workflowsDir = path.join(commandsDir, 'workflows');
|
||||
// Clean up old BMAD installation first
|
||||
await this.cleanup(projectDir);
|
||||
|
||||
await this.ensureDir(agentsDir);
|
||||
await this.ensureDir(tasksDir);
|
||||
await this.ensureDir(workflowsDir);
|
||||
// Create .iflow/commands directory structure (flat files, no bmad subfolder)
|
||||
const iflowDir = path.join(projectDir, this.configDir);
|
||||
const commandsDir = path.join(iflowDir, this.commandsDir);
|
||||
|
||||
await this.ensureDir(commandsDir);
|
||||
|
||||
// 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 artifact of agentArtifacts) {
|
||||
const commandContent = await this.createAgentCommand(artifact);
|
||||
|
||||
const targetPath = path.join(agentsDir, `${artifact.module}-${artifact.name}.md`);
|
||||
await this.writeFile(targetPath, commandContent);
|
||||
agentCount++;
|
||||
}
|
||||
// Setup agents as commands (flat files with dash naming)
|
||||
const agentCount = await agentGen.writeDashArtifacts(commandsDir, agentArtifacts);
|
||||
|
||||
// Get tasks and workflows (ALL workflows now generate commands)
|
||||
const tasks = await this.getTasks(bmadDir);
|
||||
@@ -57,26 +48,11 @@ class IFlowSetup extends BaseIdeSetup {
|
||||
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
|
||||
|
||||
// Setup tasks as commands
|
||||
let taskCount = 0;
|
||||
for (const task of tasks) {
|
||||
const content = await this.readFile(task.path);
|
||||
const commandContent = this.createTaskCommand(task, content);
|
||||
// Setup workflows as commands (flat files with dash naming)
|
||||
const workflowCount = await workflowGenerator.writeDashArtifacts(commandsDir, workflowArtifacts);
|
||||
|
||||
const targetPath = path.join(tasksDir, `${task.module}-${task.name}.md`);
|
||||
await this.writeFile(targetPath, commandContent);
|
||||
taskCount++;
|
||||
}
|
||||
|
||||
// Setup workflows as commands (already generated)
|
||||
let workflowCount = 0;
|
||||
for (const artifact of workflowArtifacts) {
|
||||
if (artifact.type === 'workflow-command') {
|
||||
const targetPath = path.join(workflowsDir, `${artifact.module}-${path.basename(artifact.relativePath, '.md')}.md`);
|
||||
await this.writeFile(targetPath, artifact.content);
|
||||
workflowCount++;
|
||||
}
|
||||
}
|
||||
// TODO: tasks not yet implemented with flat naming
|
||||
const taskCount = 0;
|
||||
|
||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||
console.log(chalk.dim(` - ${agentCount} agent commands created`));
|
||||
@@ -132,11 +108,20 @@ Part of the BMAD ${task.module.toUpperCase()} module.
|
||||
* Cleanup iFlow configuration
|
||||
*/
|
||||
async cleanup(projectDir) {
|
||||
const fs = require('fs-extra');
|
||||
const bmadCommandsDir = path.join(projectDir, this.configDir, this.commandsDir, 'bmad');
|
||||
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
||||
const bmadFolder = path.join(commandsDir, 'bmad');
|
||||
|
||||
if (await fs.pathExists(bmadCommandsDir)) {
|
||||
await fs.remove(bmadCommandsDir);
|
||||
// Remove old bmad subfolder if it exists
|
||||
if (await fs.pathExists(bmadFolder)) {
|
||||
await fs.remove(bmadFolder);
|
||||
}
|
||||
|
||||
// Also remove any bmad* files at commands root
|
||||
if (await fs.pathExists(commandsDir)) {
|
||||
const bmadFiles = (await fs.readdir(commandsDir)).filter((f) => f.startsWith('bmad'));
|
||||
for (const f of bmadFiles) {
|
||||
await fs.remove(path.join(commandsDir, f));
|
||||
}
|
||||
console.log(chalk.dim(`Removed BMAD commands from iFlow CLI`));
|
||||
}
|
||||
}
|
||||
@@ -150,11 +135,10 @@ Part of the BMAD ${task.module.toUpperCase()} module.
|
||||
* @returns {Object} Installation result
|
||||
*/
|
||||
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
|
||||
const iflowDir = path.join(projectDir, this.configDir);
|
||||
const bmadCommandsDir = path.join(iflowDir, this.commandsDir, 'bmad');
|
||||
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
||||
|
||||
// Create .iflow/commands/bmad directory if it doesn't exist
|
||||
await fs.ensureDir(bmadCommandsDir);
|
||||
// Create .iflow/commands directory if it doesn't exist
|
||||
await fs.ensureDir(commandsDir);
|
||||
|
||||
// Create custom agent launcher
|
||||
const launcherContent = `# ${agentName} Custom Agent
|
||||
@@ -173,8 +157,9 @@ The agent will follow the persona and instructions from the main agent file.
|
||||
|
||||
*Generated by BMAD Method*`;
|
||||
|
||||
const fileName = `custom-${agentName.toLowerCase()}.md`;
|
||||
const launcherPath = path.join(bmadCommandsDir, fileName);
|
||||
const { customAgentDashName } = require('./shared/path-utils');
|
||||
const fileName = customAgentDashName(agentName);
|
||||
const launcherPath = path.join(commandsDir, fileName);
|
||||
|
||||
// Write the launcher file
|
||||
await fs.writeFile(launcherPath, launcherContent, 'utf8');
|
||||
|
||||
@@ -2,19 +2,17 @@ const path = require('node:path');
|
||||
const fs = require('fs-extra');
|
||||
const { BaseIdeSetup } = require('./_base-ide');
|
||||
const chalk = require('chalk');
|
||||
const { getAgentsFromBmad, getTasksFromBmad } = require('./shared/bmad-artifacts');
|
||||
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
||||
const { UnifiedInstaller, NamingStyle, TemplateType } = require('./shared/unified-installer');
|
||||
|
||||
/**
|
||||
* Qwen Code setup handler
|
||||
* Creates TOML command files in .qwen/commands/BMad/
|
||||
* Creates TOML command files in .qwen/commands/
|
||||
*/
|
||||
class QwenSetup extends BaseIdeSetup {
|
||||
constructor() {
|
||||
super('qwen', 'Qwen Code');
|
||||
this.configDir = '.qwen';
|
||||
this.commandsDir = 'commands';
|
||||
this.bmadDir = 'bmad';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -26,118 +24,43 @@ class QwenSetup extends BaseIdeSetup {
|
||||
async setup(projectDir, bmadDir, options = {}) {
|
||||
console.log(chalk.cyan(`Setting up ${this.name}...`));
|
||||
|
||||
// Create .qwen/commands/BMad directory structure
|
||||
// Create .qwen/commands directory (flat structure, no bmad subfolder)
|
||||
const qwenDir = path.join(projectDir, this.configDir);
|
||||
const commandsDir = path.join(qwenDir, this.commandsDir);
|
||||
const bmadCommandsDir = path.join(commandsDir, this.bmadDir);
|
||||
|
||||
await this.ensureDir(bmadCommandsDir);
|
||||
await this.ensureDir(commandsDir);
|
||||
|
||||
// Update existing settings.json if present
|
||||
await this.updateSettings(qwenDir);
|
||||
|
||||
// Clean up old configuration if exists
|
||||
// Clean up old configuration
|
||||
await this.cleanupOldConfig(qwenDir);
|
||||
await this.cleanup(projectDir);
|
||||
|
||||
// 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 [...agentArtifacts, ...tasks, ...tools, ...workflows]) modules.add(item.module);
|
||||
|
||||
for (const module of modules) {
|
||||
await this.ensureDir(path.join(bmadCommandsDir, module));
|
||||
await this.ensureDir(path.join(bmadCommandsDir, module, 'agents'));
|
||||
await this.ensureDir(path.join(bmadCommandsDir, module, 'tasks'));
|
||||
await this.ensureDir(path.join(bmadCommandsDir, module, 'tools'));
|
||||
await this.ensureDir(path.join(bmadCommandsDir, module, 'workflows'));
|
||||
}
|
||||
|
||||
// Create TOML files for each agent launcher
|
||||
let agentCount = 0;
|
||||
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, artifact.module, 'agents', `${artifact.name}.toml`);
|
||||
|
||||
await this.writeFile(targetPath, tomlContent);
|
||||
|
||||
agentCount++;
|
||||
console.log(chalk.green(` ✓ Added agent: /bmad_${artifact.module}_agents_${artifact.name}`));
|
||||
}
|
||||
|
||||
// Create TOML files for each task
|
||||
let taskCount = 0;
|
||||
for (const task of tasks) {
|
||||
const content = await this.readAndProcess(task.path, {
|
||||
module: task.module,
|
||||
name: task.name,
|
||||
});
|
||||
|
||||
const targetPath = path.join(bmadCommandsDir, task.module, 'tasks', `${task.name}.toml`);
|
||||
|
||||
await this.writeFile(targetPath, content);
|
||||
|
||||
taskCount++;
|
||||
console.log(chalk.green(` ✓ Added task: /bmad_${task.module}_tasks_${task.name}`));
|
||||
}
|
||||
|
||||
// Create TOML files for each tool
|
||||
let toolCount = 0;
|
||||
for (const tool of tools) {
|
||||
const content = await this.readAndProcess(tool.path, {
|
||||
module: tool.module,
|
||||
name: tool.name,
|
||||
});
|
||||
|
||||
const targetPath = path.join(bmadCommandsDir, tool.module, 'tools', `${tool.name}.toml`);
|
||||
|
||||
await this.writeFile(targetPath, content);
|
||||
|
||||
toolCount++;
|
||||
console.log(chalk.green(` ✓ Added tool: /bmad_${tool.module}_tools_${tool.name}`));
|
||||
}
|
||||
|
||||
// Create TOML files for each workflow
|
||||
let workflowCount = 0;
|
||||
for (const workflow of workflows) {
|
||||
const content = await this.readAndProcess(workflow.path, {
|
||||
module: workflow.module,
|
||||
name: workflow.name,
|
||||
});
|
||||
|
||||
const targetPath = path.join(bmadCommandsDir, workflow.module, 'workflows', `${workflow.name}.toml`);
|
||||
|
||||
await this.writeFile(targetPath, content);
|
||||
|
||||
workflowCount++;
|
||||
console.log(chalk.green(` ✓ Added workflow: /bmad_${workflow.module}_workflows_${workflow.name}`));
|
||||
}
|
||||
// Use the unified installer with QWEN template for TOML format
|
||||
const installer = new UnifiedInstaller(this.bmadFolderName);
|
||||
const counts = await installer.install(
|
||||
projectDir,
|
||||
bmadDir,
|
||||
{
|
||||
targetDir: commandsDir,
|
||||
namingStyle: NamingStyle.FLAT_DASH,
|
||||
templateType: TemplateType.QWEN,
|
||||
fileExtension: '.toml',
|
||||
},
|
||||
options.selectedModules || [],
|
||||
);
|
||||
|
||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||
console.log(chalk.dim(` - ${agentCount} agents configured`));
|
||||
console.log(chalk.dim(` - ${taskCount} tasks configured`));
|
||||
console.log(chalk.dim(` - ${toolCount} tools configured`));
|
||||
console.log(chalk.dim(` - ${workflowCount} workflows configured`));
|
||||
console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, bmadCommandsDir)}`));
|
||||
console.log(chalk.dim(` - ${counts.agents} agents configured`));
|
||||
console.log(chalk.dim(` - ${counts.tasks} tasks configured`));
|
||||
console.log(chalk.dim(` - ${counts.tools} tools configured`));
|
||||
console.log(chalk.dim(` - ${counts.workflows} workflows configured`));
|
||||
console.log(chalk.dim(` - ${counts.total} TOML files written to ${path.relative(projectDir, commandsDir)}`));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
agents: agentCount,
|
||||
tasks: taskCount,
|
||||
tools: toolCount,
|
||||
workflows: workflowCount,
|
||||
...counts,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -145,7 +68,6 @@ class QwenSetup extends BaseIdeSetup {
|
||||
* Update settings.json to remove old agent references
|
||||
*/
|
||||
async updateSettings(qwenDir) {
|
||||
const fs = require('fs-extra');
|
||||
const settingsPath = path.join(qwenDir, 'settings.json');
|
||||
|
||||
if (await fs.pathExists(settingsPath)) {
|
||||
@@ -180,7 +102,6 @@ class QwenSetup extends BaseIdeSetup {
|
||||
* Clean up old configuration directories
|
||||
*/
|
||||
async cleanupOldConfig(qwenDir) {
|
||||
const fs = require('fs-extra');
|
||||
const agentsDir = path.join(qwenDir, 'agents');
|
||||
const bmadMethodDir = path.join(qwenDir, 'bmad-method');
|
||||
const bmadDir = path.join(qwenDir, 'bmadDir');
|
||||
@@ -201,117 +122,39 @@ class QwenSetup extends BaseIdeSetup {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and process file content
|
||||
*/
|
||||
async readAndProcess(filePath, metadata) {
|
||||
const fs = require('fs-extra');
|
||||
const content = await fs.readFile(filePath, 'utf8');
|
||||
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
|
||||
* @param {Object} metadata - File metadata
|
||||
* @returns {string} Processed content with Qwen template
|
||||
*/
|
||||
processContent(content, metadata = {}) {
|
||||
// First apply base processing (includes activation injection for agents)
|
||||
let prompt = super.processContent(content, metadata);
|
||||
|
||||
// Determine the type and description based on content
|
||||
const isAgent = content.includes('<agent');
|
||||
const isTask = content.includes('<task');
|
||||
const isTool = content.includes('<tool');
|
||||
const isWorkflow = content.includes('workflow:') || content.includes('name:');
|
||||
|
||||
let description = '';
|
||||
|
||||
if (isAgent) {
|
||||
// Extract agent title if available
|
||||
const titleMatch = content.match(/title="([^"]+)"/);
|
||||
const title = titleMatch ? titleMatch[1] : metadata.name;
|
||||
description = `BMAD ${metadata.module.toUpperCase()} Agent: ${title}`;
|
||||
} else if (isTask) {
|
||||
// Extract task name if available
|
||||
const nameMatch = content.match(/name="([^"]+)"/);
|
||||
const taskName = nameMatch ? nameMatch[1] : metadata.name;
|
||||
description = `BMAD ${metadata.module.toUpperCase()} Task: ${taskName}`;
|
||||
} else if (isTool) {
|
||||
// Extract tool name if available
|
||||
const nameMatch = content.match(/name="([^"]+)"/);
|
||||
const toolName = nameMatch ? nameMatch[1] : metadata.name;
|
||||
description = `BMAD ${metadata.module.toUpperCase()} Tool: ${toolName}`;
|
||||
} else if (isWorkflow) {
|
||||
// Workflow
|
||||
description = `BMAD ${metadata.module.toUpperCase()} Workflow: ${metadata.name}`;
|
||||
} else {
|
||||
description = `BMAD ${metadata.module.toUpperCase()}: ${metadata.name}`;
|
||||
}
|
||||
|
||||
return `description = "${description}"
|
||||
prompt = """
|
||||
${prompt}
|
||||
"""
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format name as title
|
||||
*/
|
||||
formatTitle(name) {
|
||||
return name
|
||||
.split('-')
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup Qwen configuration
|
||||
*/
|
||||
async cleanup(projectDir) {
|
||||
const fs = require('fs-extra');
|
||||
const bmadCommandsDir = path.join(projectDir, this.configDir, this.commandsDir, this.bmadDir);
|
||||
const oldBmadMethodDir = path.join(projectDir, this.configDir, 'bmad-method');
|
||||
const oldBMadDir = path.join(projectDir, this.configDir, 'BMad');
|
||||
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
||||
|
||||
if (await fs.pathExists(commandsDir)) {
|
||||
// Remove any bmad* files from the commands directory
|
||||
const entries = await fs.readdir(commandsDir);
|
||||
for (const entry of entries) {
|
||||
if (entry.startsWith('bmad')) {
|
||||
await fs.remove(path.join(commandsDir, entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also remove legacy bmad subfolder if it exists
|
||||
const bmadCommandsDir = path.join(projectDir, this.configDir, this.commandsDir, 'bmad');
|
||||
if (await fs.pathExists(bmadCommandsDir)) {
|
||||
await fs.remove(bmadCommandsDir);
|
||||
console.log(chalk.dim(`Removed BMAD configuration from Qwen Code`));
|
||||
console.log(chalk.dim(` Cleaned up existing BMAD configuration from Qwen Code`));
|
||||
}
|
||||
|
||||
const oldBmadMethodDir = path.join(projectDir, this.configDir, 'bmad-method');
|
||||
if (await fs.pathExists(oldBmadMethodDir)) {
|
||||
await fs.remove(oldBmadMethodDir);
|
||||
console.log(chalk.dim(`Removed old BMAD configuration from Qwen Code`));
|
||||
console.log(chalk.dim(` Removed old BMAD configuration from Qwen Code`));
|
||||
}
|
||||
|
||||
const oldBMadDir = path.join(projectDir, this.configDir, 'BMad');
|
||||
if (await fs.pathExists(oldBMadDir)) {
|
||||
await fs.remove(oldBMadDir);
|
||||
console.log(chalk.dim(`Removed old BMAD configuration from Qwen Code`));
|
||||
console.log(chalk.dim(` Removed old BMAD configuration from Qwen Code`));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,14 +167,12 @@ ${prompt}
|
||||
* @returns {Object} Installation result
|
||||
*/
|
||||
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
|
||||
const qwenDir = path.join(projectDir, this.configDir);
|
||||
const commandsDir = path.join(qwenDir, this.commandsDir);
|
||||
const bmadCommandsDir = path.join(commandsDir, this.bmadDir);
|
||||
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
||||
|
||||
// Create .qwen/commands/BMad directory if it doesn't exist
|
||||
await fs.ensureDir(bmadCommandsDir);
|
||||
// Create .qwen/commands directory if it doesn't exist
|
||||
await fs.ensureDir(commandsDir);
|
||||
|
||||
// Create custom agent launcher in TOML format (same pattern as regular agents)
|
||||
// Create custom agent launcher content
|
||||
const launcherContent = `# ${agentName} Custom Agent
|
||||
|
||||
**⚠️ IMPORTANT**: Run @${agentPath} first to load the complete agent!
|
||||
@@ -348,14 +189,20 @@ The agent will follow the persona and instructions from the main agent file.
|
||||
|
||||
*Generated by BMAD Method*`;
|
||||
|
||||
// Use Qwen's TOML conversion method
|
||||
const tomlContent = this.processAgentLauncherContent(launcherContent, {
|
||||
name: agentName,
|
||||
module: 'custom',
|
||||
});
|
||||
// Convert to TOML format using the same method as UnifiedInstaller
|
||||
const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
|
||||
const contentWithoutFrontmatter = launcherContent.replace(frontmatterRegex, '').trim();
|
||||
const escapedContent = contentWithoutFrontmatter.replaceAll('"""', String.raw`\"\"\"`);
|
||||
|
||||
const fileName = `custom-${agentName.toLowerCase()}.toml`;
|
||||
const launcherPath = path.join(bmadCommandsDir, fileName);
|
||||
const tomlContent = `description = "BMAD Custom Agent: ${agentName}"
|
||||
prompt = """
|
||||
${escapedContent}
|
||||
"""
|
||||
`;
|
||||
|
||||
// Use flat naming: bmad-custom-agent-agentname.toml
|
||||
const fileName = `bmad-custom-agent-${agentName.toLowerCase()}.toml`;
|
||||
const launcherPath = path.join(commandsDir, fileName);
|
||||
|
||||
// Write the launcher file
|
||||
await fs.writeFile(launcherPath, tomlContent, 'utf8');
|
||||
@@ -363,7 +210,7 @@ The agent will follow the persona and instructions from the main agent file.
|
||||
return {
|
||||
ide: 'qwen',
|
||||
path: path.relative(projectDir, launcherPath),
|
||||
command: agentName,
|
||||
command: fileName.replace('.toml', ''),
|
||||
type: 'custom-agent-launcher',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,69 +2,19 @@ const path = require('node:path');
|
||||
const fs = require('fs-extra');
|
||||
const chalk = require('chalk');
|
||||
const { BaseIdeSetup } = require('./_base-ide');
|
||||
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
||||
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
|
||||
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
|
||||
const { UnifiedInstaller, NamingStyle, TemplateType } = require('./shared/unified-installer');
|
||||
|
||||
/**
|
||||
* Rovo Dev IDE setup handler
|
||||
*
|
||||
* Installs BMAD agents as Rovo Dev subagents in .rovodev/subagents/
|
||||
* Installs workflows and tasks/tools as reference guides in .rovodev/
|
||||
* Rovo Dev automatically discovers agents and integrates with BMAD like other IDEs
|
||||
* Uses UnifiedInstaller for all artifact installation with flat file structure.
|
||||
* All BMAD artifacts are installed to .rovodev/workflows/ as flat files.
|
||||
*/
|
||||
class RovoDevSetup extends BaseIdeSetup {
|
||||
constructor() {
|
||||
super('rovo-dev', 'Atlassian Rovo Dev', false);
|
||||
this.configDir = '.rovodev';
|
||||
this.subagentsDir = 'subagents';
|
||||
this.workflowsDir = 'workflows';
|
||||
this.referencesDir = 'references';
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup old BMAD installation before reinstalling
|
||||
* @param {string} projectDir - Project directory
|
||||
*/
|
||||
async cleanup(projectDir) {
|
||||
const rovoDevDir = path.join(projectDir, this.configDir);
|
||||
|
||||
if (!(await fs.pathExists(rovoDevDir))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean BMAD agents from subagents directory
|
||||
const subagentsDir = path.join(rovoDevDir, this.subagentsDir);
|
||||
if (await fs.pathExists(subagentsDir)) {
|
||||
const entries = await fs.readdir(subagentsDir);
|
||||
const bmadFiles = entries.filter((file) => file.startsWith('bmad') && file.endsWith('.md'));
|
||||
|
||||
for (const file of bmadFiles) {
|
||||
await fs.remove(path.join(subagentsDir, file));
|
||||
}
|
||||
}
|
||||
|
||||
// Clean BMAD workflows from workflows directory
|
||||
const workflowsDir = path.join(rovoDevDir, this.workflowsDir);
|
||||
if (await fs.pathExists(workflowsDir)) {
|
||||
const entries = await fs.readdir(workflowsDir);
|
||||
const bmadFiles = entries.filter((file) => file.startsWith('bmad') && file.endsWith('.md'));
|
||||
|
||||
for (const file of bmadFiles) {
|
||||
await fs.remove(path.join(workflowsDir, file));
|
||||
}
|
||||
}
|
||||
|
||||
// Clean BMAD tasks/tools from references directory
|
||||
const referencesDir = path.join(rovoDevDir, this.referencesDir);
|
||||
if (await fs.pathExists(referencesDir)) {
|
||||
const entries = await fs.readdir(referencesDir);
|
||||
const bmadFiles = entries.filter((file) => file.startsWith('bmad') && file.endsWith('.md'));
|
||||
|
||||
for (const file of bmadFiles) {
|
||||
await fs.remove(path.join(referencesDir, file));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,155 +31,76 @@ class RovoDevSetup extends BaseIdeSetup {
|
||||
|
||||
// Create .rovodev directory structure
|
||||
const rovoDevDir = path.join(projectDir, this.configDir);
|
||||
const subagentsDir = path.join(rovoDevDir, this.subagentsDir);
|
||||
const workflowsDir = path.join(rovoDevDir, this.workflowsDir);
|
||||
const referencesDir = path.join(rovoDevDir, this.referencesDir);
|
||||
|
||||
await this.ensureDir(subagentsDir);
|
||||
await this.ensureDir(workflowsDir);
|
||||
await this.ensureDir(referencesDir);
|
||||
|
||||
// Generate and install agents
|
||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
||||
// Use the unified installer - all artifacts go to workflows folder as flat files
|
||||
const installer = new UnifiedInstaller(this.bmadFolderName);
|
||||
const counts = await installer.install(
|
||||
projectDir,
|
||||
bmadDir,
|
||||
{
|
||||
targetDir: workflowsDir,
|
||||
namingStyle: NamingStyle.FLAT_DASH,
|
||||
templateType: TemplateType.CLAUDE,
|
||||
},
|
||||
options.selectedModules || [],
|
||||
);
|
||||
|
||||
let agentCount = 0;
|
||||
for (const artifact of agentArtifacts) {
|
||||
const subagentFilename = `bmad-${artifact.module}-${artifact.name}.md`;
|
||||
const targetPath = path.join(subagentsDir, subagentFilename);
|
||||
const subagentContent = this.convertToRovoDevSubagent(artifact.content, artifact.name, artifact.module);
|
||||
await this.writeFile(targetPath, subagentContent);
|
||||
agentCount++;
|
||||
}
|
||||
|
||||
// Generate and install workflows
|
||||
const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
|
||||
|
||||
let workflowCount = 0;
|
||||
for (const artifact of workflowArtifacts) {
|
||||
if (artifact.type === 'workflow-command') {
|
||||
const workflowFilename = path.basename(artifact.relativePath);
|
||||
const targetPath = path.join(workflowsDir, workflowFilename);
|
||||
await this.writeFile(targetPath, artifact.content);
|
||||
workflowCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate and install tasks and tools
|
||||
const taskToolGen = new TaskToolCommandGenerator();
|
||||
const { tasks: taskCount, tools: toolCount } = await this.generateTaskToolReferences(bmadDir, referencesDir, taskToolGen);
|
||||
|
||||
// Summary output
|
||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||
console.log(chalk.dim(` - ${agentCount} agents installed to .rovodev/subagents/`));
|
||||
if (workflowCount > 0) {
|
||||
console.log(chalk.dim(` - ${workflowCount} workflows installed to .rovodev/workflows/`));
|
||||
console.log(chalk.dim(` - ${counts.agents} agents installed`));
|
||||
if (counts.workflows > 0) {
|
||||
console.log(chalk.dim(` - ${counts.workflows} workflows installed`));
|
||||
}
|
||||
if (taskCount + toolCount > 0) {
|
||||
console.log(
|
||||
chalk.dim(` - ${taskCount + toolCount} tasks/tools installed to .rovodev/references/ (${taskCount} tasks, ${toolCount} tools)`),
|
||||
);
|
||||
if (counts.tasks + counts.tools > 0) {
|
||||
console.log(chalk.dim(` - ${counts.tasks + counts.tools} tasks/tools installed (${counts.tasks} tasks, ${counts.tools} tools)`));
|
||||
}
|
||||
console.log(chalk.yellow(`\n Note: Agents are automatically discovered by Rovo Dev`));
|
||||
console.log(chalk.dim(` - Access agents by typing @ in Rovo Dev to see available options`));
|
||||
console.log(chalk.dim(` - Workflows and references are available in .rovodev/ directory`));
|
||||
console.log(chalk.dim(` - ${counts.total} files written to ${path.relative(projectDir, workflowsDir)}`));
|
||||
console.log(chalk.yellow(`\n Note: All BMAD items are available in .rovodev/workflows/`));
|
||||
console.log(chalk.dim(` - Access items by typing @ in Rovo Dev to see available files`));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
agents: agentCount,
|
||||
workflows: workflowCount,
|
||||
tasks: taskCount,
|
||||
tools: toolCount,
|
||||
...counts,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate task and tool reference guides
|
||||
* @param {string} bmadDir - BMAD directory
|
||||
* @param {string} referencesDir - References directory
|
||||
* @param {TaskToolCommandGenerator} taskToolGen - Generator instance
|
||||
* Cleanup old BMAD installation before reinstalling
|
||||
* @param {string} projectDir - Project directory
|
||||
*/
|
||||
async generateTaskToolReferences(bmadDir, referencesDir, taskToolGen) {
|
||||
const tasks = await taskToolGen.loadTaskManifest(bmadDir);
|
||||
const tools = await taskToolGen.loadToolManifest(bmadDir);
|
||||
async cleanup(projectDir) {
|
||||
const rovoDevDir = path.join(projectDir, this.configDir);
|
||||
|
||||
const standaloneTasks = tasks ? tasks.filter((t) => t.standalone === 'true' || t.standalone === true) : [];
|
||||
const standaloneTools = tools ? tools.filter((t) => t.standalone === 'true' || t.standalone === true) : [];
|
||||
|
||||
let taskCount = 0;
|
||||
for (const task of standaloneTasks) {
|
||||
const commandContent = taskToolGen.generateCommandContent(task, 'task');
|
||||
const targetPath = path.join(referencesDir, `bmad-task-${task.module}-${task.name}.md`);
|
||||
await this.writeFile(targetPath, commandContent);
|
||||
taskCount++;
|
||||
if (!(await fs.pathExists(rovoDevDir))) {
|
||||
return;
|
||||
}
|
||||
|
||||
let toolCount = 0;
|
||||
for (const tool of standaloneTools) {
|
||||
const commandContent = taskToolGen.generateCommandContent(tool, 'tool');
|
||||
const targetPath = path.join(referencesDir, `bmad-tool-${tool.module}-${tool.name}.md`);
|
||||
await this.writeFile(targetPath, commandContent);
|
||||
toolCount++;
|
||||
// Clean BMAD files from workflows directory
|
||||
const workflowsDir = path.join(rovoDevDir, this.workflowsDir);
|
||||
if (await fs.pathExists(workflowsDir)) {
|
||||
const entries = await fs.readdir(workflowsDir);
|
||||
const bmadFiles = entries.filter((file) => file.startsWith('bmad') && file.endsWith('.md'));
|
||||
|
||||
for (const file of bmadFiles) {
|
||||
await fs.remove(path.join(workflowsDir, file));
|
||||
}
|
||||
}
|
||||
|
||||
return { tasks: taskCount, tools: toolCount };
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert BMAD agent launcher to Rovo Dev subagent format
|
||||
*
|
||||
* Rovo Dev subagents use Markdown files with YAML frontmatter containing:
|
||||
* - name: Unique identifier for the subagent
|
||||
* - description: One-line description of the subagent's purpose
|
||||
* - tools: Array of tools the subagent can use (optional)
|
||||
* - model: Specific model for this subagent (optional)
|
||||
* - load_memory: Whether to load memory files (optional, defaults to true)
|
||||
*
|
||||
* @param {string} launcherContent - Original agent launcher content
|
||||
* @param {string} agentName - Name of the agent
|
||||
* @param {string} moduleName - Name of the module
|
||||
* @returns {string} Rovo Dev subagent-formatted content
|
||||
*/
|
||||
convertToRovoDevSubagent(launcherContent, agentName, moduleName) {
|
||||
// Extract metadata from the launcher XML
|
||||
const titleMatch = launcherContent.match(/title="([^"]+)"/);
|
||||
const title = titleMatch ? titleMatch[1] : this.formatTitle(agentName);
|
||||
|
||||
const descriptionMatch = launcherContent.match(/description="([^"]+)"/);
|
||||
const description = descriptionMatch ? descriptionMatch[1] : `BMAD agent: ${title}`;
|
||||
|
||||
const roleDefinitionMatch = launcherContent.match(/roleDefinition="([^"]+)"/);
|
||||
const roleDefinition = roleDefinitionMatch ? roleDefinitionMatch[1] : `You are a specialized agent for ${title.toLowerCase()} tasks.`;
|
||||
|
||||
// Extract the main system prompt from the launcher (content after closing tags)
|
||||
let systemPrompt = roleDefinition;
|
||||
|
||||
// Try to extract additional instructions from the launcher content
|
||||
const instructionsMatch = launcherContent.match(/<instructions>([\s\S]*?)<\/instructions>/);
|
||||
if (instructionsMatch) {
|
||||
systemPrompt += '\n\n' + instructionsMatch[1].trim();
|
||||
// Remove legacy subagents directory
|
||||
const subagentsDir = path.join(rovoDevDir, 'subagents');
|
||||
if (await fs.pathExists(subagentsDir)) {
|
||||
await fs.remove(subagentsDir);
|
||||
console.log(chalk.dim(` Removed legacy subagents directory`));
|
||||
}
|
||||
|
||||
// Build YAML frontmatter for Rovo Dev subagent
|
||||
const frontmatter = {
|
||||
name: `bmad-${moduleName}-${agentName}`,
|
||||
description: description,
|
||||
// Note: tools and model can be added by users in their .rovodev/subagents/*.md files
|
||||
// We don't enforce specific tools since BMAD agents are flexible
|
||||
};
|
||||
|
||||
// Create YAML frontmatter string with proper quoting for special characters
|
||||
let yamlContent = '---\n';
|
||||
yamlContent += `name: ${frontmatter.name}\n`;
|
||||
// Quote description to handle colons and other special characters in YAML
|
||||
yamlContent += `description: "${frontmatter.description.replaceAll('"', String.raw`\"`)}"\n`;
|
||||
yamlContent += '---\n';
|
||||
|
||||
// Combine frontmatter with system prompt
|
||||
const subagentContent = yamlContent + systemPrompt;
|
||||
|
||||
return subagentContent;
|
||||
// Remove legacy references directory
|
||||
const referencesDir = path.join(rovoDevDir, 'references');
|
||||
if (await fs.pathExists(referencesDir)) {
|
||||
await fs.remove(referencesDir);
|
||||
console.log(chalk.dim(` Removed legacy references directory`));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -244,20 +115,7 @@ class RovoDevSetup extends BaseIdeSetup {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for BMAD agents in subagents directory
|
||||
const subagentsDir = path.join(rovoDevDir, this.subagentsDir);
|
||||
if (await fs.pathExists(subagentsDir)) {
|
||||
try {
|
||||
const entries = await fs.readdir(subagentsDir);
|
||||
if (entries.some((entry) => entry.startsWith('bmad') && entry.endsWith('.md'))) {
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
// Continue checking other directories
|
||||
}
|
||||
}
|
||||
|
||||
// Check for BMAD workflows in workflows directory
|
||||
// Check for BMAD files in workflows directory
|
||||
const workflowsDir = path.join(rovoDevDir, this.workflowsDir);
|
||||
if (await fs.pathExists(workflowsDir)) {
|
||||
try {
|
||||
@@ -266,25 +124,64 @@ class RovoDevSetup extends BaseIdeSetup {
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
// Continue checking other directories
|
||||
}
|
||||
}
|
||||
|
||||
// Check for BMAD tasks/tools in references directory
|
||||
const referencesDir = path.join(rovoDevDir, this.referencesDir);
|
||||
if (await fs.pathExists(referencesDir)) {
|
||||
try {
|
||||
const entries = await fs.readdir(referencesDir);
|
||||
if (entries.some((entry) => entry.startsWith('bmad') && entry.endsWith('.md'))) {
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
// Continue
|
||||
// Continue checking
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a custom agent launcher for Rovo Dev
|
||||
* @param {string} projectDir - Project directory
|
||||
* @param {string} agentName - Agent name (e.g., "fred-commit-poet")
|
||||
* @param {string} agentPath - Path to compiled agent (relative to project root)
|
||||
* @param {Object} metadata - Agent metadata
|
||||
* @returns {Object|null} Installation result
|
||||
*/
|
||||
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
|
||||
const workflowsDir = path.join(projectDir, this.configDir, this.workflowsDir);
|
||||
|
||||
if (!(await this.exists(path.join(projectDir, this.configDir)))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
await this.ensureDir(workflowsDir);
|
||||
|
||||
const launcherContent = `---
|
||||
name: ${agentName}
|
||||
description: Custom BMAD agent: ${agentName}
|
||||
---
|
||||
|
||||
# ${agentName} Custom Agent
|
||||
|
||||
**⚠️ IMPORTANT**: Run @${agentPath} first to load the complete agent!
|
||||
|
||||
This is a launcher for the custom BMAD agent "${agentName}".
|
||||
|
||||
## Usage
|
||||
1. First run: \`${agentPath}\` to load the complete agent
|
||||
2. Then use this workflow as ${agentName}
|
||||
|
||||
The agent will follow the persona and instructions from the main agent file.
|
||||
|
||||
---
|
||||
|
||||
*Generated by BMAD Method*`;
|
||||
|
||||
// Use flat naming: bmad-custom-agent-agentname.md
|
||||
const fileName = `bmad-custom-agent-${agentName.toLowerCase()}.md`;
|
||||
const launcherPath = path.join(workflowsDir, fileName);
|
||||
|
||||
await fs.writeFile(launcherPath, launcherContent, 'utf8');
|
||||
|
||||
return {
|
||||
ide: 'rovo-dev',
|
||||
path: path.relative(projectDir, launcherPath),
|
||||
command: fileName.replace('.md', ''),
|
||||
type: 'custom-agent-launcher',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { RovoDevSetup };
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
const path = require('node:path');
|
||||
const fs = require('fs-extra');
|
||||
const chalk = require('chalk');
|
||||
const { toColonPath, toDashPath, customAgentColonName, customAgentDashName } = require('./path-utils');
|
||||
|
||||
/**
|
||||
@@ -65,9 +64,8 @@ class AgentCommandGenerator {
|
||||
.replaceAll('{{name}}', agent.name)
|
||||
.replaceAll('{{module}}', agent.module)
|
||||
.replaceAll('{{path}}', agentPathInModule)
|
||||
.replaceAll('{{description}}', agent.description || `${agent.name} agent`)
|
||||
.replaceAll('_bmad', this.bmadFolderName)
|
||||
.replaceAll('_bmad', '_bmad');
|
||||
.replaceAll('{{relativePath}}', path.join(agent.module, 'agents', agentPathInModule))
|
||||
.replaceAll('{{description}}', agent.description || `${agent.name} agent`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,7 +107,7 @@ class AgentCommandGenerator {
|
||||
// Convert relativePath to underscore format: bmm/agents/pm.md → bmad_bmm_pm.md
|
||||
const flatName = toColonPath(artifact.relativePath);
|
||||
const launcherPath = path.join(baseCommandsDir, flatName);
|
||||
await fs.ensureDir(path.dirname(launcherPath));
|
||||
await fs.ensureDir(baseCommandsDir);
|
||||
await fs.writeFile(launcherPath, artifact.content);
|
||||
writtenCount++;
|
||||
}
|
||||
@@ -119,8 +117,8 @@ class AgentCommandGenerator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Write agent launcher artifacts using underscore format (Windows-compatible)
|
||||
* Creates flat files like: bmad_bmm_pm.md
|
||||
* Write agent launcher artifacts using dash format
|
||||
* Creates flat files like: bmad-bmm-agent-pm.md
|
||||
*
|
||||
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
||||
* @param {Array} artifacts - Agent launcher artifacts
|
||||
@@ -131,10 +129,10 @@ class AgentCommandGenerator {
|
||||
|
||||
for (const artifact of artifacts) {
|
||||
if (artifact.type === 'agent-launcher') {
|
||||
// Convert relativePath to underscore format: bmm/agents/pm.md → bmad_bmm_pm.md
|
||||
// Convert relativePath to dash format: bmm/agents/pm.md → bmad-bmm-agent-pm.md
|
||||
const flatName = toDashPath(artifact.relativePath);
|
||||
const launcherPath = path.join(baseCommandsDir, flatName);
|
||||
await fs.ensureDir(path.dirname(launcherPath));
|
||||
await fs.ensureDir(baseCommandsDir);
|
||||
await fs.writeFile(launcherPath, artifact.content);
|
||||
writtenCount++;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ function toUnderscoreName(module, type, name, fileExtension = DEFAULT_FILE_EXTEN
|
||||
* Convert relative path to flat underscore-separated name
|
||||
* Converts: 'bmm/agents/pm.md' → 'bmad_bmm_agent_pm.md'
|
||||
* Converts: 'bmm/workflows/correct-course.md' → 'bmad_bmm_correct-course.md'
|
||||
* Converts: 'bmad_bmb/agents/agent-builder.md' → 'bmad_bmb_agent_agent-builder.md' (bmad prefix already in module)
|
||||
* Converts: 'core/agents/brainstorming.md' → 'bmad_agent_brainstorming.md' (core items skip module prefix)
|
||||
*
|
||||
* @param {string} relativePath - Path like 'bmm/agents/pm.md'
|
||||
@@ -54,8 +55,14 @@ function toUnderscorePath(relativePath, fileExtension = DEFAULT_FILE_EXTENSION)
|
||||
const type = parts[1];
|
||||
const name = parts.slice(2).join('_');
|
||||
|
||||
// Use toUnderscoreName for consistency
|
||||
return toUnderscoreName(module, type, name, fileExtension);
|
||||
const isAgent = type === AGENT_SEGMENT;
|
||||
// For core module, skip the module prefix: use 'bmad_name.md' instead of 'bmad_core_name.md'
|
||||
if (module === 'core') {
|
||||
return isAgent ? `bmad_agent_${name}${fileExtension}` : `bmad_${name}${fileExtension}`;
|
||||
}
|
||||
// If module already starts with 'bmad_', don't add another prefix
|
||||
const prefix = module.startsWith('bmad_') ? '' : 'bmad_';
|
||||
return isAgent ? `${prefix}${module}_agent_${name}${fileExtension}` : `${prefix}${module}_${name}${fileExtension}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,6 +175,7 @@ function toColonPath(relativePath, fileExtension = DEFAULT_FILE_EXTENSION) {
|
||||
* Convert relative path to flat dash-separated name
|
||||
* Converts: 'bmm/agents/pm.md' → 'bmad-bmm-agent-pm.md'
|
||||
* Converts: 'bmm/workflows/correct-course' → 'bmad-bmm-correct-course.md'
|
||||
* Converts: 'bmad-bmb/agents/agent-builder.md' → 'bmad-bmb-agent-agent-builder.md' (bmad prefix already in module)
|
||||
* @param {string} relativePath - Path like 'bmm/agents/pm.md'
|
||||
* @param {string} [fileExtension=DEFAULT_FILE_EXTENSION] - File extension including dot
|
||||
* @returns {string} Flat filename like 'bmad-bmm-agent-pm.md'
|
||||
@@ -188,7 +196,9 @@ function toDashPath(relativePath, fileExtension = DEFAULT_FILE_EXTENSION) {
|
||||
if (module === 'core') {
|
||||
return isAgent ? `bmad-agent-${name}${fileExtension}` : `bmad-${name}${fileExtension}`;
|
||||
}
|
||||
return isAgent ? `bmad-${module}-agent-${name}${fileExtension}` : `bmad-${module}-${name}${fileExtension}`;
|
||||
// If module already starts with 'bmad-', don't add another prefix
|
||||
const prefix = module.startsWith('bmad-') ? '' : 'bmad-';
|
||||
return isAgent ? `${prefix}${module}-agent-${name}${fileExtension}` : `${prefix}${module}-${name}${fileExtension}`;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -40,6 +40,7 @@ const TemplateType = {
|
||||
WINDSURF: 'windsurf', // YAML with auto_execution_mode
|
||||
AUGMENT: 'augment', // YAML frontmatter
|
||||
GEMINI: 'gemini', // TOML frontmatter with description/prompt
|
||||
QWEN: 'qwen', // TOML frontmatter with description/prompt (same as Gemini)
|
||||
COPILOT: 'copilot', // YAML with tools array for GitHub Copilot
|
||||
};
|
||||
|
||||
@@ -209,7 +210,8 @@ class UnifiedInstaller {
|
||||
content = this.applyTemplate(artifact, content, templateType);
|
||||
}
|
||||
|
||||
await fs.ensureDir(path.dirname(targetPath));
|
||||
// For flat files, just ensure targetDir exists (no nested dirs needed)
|
||||
await fs.ensureDir(targetDir);
|
||||
await fs.writeFile(targetPath, content, 'utf8');
|
||||
written++;
|
||||
}
|
||||
@@ -254,6 +256,11 @@ class UnifiedInstaller {
|
||||
return this.addCopilotFrontmatter(artifact, content);
|
||||
}
|
||||
|
||||
case TemplateType.QWEN: {
|
||||
// Add Qwen TOML frontmatter (same as Gemini)
|
||||
return this.addGeminiFrontmatter(artifact, content);
|
||||
}
|
||||
|
||||
default: {
|
||||
return content;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ 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/{{module}}/agents/{{path}}
|
||||
1. LOAD the FULL agent file from @_bmad/{{relativePath}}
|
||||
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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
name: '{{name}}'
|
||||
description: '{{description}}'
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user