Files
BMAD-METHOD/tools/cli/installers/lib/ide/rovo-dev.js
2026-01-25 03:56:40 -06:00

188 lines
5.9 KiB
JavaScript

const path = require('node:path');
const fs = require('fs-extra');
const chalk = require('chalk');
const { BaseIdeSetup } = require('./_base-ide');
const { UnifiedInstaller, NamingStyle, TemplateType } = require('./shared/unified-installer');
/**
* Rovo Dev IDE setup handler
*
* 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.workflowsDir = 'workflows';
}
/**
* Setup Rovo Dev configuration
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
* @param {Object} options - Setup options
*/
async setup(projectDir, bmadDir, options = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`));
// Clean up old BMAD installation first
await this.cleanup(projectDir);
// Create .rovodev directory structure
const rovoDevDir = path.join(projectDir, this.configDir);
const workflowsDir = path.join(rovoDevDir, this.workflowsDir);
await this.ensureDir(workflowsDir);
// 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 || [],
);
console.log(chalk.green(`${this.name} configured:`));
console.log(chalk.dim(` - ${counts.agents} agents installed`));
if (counts.workflows > 0) {
console.log(chalk.dim(` - ${counts.workflows} workflows installed`));
}
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.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,
...counts,
};
}
/**
* 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 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));
}
}
// 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`));
}
// 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`));
}
}
/**
* Detect whether Rovo Dev is already configured in the project
* @param {string} projectDir - Project directory
* @returns {boolean}
*/
async detect(projectDir) {
const rovoDevDir = path.join(projectDir, this.configDir);
if (!(await fs.pathExists(rovoDevDir))) {
return false;
}
// Check for BMAD files in workflows directory
const workflowsDir = path.join(rovoDevDir, this.workflowsDir);
if (await fs.pathExists(workflowsDir)) {
try {
const entries = await fs.readdir(workflowsDir);
if (entries.some((entry) => entry.startsWith('bmad') && entry.endsWith('.md'))) {
return true;
}
} catch {
// 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 };